mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 11:26:27 +00:00
Plugins - Added prerequisites system
This commit is contained in:
1 parent
77227b7e6e
commit
19679c0ba6
22 files changed
+1210
-13
No files matched your search
@@ -5,16 +5,22 @@
|
||||
/// </summary>
|
||||
public interface IPluginBootstrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when the plugin is loaded
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
void OnPluginLoaded(Plugin plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the plugin is activated
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin instance of your plugin</param>
|
||||
void Enable(Plugin plugin);
|
||||
void OnPluginEnabled(Plugin plugin);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the plugin is deactivated or when Artemis shuts down
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin instance of your plugin</param>
|
||||
void Disable(Plugin plugin);
|
||||
void OnPluginDisabled(Plugin plugin);
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,11 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public IKernel? Kernel { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of prerequisites for this plugin feature
|
||||
/// </summary>
|
||||
public List<PluginPrerequisite> Prerequisites { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// The PluginLoader backing this plugin
|
||||
/// </summary>
|
||||
@@ -235,12 +240,12 @@ namespace Artemis.Core
|
||||
|
||||
if (enable)
|
||||
{
|
||||
Bootstrapper?.Enable(this);
|
||||
Bootstrapper?.OnPluginEnabled(this);
|
||||
OnEnabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
Bootstrapper?.Disable(this);
|
||||
Bootstrapper?.OnPluginDisabled(this);
|
||||
OnDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
@@ -15,7 +16,7 @@ namespace Artemis.Core
|
||||
private readonly Stopwatch _updateStopwatch = new();
|
||||
private bool _isEnabled;
|
||||
private Exception? _loadException;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin feature info related to this feature
|
||||
/// </summary>
|
||||
@@ -59,6 +60,11 @@ namespace Artemis.Core
|
||||
/// </summary>
|
||||
public TimeSpan RenderTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of prerequisites for this plugin feature
|
||||
/// </summary>
|
||||
public List<PluginPrerequisite> Prerequisites { get; } = new();
|
||||
|
||||
internal PluginFeatureEntity Entity { get; set; } = null!; // Will be set right after construction
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a prerequisite for a <see cref="Plugin" /> or <see cref="PluginFeature" />
|
||||
/// </summary>
|
||||
public abstract class PluginPrerequisite : CorePropertyChanged
|
||||
{
|
||||
private PluginPrerequisiteAction? _currentAction;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="PluginPrerequisite" /> class
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin this is a prerequisite for</param>
|
||||
protected PluginPrerequisite(Plugin plugin)
|
||||
{
|
||||
Plugin = plugin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the <see cref="PluginPrerequisite" /> class
|
||||
/// </summary>
|
||||
/// <param name="pluginFeature">The plugin feature this is a prerequisite for</param>
|
||||
protected PluginPrerequisite(PluginFeature pluginFeature)
|
||||
{
|
||||
PluginFeature = pluginFeature;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the prerequisite
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the prerequisite
|
||||
/// </summary>
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public abstract List<PluginPrerequisiteAction> InstallActions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of actions to execute when <see cref="Uninstall" /> is called
|
||||
/// </summary>
|
||||
public abstract List<PluginPrerequisiteAction> UninstallActions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the action currently being executed
|
||||
/// </summary>
|
||||
public PluginPrerequisiteAction? CurrentAction
|
||||
{
|
||||
get => _currentAction;
|
||||
private set => SetAndNotify(ref _currentAction, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the plugin this prerequisite is for
|
||||
/// <para>Note: Only one plugin or a plugin feature can be set at once</para>
|
||||
/// </summary>
|
||||
public Plugin? Plugin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the feature this prerequisite is for
|
||||
/// <para>Note: Only one plugin or a plugin feature can be set at once</para>
|
||||
/// </summary>
|
||||
public PluginFeature? PluginFeature { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Execute all install actions
|
||||
/// </summary>
|
||||
public async Task Install(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnInstallStarting();
|
||||
foreach (PluginPrerequisiteAction installAction in InstallActions)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
CurrentAction = installAction;
|
||||
await installAction.Execute(cancellationToken);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CurrentAction = null;
|
||||
OnInstallFinished();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute all uninstall actions
|
||||
/// </summary>
|
||||
public async Task Uninstall(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnUninstallStarting();
|
||||
foreach (PluginPrerequisiteAction uninstallAction in UninstallActions)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
CurrentAction = uninstallAction;
|
||||
await uninstallAction.Execute(cancellationToken);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CurrentAction = null;
|
||||
OnUninstallFinished();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to determine whether the prerequisite is met
|
||||
/// </summary>
|
||||
/// <returns><see langword="true" /> if the prerequisite is met; otherwise <see langword="false" /></returns>
|
||||
public abstract Task<bool> IsMet();
|
||||
|
||||
/// <summary>
|
||||
/// Called before installation starts
|
||||
/// </summary>
|
||||
protected virtual void OnInstallStarting()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after installation finishes
|
||||
/// </summary>
|
||||
protected virtual void OnInstallFinished()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before uninstall starts
|
||||
/// </summary>
|
||||
protected virtual void OnUninstallStarting()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after uninstall finished
|
||||
/// </summary>
|
||||
protected virtual void OnUninstallFinished()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an action that must be taken to install or uninstall a plugin prerequisite
|
||||
/// </summary>
|
||||
public abstract class PluginPrerequisiteAction : CorePropertyChanged
|
||||
{
|
||||
private bool _progressIndeterminate;
|
||||
private string? _status;
|
||||
private bool _subProgressIndeterminate;
|
||||
|
||||
/// <summary>
|
||||
/// The base constructor for all plugin prerequisite actions
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the action</param>
|
||||
protected PluginPrerequisiteAction(string name)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name));
|
||||
}
|
||||
|
||||
#region Implementation of IPluginPrerequisiteAction
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the action
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status of the action
|
||||
/// </summary>
|
||||
public string? Status
|
||||
{
|
||||
get => _status;
|
||||
set => SetAndNotify(ref _status, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the progress is indeterminate or not
|
||||
/// </summary>
|
||||
public bool ProgressIndeterminate
|
||||
{
|
||||
get => _progressIndeterminate;
|
||||
set => SetAndNotify(ref _progressIndeterminate, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the progress is indeterminate or not
|
||||
/// </summary>
|
||||
public bool SubProgressIndeterminate
|
||||
{
|
||||
get => _subProgressIndeterminate;
|
||||
set => SetAndNotify(ref _subProgressIndeterminate, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the progress of the action (0 to 100)
|
||||
/// </summary>
|
||||
public PrerequisiteActionProgress Progress { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sub progress of the action
|
||||
/// </summary>
|
||||
public PrerequisiteActionProgress SubProgress { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the action must execute
|
||||
/// </summary>
|
||||
public abstract Task Execute(CancellationToken cancellationToken);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Humanizer;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a plugin prerequisite action that copies a folder
|
||||
/// </summary>
|
||||
public class CopyFolderAction : PluginPrerequisiteAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of a copy folder action
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the action</param>
|
||||
/// <param name="source">The source folder to copy</param>
|
||||
/// <param name="target">The target folder to copy to (will be created if needed)</param>
|
||||
public CopyFolderAction(string name, string source, string target) : base(name)
|
||||
{
|
||||
Source = source;
|
||||
Target = target;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source directory
|
||||
/// </summary>
|
||||
public string Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target directory
|
||||
/// </summary>
|
||||
public string Target { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Execute(CancellationToken cancellationToken)
|
||||
{
|
||||
DirectoryInfo source = new(Source);
|
||||
DirectoryInfo target = new(Target);
|
||||
|
||||
if (!source.Exists)
|
||||
throw new ArtemisCoreException($"The source directory at '{source}' was not found.");
|
||||
|
||||
int filesCopied = 0;
|
||||
FileInfo[] files = source.GetFiles("*", SearchOption.AllDirectories);
|
||||
|
||||
foreach (FileInfo fileInfo in files)
|
||||
{
|
||||
string outputPath = fileInfo.FullName.Replace(source.FullName, target.FullName);
|
||||
string outputDir = Path.GetDirectoryName(outputPath)!;
|
||||
Utilities.CreateAccessibleDirectory(outputDir);
|
||||
|
||||
void SubProgressOnProgressReported(object? sender, EventArgs e)
|
||||
{
|
||||
if (SubProgress.ProgressPerSecond != 0)
|
||||
Status = $"Copying {fileInfo.Name} - {SubProgress.ProgressPerSecond.Bytes().Humanize("#.##")}/sec";
|
||||
else
|
||||
Status = $"Copying {fileInfo.Name}";
|
||||
}
|
||||
|
||||
Progress.Report((filesCopied, files.Length));
|
||||
SubProgress.ProgressReported += SubProgressOnProgressReported;
|
||||
|
||||
await using FileStream sourceStream = fileInfo.OpenRead();
|
||||
await using FileStream destinationStream = File.Create(outputPath);
|
||||
|
||||
await sourceStream.CopyToAsync(fileInfo.Length, destinationStream, SubProgress, cancellationToken);
|
||||
|
||||
filesCopied++;
|
||||
SubProgress.ProgressReported -= SubProgressOnProgressReported;
|
||||
}
|
||||
|
||||
Progress.Report((filesCopied, files.Length));
|
||||
Status = $"Finished copying {filesCopied} file(s)";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a plugin prerequisite action that copies a folder
|
||||
/// </summary>
|
||||
public class WriteToFileAction : PluginPrerequisiteAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of a copy folder action
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the action</param>
|
||||
/// <param name="target">The target file to write to (will be created if needed)</param>
|
||||
/// <param name="content">The contents to write</param>
|
||||
public WriteToFileAction(string name, string target, string content) : base(name)
|
||||
{
|
||||
Target = target ?? throw new ArgumentNullException(nameof(target));
|
||||
Content = content ?? throw new ArgumentNullException(nameof(content));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a copy folder action
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the action</param>
|
||||
/// <param name="target">The target file to write to (will be created if needed)</param>
|
||||
/// <param name="content">The contents to write</param>
|
||||
public WriteToFileAction(string name, string target, byte[] content) : base(name)
|
||||
{
|
||||
Target = target;
|
||||
ByteContent = content ?? throw new ArgumentNullException(nameof(content));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target file
|
||||
/// </summary>
|
||||
public string Target { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the contents that will be written
|
||||
/// </summary>
|
||||
public string? Content { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bytes that will be written
|
||||
/// </summary>
|
||||
public byte[]? ByteContent { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Execute(CancellationToken cancellationToken)
|
||||
{
|
||||
string outputDir = Path.GetDirectoryName(Target)!;
|
||||
Utilities.CreateAccessibleDirectory(outputDir);
|
||||
|
||||
ProgressIndeterminate = true;
|
||||
Status = $"Writing to {Path.GetFileName(Target)}...";
|
||||
|
||||
if (Content != null)
|
||||
await File.WriteAllTextAsync(Target, Content, cancellationToken);
|
||||
else if (ByteContent != null)
|
||||
await File.WriteAllBytesAsync(Target, ByteContent, cancellationToken);
|
||||
|
||||
ProgressIndeterminate = false;
|
||||
Status = $"Finished writing to {Path.GetFileName(Target)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents progress on a plugin prerequisite action
|
||||
/// </summary>
|
||||
public class PrerequisiteActionProgress : CorePropertyChanged, IProgress<(long, long)>
|
||||
{
|
||||
private long _current;
|
||||
private DateTime _lastReport;
|
||||
private double _percentage;
|
||||
private double _progressPerSecond;
|
||||
private long _total;
|
||||
private long _lastReportValue;
|
||||
|
||||
/// <summary>
|
||||
/// The current amount
|
||||
/// </summary>
|
||||
public long Current
|
||||
{
|
||||
get => _current;
|
||||
set => SetAndNotify(ref _current, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The total amount
|
||||
/// </summary>
|
||||
public long Total
|
||||
{
|
||||
get => _total;
|
||||
set => SetAndNotify(ref _total, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The percentage
|
||||
/// </summary>
|
||||
public double Percentage
|
||||
{
|
||||
get => _percentage;
|
||||
set => SetAndNotify(ref _percentage, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the progress per second
|
||||
/// </summary>
|
||||
public double ProgressPerSecond
|
||||
{
|
||||
get => _progressPerSecond;
|
||||
set => SetAndNotify(ref _progressPerSecond, value);
|
||||
}
|
||||
|
||||
#region Implementation of IProgress<in (long, long)>
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Report((long, long) value)
|
||||
{
|
||||
(long newCurrent, long newTotal) = value;
|
||||
|
||||
TimeSpan timePassed = DateTime.Now - _lastReport;
|
||||
if (timePassed >= TimeSpan.FromSeconds(1))
|
||||
{
|
||||
ProgressPerSecond = Math.Max(0, Math.Round(1.0 / timePassed.TotalSeconds * (newCurrent - _lastReportValue), 2));
|
||||
_lastReportValue = newCurrent;
|
||||
_lastReport = DateTime.Now;
|
||||
}
|
||||
|
||||
Current = newCurrent;
|
||||
Total = newTotal;
|
||||
Percentage = Math.Round((double) Current / Total * 100.0, 2);
|
||||
|
||||
OnProgressReported();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when progress has been reported
|
||||
/// </summary>
|
||||
public event EventHandler? ProgressReported;
|
||||
|
||||
protected virtual void OnProgressReported()
|
||||
{
|
||||
ProgressReported?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user