mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Plugins - Added prerequisites system
This commit is contained in:
parent
77227b7e6e
commit
19679c0ba6
@ -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>
|
||||
|
||||
133
src/Artemis.Core/Extensions/StreamExtensions.cs
Normal file
133
src/Artemis.Core/Extensions/StreamExtensions.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
|
||||
156
src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs
Normal file
156
src/Artemis.Core/Plugins/Prerequisites/PluginPrerequisite.cs
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
@ -340,7 +340,10 @@ namespace Artemis.Core.Services
|
||||
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?.OnPluginLoaded(plugin);
|
||||
}
|
||||
|
||||
lock (_plugins)
|
||||
{
|
||||
|
||||
@ -53,7 +53,7 @@ namespace Artemis.UI.Shared.Services
|
||||
|
||||
public LinkedList<Color> RecentColors => RecentColorsSetting.Value;
|
||||
|
||||
public Task<object> ShowGradientPicker(ColorGradient colorGradient, string dialogHost)
|
||||
public Task<object?> ShowGradientPicker(ColorGradient colorGradient, string dialogHost)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(dialogHost))
|
||||
return _dialogService.ShowDialogAt<GradientEditorViewModel>(dialogHost, new Dictionary<string, object> {{"colorGradient", colorGradient}});
|
||||
|
||||
@ -6,7 +6,7 @@ namespace Artemis.UI.Shared.Services
|
||||
/// <summary>
|
||||
/// Represents the base class for a dialog view model
|
||||
/// </summary>
|
||||
public abstract class DialogViewModelBase : ValidatingModelBase
|
||||
public abstract class DialogViewModelBase : Screen
|
||||
{
|
||||
private DialogViewModelHost? _dialogViewModelHost;
|
||||
private DialogSession? _session;
|
||||
@ -47,6 +47,7 @@ namespace Artemis.UI.Shared.Services
|
||||
/// </summary>
|
||||
public virtual void OnDialogClosed(object sender, DialogClosingEventArgs e)
|
||||
{
|
||||
ScreenExtensions.TryClose(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -61,6 +62,7 @@ namespace Artemis.UI.Shared.Services
|
||||
internal void OnDialogOpened(object sender, DialogOpenedEventArgs e)
|
||||
{
|
||||
Session = e.Session;
|
||||
ScreenExtensions.TryActivate(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@ namespace Artemis.UI.Shared.Services
|
||||
{
|
||||
internal interface IColorPickerService : IArtemisSharedUIService
|
||||
{
|
||||
Task<object> ShowGradientPicker(ColorGradient colorGradient, string dialogHost);
|
||||
Task<object?> ShowGradientPicker(ColorGradient colorGradient, string dialogHost);
|
||||
|
||||
PluginSetting<bool> PreviewSetting { get; }
|
||||
LinkedList<Color> RecentColors { get; }
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.UI.Screens.Modules;
|
||||
using Artemis.UI.Screens.Modules.Tabs;
|
||||
using Artemis.UI.Screens.Plugins;
|
||||
using Artemis.UI.Screens.ProfileEditor;
|
||||
using Artemis.UI.Screens.ProfileEditor.Conditions;
|
||||
using Artemis.UI.Screens.ProfileEditor.LayerProperties;
|
||||
@ -95,7 +96,13 @@ namespace Artemis.UI.Ninject.Factories
|
||||
TimelineSegmentViewModel TimelineSegmentViewModel(SegmentViewModelType segment, IObservableCollection<LayerPropertyGroupViewModel> layerPropertyGroups);
|
||||
}
|
||||
|
||||
public interface IDataBindingsVmFactory
|
||||
public interface IPrerequisitesVmFactory : IVmFactory
|
||||
{
|
||||
PluginPrerequisiteViewModel PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite);
|
||||
}
|
||||
|
||||
// TODO: Move these two
|
||||
public interface IDataBindingsVmFactory
|
||||
{
|
||||
IDataBindingViewModel DataBindingViewModel(IDataBindingRegistration registration);
|
||||
DirectDataBindingModeViewModel<TLayerProperty, TProperty> DirectDataBindingModeViewModel<TLayerProperty, TProperty>(DirectDataBinding<TLayerProperty, TProperty> directDataBinding);
|
||||
@ -104,7 +111,7 @@ namespace Artemis.UI.Ninject.Factories
|
||||
DataBindingConditionViewModel<TLayerProperty, TProperty> DataBindingConditionViewModel<TLayerProperty, TProperty>(DataBindingCondition<TLayerProperty, TProperty> dataBindingCondition);
|
||||
}
|
||||
|
||||
public interface IPropertyVmFactory
|
||||
public interface IPropertyVmFactory
|
||||
{
|
||||
ITreePropertyViewModel TreePropertyViewModel(ILayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel);
|
||||
ITimelinePropertyViewModel TimelinePropertyViewModel(ILayerProperty layerProperty, LayerPropertyViewModel layerPropertyViewModel);
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisiteActionView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Plugins"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:PluginPrerequisiteActionViewModel}">
|
||||
<StackPanel>
|
||||
<ProgressBar Value="{Binding Action.Progress.Percentage, Mode=OneWay}"
|
||||
IsIndeterminate="{Binding Action.ProgressIndeterminate, Mode=OneWay}"
|
||||
Visibility="{Binding ShowProgressBar, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
|
||||
materialDesign:TransitionAssist.DisableTransitions="True"
|
||||
Margin="0 10"/>
|
||||
<ProgressBar Value="{Binding Action.SubProgress.Percentage, Mode=OneWay}"
|
||||
IsIndeterminate="{Binding Action.SubProgressIndeterminate, Mode=OneWay}"
|
||||
Visibility="{Binding ShowSubProgressBar, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}"
|
||||
materialDesign:TransitionAssist.DisableTransitions="True"
|
||||
Margin="0 10"/>
|
||||
|
||||
<TextBlock TextWrapping="Wrap" Text="{Binding Action.Status, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Artemis.Core;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public class PluginPrerequisiteActionViewModel : Screen
|
||||
{
|
||||
private bool _showProgressBar;
|
||||
private bool _showSubProgressBar;
|
||||
|
||||
public PluginPrerequisiteActionViewModel(PluginPrerequisiteAction action)
|
||||
{
|
||||
Action = action;
|
||||
}
|
||||
|
||||
public PluginPrerequisiteAction Action { get; }
|
||||
|
||||
public bool ShowProgressBar
|
||||
{
|
||||
get => _showProgressBar;
|
||||
set => SetAndNotify(ref _showProgressBar, value);
|
||||
}
|
||||
|
||||
public bool ShowSubProgressBar
|
||||
{
|
||||
get => _showSubProgressBar;
|
||||
set => SetAndNotify(ref _showSubProgressBar, value);
|
||||
}
|
||||
|
||||
private void ActionOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(Action.ProgressIndeterminate) || e.PropertyName == nameof(Action.SubProgressIndeterminate))
|
||||
UpdateProgress();
|
||||
}
|
||||
|
||||
private void ProgressReported(object? sender, EventArgs e)
|
||||
{
|
||||
UpdateProgress();
|
||||
}
|
||||
|
||||
private void UpdateProgress()
|
||||
{
|
||||
ShowSubProgressBar = Action.SubProgress.Percentage != 0 || Action.SubProgressIndeterminate;
|
||||
ShowProgressBar = ShowSubProgressBar || Action.Progress.Percentage != 0 || Action.ProgressIndeterminate;
|
||||
}
|
||||
|
||||
#region Overrides of Screen
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
Action.Progress.ProgressReported += ProgressReported;
|
||||
Action.SubProgress.ProgressReported += ProgressReported;
|
||||
Action.PropertyChanged += ActionOnPropertyChanged;
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnClose()
|
||||
{
|
||||
Action.Progress.ProgressReported -= ProgressReported;
|
||||
Action.SubProgress.ProgressReported -= ProgressReported;
|
||||
Action.PropertyChanged -= ActionOnPropertyChanged;
|
||||
base.OnClose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
37
src/Artemis.UI/Screens/Plugins/PluginPrerequisiteView.xaml
Normal file
37
src/Artemis.UI/Screens/Plugins/PluginPrerequisiteView.xaml
Normal file
@ -0,0 +1,37 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisiteView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Plugins"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:PluginPrerequisiteViewModel}">
|
||||
|
||||
<StackPanel>
|
||||
<TextBlock Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Text="{Binding PluginPrerequisite.Name, Mode=OneWay}" />
|
||||
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" TextWrapping="Wrap" Text="{Binding PluginPrerequisite.Description, Mode=OneWay}" Margin="0 0 0 15" />
|
||||
|
||||
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
|
||||
Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="{Binding HasMultipleActions, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||
<Run Text="Step" />
|
||||
<Run Text="{Binding ActiveStemNumber, Mode=OneWay}" /><Run Text="/" /><Run Text="{Binding Items.Count, Mode=OneWay}" />
|
||||
<Run Text="-" />
|
||||
<Run Text="{Binding ActiveItem.Action.Name, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}"
|
||||
Foreground="{DynamicResource MaterialDesignBodyLight}"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding ActiveItem.Action.Name, Mode=OneWay}"
|
||||
Visibility="{Binding HasMultipleActions, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}" />
|
||||
<ContentControl s:View.Model="{Binding ActiveItem}"
|
||||
VerticalContentAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
IsTabStop="False"
|
||||
Margin="0 10 0 0" />
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
142
src/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs
Normal file
142
src/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs
Normal file
@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public class PluginPrerequisiteViewModel : Conductor<PluginPrerequisiteActionViewModel>.Collection.OneActive
|
||||
{
|
||||
private readonly ICoreService _coreService;
|
||||
private readonly IDialogService _dialogService;
|
||||
private bool _installing;
|
||||
private bool _uninstalling;
|
||||
private bool _isMet;
|
||||
|
||||
public PluginPrerequisiteViewModel(PluginPrerequisite pluginPrerequisite, ICoreService coreService, IDialogService dialogService)
|
||||
{
|
||||
_coreService = coreService;
|
||||
_dialogService = dialogService;
|
||||
|
||||
PluginPrerequisite = pluginPrerequisite;
|
||||
}
|
||||
|
||||
public PluginPrerequisite PluginPrerequisite { get; }
|
||||
|
||||
public bool Installing
|
||||
{
|
||||
get => _installing;
|
||||
set
|
||||
{
|
||||
SetAndNotify(ref _installing, value);
|
||||
NotifyOfPropertyChange(nameof(Busy));
|
||||
}
|
||||
}
|
||||
|
||||
public bool Uninstalling
|
||||
{
|
||||
get => _uninstalling;
|
||||
set
|
||||
{
|
||||
SetAndNotify(ref _uninstalling, value);
|
||||
NotifyOfPropertyChange(nameof(Busy));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsMet
|
||||
{
|
||||
get => _isMet;
|
||||
set => SetAndNotify(ref _isMet, value);
|
||||
}
|
||||
|
||||
public bool Busy => Installing || Uninstalling;
|
||||
public int ActiveStemNumber => Items.IndexOf(ActiveItem) + 1;
|
||||
public bool HasMultipleActions => Items.Count > 1;
|
||||
|
||||
public async Task Install(CancellationToken cancellationToken)
|
||||
{
|
||||
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
|
||||
{
|
||||
await PluginPrerequisite.Install(cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Installing = false;
|
||||
IsMet = await PluginPrerequisite.IsMet();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Uninstall(CancellationToken cancellationToken)
|
||||
{
|
||||
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
|
||||
{
|
||||
await PluginPrerequisite.Uninstall(cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Uninstalling = false;
|
||||
IsMet = await PluginPrerequisite.IsMet();
|
||||
}
|
||||
}
|
||||
|
||||
private void PluginPrerequisiteOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(PluginPrerequisite.CurrentAction))
|
||||
ActivateCurrentAction();
|
||||
}
|
||||
|
||||
private void ActivateCurrentAction()
|
||||
{
|
||||
ActiveItem = Items.FirstOrDefault(i => i.Action == PluginPrerequisite.CurrentAction);
|
||||
NotifyOfPropertyChange(nameof(ActiveStemNumber));
|
||||
}
|
||||
|
||||
#region Overrides of Screen
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnClose()
|
||||
{
|
||||
PluginPrerequisite.PropertyChanged += PluginPrerequisiteOnPropertyChanged;
|
||||
base.OnClose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged;
|
||||
Task.Run(async () => IsMet = await PluginPrerequisite.IsMet());
|
||||
|
||||
Items.AddRange(PluginPrerequisite.InstallActions.Select(a => new PluginPrerequisiteActionViewModel(a)));
|
||||
Items.AddRange(PluginPrerequisite.UninstallActions.Select(a => new PluginPrerequisiteActionViewModel(a)));
|
||||
|
||||
base.OnInitialActivate();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,126 @@
|
||||
<UserControl x:Class="Artemis.UI.Screens.Plugins.PluginPrerequisitesDialogView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Artemis.UI.Screens.Plugins"
|
||||
xmlns:s="https://github.com/canton7/Stylet"
|
||||
xmlns:shared="clr-namespace:Artemis.UI.Shared;assembly=Artemis.UI.Shared"
|
||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800"
|
||||
d:DataContext="{d:DesignInstance local:PluginPrerequisitesDialogViewModel}">
|
||||
<UserControl.Resources>
|
||||
<shared:NullToVisibilityConverter x:Key="NullToVisibilityConverter"/>
|
||||
</UserControl.Resources>
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition MinHeight="150"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="300" />
|
||||
<ColumnDefinition Width="500" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Row="0" Style="{StaticResource MaterialDesignHeadline6TextBlock}" TextWrapping="Wrap" Margin="0 0 0 20">
|
||||
Plugin prerequisites
|
||||
</TextBlock>
|
||||
|
||||
<ListBox Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
ItemsSource="{Binding Prerequisites}"
|
||||
SelectedItem="{Binding ActivePrerequisite, Mode=OneWay}"
|
||||
HorizontalContentAlignment="Stretch">
|
||||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="{x:Type local:PluginPrerequisiteViewModel}">
|
||||
<Border Padding="8" BorderThickness="0 0 0 1" BorderBrush="{DynamicResource MaterialDesignDivider}" VerticalAlignment="Stretch">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ToggleButton VerticalAlignment="Center"
|
||||
Style="{StaticResource MaterialDesignActionToggleButton}"
|
||||
Focusable="False"
|
||||
IsHitTestVisible="False"
|
||||
IsChecked="{Binding IsMet}">
|
||||
<ToggleButton.Content>
|
||||
<Border Background="#E74C4C" Width="32" Height="32">
|
||||
<materialDesign:PackIcon Kind="Close" VerticalAlignment="Center" HorizontalAlignment="Center" />
|
||||
</Border>
|
||||
</ToggleButton.Content>
|
||||
<materialDesign:ToggleButtonAssist.OnContent>
|
||||
<materialDesign:PackIcon Kind="Check" />
|
||||
</materialDesign:ToggleButtonAssist.OnContent>
|
||||
</ToggleButton>
|
||||
|
||||
<StackPanel Margin="8 0 0 0" Grid.Column="1" VerticalAlignment="Stretch">
|
||||
<TextBlock FontWeight="Bold" Text="{Binding PluginPrerequisite.Name}" TextWrapping="Wrap" />
|
||||
<TextBlock Text="{Binding PluginPrerequisite.Description}" TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<ContentControl Grid.Row="0"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="1"
|
||||
s:View.Model="{Binding ActivePrerequisite}"
|
||||
VerticalContentAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Margin="10 0"
|
||||
IsTabStop="False"
|
||||
Visibility="{Binding ActivePrerequisite, Converter={StaticResource NullToVisibilityConverter}}"/>
|
||||
|
||||
<TextBlock Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource MaterialDesignBody1TextBlock}"
|
||||
TextWrapping="Wrap"
|
||||
Margin="10 0"
|
||||
Visibility="{Binding ActivePrerequisite, Converter={StaticResource NullToVisibilityConverter}, ConverterParameter=Inverted}">
|
||||
In order for this plugin to function certain prerequisites must be met. <LineBreak /><LineBreak />
|
||||
On the left side you can see all prerequisites and whether they are currently met or not.
|
||||
Clicking install prerequisites will automatically set everything up for you.
|
||||
</TextBlock>
|
||||
|
||||
<StackPanel Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 8 0 0"
|
||||
Visibility="{Binding IsFinished, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}, Mode=OneWay}">
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Focusable="False"
|
||||
IsCancel="True"
|
||||
Command="{s:Action Cancel}"
|
||||
Content="CANCEL" />
|
||||
<Button x:Name="ConfirmButton"
|
||||
Style="{StaticResource MaterialDesignFlatButton}"
|
||||
IsDefault="True"
|
||||
Focusable="True"
|
||||
Command="{s:Action Install}"
|
||||
Content="INSTALL PREREQUISITES" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 8 0 0"
|
||||
Visibility="{Binding IsFinished, Converter={x:Static s:BoolToVisibilityConverter.Instance}, Mode=OneWay}">
|
||||
<Button Style="{StaticResource MaterialDesignFlatButton}"
|
||||
Focusable="False"
|
||||
IsCancel="True"
|
||||
Command="{s:Action Accept}"
|
||||
Content="FINISH" />
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.UI.Exceptions;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using Stylet;
|
||||
|
||||
namespace Artemis.UI.Screens.Plugins
|
||||
{
|
||||
public class PluginPrerequisitesDialogViewModel : DialogViewModelBase
|
||||
{
|
||||
private PluginPrerequisiteViewModel _activePrerequisite;
|
||||
private bool _canInstall;
|
||||
private bool _isFinished;
|
||||
private CancellationTokenSource _tokenSource;
|
||||
|
||||
public PluginPrerequisitesDialogViewModel(object pluginOrFeature, IPrerequisitesVmFactory prerequisitesVmFactory)
|
||||
{
|
||||
// Constructor overloading doesn't work very well with Kernel.Get<T> :(
|
||||
if (pluginOrFeature is Plugin plugin)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(plugin.Prerequisites.Select(prerequisitesVmFactory.PluginPrerequisiteViewModel));
|
||||
}
|
||||
else if (pluginOrFeature is PluginFeature feature)
|
||||
{
|
||||
Feature = feature;
|
||||
Prerequisites = new BindableCollection<PluginPrerequisiteViewModel>(feature.Prerequisites.Select(prerequisitesVmFactory.PluginPrerequisiteViewModel));
|
||||
}
|
||||
else
|
||||
throw new ArtemisUIException($"Expected plugin or feature to be passed to {nameof(PluginPrerequisitesDialogViewModel)}");
|
||||
|
||||
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
|
||||
pluginPrerequisiteViewModel.ConductWith(this);
|
||||
}
|
||||
|
||||
|
||||
public PluginFeature Feature { get; }
|
||||
public Plugin Plugin { get; }
|
||||
public BindableCollection<PluginPrerequisiteViewModel> Prerequisites { get; }
|
||||
|
||||
public PluginPrerequisiteViewModel ActivePrerequisite
|
||||
{
|
||||
get => _activePrerequisite;
|
||||
set => SetAndNotify(ref _activePrerequisite, value);
|
||||
}
|
||||
|
||||
public bool CanInstall
|
||||
{
|
||||
get => _canInstall;
|
||||
set => SetAndNotify(ref _canInstall, value);
|
||||
}
|
||||
|
||||
public bool IsFinished
|
||||
{
|
||||
get => _isFinished;
|
||||
set => SetAndNotify(ref _isFinished, value);
|
||||
}
|
||||
|
||||
#region Overrides of DialogViewModelBase
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnDialogClosed(object sender, DialogClosingEventArgs e)
|
||||
{
|
||||
_tokenSource?.Cancel();
|
||||
base.OnDialogClosed(sender, e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public async void Install()
|
||||
{
|
||||
CanInstall = false;
|
||||
_tokenSource = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (PluginPrerequisiteViewModel pluginPrerequisiteViewModel in Prerequisites)
|
||||
{
|
||||
ActivePrerequisite = pluginPrerequisiteViewModel;
|
||||
ActivePrerequisite.IsMet = await ActivePrerequisite.PluginPrerequisite.IsMet();
|
||||
if (ActivePrerequisite.IsMet)
|
||||
continue;
|
||||
|
||||
await ActivePrerequisite.Install(_tokenSource.Token);
|
||||
|
||||
// Wait after the task finished for the user to process what happened
|
||||
if (pluginPrerequisiteViewModel != Prerequisites.Last())
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
if (Prerequisites.All(p => p.IsMet))
|
||||
IsFinished = true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
finally
|
||||
{
|
||||
CanInstall = true;
|
||||
_tokenSource.Dispose();
|
||||
_tokenSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Accept()
|
||||
{
|
||||
Session?.Close(true);
|
||||
}
|
||||
|
||||
#region Overrides of Screen
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnInitialActivate()
|
||||
{
|
||||
base.OnInitialActivate();
|
||||
CanInstall = Prerequisites.Any(p => !p.IsMet);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -6,6 +7,7 @@ using System.Threading.Tasks;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.UI.Ninject.Factories;
|
||||
using Artemis.UI.Screens.Plugins;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Services;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
@ -162,9 +164,18 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
bool confirmed = await _dialogService.ShowConfirmDialog("Enable plugin", "This plugin requires admin rights, are you sure you want to enable it?");
|
||||
if (!confirmed)
|
||||
{
|
||||
Enabling = false;
|
||||
NotifyOfPropertyChange(nameof(IsEnabled));
|
||||
NotifyOfPropertyChange(nameof(CanOpenSettings));
|
||||
CancelEnable();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if all prerequisites are met async
|
||||
if (!await ArePrerequisitesMetAsync())
|
||||
{
|
||||
await _dialogService.ShowDialog<PluginPrerequisitesDialogViewModel>(new Dictionary<string, object> {{"pluginOrFeature", Plugin}});
|
||||
if (!await ArePrerequisitesMetAsync())
|
||||
{
|
||||
CancelEnable();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -190,5 +201,27 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
|
||||
NotifyOfPropertyChange(nameof(IsEnabled));
|
||||
NotifyOfPropertyChange(nameof(CanOpenSettings));
|
||||
}
|
||||
|
||||
private void CancelEnable()
|
||||
{
|
||||
Enabling = false;
|
||||
NotifyOfPropertyChange(nameof(IsEnabled));
|
||||
NotifyOfPropertyChange(nameof(CanOpenSettings));
|
||||
}
|
||||
|
||||
private async Task<bool> ArePrerequisitesMetAsync()
|
||||
{
|
||||
bool needsPrerequisites = false;
|
||||
foreach (PluginPrerequisite pluginPrerequisite in Plugin.Prerequisites)
|
||||
{
|
||||
if (await pluginPrerequisite.IsMet())
|
||||
continue;
|
||||
|
||||
needsPrerequisites = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return !needsPrerequisites;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user