mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-11 19:36:28 +00:00
Merge branch 'prerequisites' into development
This commit is contained in:
commit
72c95a49d2
41 files changed
+2171
-134
No files matched your search
@@ -53,6 +53,8 @@
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Clayerbrushes_005Cinternal/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Clayereffects_005Cinternal/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cmodules_005Cactivationrequirements/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprerequisites/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Cprerequisites_005Cprerequisiteaction/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=plugins_005Csettings/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=rgb_002Enet/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=services_005Ccolorquantizer/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception thrown when a plugin prerequisite-related error occurs
|
||||
/// </summary>
|
||||
public class ArtemisPluginPrerequisiteException : Exception
|
||||
{
|
||||
internal ArtemisPluginPrerequisiteException(IPrerequisitesSubject subject)
|
||||
{
|
||||
Subject = subject;
|
||||
}
|
||||
|
||||
internal ArtemisPluginPrerequisiteException(IPrerequisitesSubject subject, string message) : base(message)
|
||||
{
|
||||
Subject = subject;
|
||||
}
|
||||
|
||||
internal ArtemisPluginPrerequisiteException(IPrerequisitesSubject subject, string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
Subject = subject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subject the error is related to
|
||||
/// </summary>
|
||||
public IPrerequisitesSubject Subject { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// Based on: https://www.codeproject.com/Tips/5274597/An-Improved-Stream-CopyToAsync-that-Reports-Progre
|
||||
// The MIT License
|
||||
//
|
||||
// Copyright (c) 2020 honey the codewitch
|
||||
//
|
||||
// Permission is hereby granted, free of charge,
|
||||
// to any person obtaining a copy of this software and
|
||||
// associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify,
|
||||
// merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom
|
||||
// the Software is furnished to do so,
|
||||
// subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice
|
||||
// shall be included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
internal static class StreamExtensions
|
||||
{
|
||||
private const int DefaultBufferSize = 81920;
|
||||
|
||||
/// <summary>
|
||||
/// Copys a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="sourceLength">The length of the source stream, if known - used for progress reporting</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="bufferSize">The size of the copy block buffer</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <param name="cancellationToken">A cancellation token</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static async Task CopyToAsync(
|
||||
this Stream source,
|
||||
long sourceLength,
|
||||
Stream destination,
|
||||
int bufferSize,
|
||||
IProgress<(long, long)> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (0 == bufferSize)
|
||||
bufferSize = DefaultBufferSize;
|
||||
byte[]? buffer = new byte[bufferSize];
|
||||
if (0 > sourceLength && source.CanSeek)
|
||||
sourceLength = source.Length - source.Position;
|
||||
long totalBytesCopied = 0L;
|
||||
if (null != progress)
|
||||
progress.Report((totalBytesCopied, sourceLength));
|
||||
int bytesRead = -1;
|
||||
while (0 != bytesRead && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
|
||||
if (0 == bytesRead || cancellationToken.IsCancellationRequested)
|
||||
break;
|
||||
await destination.WriteAsync(buffer, 0, buffer.Length, cancellationToken);
|
||||
totalBytesCopied += bytesRead;
|
||||
progress?.Report((totalBytesCopied, sourceLength));
|
||||
}
|
||||
|
||||
if (0 < totalBytesCopied)
|
||||
progress?.Report((totalBytesCopied, sourceLength));
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copys a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="sourceLength">The length of the source stream, if known - used for progress reporting</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <param name="cancellationToken">A cancellation token</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static Task CopyToAsync(this Stream source, long sourceLength, Stream destination, IProgress<(long, long)> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
return CopyToAsync(source, sourceLength, destination, 0, progress, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copys a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <param name="cancellationToken">A cancellation token</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static Task CopyToAsync(this Stream source, Stream destination, IProgress<(long, long)> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
return CopyToAsync(source, 0L, destination, 0, progress, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copys a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="sourceLength">The length of the source stream, if known - used for progress reporting</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static Task CopyToAsync(this Stream source, long sourceLength, Stream destination, IProgress<(long, long)> progress)
|
||||
{
|
||||
return CopyToAsync(source, sourceLength, destination, 0, progress, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copys a stream to another stream
|
||||
/// </summary>
|
||||
/// <param name="source">The source <see cref="Stream" /> to copy from</param>
|
||||
/// <param name="destination">The destination <see cref="Stream" /> to copy to</param>
|
||||
/// <param name="progress">An <see cref="IProgress{T}" /> implementation for reporting progress</param>
|
||||
/// <returns>A task representing the operation</returns>
|
||||
public static Task CopyToAsync(this Stream source, Stream destination, IProgress<(long, long)> progress)
|
||||
{
|
||||
return CopyToAsync(source, 0L, destination, 0, progress, default);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// An optional entry point for your plugin
|
||||
/// </summary>
|
||||
public interface IPluginBootstrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when the plugin is activated
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin instance of your plugin</param>
|
||||
void Enable(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);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ namespace Artemis.Core
|
||||
Info = info;
|
||||
Directory = directory;
|
||||
Entity = pluginEntity ?? new PluginEntity {Id = Guid, IsEnabled = true};
|
||||
Info.Plugin = this;
|
||||
|
||||
_features = new List<PluginFeatureInfo>();
|
||||
}
|
||||
@@ -71,7 +72,7 @@ namespace Artemis.Core
|
||||
/// <summary>
|
||||
/// Gets the plugin bootstrapper
|
||||
/// </summary>
|
||||
public IPluginBootstrapper? Bootstrapper { get; internal set; }
|
||||
public PluginBootstrapper? Bootstrapper { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Ninject kernel of the plugin
|
||||
@@ -113,6 +114,17 @@ namespace Artemis.Core
|
||||
{
|
||||
return _features.FirstOrDefault(i => i.Instance is T)?.Instance as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up the feature info the feature of type <typeparamref name="T" />
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of feature to find</typeparam>
|
||||
/// <returns>Feature info of the feature</returns>
|
||||
public PluginFeatureInfo GetFeatureInfo<T>() where T : PluginFeature
|
||||
{
|
||||
// This should be a safe assumption because any type of PluginFeature is registered and added
|
||||
return _features.First(i => i.FeatureType == typeof(T));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
@@ -235,12 +247,12 @@ namespace Artemis.Core
|
||||
|
||||
if (enable)
|
||||
{
|
||||
Bootstrapper?.Enable(this);
|
||||
Bootstrapper?.OnPluginEnabled(this);
|
||||
OnEnabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
Bootstrapper?.Disable(this);
|
||||
Bootstrapper?.OnPluginDisabled(this);
|
||||
OnDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// An optional entry point for your plugin
|
||||
/// </summary>
|
||||
public abstract class PluginBootstrapper
|
||||
{
|
||||
private Plugin? _plugin;
|
||||
|
||||
/// <summary>
|
||||
/// Called when the plugin is loaded
|
||||
/// </summary>
|
||||
/// <param name="plugin"></param>
|
||||
public virtual void OnPluginLoaded(Plugin plugin)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the plugin is activated
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin instance of your plugin</param>
|
||||
public virtual 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>
|
||||
public virtual void OnPluginDisabled(Plugin plugin)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the provided prerequisite to the plugin.
|
||||
/// </summary>
|
||||
/// <param name="prerequisite">The prerequisite to add</param>
|
||||
public void AddPluginPrerequisite(PluginPrerequisite prerequisite)
|
||||
{
|
||||
// TODO: We can keep track of them and add them after load, same goes for the others
|
||||
if (_plugin == null)
|
||||
throw new ArtemisPluginException("Cannot add plugin prerequisites before the plugin is loaded");
|
||||
|
||||
if (!_plugin.Info.Prerequisites.Contains(prerequisite))
|
||||
_plugin.Info.Prerequisites.Add(prerequisite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the provided prerequisite from the plugin.
|
||||
/// </summary>
|
||||
/// <param name="prerequisite">The prerequisite to remove</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> is successfully removed; otherwise <see langword="false" />. This method also returns
|
||||
/// <see langword="false" /> if the prerequisite was not found.
|
||||
/// </returns>
|
||||
public bool RemovePluginPrerequisite(PluginPrerequisite prerequisite)
|
||||
{
|
||||
if (_plugin == null)
|
||||
throw new ArtemisPluginException("Cannot add plugin prerequisites before the plugin is loaded");
|
||||
|
||||
return _plugin.Info.Prerequisites.Remove(prerequisite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the provided prerequisite to the feature of type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <param name="prerequisite">The prerequisite to add</param>
|
||||
public void AddFeaturePrerequisite<T>(PluginPrerequisite prerequisite) where T : PluginFeature
|
||||
{
|
||||
if (_plugin == null)
|
||||
throw new ArtemisPluginException("Cannot add feature prerequisites before the plugin is loaded");
|
||||
|
||||
PluginFeatureInfo info = _plugin.GetFeatureInfo<T>();
|
||||
if (!info.Prerequisites.Contains(prerequisite))
|
||||
info.Prerequisites.Add(prerequisite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the provided prerequisite from the feature of type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <param name="prerequisite">The prerequisite to remove</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> is successfully removed; otherwise <see langword="false" />. This method also returns
|
||||
/// <see langword="false" /> if the prerequisite was not found.
|
||||
/// </returns>
|
||||
public bool RemoveFeaturePrerequisite<T>(PluginPrerequisite prerequisite) where T : PluginFeature
|
||||
{
|
||||
if (_plugin == null)
|
||||
throw new ArtemisPluginException("Cannot add feature prerequisites before the plugin is loaded");
|
||||
|
||||
return _plugin.GetFeatureInfo<T>().Prerequisites.Remove(prerequisite);
|
||||
}
|
||||
|
||||
internal void InternalOnPluginLoaded(Plugin plugin)
|
||||
{
|
||||
_plugin = plugin;
|
||||
OnPluginLoaded(plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Artemis.Core.DataModelExpansions;
|
||||
using Artemis.Core.DeviceProviders;
|
||||
using Artemis.Core.LayerBrushes;
|
||||
using Artemis.Core.LayerEffects;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.Storage.Entities.Plugins;
|
||||
using Humanizer;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@@ -13,23 +16,24 @@ namespace Artemis.Core
|
||||
/// Represents basic info about a plugin feature and contains a reference to the instance of said feature
|
||||
/// </summary>
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
public class PluginFeatureInfo : CorePropertyChanged
|
||||
public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject
|
||||
{
|
||||
private string? _description;
|
||||
private string? _icon;
|
||||
private PluginFeature? _instance;
|
||||
private string _name = null!;
|
||||
|
||||
internal PluginFeatureInfo(Plugin plugin, Type featureType, PluginFeatureAttribute? attribute)
|
||||
internal PluginFeatureInfo(Plugin plugin, Type featureType, PluginFeatureEntity pluginFeatureEntity, PluginFeatureAttribute? attribute)
|
||||
{
|
||||
Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin));
|
||||
FeatureType = featureType ?? throw new ArgumentNullException(nameof(featureType));
|
||||
Entity = pluginFeatureEntity;
|
||||
|
||||
Name = attribute?.Name ?? featureType.Name.Humanize(LetterCasing.Title);
|
||||
Description = attribute?.Description;
|
||||
Icon = attribute?.Icon;
|
||||
AlwaysEnabled = attribute?.AlwaysEnabled ?? false;
|
||||
|
||||
|
||||
if (Icon != null) return;
|
||||
if (typeof(BaseDataModelExpansion).IsAssignableFrom(featureType))
|
||||
Icon = "TableAdd";
|
||||
@@ -46,7 +50,7 @@ namespace Artemis.Core
|
||||
else
|
||||
Icon = "Plugin";
|
||||
}
|
||||
|
||||
|
||||
internal PluginFeatureInfo(Plugin plugin, PluginFeatureAttribute? attribute, PluginFeature instance)
|
||||
{
|
||||
if (instance == null) throw new ArgumentNullException(nameof(instance));
|
||||
@@ -119,6 +123,11 @@ namespace Artemis.Core
|
||||
[JsonProperty]
|
||||
public bool AlwaysEnabled { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a boolean indicating whether the feature is enabled in persistent storage
|
||||
/// </summary>
|
||||
public bool EnabledInStorage => Entity.IsEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the feature this info is associated with
|
||||
/// </summary>
|
||||
@@ -128,6 +137,14 @@ namespace Artemis.Core
|
||||
internal set => SetAndNotify(ref _instance, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<PluginPrerequisite> Prerequisites { get; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ArePrerequisitesMet() => Prerequisites.All(p => p.IsMet());
|
||||
|
||||
internal PluginFeatureEntity Entity { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Artemis.Core
|
||||
@@ -8,7 +10,7 @@ namespace Artemis.Core
|
||||
/// Represents basic info about a plugin and contains a reference to the instance of said plugin
|
||||
/// </summary>
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
public class PluginInfo : CorePropertyChanged
|
||||
public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject
|
||||
{
|
||||
private bool _autoEnableFeatures = true;
|
||||
private string? _description;
|
||||
@@ -117,6 +119,12 @@ namespace Artemis.Core
|
||||
internal set => SetAndNotify(ref _plugin, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<PluginPrerequisite> Prerequisites { get; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ArePrerequisitesMet() => Prerequisites.All(p => p.IsMet());
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a type that has prerequisites
|
||||
/// </summary>
|
||||
public interface IPrerequisitesSubject
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of prerequisites for this plugin
|
||||
/// </summary>
|
||||
List<PluginPrerequisite> Prerequisites { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the prerequisites of this plugin are met
|
||||
/// </summary>
|
||||
bool ArePrerequisitesMet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
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>
|
||||
/// 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>
|
||||
/// 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 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,96 @@
|
||||
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 bool _showProgressBar;
|
||||
private bool _showSubProgressBar;
|
||||
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 a boolean indicating whether the progress bar should be shown
|
||||
/// </summary>
|
||||
public bool ShowProgressBar
|
||||
{
|
||||
get => _showProgressBar;
|
||||
set => SetAndNotify(ref _showProgressBar, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the sub progress bar should be shown
|
||||
/// </summary>
|
||||
public bool ShowSubProgressBar
|
||||
{
|
||||
get => _showSubProgressBar;
|
||||
set => SetAndNotify(ref _showSubProgressBar, 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,81 @@
|
||||
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;
|
||||
|
||||
ShowProgressBar = true;
|
||||
ShowSubProgressBar = true;
|
||||
}
|
||||
|
||||
/// <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,44 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a plugin prerequisite action that deletes a file
|
||||
/// </summary>
|
||||
public class DeleteFileAction : 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 folder to delete recursively</param>
|
||||
public DeleteFileAction(string name, string target) : base(name)
|
||||
{
|
||||
Target = target;
|
||||
ProgressIndeterminate = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target directory
|
||||
/// </summary>
|
||||
public string Target { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Execute(CancellationToken cancellationToken)
|
||||
{
|
||||
ShowProgressBar = true;
|
||||
Status = $"Removing {Target}";
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (File.Exists(Target))
|
||||
File.Delete(Target);
|
||||
}, cancellationToken);
|
||||
|
||||
ShowProgressBar = false;
|
||||
Status = $"Removed {Target}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a plugin prerequisite action that recursively deletes a folder
|
||||
/// </summary>
|
||||
public class DeleteFolderAction : 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 folder to delete recursively</param>
|
||||
public DeleteFolderAction(string name, string target) : base(name)
|
||||
{
|
||||
if (Enum.GetValues<Environment.SpecialFolder>().Select(Environment.GetFolderPath).Contains(target))
|
||||
throw new ArtemisCoreException($"Cannot delete special folder {target}, silly goose.");
|
||||
|
||||
Target = target;
|
||||
ProgressIndeterminate = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target directory
|
||||
/// </summary>
|
||||
public string Target { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Execute(CancellationToken cancellationToken)
|
||||
{
|
||||
ShowProgressBar = true;
|
||||
Status = $"Removing {Target}";
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (Directory.Exists(Target))
|
||||
Directory.Delete(Target, true);
|
||||
}, cancellationToken);
|
||||
|
||||
ShowProgressBar = false;
|
||||
Status = $"Removed {Target}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Humanizer;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a plugin prerequisite action that downloads a file
|
||||
/// </summary>
|
||||
public class DownloadFileAction : 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 URL to download</param>
|
||||
/// <param name="target">The target file to save as (will be created if needed)</param>
|
||||
public DownloadFileAction(string name, string source, string target) : base(name)
|
||||
{
|
||||
Source = source ?? throw new ArgumentNullException(nameof(source));
|
||||
Target = target ?? throw new ArgumentNullException(nameof(target));
|
||||
|
||||
ShowProgressBar = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source URL to download
|
||||
/// </summary>
|
||||
public string Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target file to save as (will be created if needed)
|
||||
/// </summary>
|
||||
public string Target { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Execute(CancellationToken cancellationToken)
|
||||
{
|
||||
using HttpClient client = new();
|
||||
await using FileStream destinationStream = File.Create(Target);
|
||||
|
||||
void ProgressOnProgressReported(object? sender, EventArgs e)
|
||||
{
|
||||
if (Progress.ProgressPerSecond != 0)
|
||||
Status = $"Downloading {Target} - {Progress.ProgressPerSecond.Bytes().Humanize("#.##")}/sec";
|
||||
else
|
||||
Status = $"Downloading {Target}";
|
||||
}
|
||||
|
||||
Progress.ProgressReported += ProgressOnProgressReported;
|
||||
|
||||
// Get the http headers first to examine the content length
|
||||
using HttpResponseMessage response = await client.GetAsync(Target, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||
await using Stream download = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
long? contentLength = response.Content.Headers.ContentLength;
|
||||
|
||||
// Ignore progress reporting when no progress reporter was
|
||||
// passed or when the content length is unknown
|
||||
if (!contentLength.HasValue)
|
||||
{
|
||||
ProgressIndeterminate = true;
|
||||
await download.CopyToAsync(destinationStream, Progress, cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgressIndeterminate = false;
|
||||
await download.CopyToAsync(contentLength.Value, destinationStream, Progress, cancellationToken);
|
||||
}
|
||||
|
||||
Progress.ProgressReported -= ProgressOnProgressReported;
|
||||
|
||||
Progress.Report((1, 1));
|
||||
Status = "Finished downloading";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a plugin prerequisite action that copies a folder
|
||||
/// </summary>
|
||||
public class WriteBytesToFileAction : 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 WriteBytesToFileAction(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 or sets a boolean indicating whether or not to append to the file if it exists already, if set to
|
||||
/// <see langword="false" /> the file will be deleted and recreated
|
||||
/// </summary>
|
||||
public bool Append { get; set; } = false;
|
||||
|
||||
/// <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);
|
||||
|
||||
ShowProgressBar = true;
|
||||
Status = $"Writing to {Path.GetFileName(Target)}...";
|
||||
|
||||
if (!Append && File.Exists(Target))
|
||||
File.Delete(Target);
|
||||
|
||||
await using Stream fileStream = File.OpenWrite(Target);
|
||||
await using MemoryStream sourceStream = new(ByteContent);
|
||||
await sourceStream.CopyToAsync(sourceStream.Length, fileStream, Progress, cancellationToken);
|
||||
|
||||
ShowProgressBar = false;
|
||||
Status = $"Finished writing to {Path.GetFileName(Target)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Artemis.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a plugin prerequisite action that copies a folder
|
||||
/// </summary>
|
||||
public class WriteStringToFileAction : 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 WriteStringToFileAction(string name, string target, string content) : base(name)
|
||||
{
|
||||
Target = target;
|
||||
Content = content ?? throw new ArgumentNullException(nameof(content));
|
||||
|
||||
ProgressIndeterminate = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target file
|
||||
/// </summary>
|
||||
public string Target { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether or not to append to the file if it exists already, if set to
|
||||
/// <see langword="false" /> the file will be deleted and recreated
|
||||
/// </summary>
|
||||
public bool Append { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the string that will be written
|
||||
/// </summary>
|
||||
public string Content { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Execute(CancellationToken cancellationToken)
|
||||
{
|
||||
string outputDir = Path.GetDirectoryName(Target)!;
|
||||
Utilities.CreateAccessibleDirectory(outputDir);
|
||||
|
||||
ShowProgressBar = true;
|
||||
Status = $"Writing to {Path.GetFileName(Target)}...";
|
||||
|
||||
if (Append)
|
||||
await File.AppendAllTextAsync(Target, Content, cancellationToken);
|
||||
else
|
||||
await File.WriteAllTextAsync(Target, Content, cancellationToken);
|
||||
|
||||
ShowProgressBar = 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
|
||||
}
|
||||
}
|
||||
@@ -241,7 +241,16 @@ namespace Artemis.Core.Services
|
||||
}
|
||||
|
||||
foreach (Plugin plugin in _plugins.Where(p => p.Entity.IsEnabled))
|
||||
EnablePlugin(plugin, false, ignorePluginLock);
|
||||
{
|
||||
try
|
||||
{
|
||||
EnablePlugin(plugin, false, ignorePluginLock);
|
||||
}
|
||||
catch (ArtemisPluginPrerequisiteException)
|
||||
{
|
||||
_logger.Warning("Skipped enabling plugin {plugin} because not all prerequisites are met", plugin);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug("Enabled {count} plugin(s)", _plugins.Count(p => p.IsEnabled));
|
||||
// ReSharper restore InconsistentlySynchronizedField
|
||||
@@ -331,16 +340,24 @@ namespace Artemis.Core.Services
|
||||
}
|
||||
|
||||
foreach (Type featureType in featureTypes)
|
||||
plugin.AddFeature(new PluginFeatureInfo(plugin, featureType, (PluginFeatureAttribute?) Attribute.GetCustomAttribute(featureType, typeof(PluginFeatureAttribute))));
|
||||
{
|
||||
// Load the enabled state and if not found, default to true
|
||||
PluginFeatureEntity featureEntity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureType.FullName) ??
|
||||
new PluginFeatureEntity { IsEnabled = plugin.Info.AutoEnableFeatures, Type = featureType.FullName! };
|
||||
plugin.AddFeature(new PluginFeatureInfo(plugin, featureType, featureEntity, (PluginFeatureAttribute?) Attribute.GetCustomAttribute(featureType, typeof(PluginFeatureAttribute))));
|
||||
}
|
||||
|
||||
if (!featureTypes.Any())
|
||||
_logger.Warning("Plugin {plugin} contains no features", plugin);
|
||||
|
||||
List<Type> bootstrappers = plugin.Assembly.GetTypes().Where(t => typeof(IPluginBootstrapper).IsAssignableFrom(t)).ToList();
|
||||
List<Type> bootstrappers = plugin.Assembly.GetTypes().Where(t => typeof(PluginBootstrapper).IsAssignableFrom(t)).ToList();
|
||||
if (bootstrappers.Count > 1)
|
||||
_logger.Warning($"{plugin} has more than one bootstrapper, only initializing {bootstrappers.First().FullName}");
|
||||
if (bootstrappers.Any())
|
||||
plugin.Bootstrapper = (IPluginBootstrapper?) Activator.CreateInstance(bootstrappers.First());
|
||||
{
|
||||
plugin.Bootstrapper = (PluginBootstrapper?) Activator.CreateInstance(bootstrappers.First());
|
||||
plugin.Bootstrapper?.InternalOnPluginLoaded(plugin);
|
||||
}
|
||||
|
||||
lock (_plugins)
|
||||
{
|
||||
@@ -369,6 +386,9 @@ namespace Artemis.Core.Services
|
||||
return;
|
||||
}
|
||||
|
||||
if (!plugin.Info.ArePrerequisitesMet())
|
||||
throw new ArtemisPluginPrerequisiteException(plugin.Info, "Cannot enable a plugin whose prerequisites aren't all met");
|
||||
|
||||
// Create the Ninject child kernel and load the module
|
||||
plugin.Kernel = new ChildKernel(_kernel, new PluginModule(plugin));
|
||||
OnPluginEnabling(new PluginEventArgs(plugin));
|
||||
@@ -391,10 +411,7 @@ namespace Artemis.Core.Services
|
||||
featureInfo.Instance = instance;
|
||||
instance.Info = featureInfo;
|
||||
instance.Plugin = plugin;
|
||||
|
||||
// Load the enabled state and if not found, default to true
|
||||
instance.Entity = plugin.Entity.Features.FirstOrDefault(i => i.Type == featureInfo.FeatureType.FullName) ??
|
||||
new PluginFeatureEntity {IsEnabled = plugin.Info.AutoEnableFeatures, Type = featureInfo.FeatureType.FullName!};
|
||||
instance.Entity = featureInfo.Entity;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -403,17 +420,8 @@ namespace Artemis.Core.Services
|
||||
}
|
||||
|
||||
// Activate features after they are all loaded
|
||||
foreach (PluginFeatureInfo pluginFeature in plugin.Features.Where(f => f.Instance != null && (f.Instance.Entity.IsEnabled || f.AlwaysEnabled)))
|
||||
{
|
||||
try
|
||||
{
|
||||
EnablePluginFeature(pluginFeature.Instance!, false, !ignorePluginLock);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored, logged in EnablePluginFeature
|
||||
}
|
||||
}
|
||||
foreach (PluginFeatureInfo pluginFeature in plugin.Features.Where(f => f.Instance != null && (f.EnabledInStorage || f.AlwaysEnabled)))
|
||||
EnablePluginFeature(pluginFeature.Instance!, false, !ignorePluginLock);
|
||||
|
||||
if (saveState)
|
||||
{
|
||||
@@ -570,7 +578,10 @@ namespace Artemis.Core.Services
|
||||
if (pluginFeature.Plugin.Info.RequiresAdmin && !_isElevated)
|
||||
{
|
||||
if (!saveState)
|
||||
{
|
||||
OnPluginFeatureEnableFailed(new PluginFeatureEventArgs(pluginFeature));
|
||||
throw new ArtemisCoreException("Cannot enable a feature that requires elevation without saving it's state.");
|
||||
}
|
||||
|
||||
pluginFeature.Entity.IsEnabled = true;
|
||||
pluginFeature.Plugin.Entity.IsEnabled = true;
|
||||
@@ -581,6 +592,12 @@ namespace Artemis.Core.Services
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pluginFeature.Info.ArePrerequisitesMet())
|
||||
{
|
||||
OnPluginFeatureEnableFailed(new PluginFeatureEventArgs(pluginFeature));
|
||||
throw new ArtemisPluginPrerequisiteException(pluginFeature.Info, "Cannot enable a plugin feature whose prerequisites aren't all met");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
pluginFeature.SetEnabled(true, isAutoEnable);
|
||||
@@ -593,7 +610,6 @@ namespace Artemis.Core.Services
|
||||
new ArtemisPluginException(pluginFeature.Plugin, $"Exception during SetEnabled(true) on {pluginFeature}", e),
|
||||
"Failed to enable plugin"
|
||||
);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user