mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Merge branch 'development'
This commit is contained in:
commit
4152c290d2
@ -17,6 +17,7 @@ namespace Artemis.Core
|
||||
private DataModel? _dynamicDataModel;
|
||||
private Type? _dynamicDataModelType;
|
||||
private DataModelPropertyAttribute? _dynamicDataModelAttribute;
|
||||
private PropertyInfo? _property;
|
||||
|
||||
internal DataModelPathSegment(DataModelPath dataModelPath, string identifier, string path)
|
||||
{
|
||||
@ -99,7 +100,7 @@ namespace Artemis.Core
|
||||
return null;
|
||||
|
||||
// If this is not the first segment in a path, the property is located on the previous segment
|
||||
return Previous?.GetPropertyType()?.GetProperty(Identifier);
|
||||
return Previous?.GetPropertyType()?.GetProperties().FirstOrDefault(p => p.Name == Identifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -209,14 +210,18 @@ namespace Artemis.Core
|
||||
accessorExpression = expression;
|
||||
// A static segment just needs to access the property or filed
|
||||
else if (Type == DataModelPathSegmentType.Static)
|
||||
accessorExpression = Expression.PropertyOrField(expression, Identifier);
|
||||
{
|
||||
accessorExpression = _property != null
|
||||
? Expression.Property(expression, _property)
|
||||
: Expression.PropertyOrField(expression, Identifier);
|
||||
}
|
||||
// A dynamic segment calls the generic method DataModel.DynamicChild<T> and provides the identifier as an argument
|
||||
else
|
||||
{
|
||||
accessorExpression = Expression.Call(
|
||||
expression,
|
||||
nameof(DataModel.GetDynamicChildValue),
|
||||
_dynamicDataModelType != null ? new[] { _dynamicDataModelType } : null,
|
||||
_dynamicDataModelType != null ? new[] {_dynamicDataModelType} : null,
|
||||
Expression.Constant(Identifier)
|
||||
);
|
||||
}
|
||||
@ -243,8 +248,11 @@ namespace Artemis.Core
|
||||
|
||||
private void DetermineStaticType(Type previousType)
|
||||
{
|
||||
PropertyInfo? property = previousType.GetProperty(Identifier, BindingFlags.Public | BindingFlags.Instance);
|
||||
Type = property == null ? DataModelPathSegmentType.Invalid : DataModelPathSegmentType.Static;
|
||||
// Situations in which AmbiguousMatchException occurs ...
|
||||
//
|
||||
// ...derived type declares a property that hides an inherited property with the same name, by using the new modifier
|
||||
_property = previousType.GetProperties(BindingFlags.Public | BindingFlags.Instance).FirstOrDefault(p => p.Name == Identifier);
|
||||
Type = _property == null ? DataModelPathSegmentType.Invalid : DataModelPathSegmentType.Static;
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
@ -33,6 +33,8 @@ namespace Artemis.Core
|
||||
BlueScale = 1;
|
||||
IsEnabled = true;
|
||||
|
||||
LedIds = new ReadOnlyDictionary<LedId, ArtemisLed>(new Dictionary<LedId, ArtemisLed>());
|
||||
Leds = new ReadOnlyCollection<ArtemisLed>(new List<ArtemisLed>());
|
||||
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
|
||||
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
|
||||
Categories = new HashSet<DeviceCategory>();
|
||||
@ -51,6 +53,8 @@ namespace Artemis.Core
|
||||
RgbDevice = rgbDevice;
|
||||
DeviceProvider = deviceProvider;
|
||||
|
||||
LedIds = new ReadOnlyDictionary<LedId, ArtemisLed>(new Dictionary<LedId, ArtemisLed>());
|
||||
Leds = new ReadOnlyCollection<ArtemisLed>(new List<ArtemisLed>());
|
||||
InputIdentifiers = new List<ArtemisDeviceInputIdentifier>();
|
||||
InputMappings = new Dictionary<ArtemisLed, ArtemisLed>();
|
||||
Categories = new HashSet<DeviceCategory>();
|
||||
@ -258,6 +262,19 @@ namespace Artemis.Core
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether falling back to default layouts is enabled or not
|
||||
/// </summary>
|
||||
public bool DisableDefaultLayout
|
||||
{
|
||||
get => DeviceEntity.DisableDefaultLayout;
|
||||
set
|
||||
{
|
||||
DeviceEntity.DisableDefaultLayout = value;
|
||||
OnPropertyChanged(nameof(DisableDefaultLayout));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the logical layout of the device e.g. DE, UK or US.
|
||||
/// <para>Only applicable to keyboards</para>
|
||||
@ -532,6 +549,11 @@ namespace Artemis.Core
|
||||
else
|
||||
LogicalLayout = DeviceEntity.LogicalLayout;
|
||||
}
|
||||
|
||||
public void ClearLayout()
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -22,11 +22,6 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// [NYI] Gets a boolean indicating whether installing or uninstalling this prerequisite requires admin privileges
|
||||
/// </summary>
|
||||
public abstract bool RequiresElevation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of actions to execute when <see cref="Install" /> is called
|
||||
/// </summary>
|
||||
|
||||
@ -18,7 +18,7 @@ namespace Artemis.Core
|
||||
/// <param name="fileName">The target file to execute</param>
|
||||
/// <param name="arguments">A set of command-line arguments to use when starting the application</param>
|
||||
/// <param name="waitForExit">A boolean indicating whether the action should wait for the process to exit</param>
|
||||
/// <param name="elevate">A boolean indicating whether the file should run with administrator privileges (does not require <see cref="PluginPrerequisite.RequiresElevation"/>)</param>
|
||||
/// <param name="elevate">A boolean indicating whether the file should run with administrator privileges</param>
|
||||
public ExecuteFileAction(string name, string fileName, string? arguments = null, bool waitForExit = true, bool elevate = false) : base(name)
|
||||
{
|
||||
FileName = fileName ?? throw new ArgumentNullException(nameof(fileName));
|
||||
@ -72,7 +72,7 @@ namespace Artemis.Core
|
||||
}
|
||||
}
|
||||
|
||||
private static Task<int> RunProcessAsync(string fileName, string? arguments, bool elevate)
|
||||
internal static Task<int> RunProcessAsync(string fileName, string? arguments, bool elevate)
|
||||
{
|
||||
TaskCompletionSource<int> tcs = new();
|
||||
|
||||
|
||||
@ -0,0 +1,81 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a plugin prerequisite action runs inline powershell
|
||||
/// </summary>
|
||||
public class RunInlinePowerShellAction : PluginPrerequisiteAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of a copy folder action
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the action</param>
|
||||
/// <param name="code">The inline code to run</param>
|
||||
/// <param name="elevate">A boolean indicating whether the file should run with administrator privileges</param>
|
||||
/// <param name="arguments">
|
||||
/// Optional arguments to pass to your script, you are responsible for proper quoting etc.
|
||||
/// <para>Arguments are available in PowerShell as <c>$args[0], $args[1]</c> etc.</para>
|
||||
/// </param>
|
||||
public RunInlinePowerShellAction(string name, string code, bool elevate = false, string? arguments = null) : base(name)
|
||||
{
|
||||
Code = code;
|
||||
Elevate = elevate;
|
||||
Arguments = arguments;
|
||||
ProgressIndeterminate = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the inline code to run
|
||||
/// </summary>
|
||||
public string Code { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the file should run with administrator privileges
|
||||
/// </summary>
|
||||
public bool Elevate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets optional arguments to pass to your script, you are responsible for proper quoting etc.
|
||||
/// <para>Arguments are available in PowerShell as <c>$args[0], $args[1]</c> etc.</para>
|
||||
/// </summary>
|
||||
public string? Arguments { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Execute(CancellationToken cancellationToken)
|
||||
{
|
||||
string file = Path.GetTempFileName().Replace(".tmp", ".ps1");
|
||||
try
|
||||
{
|
||||
string code =
|
||||
@"try
|
||||
{
|
||||
" + Code + @"
|
||||
Start-Sleep 1
|
||||
}
|
||||
catch
|
||||
{
|
||||
Write-Error $_.Exception.ToString()
|
||||
pause
|
||||
}";
|
||||
|
||||
await File.WriteAllTextAsync(file, code, cancellationToken);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
Status = "Running PowerShell script and waiting for exit..";
|
||||
ShowProgressBar = true;
|
||||
ProgressIndeterminate = true;
|
||||
|
||||
int result = await ExecuteFileAction.RunProcessAsync("powershell.exe", $"-ExecutionPolicy Unrestricted -File {file} {Arguments}", Elevate);
|
||||
|
||||
Status = $"PowerShell exited with code {result}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a plugin prerequisite action that runs a PowerShell script
|
||||
/// <para>Note: To run an inline script instead, use <see cref="RunInlinePowerShellAction" /></para>
|
||||
/// </summary>
|
||||
public class RunPowerShellAction : PluginPrerequisiteAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of a copy folder action
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the action</param>
|
||||
/// <param name="scriptPath">The full path of the script to run</param>
|
||||
/// <param name="elevate">A boolean indicating whether the file should run with administrator privileges</param>
|
||||
/// <param name="arguments">
|
||||
/// Optional arguments to pass to your script, you are responsible for proper quoting etc.
|
||||
/// <para>Arguments are available in PowerShell as <c>$args[0], $args[1]</c> etc.</para>
|
||||
/// </param>
|
||||
public RunPowerShellAction(string name, string scriptPath, bool elevate = false, string? arguments = null) : base(name)
|
||||
{
|
||||
ScriptPath = scriptPath;
|
||||
Elevate = elevate;
|
||||
Arguments = arguments;
|
||||
ProgressIndeterminate = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the inline full path of the script to run
|
||||
/// </summary>
|
||||
public string ScriptPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the file should run with administrator privileges
|
||||
/// </summary>
|
||||
public bool Elevate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets optional arguments to pass to your script, you are responsible for proper quoting etc.
|
||||
/// <para>Arguments are available in PowerShell as <c>$args[0], $args[1]</c> etc.</para>
|
||||
/// </summary>
|
||||
public string? Arguments { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Execute(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!ScriptPath.EndsWith(".ps1"))
|
||||
throw new ArtemisPluginException($"Script at path {ScriptPath} must have the .ps1 extension or PowerShell will refuse to run it");
|
||||
if (!File.Exists(ScriptPath))
|
||||
throw new ArtemisCoreException($"Script not found at path {ScriptPath}");
|
||||
|
||||
Status = "Running PowerShell script and waiting for exit..";
|
||||
ShowProgressBar = true;
|
||||
ProgressIndeterminate = true;
|
||||
|
||||
int result = await ExecuteFileAction.RunProcessAsync("powershell.exe", $"-ExecutionPolicy Unrestricted -File {ScriptPath} {Arguments}", Elevate);
|
||||
|
||||
Status = $"PowerShell exited with code {result}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -135,11 +135,16 @@ namespace Artemis.Core.Services
|
||||
DeviceProvider GetDeviceProviderByDevice(IRGBDevice device);
|
||||
|
||||
/// <summary>
|
||||
/// Queues an action for the provided plugin for the next time Artemis starts, before plugins are loaded
|
||||
/// Queues the provided plugin to be deleted the next time Artemis starts, before plugins are loaded
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin to queue the action for</param>
|
||||
/// <param name="pluginAction">The action to take</param>
|
||||
void QueuePluginAction(Plugin plugin, PluginManagementAction pluginAction);
|
||||
/// <param name="plugin">The plugin to delete</param>
|
||||
void QueuePluginDeletion(Plugin plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the provided plugin for the deletion queue it was added to via <see cref="QueuePluginDeletion" />
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin to dequeue</param>
|
||||
void DequeuePluginDeletion(Plugin plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when built-in plugins are being loaded
|
||||
|
||||
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.DeviceProviders;
|
||||
using Artemis.Core.Ninject;
|
||||
using Artemis.Storage.Entities.General;
|
||||
using Artemis.Storage.Entities.Plugins;
|
||||
using Artemis.Storage.Repositories.Interfaces;
|
||||
using McMaster.NETCore.Plugins;
|
||||
@ -26,17 +27,19 @@ namespace Artemis.Core.Services
|
||||
private readonly IKernel _kernel;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IPluginRepository _pluginRepository;
|
||||
private readonly IQueuedActionRepository _queuedActionRepository;
|
||||
private readonly List<Plugin> _plugins;
|
||||
private bool _isElevated;
|
||||
|
||||
public PluginManagementService(IKernel kernel, ILogger logger, IPluginRepository pluginRepository)
|
||||
public PluginManagementService(IKernel kernel, ILogger logger, IPluginRepository pluginRepository, IQueuedActionRepository queuedActionRepository)
|
||||
{
|
||||
_kernel = kernel;
|
||||
_logger = logger;
|
||||
_pluginRepository = pluginRepository;
|
||||
_queuedActionRepository = queuedActionRepository;
|
||||
_plugins = new List<Plugin>();
|
||||
|
||||
ProcessQueuedActions();
|
||||
ProcessPluginDeletionQueue();
|
||||
}
|
||||
|
||||
private void CopyBuiltInPlugin(ZipArchive zipArchive, string targetDirectory)
|
||||
@ -664,27 +667,51 @@ namespace Artemis.Core.Services
|
||||
|
||||
#region Queued actions
|
||||
|
||||
public void QueuePluginAction(Plugin plugin, PluginManagementAction pluginAction)
|
||||
public void QueuePluginDeletion(Plugin plugin)
|
||||
{
|
||||
List<PluginQueuedActionEntity> existing = _pluginRepository.GetQueuedActions(plugin.Guid);
|
||||
if (existing.Any(e => pluginAction == PluginManagementAction.Delete && e is PluginQueuedDeleteEntity))
|
||||
return;
|
||||
|
||||
if (pluginAction == PluginManagementAction.Delete)
|
||||
_pluginRepository.AddQueuedAction(new PluginQueuedDeleteEntity {PluginGuid = plugin.Guid, Directory = plugin.Directory.FullName});
|
||||
_queuedActionRepository.Add(new QueuedActionEntity
|
||||
{
|
||||
Type = "DeletePlugin",
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
Parameters = new Dictionary<string, object>()
|
||||
{
|
||||
{"pluginGuid", plugin.Guid.ToString()},
|
||||
{"plugin", plugin.ToString()},
|
||||
{"directory", plugin.Directory.FullName}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ProcessQueuedActions()
|
||||
public void DequeuePluginDeletion(Plugin plugin)
|
||||
{
|
||||
foreach (PluginQueuedActionEntity pluginQueuedActionEntity in _pluginRepository.GetQueuedActions())
|
||||
{
|
||||
if (pluginQueuedActionEntity is PluginQueuedDeleteEntity deleteAction)
|
||||
{
|
||||
if (Directory.Exists(deleteAction.Directory))
|
||||
Directory.Delete(deleteAction.Directory, true);
|
||||
}
|
||||
QueuedActionEntity? queuedActionEntity = _queuedActionRepository.GetByType("DeletePlugin").FirstOrDefault(q => q.Parameters["pluginGuid"].Equals(plugin.Guid.ToString()));
|
||||
if (queuedActionEntity != null)
|
||||
_queuedActionRepository.Remove(queuedActionEntity);
|
||||
}
|
||||
|
||||
_pluginRepository.RemoveQueuedAction(pluginQueuedActionEntity);
|
||||
private void ProcessPluginDeletionQueue()
|
||||
{
|
||||
foreach (QueuedActionEntity queuedActionEntity in _queuedActionRepository.GetByType("DeletePlugin"))
|
||||
{
|
||||
string? directory = queuedActionEntity.Parameters["directory"].ToString();
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(directory))
|
||||
{
|
||||
_logger.Information("Queued plugin deletion - deleting folder - {plugin}", queuedActionEntity.Parameters["plugin"]);
|
||||
Directory.Delete(directory!, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Information("Queued plugin deletion - folder already deleted - {plugin}", queuedActionEntity.Parameters["plugin"]);
|
||||
}
|
||||
|
||||
_queuedActionRepository.Remove(queuedActionEntity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Warning(e, "Queued plugin deletion failed - {plugin}", queuedActionEntity.Parameters["plugin"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -354,14 +354,21 @@ namespace Artemis.Core.Services
|
||||
}
|
||||
|
||||
// Finally fall back to a default layout
|
||||
layout = ArtemisLayout.GetDefaultLayout(device);
|
||||
if (layout != null)
|
||||
ApplyDeviceLayout(device, layout);
|
||||
if (!device.DisableDefaultLayout)
|
||||
layout = ArtemisLayout.GetDefaultLayout(device);
|
||||
ApplyDeviceLayout(device, layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout)
|
||||
public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout)
|
||||
{
|
||||
if (layout == null)
|
||||
{
|
||||
if (device.Layout != null)
|
||||
device.ClearLayout();
|
||||
return;
|
||||
}
|
||||
|
||||
if (layout.Source == LayoutSource.Default)
|
||||
device.ApplyLayout(layout, false, false);
|
||||
else
|
||||
|
||||
19
src/Artemis.Storage/Entities/General/QueuedActionEntity.cs
Normal file
19
src/Artemis.Storage/Entities/General/QueuedActionEntity.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Artemis.Storage.Entities.General
|
||||
{
|
||||
public class QueuedActionEntity
|
||||
{
|
||||
public QueuedActionEntity()
|
||||
{
|
||||
Parameters = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string Type { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
|
||||
public Dictionary<string, object> Parameters { get; set; }
|
||||
}
|
||||
}
|
||||
@ -13,21 +13,4 @@ namespace Artemis.Storage.Entities.Plugins
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a queued action for a plugin
|
||||
/// </summary>
|
||||
public abstract class PluginQueuedActionEntity
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid PluginGuid { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a queued delete action for a plugin
|
||||
/// </summary>
|
||||
public class PluginQueuedDeleteEntity : PluginQueuedActionEntity
|
||||
{
|
||||
public string Directory { get; set; }
|
||||
}
|
||||
}
|
||||
@ -22,6 +22,7 @@ namespace Artemis.Storage.Entities.Surface
|
||||
public float BlueScale { get; set; }
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
public bool DisableDefaultLayout { get; set; }
|
||||
public int PhysicalLayout { get; set; }
|
||||
public string LogicalLayout { get; set; }
|
||||
public string CustomLayoutPath { get; set; }
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Storage.Entities.Plugins;
|
||||
|
||||
namespace Artemis.Storage.Repositories.Interfaces
|
||||
@ -15,10 +14,5 @@ namespace Artemis.Storage.Repositories.Interfaces
|
||||
PluginSettingEntity GetSettingByNameAndGuid(string name, Guid pluginGuid);
|
||||
void SaveSetting(PluginSettingEntity pluginSettingEntity);
|
||||
void RemoveSettings(Guid pluginGuid);
|
||||
|
||||
void AddQueuedAction(PluginQueuedActionEntity pluginQueuedActionEntity);
|
||||
List<PluginQueuedActionEntity> GetQueuedActions();
|
||||
List<PluginQueuedActionEntity> GetQueuedActions(Guid pluginGuid);
|
||||
void RemoveQueuedAction(PluginQueuedActionEntity pluginQueuedActionEntity);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Storage.Entities.General;
|
||||
|
||||
namespace Artemis.Storage.Repositories.Interfaces
|
||||
{
|
||||
public interface IQueuedActionRepository : IRepository
|
||||
{
|
||||
void Add(QueuedActionEntity queuedActionEntity);
|
||||
void Remove(QueuedActionEntity queuedActionEntity);
|
||||
List<QueuedActionEntity> GetAll();
|
||||
List<QueuedActionEntity> GetByType(string type);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Storage.Entities.Module;
|
||||
using Artemis.Storage.Repositories.Interfaces;
|
||||
using LiteDB;
|
||||
|
||||
@ -15,7 +15,6 @@ namespace Artemis.Storage.Repositories
|
||||
_repository = repository;
|
||||
|
||||
_repository.Database.GetCollection<PluginSettingEntity>().EnsureIndex(s => new {s.Name, s.PluginGuid}, true);
|
||||
_repository.Database.GetCollection<PluginQueuedActionEntity>().EnsureIndex(s => s.PluginGuid);
|
||||
}
|
||||
|
||||
public void AddPlugin(PluginEntity pluginEntity)
|
||||
@ -59,24 +58,5 @@ namespace Artemis.Storage.Repositories
|
||||
{
|
||||
_repository.DeleteMany<PluginSettingEntity>(s => s.PluginGuid == pluginGuid);
|
||||
}
|
||||
|
||||
public List<PluginQueuedActionEntity> GetQueuedActions()
|
||||
{
|
||||
return _repository.Query<PluginQueuedActionEntity>().ToList();
|
||||
}
|
||||
|
||||
public List<PluginQueuedActionEntity> GetQueuedActions(Guid pluginGuid)
|
||||
{
|
||||
return _repository.Query<PluginQueuedActionEntity>().Where(q => q.PluginGuid == pluginGuid).ToList();
|
||||
}
|
||||
|
||||
public void AddQueuedAction(PluginQueuedActionEntity pluginQueuedActionEntity)
|
||||
{
|
||||
_repository.Upsert(pluginQueuedActionEntity);
|
||||
}
|
||||
public void RemoveQueuedAction(PluginQueuedActionEntity pluginQueuedActionEntity)
|
||||
{
|
||||
_repository.Delete<PluginQueuedActionEntity>(pluginQueuedActionEntity.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/Artemis.Storage/Repositories/QueuedActionRepository.cs
Normal file
46
src/Artemis.Storage/Repositories/QueuedActionRepository.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using Artemis.Storage.Entities.General;
|
||||
using Artemis.Storage.Repositories.Interfaces;
|
||||
using LiteDB;
|
||||
|
||||
namespace Artemis.Storage.Repositories
|
||||
{
|
||||
public class QueuedActionRepository : IQueuedActionRepository
|
||||
{
|
||||
private readonly LiteRepository _repository;
|
||||
|
||||
public QueuedActionRepository(LiteRepository repository)
|
||||
{
|
||||
_repository = repository;
|
||||
_repository.Database.GetCollection<QueuedActionEntity>().EnsureIndex(s => s.Type);
|
||||
}
|
||||
|
||||
#region Implementation of IQueuedActionRepository
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(QueuedActionEntity queuedActionEntity)
|
||||
{
|
||||
_repository.Insert(queuedActionEntity);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Remove(QueuedActionEntity queuedActionEntity)
|
||||
{
|
||||
_repository.Delete<QueuedActionEntity>(queuedActionEntity.Id);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<QueuedActionEntity> GetAll()
|
||||
{
|
||||
return _repository.Query<QueuedActionEntity>().ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<QueuedActionEntity> GetByType(string type)
|
||||
{
|
||||
return _repository.Query<QueuedActionEntity>().Where(q => q.Type == type).ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
41
src/Artemis.UI.Shared/Behaviors/OpenInBrowser.cs
Normal file
41
src/Artemis.UI.Shared/Behaviors/OpenInBrowser.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Navigation;
|
||||
using Artemis.Core;
|
||||
using Microsoft.Xaml.Behaviors;
|
||||
|
||||
namespace Artemis.UI.Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a behavior that opens the URI of the hyperlink in the browser when requested
|
||||
/// </summary>
|
||||
public class OpenInBrowser : Behavior<Hyperlink>
|
||||
{
|
||||
private Hyperlink? _hyperLink;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnAttached()
|
||||
{
|
||||
base.OnAttached();
|
||||
|
||||
_hyperLink = AssociatedObject;
|
||||
if (_hyperLink == null)
|
||||
return;
|
||||
|
||||
_hyperLink.RequestNavigate += HyperLinkOnRequestNavigate;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnDetaching()
|
||||
{
|
||||
if (_hyperLink == null) return;
|
||||
_hyperLink.RequestNavigate -= HyperLinkOnRequestNavigate;
|
||||
|
||||
base.OnDetaching();
|
||||
}
|
||||
|
||||
private void HyperLinkOnRequestNavigate(object sender, RequestNavigateEventArgs e)
|
||||
{
|
||||
Utilities.OpenUrl(e.Uri.AbsoluteUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,5 +14,11 @@
|
||||
SelectedValuePath="Value"
|
||||
DisplayMemberPath="Description"
|
||||
SelectedValue="{Binding Path=InputValue}"
|
||||
IsDropDownOpen="True"/>
|
||||
IsDropDownOpen="True">
|
||||
<ComboBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
</ItemsPanelTemplate>
|
||||
</ComboBox.ItemsPanel>
|
||||
</ComboBox>
|
||||
</UserControl>
|
||||
@ -13,18 +13,13 @@ namespace Artemis.UI.Screens.Plugins
|
||||
public class PluginPrerequisiteViewModel : Conductor<PluginPrerequisiteActionViewModel>.Collection.OneActive
|
||||
{
|
||||
private readonly bool _uninstall;
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private bool _installing;
|
||||
private bool _uninstalling;
|
||||
private bool _isMet;
|
||||
|
||||
public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall, ICoreService coreService, IDialogService dialogService)
|
||||
public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, bool uninstall)
|
||||
{
|
||||
_uninstall = uninstall;
|
||||
_coreService = coreService;
|
||||
_dialogService = dialogService;
|
||||
|
||||
PluginPrerequisite = pluginPrerequisite;
|
||||
}
|
||||
|
||||
@ -65,12 +60,6 @@ namespace Artemis.UI.Screens.Plugins
|
||||
if (Busy)
|
||||
return;
|
||||
|
||||
if (PluginPrerequisite.RequiresElevation && !_coreService.IsElevated)
|
||||
{
|
||||
await _dialogService.ShowConfirmDialog("Install plugin prerequisite", "This plugin prerequisite admin rights to install (restart & elevate NYI)");
|
||||
return;
|
||||
}
|
||||
|
||||
Installing = true;
|
||||
try
|
||||
{
|
||||
@ -88,12 +77,6 @@ namespace Artemis.UI.Screens.Plugins
|
||||
if (Busy)
|
||||
return;
|
||||
|
||||
if (PluginPrerequisite.RequiresElevation && !_coreService.IsElevated)
|
||||
{
|
||||
await _dialogService.ShowConfirmDialog("Install plugin prerequisite", "This plugin prerequisite admin rights to install (restart & elevate NYI)");
|
||||
return;
|
||||
}
|
||||
|
||||
Uninstalling = true;
|
||||
try
|
||||
{
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:DevicePropertiesTabViewModel}">
|
||||
@ -29,7 +30,7 @@
|
||||
Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
TextWrapping="Wrap"
|
||||
TextAlignment="Justify">
|
||||
Artemis uses categories to determine where the layers of imported profiles are applied to. <LineBreak/>
|
||||
Artemis uses categories to determine where the layers of imported profiles are applied to. <LineBreak />
|
||||
You can hover over a category for a more detailed description.
|
||||
</TextBlock>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
@ -192,15 +193,25 @@
|
||||
|
||||
<!-- Layout -->
|
||||
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}">
|
||||
Custom layout
|
||||
Layout
|
||||
</TextBlock>
|
||||
<TextBlock Style="{StaticResource MaterialDesignCaptionTextBlock}"
|
||||
Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
TextWrapping="Wrap"
|
||||
TextAlignment="Justify">
|
||||
Select a custom layout below if you want to change the appearance and/or LEDs of this device.
|
||||
The device layout is used to determine the position of LEDs and to create the visual representation of the device you see on the left side of this window.
|
||||
</TextBlock>
|
||||
|
||||
<CheckBox Margin="0 0 0 5" Style="{StaticResource MaterialDesignCheckBox}" IsChecked="{Binding UseDefaultLayout}">
|
||||
<CheckBox.Content>
|
||||
<TextBlock Margin="0 -5 0 0">
|
||||
Use default layout if needed
|
||||
<materialDesign:PackIcon Kind="HelpCircle"
|
||||
ToolTip="If there is no built-in layout and no custom layout, with this enabled Artemis will fall back to the default layout of this device type." />
|
||||
</TextBlock>
|
||||
</CheckBox.Content>
|
||||
</CheckBox>
|
||||
|
||||
<TextBox Style="{StaticResource MaterialDesignFilledTextBox}"
|
||||
Text="{Binding Device.CustomLayoutPath}"
|
||||
VerticalAlignment="Center"
|
||||
@ -210,10 +221,25 @@
|
||||
<materialDesign:HintAssist.Hint>
|
||||
<StackPanel Orientation="Horizontal" Margin="-2 0 0 0">
|
||||
<materialDesign:PackIcon Kind="Xml" Width="20" />
|
||||
<TextBlock>Layout path</TextBlock>
|
||||
<TextBlock>Custom layout path</TextBlock>
|
||||
</StackPanel>
|
||||
</materialDesign:HintAssist.Hint>
|
||||
</TextBox>
|
||||
<TextBlock Style="{StaticResource MaterialDesignCaptionTextBlock}"
|
||||
Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
TextWrapping="Wrap"
|
||||
TextAlignment="Justify">
|
||||
Select a custom layout below if you want to change the appearance and/or LEDs of this device.
|
||||
For info on how to create layouts, check out
|
||||
<Hyperlink Style="{StaticResource ArtemisHyperlink}"
|
||||
NavigateUri="https://wiki.artemis-rgb.com/en/guides/developer/layouts">
|
||||
this wiki article
|
||||
<b:Interaction.Behaviors>
|
||||
<shared:OpenInBrowser />
|
||||
</b:Interaction.Behaviors>
|
||||
</Hyperlink>
|
||||
.
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Buttons -->
|
||||
|
||||
@ -134,6 +134,16 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
|
||||
set => SetCategory(DeviceCategory.Peripherals, value);
|
||||
}
|
||||
|
||||
public bool UseDefaultLayout
|
||||
{
|
||||
get => !Device.DisableDefaultLayout;
|
||||
set
|
||||
{
|
||||
Device.DisableDefaultLayout = !value;
|
||||
NotifyOfPropertyChange(nameof(UseDefaultLayout));
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyScaling()
|
||||
{
|
||||
Device.RedScale = RedScale / 100f;
|
||||
@ -255,7 +265,7 @@ namespace Artemis.UI.Screens.Settings.Device.Tabs
|
||||
|
||||
private void DeviceOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(Device.CustomLayoutPath))
|
||||
if (e.PropertyName == nameof(Device.CustomLayoutPath) || e.PropertyName == nameof(Device.DisableDefaultLayout))
|
||||
_rgbService.ApplyBestDeviceLayout(Device);
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user