From 91cd23dadf324b618bde72c112f8a7ad542899db Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 22 Oct 2019 22:19:35 +0200 Subject: [PATCH] Fully implemented the surface service which contains all storage logic --- src/Artemis.Core/Artemis.Core.csproj | 1 + .../Events/DeviceConfigurationEventArgs.cs | 5 +- .../Models/Surface/SurfaceConfiguration.cs | 32 ++- .../Surface/SurfaceDeviceConfiguration.cs | 97 +++++++-- .../Services/Storage/ISurfaceService.cs | 54 +++++ .../Services/Storage/SurfaceService.cs | 206 ++++++++++-------- .../Interfaces/ISurfaceRepository.cs | 1 + .../Repositories/SurfaceRepository.cs | 5 + 8 files changed, 284 insertions(+), 117 deletions(-) create mode 100644 src/Artemis.Core/Services/Storage/ISurfaceService.cs diff --git a/src/Artemis.Core/Artemis.Core.csproj b/src/Artemis.Core/Artemis.Core.csproj index b672beb96..c5242f460 100644 --- a/src/Artemis.Core/Artemis.Core.csproj +++ b/src/Artemis.Core/Artemis.Core.csproj @@ -186,6 +186,7 @@ + diff --git a/src/Artemis.Core/Events/DeviceConfigurationEventArgs.cs b/src/Artemis.Core/Events/DeviceConfigurationEventArgs.cs index 36ad829e5..327647ef7 100644 --- a/src/Artemis.Core/Events/DeviceConfigurationEventArgs.cs +++ b/src/Artemis.Core/Events/DeviceConfigurationEventArgs.cs @@ -1,18 +1,15 @@ using System; using Artemis.Core.Models.Surface; -using RGB.NET.Core; namespace Artemis.Core.Events { public class SurfaceConfigurationEventArgs : EventArgs { - public SurfaceConfigurationEventArgs(SurfaceConfiguration surfaceConfiguration, IRGBDevice device) + public SurfaceConfigurationEventArgs(SurfaceConfiguration surfaceConfiguration) { SurfaceConfiguration = surfaceConfiguration; - Device = device; } public SurfaceConfiguration SurfaceConfiguration { get; } - public IRGBDevice Device { get; } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Surface/SurfaceConfiguration.cs b/src/Artemis.Core/Models/Surface/SurfaceConfiguration.cs index 4411ebb81..b59e54983 100644 --- a/src/Artemis.Core/Models/Surface/SurfaceConfiguration.cs +++ b/src/Artemis.Core/Models/Surface/SurfaceConfiguration.cs @@ -1,19 +1,27 @@ using System.Collections.Generic; -using System.Linq; using Artemis.Storage.Entities; -using RGB.NET.Core; namespace Artemis.Core.Models.Surface { public class SurfaceConfiguration { - internal SurfaceConfiguration() + internal SurfaceConfiguration(string name) { + SurfaceEntity = new SurfaceEntity(); + Guid = System.Guid.NewGuid().ToString(); + + Name = name; + IsActive = false; + DeviceConfigurations = new List(); + + ApplyToEntity(); } internal SurfaceConfiguration(SurfaceEntity surfaceEntity) { + SurfaceEntity = surfaceEntity; Guid = surfaceEntity.Guid; + Name = surfaceEntity.Name; IsActive = surfaceEntity.IsActive; DeviceConfigurations = new List(); @@ -24,9 +32,27 @@ namespace Artemis.Core.Models.Surface DeviceConfigurations.Add(new SurfaceDeviceConfiguration(position, this)); } + internal SurfaceEntity SurfaceEntity { get; set; } internal string Guid { get; set; } + public string Name { get; set; } public bool IsActive { get; internal set; } public List DeviceConfigurations { get; internal set; } + + internal void ApplyToEntity() + { + SurfaceEntity.Guid = Guid; + SurfaceEntity.Name = Name; + SurfaceEntity.IsActive = IsActive; + } + + internal void Destroy() + { + SurfaceEntity = null; + + foreach (var deviceConfiguration in DeviceConfigurations) + deviceConfiguration.Destroy(); + DeviceConfigurations.Clear(); + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Surface/SurfaceDeviceConfiguration.cs b/src/Artemis.Core/Models/Surface/SurfaceDeviceConfiguration.cs index 3e7a895ac..0f35084c3 100644 --- a/src/Artemis.Core/Models/Surface/SurfaceDeviceConfiguration.cs +++ b/src/Artemis.Core/Models/Surface/SurfaceDeviceConfiguration.cs @@ -1,49 +1,102 @@ -using Artemis.Storage.Entities; +using System.Linq; +using Artemis.Storage.Entities; using RGB.NET.Core; namespace Artemis.Core.Models.Surface { public class SurfaceDeviceConfiguration { - internal SurfaceDeviceConfiguration(int deviceId, IRGBDeviceInfo deviceInfo, SurfaceConfiguration surface) + internal SurfaceDeviceConfiguration(IRGBDevice device, int deviceId, SurfaceConfiguration surface) { + PositionEntity = new SurfacePositionEntity(); + Guid = System.Guid.NewGuid().ToString(); + + Device = device; DeviceId = deviceId; - DeviceName = deviceInfo.DeviceName; - DeviceModel = deviceInfo.Model; - DeviceManufacturer = deviceInfo.Manufacturer; + DeviceName = device.DeviceInfo.DeviceName; + DeviceModel = device.DeviceInfo.Model; + DeviceManufacturer = device.DeviceInfo.Manufacturer; + + X = device.Location.X; + Y = device.Location.Y; + Rotation = 0; + ZIndex = 1; Surface = surface; + + ApplyToEntity(); } - internal SurfaceDeviceConfiguration(SurfacePositionEntity position, SurfaceConfiguration surfaceConfiguration) + internal SurfaceDeviceConfiguration(SurfacePositionEntity positionEntity, SurfaceConfiguration surfaceConfiguration) { - Guid = position.Guid; + PositionEntity = positionEntity; + Guid = positionEntity.Guid; - DeviceId = position.DeviceId; - DeviceName = position.DeviceName; - DeviceModel = position.DeviceModel; - DeviceManufacturer = position.DeviceManufacturer; + DeviceId = positionEntity.DeviceId; + DeviceName = positionEntity.DeviceName; + DeviceModel = positionEntity.DeviceModel; + DeviceManufacturer = positionEntity.DeviceManufacturer; - X = position.X; - Y = position.Y; - Rotation = position.Rotation; - ZIndex = position.ZIndex; + X = positionEntity.X; + Y = positionEntity.Y; + Rotation = positionEntity.Rotation; + ZIndex = positionEntity.ZIndex; Surface = surfaceConfiguration; } - - internal string Guid { get; set; } - public int DeviceId { get; set; } - public string DeviceName { get; set; } - public string DeviceModel { get; set; } - public string DeviceManufacturer { get; set; } + internal SurfacePositionEntity PositionEntity { get; set; } + internal string Guid { get; } + + public IRGBDevice Device { get; private set; } + public int DeviceId { get; } + public string DeviceName { get; } + public string DeviceModel { get; } + public string DeviceManufacturer { get; } public double X { get; set; } public double Y { get; set; } public double Rotation { get; set; } public int ZIndex { get; set; } - public SurfaceConfiguration Surface { get; internal set; } + public SurfaceConfiguration Surface { get; private set; } + + /// + /// Applies the configuration to the device + /// + public void ApplyToDevice() + { + Device.Location = new Point(X, Y); + } + + /// + /// Must be called when saving to the database + /// + internal void ApplyToEntity() + { + PositionEntity.Guid = Guid; + PositionEntity.SurfaceId = Surface.Guid; + + PositionEntity.DeviceId = DeviceId; + PositionEntity.DeviceName = DeviceName; + PositionEntity.DeviceModel = DeviceModel; + PositionEntity.DeviceManufacturer = DeviceManufacturer; + + PositionEntity.X = X; + PositionEntity.Y = Y; + PositionEntity.Rotation = Rotation; + PositionEntity.ZIndex = ZIndex; + + // Ensure the position entity is in the surface entity's' collection of positions + if (Surface.SurfaceEntity.SurfacePositions.All(p => p.Guid != Guid)) + Surface.SurfaceEntity.SurfacePositions.Add(PositionEntity); + } + + public void Destroy() + { + PositionEntity = null; + Device = null; + Surface = null; + } } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/ISurfaceService.cs b/src/Artemis.Core/Services/Storage/ISurfaceService.cs new file mode 100644 index 000000000..f7e965d3f --- /dev/null +++ b/src/Artemis.Core/Services/Storage/ISurfaceService.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Artemis.Core.Events; +using Artemis.Core.Models.Surface; +using Artemis.Core.Services.Interfaces; + +namespace Artemis.Core.Services.Storage +{ + public interface ISurfaceService : IArtemisService + { + /// + /// Gets or sets the currently active surface configuration + /// + SurfaceConfiguration ActiveSurfaceConfiguration { get; set; } + + /// + /// Gets a read-only list of all surface configurations + /// + ReadOnlyCollection SurfaceConfigurations { get; } + + /// + /// Creates a new surface configuration with the supplied name + /// + /// The name for the new surface configuration + /// + SurfaceConfiguration CreateSurfaceConfiguration(string name); + + /// + /// Deletes the supplied surface configuration, surface configuration may not be the active surface configuration + /// + /// The surface configuration to delete, may not be the active surface configuration + void DeleteSurfaceConfiguration(SurfaceConfiguration surfaceConfiguration); + + /// + /// Saves the provided surface configurations to permanent storage + /// + /// The configurations to save + /// Whether to also save devices + void SaveToRepository(List surfaceConfigurations, bool includeDevices); + + /// + /// Saves the provided surface configuration to permanent storage + /// + /// The configuration to save + /// Whether to also save devices + void SaveToRepository(SurfaceConfiguration surfaceConfiguration, bool includeDevices); + + /// + /// Occurs when the active device configuration has been changed + /// + event EventHandler ActiveSurfaceConfigurationChanged; + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/SurfaceService.cs b/src/Artemis.Core/Services/Storage/SurfaceService.cs index d02c54c93..ea3d28453 100644 --- a/src/Artemis.Core/Services/Storage/SurfaceService.cs +++ b/src/Artemis.Core/Services/Storage/SurfaceService.cs @@ -1,8 +1,9 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; -using System.Threading.Tasks; using Artemis.Core.Events; +using Artemis.Core.Exceptions; using Artemis.Core.Models.Surface; using Artemis.Core.Services.Interfaces; using Artemis.Storage.Repositories.Interfaces; @@ -14,100 +15,155 @@ namespace Artemis.Core.Services.Storage public class SurfaceService : ISurfaceService { private readonly ILogger _logger; - private readonly ISurfaceRepository _surfaceRepository; private readonly IRgbService _rgbService; + private readonly List _surfaceConfigurations; + private readonly ISurfaceRepository _surfaceRepository; + private SurfaceConfiguration _activeSurfaceConfiguration; public SurfaceService(ILogger logger, ISurfaceRepository surfaceRepository, IRgbService rgbService) { _logger = logger; _surfaceRepository = surfaceRepository; _rgbService = rgbService; + _surfaceConfigurations = new List(); + + LoadFromRepository(); _rgbService.DeviceLoaded += RgbServiceOnDeviceLoaded; } - public async Task> GetSurfaceConfigurationsAsync() + public SurfaceConfiguration ActiveSurfaceConfiguration { - var surfaceEntities = await _surfaceRepository.GetAllAsync(); - var configs = new List(); - foreach (var surfaceEntity in surfaceEntities) - configs.Add(new SurfaceConfiguration(surfaceEntity)); + get => _activeSurfaceConfiguration; + set + { + if (_activeSurfaceConfiguration == value) + return; - return configs; + _activeSurfaceConfiguration = value; + + // Mark only the new value as active + foreach (var surfaceConfiguration in _surfaceConfigurations) + surfaceConfiguration.IsActive = false; + _activeSurfaceConfiguration.IsActive = true; + + SaveToRepository(_surfaceConfigurations, true); + OnActiveSurfaceConfigurationChanged(new SurfaceConfigurationEventArgs(_activeSurfaceConfiguration)); + } } - public async Task GetActiveSurfaceConfigurationAsync() - { - var entity = (await _surfaceRepository.GetAllAsync()).FirstOrDefault(d => d.IsActive); - return entity != null ? new SurfaceConfiguration(entity) : null; - } - - public async Task SetActiveSurfaceConfigurationAsync(SurfaceConfiguration surfaceConfiguration) - { - var surfaceEntities = await _surfaceRepository.GetAllAsync(); - foreach (var surfaceEntity in surfaceEntities) - surfaceEntity.IsActive = surfaceEntity.Guid == surfaceConfiguration.Guid; - - await _surfaceRepository.SaveAsync(); - } - - public List GetSurfaceConfigurations() - { - var surfaceEntities = _surfaceRepository.GetAll(); - var configs = new List(); - foreach (var surfaceEntity in surfaceEntities) - configs.Add(new SurfaceConfiguration(surfaceEntity)); - - return configs; - } - - public SurfaceConfiguration GetActiveSurfaceConfiguration() - { - var entity = _surfaceRepository.GetAll().FirstOrDefault(d => d.IsActive); - return entity != null ? new SurfaceConfiguration(entity) : null; - } - - public void SetActiveSurfaceConfiguration(SurfaceConfiguration surfaceConfiguration) - { - var surfaceEntities = _surfaceRepository.GetAll(); - foreach (var surfaceEntity in surfaceEntities) - surfaceEntity.IsActive = surfaceEntity.Guid == surfaceConfiguration.Guid; - - _surfaceRepository.Save(); - } + public ReadOnlyCollection SurfaceConfigurations => _surfaceConfigurations.AsReadOnly(); public SurfaceConfiguration CreateSurfaceConfiguration(string name) { // Create a blank config - var configuration = new SurfaceConfiguration {Name = name, DeviceConfigurations = new List()}; + var configuration = new SurfaceConfiguration(name); // Add all current devices foreach (var rgbDevice in _rgbService.LoadedDevices) { var deviceId = GetDeviceId(rgbDevice); - configuration.DeviceConfigurations.Add(new SurfaceDeviceConfiguration(deviceId, rgbDevice.DeviceInfo, configuration)); + configuration.DeviceConfigurations.Add(new SurfaceDeviceConfiguration(rgbDevice, deviceId, configuration)); } + _surfaceRepository.Add(configuration.SurfaceEntity); + SaveToRepository(configuration, true); return configuration; } - private void ApplyDeviceConfiguration(IRGBDevice rgbDevice, SurfaceConfiguration surface) + public void DeleteSurfaceConfiguration(SurfaceConfiguration surfaceConfiguration) + { + if (surfaceConfiguration == ActiveSurfaceConfiguration) + throw new ArtemisCoreException($"Cannot delete surface configuration '{surfaceConfiguration.Name}' because it is active."); + + surfaceConfiguration.Destroy(); + _surfaceConfigurations.Remove(surfaceConfiguration); + + _surfaceRepository.Remove(surfaceConfiguration.SurfaceEntity); + _surfaceRepository.Save(); + } + + #region Event handlers + + private void RgbServiceOnDeviceLoaded(object sender, DeviceEventArgs e) + { + // Match the newly loaded device with the current config + if (ActiveSurfaceConfiguration != null) + MatchDeviceConfiguration(e.Device, ActiveSurfaceConfiguration); + } + + #endregion + + #region Repository + + private void LoadFromRepository() + { + var configs = _surfaceRepository.GetAll(); + foreach (var surfaceEntity in configs) + { + // Create the surface configuration + var surfaceConfiguration = new SurfaceConfiguration(surfaceEntity); + // For each loaded device, match a device configuration + var devices = _rgbService.LoadedDevices; + foreach (var rgbDevice in devices) + MatchDeviceConfiguration(rgbDevice, surfaceConfiguration); + // Finally, add the surface config to the collection + _surfaceConfigurations.Add(surfaceConfiguration); + } + + // When all surface configs are loaded, apply the active surface config + var active = SurfaceConfigurations.FirstOrDefault(c => c.IsActive); + if (active != null) + ActiveSurfaceConfiguration = active; + } + + public void SaveToRepository(List surfaceConfigurations, bool includeDevices) + { + foreach (var surfaceConfiguration in surfaceConfigurations) + { + surfaceConfiguration.ApplyToEntity(); + if (!includeDevices) + { + foreach (var deviceConfiguration in surfaceConfiguration.DeviceConfigurations) + deviceConfiguration.ApplyToEntity(); + } + } + + _surfaceRepository.Save(); + } + + public void SaveToRepository(SurfaceConfiguration surfaceConfiguration, bool includeDevices) + { + surfaceConfiguration.ApplyToEntity(); + if (includeDevices) + { + foreach (var deviceConfiguration in surfaceConfiguration.DeviceConfigurations) + deviceConfiguration.ApplyToEntity(); + } + + _surfaceRepository.Save(); + } + + #endregion + + #region Utilities + + private void MatchDeviceConfiguration(IRGBDevice rgbDevice, SurfaceConfiguration surfaceConfiguration) { var deviceId = GetDeviceId(rgbDevice); - var deviceConfig = surface.DeviceConfigurations.FirstOrDefault(d => d.DeviceName == rgbDevice.DeviceInfo.DeviceName && - d.DeviceModel == rgbDevice.DeviceInfo.Model && - d.DeviceManufacturer == rgbDevice.DeviceInfo.Manufacturer && - d.DeviceId == deviceId); + var deviceConfig = surfaceConfiguration.DeviceConfigurations.FirstOrDefault(d => d.DeviceName == rgbDevice.DeviceInfo.DeviceName && + d.DeviceModel == rgbDevice.DeviceInfo.Model && + d.DeviceManufacturer == rgbDevice.DeviceInfo.Manufacturer && + d.DeviceId == deviceId); if (deviceConfig == null) { _logger.Information("No active surface config found for {deviceInfo}, device ID: {deviceId}. Adding a new entry.", rgbDevice.DeviceInfo, deviceId); - deviceConfig = new SurfaceDeviceConfiguration(deviceId, rgbDevice.DeviceInfo, surface); - surface.DeviceConfigurations.Add(deviceConfig); + deviceConfig = new SurfaceDeviceConfiguration(rgbDevice, deviceId, surfaceConfiguration); + surfaceConfiguration.DeviceConfigurations.Add(deviceConfig); } - rgbDevice.Location = new Point(deviceConfig.X, deviceConfig.Y); - OnDeviceConfigurationApplied(new SurfaceConfigurationEventArgs(surface, rgbDevice)); + deviceConfig.ApplyToDevice(); } private int GetDeviceId(IRGBDevice rgbDevice) @@ -120,43 +176,17 @@ namespace Artemis.Core.Services.Storage .IndexOf(rgbDevice) + 1; } - private void RgbServiceOnDeviceLoaded(object sender, DeviceEventArgs e) - { - var activeConfiguration = GetActiveSurfaceConfiguration(); - if (activeConfiguration == null) - { - _logger.Information("No active surface config found, cannot apply settings to {deviceInfo}", e.Device.DeviceInfo); - return; - } - - ApplyDeviceConfiguration(e.Device, GetActiveSurfaceConfiguration()); - } + #endregion #region Events - public event EventHandler SurfaceConfigurationApplied; + public event EventHandler ActiveSurfaceConfigurationChanged; - private void OnDeviceConfigurationApplied(SurfaceConfigurationEventArgs e) + protected virtual void OnActiveSurfaceConfigurationChanged(SurfaceConfigurationEventArgs e) { - SurfaceConfigurationApplied?.Invoke(this, e); + ActiveSurfaceConfigurationChanged?.Invoke(this, e); } #endregion } - - public interface ISurfaceService : IArtemisService - { - Task> GetSurfaceConfigurationsAsync(); - Task GetActiveSurfaceConfigurationAsync(); - Task SetActiveSurfaceConfigurationAsync(SurfaceConfiguration surfaceConfiguration); - List GetSurfaceConfigurations(); - SurfaceConfiguration GetActiveSurfaceConfiguration(); - SurfaceConfiguration CreateSurfaceConfiguration(string name); - void SetActiveSurfaceConfiguration(SurfaceConfiguration surfaceConfiguration); - - /// - /// Occurs when a device has a new surface configuration applied to it - /// - event EventHandler SurfaceConfigurationApplied; - } } \ No newline at end of file diff --git a/src/Artemis.Storage/Repositories/Interfaces/ISurfaceRepository.cs b/src/Artemis.Storage/Repositories/Interfaces/ISurfaceRepository.cs index 47be22342..220fdc2e4 100644 --- a/src/Artemis.Storage/Repositories/Interfaces/ISurfaceRepository.cs +++ b/src/Artemis.Storage/Repositories/Interfaces/ISurfaceRepository.cs @@ -7,6 +7,7 @@ namespace Artemis.Storage.Repositories.Interfaces public interface ISurfaceRepository : IRepository { void Add(SurfaceEntity surfaceEntity); + void Remove(SurfaceEntity surfaceEntity); SurfaceEntity Get(string name); Task GetAsync(string name); List GetAll(); diff --git a/src/Artemis.Storage/Repositories/SurfaceRepository.cs b/src/Artemis.Storage/Repositories/SurfaceRepository.cs index 69ef4d6c7..0d29da7c2 100644 --- a/src/Artemis.Storage/Repositories/SurfaceRepository.cs +++ b/src/Artemis.Storage/Repositories/SurfaceRepository.cs @@ -22,6 +22,11 @@ namespace Artemis.Storage.Repositories _dbContext.Surfaces.Add(surfaceEntity); } + public void Remove(SurfaceEntity surfaceEntity) + { + _dbContext.Surfaces.Remove(surfaceEntity); + } + public SurfaceEntity Get(string name) { return _dbContext.Surfaces.Include(s => s.SurfacePositions).FirstOrDefault(p => p.Name == name);