Devices - Added custom layout loading

This commit is contained in:
Robert committed 2021-02-19 16:42:09 +01:00
1 parent 056b2bface
commit b154badd9c
21 files changed
+414 -110

No files matched your search

@@ -249,6 +249,20 @@ namespace Artemis.Core
}
}
/// <summary>
/// Gets or sets the path of the custom layout to load when calling <see cref="IRgbService.ApplyBestDeviceLayout" />
/// for this device
/// </summary>
public string? CustomLayoutPath
{
get => DeviceEntity.CustomLayoutPath;
set
{
DeviceEntity.CustomLayoutPath = value;
OnPropertyChanged(nameof(CustomLayoutPath));
}
}
/// <summary>
/// Gets the layout of the device expanded with Artemis-specific data
/// </summary>
@@ -304,10 +318,12 @@ namespace Artemis.Core
/// Applies the provided layout to the device
/// </summary>
/// <param name="layout">The layout to apply</param>
internal void ApplyLayout(ArtemisLayout layout)
/// <param name="createMissingLeds">A boolean indicating whether to add missing LEDs defined in the layout but missing on the device</param>
/// <param name="removeExcessiveLeds">A boolean indicating whether to remove excess LEDs present in the device but missing in the layout</param>
internal void ApplyLayout(ArtemisLayout layout, bool createMissingLeds, bool removeExcessiveLeds)
{
if (layout.IsValid)
layout.RgbLayout!.ApplyTo(RgbDevice);
layout.RgbLayout!.ApplyTo(RgbDevice, createMissingLeds, removeExcessiveLeds);
Leds = RgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly();
LedIds = new ReadOnlyDictionary<LedId, ArtemisLed>(Leds.ToDictionary(l => l.RgbLed.Id, l => l));
@@ -15,9 +15,11 @@ namespace Artemis.Core
/// Creates a new instance of the <see cref="ArtemisLayout" /> class
/// </summary>
/// <param name="filePath">The path of the layout XML file</param>
public ArtemisLayout(string filePath)
/// <param name="source">The source from where this layout is being loaded</param>
public ArtemisLayout(string filePath, LayoutSource source)
{
FilePath = filePath;
Source = source;
Leds = new List<ArtemisLedLayout>();
LoadLayout();
@@ -29,9 +31,9 @@ namespace Artemis.Core
public string FilePath { get; }
/// <summary>
/// Gets the RGB.NET device layout
/// Gets the source from where this layout was loaded
/// </summary>
public DeviceLayout RgbLayout { get; private set; }
public LayoutSource Source { get; }
/// <summary>
/// Gets the device this layout is applied to
@@ -48,9 +50,20 @@ namespace Artemis.Core
/// </summary>
public Uri? Image { get; private set; }
/// <summary>
/// Gets a list of LEDs this layout contains
/// </summary>
public List<ArtemisLedLayout> Leds { get; }
internal LayoutCustomDeviceData LayoutCustomDeviceData { get; set; }
/// <summary>
/// Gets the RGB.NET device layout
/// </summary>
public DeviceLayout RgbLayout { get; private set; } = null!;
/// <summary>
/// Gets the custom layout data embedded in the RGB.NET layout
/// </summary>
public LayoutCustomDeviceData LayoutCustomDeviceData { get; private set; } = null!;
public void ReloadFromDisk()
{
@@ -88,11 +101,43 @@ namespace Artemis.Core
private void ApplyCustomDeviceData()
{
Uri layoutDirectory = new(Path.GetDirectoryName(FilePath)! + "\\", UriKind.Absolute);
if (!IsValid)
{
Image = null;
return;
}
Uri layoutDirectory = new(Path.GetDirectoryName(FilePath)! + "/", UriKind.Absolute);
if (LayoutCustomDeviceData.DeviceImage != null)
Image = new Uri(layoutDirectory, new Uri(LayoutCustomDeviceData.DeviceImage, UriKind.Relative));
else
Image = null;
}
}
/// <summary>
/// Represents a source from where a layout came
/// </summary>
public enum LayoutSource
{
/// <summary>
/// A layout loaded from config
/// </summary>
Configured,
/// <summary>
/// A layout loaded from the user layout folder
/// </summary>
User,
/// <summary>
/// A layout loaded from the plugin folder
/// </summary>
Plugin,
/// <summary>
/// A default layout loaded as a fallback option
/// </summary>
Default
}
}
@@ -39,7 +39,10 @@ namespace Artemis.Core
/// </summary>
public Uri? Image { get; private set; }
internal LayoutCustomLedData LayoutCustomLedData { get; set; }
/// <summary>
/// Gets the custom layout data embedded in the RGB.NET layout
/// </summary>
public LayoutCustomLedData LayoutCustomLedData { get; }
internal void ApplyDevice(ArtemisDevice device)
{
@@ -60,18 +60,35 @@ namespace Artemis.Core.DeviceProviders
/// <summary>
/// Loads a layout for the specified device and wraps it in an <see cref="ArtemisLayout" />
/// </summary>
/// <param name="rgbDevice">The device to load the layout for</param>
/// <param name="device">The device to load the layout for</param>
/// <returns>The resulting Artemis layout</returns>
public virtual ArtemisLayout LoadLayout(ArtemisDevice rgbDevice)
public virtual ArtemisLayout LoadLayout(ArtemisDevice device)
{
string layoutDir = Path.Combine(Plugin.Directory.FullName, "Layouts");
string filePath = Path.Combine(
layoutDir,
rgbDevice.RgbDevice.DeviceInfo.Manufacturer,
rgbDevice.RgbDevice.DeviceInfo.DeviceType.ToString(),
rgbDevice.GetLayoutFileName()
device.RgbDevice.DeviceInfo.Manufacturer,
device.RgbDevice.DeviceInfo.DeviceType.ToString(),
device.GetLayoutFileName()
);
return new ArtemisLayout(filePath);
return new ArtemisLayout(filePath, LayoutSource.Plugin);
}
/// <summary>
/// Loads a layout from the user layout folder for the specified device and wraps it in an <see cref="ArtemisLayout" />
/// </summary>
/// <param name="device">The device to load the layout for</param>
/// <returns>The resulting Artemis layout</returns>
public virtual ArtemisLayout LoadUserLayout(ArtemisDevice device)
{
string layoutDir = Path.Combine(Constants.DataFolder, "user layouts");
string filePath = Path.Combine(
layoutDir,
device.RgbDevice.DeviceInfo.Manufacturer,
device.RgbDevice.DeviceInfo.DeviceType.ToString(),
device.GetLayoutFileName()
);
return new ArtemisLayout(filePath, LayoutSource.User);
}
/// <summary>
@@ -74,7 +74,9 @@ namespace Artemis.Core.Services
/// </summary>
/// <param name="device"></param>
/// <param name="layout"></param>
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout);
/// <param name="createMissingLeds"></param>
/// <param name="removeExessiveLeds"></param>
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout, bool createMissingLeds, bool removeExessiveLeds);
/// <summary>
/// Attempts to retrieve the <see cref="ArtemisDevice" /> that corresponds the provided RGB.NET
@@ -9,7 +9,6 @@ using Artemis.Core.DeviceProviders;
using Artemis.Core.Ninject;
using Artemis.Storage.Entities.Plugins;
using Artemis.Storage.Repositories.Interfaces;
using Humanizer;
using McMaster.NETCore.Plugins;
using Ninject;
using Ninject.Extensions.ChildKernel;
@@ -147,7 +146,7 @@ namespace Artemis.Core.Services
// TODO: move to a more appropriate service
public DeviceProvider GetDeviceProviderByDevice(IRGBDevice rgbDevice)
{
return GetFeaturesOfType<DeviceProvider>().First(d => d.RgbDeviceProvider.Devices != null && d.RgbDeviceProvider.Devices.Contains(rgbDevice));
return GetFeaturesOfType<DeviceProvider>().First(d => d.RgbDeviceProvider.Devices.Contains(rgbDevice));
}
public Plugin? GetCallingPlugin()
@@ -186,7 +185,6 @@ namespace Artemis.Core.Services
// Load the plugin assemblies into the plugin context
DirectoryInfo pluginDirectory = new(Path.Combine(Constants.DataFolder, "plugins"));
foreach (DirectoryInfo subDirectory in pluginDirectory.EnumerateDirectories())
{
try
{
LoadPlugin(subDirectory);
@@ -195,7 +193,6 @@ namespace Artemis.Core.Services
{
_logger.Warning(new ArtemisPluginException("Failed to load plugin", e), "Plugin exception");
}
}
// ReSharper disable InconsistentlySynchronizedField - It's read-only, idc
_logger.Debug("Loaded {count} plugin(s)", _plugins.Count);
@@ -338,7 +335,6 @@ namespace Artemis.Core.Services
// Create instances of each feature and add them to the plugin
// Construction should be simple and not contain any logic so failure at this point means the entire plugin fails
foreach (Type featureType in featureTypes)
{
try
{
plugin.Kernel.Bind(featureType).ToSelf().InSingletonScope();
@@ -360,11 +356,9 @@ namespace Artemis.Core.Services
{
_logger.Warning(new ArtemisPluginException(plugin, "Failed to instantiate feature", e), "Failed to instantiate feature", plugin);
}
}
// Activate plugins after they are all loaded
foreach (PluginFeature pluginFeature in plugin.Features.Where(i => i.Entity.IsEnabled))
{
try
{
EnablePluginFeature(pluginFeature, false, !ignorePluginLock);
@@ -373,7 +367,6 @@ namespace Artemis.Core.Services
{
// ignored, logged in EnablePluginFeature
}
}
if (saveState)
{
@@ -474,13 +467,11 @@ namespace Artemis.Core.Services
Directory.CreateDirectory(directoryInfo.FullName);
string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, "");
foreach (ZipArchiveEntry zipArchiveEntry in archive.Entries)
{
if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory))
{
string target = Path.Combine(directoryInfo.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length));
zipArchiveEntry.ExtractToFile(target);
}
}
// Load the newly extracted plugin and return the result
return LoadPlugin(directoryInfo);
@@ -563,10 +554,8 @@ namespace Artemis.Core.Services
private void SavePlugin(Plugin plugin)
{
foreach (PluginFeature pluginFeature in plugin.Features)
{
if (plugin.Entity.Features.All(i => i.Type != pluginFeature.GetType().FullName))
plugin.Entity.Features.Add(pluginFeature.Entity);
}
_pluginRepository.SavePlugin(plugin.Entity);
}
+37 -10
View File
@@ -191,26 +191,53 @@ namespace Artemis.Core.Services
public ArtemisLayout ApplyBestDeviceLayout(ArtemisDevice device)
{
// Look for a user layout
// ... here
ArtemisLayout layout;
// Try loading a device provider layout, if that comes back valid we use that
ArtemisLayout layout = device.DeviceProvider.LoadLayout(device);
// Configured layout path takes precedence over all other options
if (device.CustomLayoutPath != null)
{
layout = new ArtemisLayout(device.CustomLayoutPath, LayoutSource.Configured);
if (layout.IsValid)
{
ApplyDeviceLayout(device, layout, true, true);
return layout;
}
}
// Look for a layout provided by the user
layout = device.DeviceProvider.LoadUserLayout(device);
if (layout.IsValid)
{
ApplyDeviceLayout(device, layout, true, true);
return layout;
}
// Look for a layout provided by the plugin
layout = device.DeviceProvider.LoadLayout(device);
if (layout.IsValid)
{
ApplyDeviceLayout(device, layout, true, true);
return layout;
}
// Finally fall back to a default layout
// .. do it!
ApplyDeviceLayout(device, layout);
layout = LoadDefaultLayout(device);
ApplyDeviceLayout(device, layout, true, true);
return layout;
}
public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout)
private ArtemisLayout LoadDefaultLayout(ArtemisDevice device)
{
device.ApplyLayout(layout);
return new ArtemisLayout("NYI", LayoutSource.Default);
}
public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout, bool createMissingLeds, bool removeExessiveLeds)
{
device.ApplyLayout(layout, createMissingLeds, removeExessiveLeds);
// Applying layouts can affect LEDs, update LED group
UpdateBitmapBrush();
}
public ArtemisDevice? GetDevice(IRGBDevice rgbDevice)
{
return _devices.FirstOrDefault(d => d.RgbDevice == rgbDevice);
+16 -11
View File
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
@@ -18,8 +17,9 @@ namespace Artemis.Core
/// </summary>
public static void PrepareFirstLaunch()
{
CreateArtemisFolderIfMissing(Constants.DataFolder);
CreateArtemisFolderIfMissing(Constants.DataFolder + "plugins");
CreateAccessibleDirectory(Constants.DataFolder);
CreateAccessibleDirectory(Constants.DataFolder + "plugins");
CreateAccessibleDirectory(Constants.DataFolder + "user layouts");
}
/// <summary>
@@ -62,15 +62,11 @@ namespace Artemis.Core
}
/// <summary>
/// Gets the current application location
/// Creates all directories and subdirectories in the specified path unless they already exist with permissions
/// allowing access by everyone.
/// </summary>
/// <returns></returns>
internal static string GetCurrentLocation()
{
return Process.GetCurrentProcess().MainModule!.FileName!;
}
private static void CreateArtemisFolderIfMissing(string path)
/// <param name="path">The directory to create.</param>
public static void CreateAccessibleDirectory(string path)
{
if (!Directory.Exists(path))
{
@@ -92,6 +88,15 @@ namespace Artemis.Core
}
}
/// <summary>
/// Gets the current application location
/// </summary>
/// <returns></returns>
internal static string GetCurrentLocation()
{
return Process.GetCurrentProcess().MainModule!.FileName!;
}
private static void OnRestartRequested(RestartEventArgs e)
{
RestartRequested?.Invoke(null, e);