1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Profile modules - Added option to provide default profiles

Profiles - Run adaption on freshly imported profiles when activating
Profiles - Run adaption on freshly imported profiles when surface changes
This commit is contained in:
Robert 2021-05-15 22:41:15 +02:00
parent c7c78dfecc
commit 14f82284ac
7 changed files with 121 additions and 53 deletions

View File

@ -14,6 +14,7 @@ namespace Artemis.Core
{ {
private readonly object _lock = new(); private readonly object _lock = new();
private bool _isActivated; private bool _isActivated;
private bool _isFreshImport;
internal Profile(ProfileModule module, string name) : base(null!) internal Profile(ProfileModule module, string name) : base(null!)
{ {
@ -57,6 +58,20 @@ namespace Artemis.Core
private set => SetAndNotify(ref _isActivated, value); private set => SetAndNotify(ref _isActivated, value);
} }
/// <summary>
/// Gets or sets a boolean indicating whether this profile is freshly imported i.e. no changes have been made to it
/// since import
/// <para>
/// Note: As long as this is <see langword="true" />, profile adaption will be performed on load and any surface
/// changes
/// </para>
/// </summary>
public bool IsFreshImport
{
get => _isFreshImport;
set => SetAndNotify(ref _isFreshImport, value);
}
/// <summary> /// <summary>
/// Gets the profile entity this profile uses for persistent storage /// Gets the profile entity this profile uses for persistent storage
/// </summary> /// </summary>
@ -134,6 +149,16 @@ namespace Artemis.Core
layer.PopulateLeds(devices); layer.PopulateLeds(devices);
} }
/// <summary>
/// Occurs when the profile has been activated.
/// </summary>
public event EventHandler? Activated;
/// <summary>
/// Occurs when the profile is being deactivated.
/// </summary>
public event EventHandler? Deactivated;
/// <inheritdoc /> /// <inheritdoc />
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
{ {
@ -156,6 +181,7 @@ namespace Artemis.Core
throw new ObjectDisposedException("Profile"); throw new ObjectDisposedException("Profile");
Name = ProfileEntity.Name; Name = ProfileEntity.Name;
IsFreshImport = ProfileEntity.IsFreshImport;
lock (ChildrenList) lock (ChildrenList)
{ {
@ -171,9 +197,7 @@ namespace Artemis.Core
Folder _ = new(this, "Root folder"); Folder _ = new(this, "Root folder");
} }
else else
{
AddChild(new Folder(this, this, rootFolder)); AddChild(new Folder(this, this, rootFolder));
}
} }
} }
@ -186,6 +210,7 @@ namespace Artemis.Core
ProfileEntity.ModuleId = Module.Id; ProfileEntity.ModuleId = Module.Id;
ProfileEntity.Name = Name; ProfileEntity.Name = Name;
ProfileEntity.IsActive = IsActivated; ProfileEntity.IsActive = IsActivated;
ProfileEntity.IsFreshImport = IsFreshImport;
foreach (ProfileElement profileElement in Children) foreach (ProfileElement profileElement in Children)
profileElement.Save(); profileElement.Save();
@ -196,7 +221,7 @@ namespace Artemis.Core
ProfileEntity.Layers.Clear(); ProfileEntity.Layers.Clear();
ProfileEntity.Layers.AddRange(GetAllLayers().Select(f => f.LayerEntity)); ProfileEntity.Layers.AddRange(GetAllLayers().Select(f => f.LayerEntity));
} }
internal void Activate(IEnumerable<ArtemisDevice> devices) internal void Activate(IEnumerable<ArtemisDevice> devices)
{ {
lock (_lock) lock (_lock)
@ -212,18 +237,6 @@ namespace Artemis.Core
} }
} }
#region Events
/// <summary>
/// Occurs when the profile has been activated.
/// </summary>
public event EventHandler? Activated;
/// <summary>
/// Occurs when the profile is being deactivated.
/// </summary>
public event EventHandler? Deactivated;
private void OnActivated() private void OnActivated()
{ {
Activated?.Invoke(this, EventArgs.Empty); Activated?.Invoke(this, EventArgs.Empty);
@ -233,7 +246,5 @@ namespace Artemis.Core
{ {
Deactivated?.Invoke(this, EventArgs.Empty); Deactivated?.Invoke(this, EventArgs.Empty);
} }
#endregion
} }
} }

View File

@ -37,10 +37,5 @@ namespace Artemis.Core
/// Gets a boolean indicating whether this was the last active profile /// Gets a boolean indicating whether this was the last active profile
/// </summary> /// </summary>
public bool IsLastActiveProfile { get; } public bool IsLastActiveProfile { get; }
/// <summary>
/// Gets or sets a boolean indicating whether the profile will be adapted the next time it is activated
/// </summary>
public bool NeedsAdaption { get; set; }
} }
} }

View File

@ -7,6 +7,7 @@ using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Artemis.Core.DataModelExpansions; using Artemis.Core.DataModelExpansions;
using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Newtonsoft.Json; using Newtonsoft.Json;
using SkiaSharp; using SkiaSharp;
@ -94,7 +95,9 @@ namespace Artemis.Core.Modules
/// </summary> /// </summary>
public abstract class ProfileModule : Module public abstract class ProfileModule : Module
{ {
private readonly List<ProfileDescriptor> _defaultProfiles; private readonly List<string> _defaultProfilePaths = new();
private readonly List<string> _pendingDefaultProfilePaths = new();
private readonly List<ProfileEntity> _defaultProfiles = new();
private readonly object _lock = new(); private readonly object _lock = new();
/// <summary> /// <summary>
@ -102,13 +105,13 @@ namespace Artemis.Core.Modules
/// </summary> /// </summary>
protected internal readonly List<PropertyInfo> HiddenPropertiesList = new(); protected internal readonly List<PropertyInfo> HiddenPropertiesList = new();
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="ProfileModule" /> class /// Creates a new instance of the <see cref="ProfileModule" /> class
/// </summary> /// </summary>
protected ProfileModule() protected ProfileModule()
{ {
OpacityOverride = 1; OpacityOverride = 1;
_defaultProfiles = new List<ProfileDescriptor>();
} }
/// <summary> /// <summary>
@ -139,7 +142,7 @@ namespace Artemis.Core.Modules
/// <summary> /// <summary>
/// Gets a list of default profiles, to add a new default profile use <see cref="AddDefaultProfile" /> /// Gets a list of default profiles, to add a new default profile use <see cref="AddDefaultProfile" />
/// </summary> /// </summary>
public ReadOnlyCollection<ProfileDescriptor> DefaultProfiles => _defaultProfiles.AsReadOnly(); internal ReadOnlyCollection<ProfileEntity> DefaultProfiles => _defaultProfiles.AsReadOnly();
/// <summary> /// <summary>
/// Called after the profile has updated /// Called after the profile has updated
@ -167,22 +170,44 @@ namespace Artemis.Core.Modules
/// <summary> /// <summary>
/// Adds a default profile by reading it from the file found at the provided path /// Adds a default profile by reading it from the file found at the provided path
/// </summary> /// </summary>
/// <param name="file"></param> /// <param name="file">A path pointing towards a profile file. May be relative to the plugin directory.</param>
protected void AddDefaultProfile(string file) /// <returns>
/// <see langword="true" /> if the default profile was added; <see langword="false" /> if it was not because it is
/// already in the list.
/// </returns>
protected bool AddDefaultProfile(string file)
{ {
// It can be null if the plugin has not loaded yet...
if (Plugin == null!)
{
if (_pendingDefaultProfilePaths.Contains(file))
return false;
_pendingDefaultProfilePaths.Add(file);
return true;
}
if (!Path.IsPathRooted(file))
file = Plugin.ResolveRelativePath(file);
if (_defaultProfilePaths.Contains(file))
return false;
_defaultProfilePaths.Add(file);
// Ensure the file exists // Ensure the file exists
if (!File.Exists(file)) if (!File.Exists(file))
throw new ArtemisPluginFeatureException(this, $"Could not find default profile at {file}."); throw new ArtemisPluginFeatureException(this, $"Could not find default profile at {file}.");
// Deserialize and make sure that succeeded // Deserialize and make sure that succeeded
ProfileEntity? profileEntity = JsonConvert.DeserializeObject<ProfileEntity>(File.ReadAllText(file)); ProfileEntity? profileEntity = JsonConvert.DeserializeObject<ProfileEntity>(File.ReadAllText(file), ProfileService.ExportSettings);
if (profileEntity == null) if (profileEntity == null)
throw new ArtemisPluginFeatureException(this, $"Failed to deserialize default profile at {file}."); throw new ArtemisPluginFeatureException(this, $"Failed to deserialize default profile at {file}.");
// Ensure the profile ID is unique // Ensure the profile ID is unique
ProfileDescriptor descriptor = new(this, profileEntity) {NeedsAdaption = true}; if (_defaultProfiles.Any(d => d.Id == profileEntity.Id))
if (_defaultProfiles.Any(d => d.Id == descriptor.Id)) throw new ArtemisPluginFeatureException(this, $"Cannot add default profile from {file}, profile ID {profileEntity.Id} already in use.");
throw new ArtemisPluginFeatureException(this, $"Cannot add default profile from {file}, profile ID {descriptor.Id} already in use.");
_defaultProfiles.Add(descriptor); profileEntity.IsFreshImport = true;
_defaultProfiles.Add(profileEntity);
return true;
} }
/// <summary> /// <summary>
@ -193,6 +218,15 @@ namespace Artemis.Core.Modules
ActiveProfileChanged?.Invoke(this, EventArgs.Empty); ActiveProfileChanged?.Invoke(this, EventArgs.Empty);
} }
internal override void InternalEnable()
{
foreach (string pendingDefaultProfile in _pendingDefaultProfilePaths)
AddDefaultProfile(pendingDefaultProfile);
_pendingDefaultProfilePaths.Clear();
base.InternalEnable();
}
internal override void InternalUpdate(double deltaTime) internal override void InternalUpdate(double deltaTime)
{ {
StartUpdateMeasure(); StartUpdateMeasure();

View File

@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Timers; using System.Timers;
using Artemis.Core.Modules; using Artemis.Core.Modules;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Repositories.Interfaces; using Artemis.Storage.Repositories.Interfaces;
using Serilog; using Serilog;
using Timer = System.Timers.Timer; using Timer = System.Timers.Timer;
@ -17,13 +18,15 @@ namespace Artemis.Core.Services
private static readonly SemaphoreSlim ActiveModuleSemaphore = new(1, 1); private static readonly SemaphoreSlim ActiveModuleSemaphore = new(1, 1);
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IModuleRepository _moduleRepository; private readonly IModuleRepository _moduleRepository;
private readonly IProfileRepository _profileRepository;
private readonly IPluginManagementService _pluginManagementService; private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileService _profileService; private readonly IProfileService _profileService;
public ModuleService(ILogger logger, IModuleRepository moduleRepository, IPluginManagementService pluginManagementService, IProfileService profileService) public ModuleService(ILogger logger, IModuleRepository moduleRepository, IProfileRepository profileRepository, IPluginManagementService pluginManagementService, IProfileService profileService)
{ {
_logger = logger; _logger = logger;
_moduleRepository = moduleRepository; _moduleRepository = moduleRepository;
_profileRepository = profileRepository;
_pluginManagementService = pluginManagementService; _pluginManagementService = pluginManagementService;
_profileService = profileService; _profileService = profileService;
_pluginManagementService.PluginFeatureEnabled += OnPluginFeatureEnabled; _pluginManagementService.PluginFeatureEnabled += OnPluginFeatureEnabled;
@ -45,12 +48,24 @@ namespace Artemis.Core.Services
{ {
try 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); module.Activate(false);
try try
{ {
// If this is a profile module, activate the last active profile after module activation // If this is a profile module, activate the last active profile after module activation
if (module is ProfileModule profileModule) if (profileModule != null)
await _profileService.ActivateLastProfileAnimated(profileModule); await _profileService.ActivateLastProfileAnimated(profileModule);
} }
catch (Exception e) catch (Exception e)

View File

@ -32,8 +32,8 @@ namespace Artemis.Core.Services
_rgbService.LedsChanged += RgbServiceOnLedsChanged; _rgbService.LedsChanged += RgbServiceOnLedsChanged;
} }
public JsonSerializerSettings MementoSettings { get; set; } = new() {TypeNameHandling = TypeNameHandling.All}; public static JsonSerializerSettings MementoSettings { get; set; } = new() {TypeNameHandling = TypeNameHandling.All};
public JsonSerializerSettings ExportSettings { get; set; } = new() {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented}; public static JsonSerializerSettings ExportSettings { get; set; } = new() {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented};
public ProfileDescriptor? GetLastActiveProfile(ProfileModule module) public ProfileDescriptor? GetLastActiveProfile(ProfileModule module)
{ {
@ -64,8 +64,16 @@ namespace Artemis.Core.Services
private void ActiveProfilesPopulateLeds() private void ActiveProfilesPopulateLeds()
{ {
List<ProfileModule> profileModules = _pluginManagementService.GetFeaturesOfType<ProfileModule>(); List<ProfileModule> profileModules = _pluginManagementService.GetFeaturesOfType<ProfileModule>();
foreach (ProfileModule profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList()) foreach (ProfileModule profileModule in profileModules)
profileModule.ActiveProfile?.PopulateLeds(_rgbService.EnabledDevices); // Avoid race condition {
// Avoid race condition, make the check here
if (profileModule.ActiveProfile != null)
{
profileModule.ActiveProfile.PopulateLeds(_rgbService.EnabledDevices);
_logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profileModule.ActiveProfile);
AdaptProfile(profileModule.ActiveProfile);
}
}
} }
public List<ProfileDescriptor> GetProfileDescriptors(ProfileModule module) public List<ProfileDescriptor> GetProfileDescriptors(ProfileModule module)
@ -107,13 +115,14 @@ namespace Artemis.Core.Services
Profile profile = new(profileDescriptor.ProfileModule, profileEntity); Profile profile = new(profileDescriptor.ProfileModule, profileEntity);
InstantiateProfile(profile); InstantiateProfile(profile);
if (profileDescriptor.NeedsAdaption)
{
AdaptProfile(profile);
profileDescriptor.NeedsAdaption = false;
}
profileDescriptor.ProfileModule.ChangeActiveProfile(profile, _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); SaveActiveProfile(profileDescriptor.ProfileModule);
return profile; return profile;
@ -143,7 +152,7 @@ namespace Artemis.Core.Services
Profile profile = new(profileDescriptor.ProfileModule, profileEntity); Profile profile = new(profileDescriptor.ProfileModule, profileEntity);
InstantiateProfile(profile); InstantiateProfile(profile);
void ActivatingRgbServiceOnLedsChanged(object? sender, EventArgs e) void ActivatingRgbServiceOnLedsChanged(object? sender, EventArgs e)
{ {
profile.PopulateLeds(_rgbService.EnabledDevices); profile.PopulateLeds(_rgbService.EnabledDevices);
@ -161,6 +170,12 @@ namespace Artemis.Core.Services
_rgbService.LedsChanged += ActivatingRgbServiceOnLedsChanged; _rgbService.LedsChanged += ActivatingRgbServiceOnLedsChanged;
await profileDescriptor.ProfileModule.ChangeActiveProfileAnimated(profile, _rgbService.EnabledDevices); 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); SaveActiveProfile(profileDescriptor.ProfileModule);
_pluginManagementService.PluginEnabled -= ActivatingProfilePluginToggle; _pluginManagementService.PluginEnabled -= ActivatingProfilePluginToggle;
@ -198,6 +213,8 @@ namespace Artemis.Core.Services
public void DeleteProfile(ProfileDescriptor profileDescriptor) public void DeleteProfile(ProfileDescriptor profileDescriptor)
{ {
ProfileEntity profileEntity = _profileRepository.Get(profileDescriptor.Id); ProfileEntity profileEntity = _profileRepository.Get(profileDescriptor.Id);
if (profileEntity == null)
return;
_profileRepository.Remove(profileEntity); _profileRepository.Remove(profileEntity);
} }
@ -208,6 +225,7 @@ namespace Artemis.Core.Services
profile.RedoStack.Clear(); profile.RedoStack.Clear();
profile.UndoStack.Push(memento); profile.UndoStack.Push(memento);
profile.IsFreshImport = false;
profile.Save(); profile.Save();
if (includeChildren) if (includeChildren)
{ {
@ -294,8 +312,9 @@ namespace Artemis.Core.Services
profileEntity.UpdateGuid(Guid.NewGuid()); profileEntity.UpdateGuid(Guid.NewGuid());
profileEntity.Name = $"{profileEntity.Name} - {nameAffix}"; profileEntity.Name = $"{profileEntity.Name} - {nameAffix}";
profileEntity.IsFreshImport = true;
_profileRepository.Add(profileEntity); _profileRepository.Add(profileEntity);
return new ProfileDescriptor(profileModule, profileEntity) {NeedsAdaption = true}; return new ProfileDescriptor(profileModule, profileEntity);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -17,6 +17,7 @@ namespace Artemis.Storage.Entities.Profile
public string Name { get; set; } public string Name { get; set; }
public bool IsActive { get; set; } public bool IsActive { get; set; }
public bool IsFreshImport { get; set; }
public List<FolderEntity> Folders { get; set; } public List<FolderEntity> Folders { get; set; }
public List<LayerEntity> Layers { get; set; } public List<LayerEntity> Layers { get; set; }

View File

@ -14,7 +14,6 @@ using Artemis.UI.Screens.ProfileEditor.LayerProperties;
using Artemis.UI.Screens.ProfileEditor.ProfileTree; using Artemis.UI.Screens.ProfileEditor.ProfileTree;
using Artemis.UI.Screens.ProfileEditor.Visualization; using Artemis.UI.Screens.ProfileEditor.Visualization;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using MaterialDesignThemes.Wpf;
using Stylet; using Stylet;
namespace Artemis.UI.Screens.ProfileEditor namespace Artemis.UI.Screens.ProfileEditor
@ -59,7 +58,6 @@ namespace Artemis.UI.Screens.ProfileEditor
Module = module; Module = module;
DialogService = dialogService; DialogService = dialogService;
DefaultProfiles = new BindableCollection<ProfileDescriptor>(module.DefaultProfiles);
Profiles = new BindableCollection<ProfileDescriptor>(); Profiles = new BindableCollection<ProfileDescriptor>();
// Populate the panels // Populate the panels
@ -100,9 +98,6 @@ namespace Artemis.UI.Screens.ProfileEditor
set => SetAndNotify(ref _profileViewModel, value); set => SetAndNotify(ref _profileViewModel, value);
} }
public BindableCollection<ProfileDescriptor> DefaultProfiles { get; }
public bool HasDefaultProfiles => DefaultProfiles.Any();
public BindableCollection<ProfileDescriptor> Profiles public BindableCollection<ProfileDescriptor> Profiles
{ {
get => _profiles; get => _profiles;
@ -392,9 +387,7 @@ namespace Artemis.UI.Screens.ProfileEditor
{ {
// Get all profiles from the database // Get all profiles from the database
Profiles.Clear(); Profiles.Clear();
Profiles.AddRange(_profileService.GetProfileDescriptors(Module)); Profiles.AddRange(_profileService.GetProfileDescriptors(Module).OrderBy(p => p.Name));
Profiles.AddRange(Module.DefaultProfiles.Where(d => Profiles.All(p => p.Id != d.Id)));
Profiles.Sort(p => p.Name);
} }
} }
} }