diff --git a/src/Artemis.Core/Models/Profile/Profile.cs b/src/Artemis.Core/Models/Profile/Profile.cs index 09e1dc62e..8bb05cb45 100644 --- a/src/Artemis.Core/Models/Profile/Profile.cs +++ b/src/Artemis.Core/Models/Profile/Profile.cs @@ -14,6 +14,7 @@ namespace Artemis.Core { private readonly object _lock = new(); private bool _isActivated; + private bool _isFreshImport; internal Profile(ProfileModule module, string name) : base(null!) { @@ -57,6 +58,20 @@ namespace Artemis.Core private set => SetAndNotify(ref _isActivated, value); } + /// + /// Gets or sets a boolean indicating whether this profile is freshly imported i.e. no changes have been made to it + /// since import + /// + /// Note: As long as this is , profile adaption will be performed on load and any surface + /// changes + /// + /// + public bool IsFreshImport + { + get => _isFreshImport; + set => SetAndNotify(ref _isFreshImport, value); + } + /// /// Gets the profile entity this profile uses for persistent storage /// @@ -134,6 +149,16 @@ namespace Artemis.Core layer.PopulateLeds(devices); } + /// + /// Occurs when the profile has been activated. + /// + public event EventHandler? Activated; + + /// + /// Occurs when the profile is being deactivated. + /// + public event EventHandler? Deactivated; + /// protected override void Dispose(bool disposing) { @@ -156,6 +181,7 @@ namespace Artemis.Core throw new ObjectDisposedException("Profile"); Name = ProfileEntity.Name; + IsFreshImport = ProfileEntity.IsFreshImport; lock (ChildrenList) { @@ -171,9 +197,7 @@ namespace Artemis.Core Folder _ = new(this, "Root folder"); } else - { AddChild(new Folder(this, this, rootFolder)); - } } } @@ -186,6 +210,7 @@ namespace Artemis.Core ProfileEntity.ModuleId = Module.Id; ProfileEntity.Name = Name; ProfileEntity.IsActive = IsActivated; + ProfileEntity.IsFreshImport = IsFreshImport; foreach (ProfileElement profileElement in Children) profileElement.Save(); @@ -196,7 +221,7 @@ namespace Artemis.Core ProfileEntity.Layers.Clear(); ProfileEntity.Layers.AddRange(GetAllLayers().Select(f => f.LayerEntity)); } - + internal void Activate(IEnumerable devices) { lock (_lock) @@ -212,18 +237,6 @@ namespace Artemis.Core } } - #region Events - - /// - /// Occurs when the profile has been activated. - /// - public event EventHandler? Activated; - - /// - /// Occurs when the profile is being deactivated. - /// - public event EventHandler? Deactivated; - private void OnActivated() { Activated?.Invoke(this, EventArgs.Empty); @@ -233,7 +246,5 @@ namespace Artemis.Core { Deactivated?.Invoke(this, EventArgs.Empty); } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/ProfileDescriptor.cs b/src/Artemis.Core/Models/Profile/ProfileDescriptor.cs index 7171577fc..7e5d776e5 100644 --- a/src/Artemis.Core/Models/Profile/ProfileDescriptor.cs +++ b/src/Artemis.Core/Models/Profile/ProfileDescriptor.cs @@ -37,10 +37,5 @@ namespace Artemis.Core /// Gets a boolean indicating whether this was the last active profile /// public bool IsLastActiveProfile { get; } - - /// - /// Gets or sets a boolean indicating whether the profile will be adapted the next time it is activated - /// - public bool NeedsAdaption { get; set; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs index 0e04cd3b6..cfbdc89e0 100644 --- a/src/Artemis.Core/Plugins/Modules/ProfileModule.cs +++ b/src/Artemis.Core/Plugins/Modules/ProfileModule.cs @@ -7,6 +7,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using Artemis.Core.DataModelExpansions; +using Artemis.Core.Services; using Artemis.Storage.Entities.Profile; using Newtonsoft.Json; using SkiaSharp; @@ -94,7 +95,9 @@ namespace Artemis.Core.Modules /// public abstract class ProfileModule : Module { - private readonly List _defaultProfiles; + private readonly List _defaultProfilePaths = new(); + private readonly List _pendingDefaultProfilePaths = new(); + private readonly List _defaultProfiles = new(); private readonly object _lock = new(); /// @@ -102,13 +105,13 @@ namespace Artemis.Core.Modules /// protected internal readonly List HiddenPropertiesList = new(); + /// /// Creates a new instance of the class /// protected ProfileModule() { OpacityOverride = 1; - _defaultProfiles = new List(); } /// @@ -139,7 +142,7 @@ namespace Artemis.Core.Modules /// /// Gets a list of default profiles, to add a new default profile use /// - public ReadOnlyCollection DefaultProfiles => _defaultProfiles.AsReadOnly(); + internal ReadOnlyCollection DefaultProfiles => _defaultProfiles.AsReadOnly(); /// /// Called after the profile has updated @@ -167,22 +170,44 @@ namespace Artemis.Core.Modules /// /// Adds a default profile by reading it from the file found at the provided path /// - /// - protected void AddDefaultProfile(string file) + /// A path pointing towards a profile file. May be relative to the plugin directory. + /// + /// if the default profile was added; if it was not because it is + /// already in the list. + /// + 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 if (!File.Exists(file)) throw new ArtemisPluginFeatureException(this, $"Could not find default profile at {file}."); // Deserialize and make sure that succeeded - ProfileEntity? profileEntity = JsonConvert.DeserializeObject(File.ReadAllText(file)); + ProfileEntity? profileEntity = JsonConvert.DeserializeObject(File.ReadAllText(file), ProfileService.ExportSettings); if (profileEntity == null) throw new ArtemisPluginFeatureException(this, $"Failed to deserialize default profile at {file}."); // Ensure the profile ID is unique - ProfileDescriptor descriptor = new(this, profileEntity) {NeedsAdaption = true}; - if (_defaultProfiles.Any(d => d.Id == descriptor.Id)) - throw new ArtemisPluginFeatureException(this, $"Cannot add default profile from {file}, profile ID {descriptor.Id} already in use."); + if (_defaultProfiles.Any(d => d.Id == profileEntity.Id)) + throw new ArtemisPluginFeatureException(this, $"Cannot add default profile from {file}, profile ID {profileEntity.Id} already in use."); - _defaultProfiles.Add(descriptor); + profileEntity.IsFreshImport = true; + _defaultProfiles.Add(profileEntity); + + return true; } /// @@ -193,6 +218,15 @@ namespace Artemis.Core.Modules 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) { StartUpdateMeasure(); diff --git a/src/Artemis.Core/Services/ModuleService.cs b/src/Artemis.Core/Services/ModuleService.cs index a6f85bcfd..22bc8299c 100644 --- a/src/Artemis.Core/Services/ModuleService.cs +++ b/src/Artemis.Core/Services/ModuleService.cs @@ -6,6 +6,7 @@ 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 Serilog; using Timer = System.Timers.Timer; @@ -17,13 +18,15 @@ namespace Artemis.Core.Services private static readonly SemaphoreSlim ActiveModuleSemaphore = new(1, 1); private readonly ILogger _logger; private readonly IModuleRepository _moduleRepository; + private readonly IProfileRepository _profileRepository; private readonly IPluginManagementService _pluginManagementService; 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; _moduleRepository = moduleRepository; + _profileRepository = profileRepository; _pluginManagementService = pluginManagementService; _profileService = profileService; _pluginManagementService.PluginFeatureEnabled += OnPluginFeatureEnabled; @@ -45,12 +48,24 @@ namespace Artemis.Core.Services { try { + ProfileModule? profileModule = module as ProfileModule; + + if (profileModule != null && profileModule.DefaultProfiles.Any()) + { + List 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 (module is ProfileModule profileModule) + if (profileModule != null) await _profileService.ActivateLastProfileAnimated(profileModule); } catch (Exception e) diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 380c9e563..61c5f147a 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -32,8 +32,8 @@ namespace Artemis.Core.Services _rgbService.LedsChanged += RgbServiceOnLedsChanged; } - public JsonSerializerSettings MementoSettings { get; set; } = new() {TypeNameHandling = TypeNameHandling.All}; - public JsonSerializerSettings ExportSettings { get; set; } = new() {TypeNameHandling = TypeNameHandling.All, Formatting = Formatting.Indented}; + 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) { @@ -64,8 +64,16 @@ namespace Artemis.Core.Services private void ActiveProfilesPopulateLeds() { List profileModules = _pluginManagementService.GetFeaturesOfType(); - foreach (ProfileModule profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList()) - profileModule.ActiveProfile?.PopulateLeds(_rgbService.EnabledDevices); // Avoid race condition + foreach (ProfileModule profileModule in profileModules) + { + // 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 GetProfileDescriptors(ProfileModule module) @@ -107,13 +115,14 @@ namespace Artemis.Core.Services Profile profile = new(profileDescriptor.ProfileModule, profileEntity); InstantiateProfile(profile); - if (profileDescriptor.NeedsAdaption) - { - AdaptProfile(profile); - profileDescriptor.NeedsAdaption = false; - } 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); return profile; @@ -143,7 +152,7 @@ namespace Artemis.Core.Services Profile profile = new(profileDescriptor.ProfileModule, profileEntity); InstantiateProfile(profile); - + void ActivatingRgbServiceOnLedsChanged(object? sender, EventArgs e) { profile.PopulateLeds(_rgbService.EnabledDevices); @@ -161,6 +170,12 @@ namespace Artemis.Core.Services _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; @@ -198,6 +213,8 @@ namespace Artemis.Core.Services public void DeleteProfile(ProfileDescriptor profileDescriptor) { ProfileEntity profileEntity = _profileRepository.Get(profileDescriptor.Id); + if (profileEntity == null) + return; _profileRepository.Remove(profileEntity); } @@ -208,6 +225,7 @@ namespace Artemis.Core.Services profile.RedoStack.Clear(); profile.UndoStack.Push(memento); + profile.IsFreshImport = false; profile.Save(); if (includeChildren) { @@ -294,8 +312,9 @@ namespace Artemis.Core.Services profileEntity.UpdateGuid(Guid.NewGuid()); profileEntity.Name = $"{profileEntity.Name} - {nameAffix}"; + profileEntity.IsFreshImport = true; _profileRepository.Add(profileEntity); - return new ProfileDescriptor(profileModule, profileEntity) {NeedsAdaption = true}; + return new ProfileDescriptor(profileModule, profileEntity); } /// diff --git a/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs b/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs index 177d8c00b..8fac49757 100644 --- a/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs +++ b/src/Artemis.Storage/Entities/Profile/ProfileEntity.cs @@ -17,6 +17,7 @@ namespace Artemis.Storage.Entities.Profile public string Name { get; set; } public bool IsActive { get; set; } + public bool IsFreshImport { get; set; } public List Folders { get; set; } public List Layers { get; set; } diff --git a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs index 7f7b69743..7e9662b5e 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/ProfileEditorViewModel.cs @@ -14,7 +14,6 @@ using Artemis.UI.Screens.ProfileEditor.LayerProperties; using Artemis.UI.Screens.ProfileEditor.ProfileTree; using Artemis.UI.Screens.ProfileEditor.Visualization; using Artemis.UI.Shared.Services; -using MaterialDesignThemes.Wpf; using Stylet; namespace Artemis.UI.Screens.ProfileEditor @@ -59,7 +58,6 @@ namespace Artemis.UI.Screens.ProfileEditor Module = module; DialogService = dialogService; - DefaultProfiles = new BindableCollection(module.DefaultProfiles); Profiles = new BindableCollection(); // Populate the panels @@ -100,9 +98,6 @@ namespace Artemis.UI.Screens.ProfileEditor set => SetAndNotify(ref _profileViewModel, value); } - public BindableCollection DefaultProfiles { get; } - public bool HasDefaultProfiles => DefaultProfiles.Any(); - public BindableCollection Profiles { get => _profiles; @@ -392,9 +387,7 @@ namespace Artemis.UI.Screens.ProfileEditor { // Get all profiles from the database Profiles.Clear(); - Profiles.AddRange(_profileService.GetProfileDescriptors(Module)); - Profiles.AddRange(Module.DefaultProfiles.Where(d => Profiles.All(p => p.Id != d.Id))); - Profiles.Sort(p => p.Name); + Profiles.AddRange(_profileService.GetProfileDescriptors(Module).OrderBy(p => p.Name)); } } } \ No newline at end of file