diff --git a/src/Artemis.Core/Exceptions/ArtemisPluginPrerequisiteException.cs b/src/Artemis.Core/Exceptions/ArtemisPluginPrerequisiteException.cs
new file mode 100644
index 000000000..fd2dbe169
--- /dev/null
+++ b/src/Artemis.Core/Exceptions/ArtemisPluginPrerequisiteException.cs
@@ -0,0 +1,38 @@
+using System;
+
+namespace Artemis.Core
+{
+ ///
+ /// An exception thrown when a plugin prerequisite-related error occurs
+ ///
+ public class ArtemisPluginPrerequisiteException : Exception
+ {
+ internal ArtemisPluginPrerequisiteException(Plugin plugin, PluginPrerequisite? pluginPrerequisite)
+ {
+ Plugin = plugin;
+ PluginPrerequisite = pluginPrerequisite;
+ }
+
+ internal ArtemisPluginPrerequisiteException(Plugin plugin, PluginPrerequisite? pluginPrerequisite, string message) : base(message)
+ {
+ Plugin = plugin;
+ PluginPrerequisite = pluginPrerequisite;
+ }
+
+ internal ArtemisPluginPrerequisiteException(Plugin plugin, PluginPrerequisite? pluginPrerequisite, string message, Exception inner) : base(message, inner)
+ {
+ Plugin = plugin;
+ PluginPrerequisite = pluginPrerequisite;
+ }
+
+ ///
+ /// Gets the plugin the error is related to
+ ///
+ public Plugin Plugin { get; }
+
+ ///
+ /// Gets the plugin prerequisite the error is related to
+ ///
+ public PluginPrerequisite? PluginPrerequisite { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs
index 03403cf51..a8dbaf7ad 100644
--- a/src/Artemis.Core/Plugins/Plugin.cs
+++ b/src/Artemis.Core/Plugins/Plugin.cs
@@ -125,6 +125,14 @@ namespace Artemis.Core
return Info.ToString();
}
+ ///
+ /// Determines whether the prerequisites of this plugin are met
+ ///
+ public bool ArePrerequisitesMet()
+ {
+ return Prerequisites.All(p => p.IsMet());
+ }
+
///
/// Occurs when the plugin is enabled
///
diff --git a/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs b/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs
index ea272786b..5267b4d33 100644
--- a/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs
+++ b/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs
@@ -123,7 +123,7 @@ namespace Artemis.Core
/// Called to determine whether the prerequisite is met
///
/// if the prerequisite is met; otherwise
- public abstract Task IsMet();
+ public abstract bool IsMet();
///
/// Called before installation starts
diff --git a/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisiteAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisiteAction.cs
index 344d4412d..90d9a787e 100644
--- a/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisiteAction.cs
+++ b/src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisiteAction.cs
@@ -10,6 +10,8 @@ namespace Artemis.Core
public abstract class PluginPrerequisiteAction : CorePropertyChanged
{
private bool _progressIndeterminate;
+ private bool _showProgressBar;
+ private bool _showSubProgressBar;
private string? _status;
private bool _subProgressIndeterminate;
@@ -56,6 +58,24 @@ namespace Artemis.Core
set => SetAndNotify(ref _subProgressIndeterminate, value);
}
+ ///
+ /// Gets or sets a boolean indicating whether the progress bar should be shown
+ ///
+ public bool ShowProgressBar
+ {
+ get => _showProgressBar;
+ set => SetAndNotify(ref _showProgressBar, value);
+ }
+
+ ///
+ /// Gets or sets a boolean indicating whether the sub progress bar should be shown
+ ///
+ public bool ShowSubProgressBar
+ {
+ get => _showSubProgressBar;
+ set => SetAndNotify(ref _showSubProgressBar, value);
+ }
+
///
/// Gets or sets the progress of the action (0 to 100)
///
diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/CopyFolderAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/CopyFolderAction.cs
index 7a18f5f08..6602567e0 100644
--- a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/CopyFolderAction.cs
+++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/CopyFolderAction.cs
@@ -21,6 +21,9 @@ namespace Artemis.Core
{
Source = source;
Target = target;
+
+ ShowProgressBar = true;
+ ShowSubProgressBar = true;
}
///
diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DeleteFileAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DeleteFileAction.cs
new file mode 100644
index 000000000..e6324bf8f
--- /dev/null
+++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DeleteFileAction.cs
@@ -0,0 +1,44 @@
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents a plugin prerequisite action that deletes a file
+ ///
+ public class DeleteFileAction : PluginPrerequisiteAction
+ {
+ ///
+ /// Creates a new instance of a copy folder action
+ ///
+ /// The name of the action
+ /// The target folder to delete recursively
+ public DeleteFileAction(string name, string target) : base(name)
+ {
+ Target = target;
+ ProgressIndeterminate = true;
+ }
+
+ ///
+ /// Gets or sets the target directory
+ ///
+ public string Target { get; }
+
+ ///
+ 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}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DeleteFolderAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DeleteFolderAction.cs
new file mode 100644
index 000000000..62b4dfc41
--- /dev/null
+++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/DeleteFolderAction.cs
@@ -0,0 +1,49 @@
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents a plugin prerequisite action that recursively deletes a folder
+ ///
+ public class DeleteFolderAction : PluginPrerequisiteAction
+ {
+ ///
+ /// Creates a new instance of a copy folder action
+ ///
+ /// The name of the action
+ /// The target folder to delete recursively
+ public DeleteFolderAction(string name, string target) : base(name)
+ {
+ if (Enum.GetValues().Select(Environment.GetFolderPath).Contains(target))
+ throw new ArtemisCoreException($"Cannot delete special folder {target}, silly goose.");
+
+ Target = target;
+ ProgressIndeterminate = true;
+ }
+
+ ///
+ /// Gets or sets the target directory
+ ///
+ public string Target { get; }
+
+ ///
+ 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}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteBytesToFileAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteBytesToFileAction.cs
new file mode 100644
index 000000000..92c7d0294
--- /dev/null
+++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteBytesToFileAction.cs
@@ -0,0 +1,62 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Artemis.Core
+{
+ ///
+ /// Represents a plugin prerequisite action that copies a folder
+ ///
+ public class WriteBytesToFileAction : PluginPrerequisiteAction
+ {
+ ///
+ /// Creates a new instance of a copy folder action
+ ///
+ /// The name of the action
+ /// The target file to write to (will be created if needed)
+ /// The contents to write
+ public WriteBytesToFileAction(string name, string target, byte[] content) : base(name)
+ {
+ Target = target;
+ ByteContent = content ?? throw new ArgumentNullException(nameof(content));
+ }
+
+ ///
+ /// Gets or sets the target file
+ ///
+ public string Target { get; }
+
+ ///
+ /// Gets or sets a boolean indicating whether or not to append to the file if it exists already, if set to
+ /// the file will be deleted and recreated
+ ///
+ public bool Append { get; set; } = false;
+
+ ///
+ /// Gets the bytes that will be written
+ ///
+ public byte[] ByteContent { get; }
+
+ ///
+ 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)}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteToFileAction.cs b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteStringToFileAction.cs
similarity index 55%
rename from src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteToFileAction.cs
rename to src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteStringToFileAction.cs
index 0dedc3f28..e4e6a9875 100644
--- a/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteToFileAction.cs
+++ b/src/Artemis.Core/Plugins/Prerequisites/PrerequisiteAction/WriteStringToFileAction.cs
@@ -1,5 +1,6 @@
using System;
using System.IO;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -8,7 +9,7 @@ namespace Artemis.Core
///
/// Represents a plugin prerequisite action that copies a folder
///
- public class WriteToFileAction : PluginPrerequisiteAction
+ public class WriteStringToFileAction : PluginPrerequisiteAction
{
///
/// Creates a new instance of a copy folder action
@@ -16,22 +17,12 @@ namespace Artemis.Core
/// The name of the action
/// The target file to write to (will be created if needed)
/// The contents to write
- public WriteToFileAction(string name, string target, string content) : base(name)
- {
- Target = target ?? throw new ArgumentNullException(nameof(target));
- Content = content ?? throw new ArgumentNullException(nameof(content));
- }
-
- ///
- /// Creates a new instance of a copy folder action
- ///
- /// The name of the action
- /// The target file to write to (will be created if needed)
- /// The contents to write
- public WriteToFileAction(string name, string target, byte[] content) : base(name)
+ public WriteStringToFileAction(string name, string target, string content) : base(name)
{
Target = target;
- ByteContent = content ?? throw new ArgumentNullException(nameof(content));
+ Content = content ?? throw new ArgumentNullException(nameof(content));
+
+ ProgressIndeterminate = true;
}
///
@@ -40,30 +31,31 @@ namespace Artemis.Core
public string Target { get; }
///
- /// Gets the contents that will be written
+ /// Gets or sets a boolean indicating whether or not to append to the file if it exists already, if set to
+ /// the file will be deleted and recreated
///
- public string? Content { get; }
+ public bool Append { get; set; } = false;
///
- /// Gets the bytes that will be written
+ /// Gets the string that will be written
///
- public byte[]? ByteContent { get; }
-
+ public string Content { get; }
+
///
public override async Task Execute(CancellationToken cancellationToken)
{
string outputDir = Path.GetDirectoryName(Target)!;
Utilities.CreateAccessibleDirectory(outputDir);
- ProgressIndeterminate = true;
+ ShowProgressBar = true;
Status = $"Writing to {Path.GetFileName(Target)}...";
- if (Content != null)
+ if (Append)
+ await File.AppendAllTextAsync(Target, Content, cancellationToken);
+ else
await File.WriteAllTextAsync(Target, Content, cancellationToken);
- else if (ByteContent != null)
- await File.WriteAllBytesAsync(Target, ByteContent, cancellationToken);
- ProgressIndeterminate = false;
+ ShowProgressBar = false;
Status = $"Finished writing to {Path.GetFileName(Target)}";
}
}
diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs
index 9fcd8829a..a46c1e3fb 100644
--- a/src/Artemis.Core/Services/PluginManagementService.cs
+++ b/src/Artemis.Core/Services/PluginManagementService.cs
@@ -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
@@ -372,6 +381,9 @@ namespace Artemis.Core.Services
return;
}
+ if (!plugin.ArePrerequisitesMet())
+ throw new ArtemisPluginPrerequisiteException(plugin, null, "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));
diff --git a/src/Artemis.UI.Shared/Screens/Dialogs/ConfirmDialogView.xaml b/src/Artemis.UI.Shared/Screens/Dialogs/ConfirmDialogView.xaml
index 9fced07e6..a65e58422 100644
--- a/src/Artemis.UI.Shared/Screens/Dialogs/ConfirmDialogView.xaml
+++ b/src/Artemis.UI.Shared/Screens/Dialogs/ConfirmDialogView.xaml
@@ -1,14 +1,18 @@
-
+ d:DataContext="{d:DesignInstance {x:Type dialogs:ConfirmDialogViewModel}}">
+
+
+
+ Content="{Binding CancelText}"
+ Visibility="{Binding CancelText, Converter={StaticResource NullToVisibilityConverter}, Mode=OneWay}" />