mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-10 00:09:18 +01:00
Allow scheduling installs/updates of future plugins (#5537)
Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
This commit is contained in:
parent
26374ef476
commit
7a06423bc7
3 changed files with 215 additions and 150 deletions
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
@ -10,7 +9,6 @@ using System.Reflection;
|
|||
using System.Text.RegularExpressions;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Logging;
|
||||
using McMaster.NETCore.Plugins;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
@ -19,14 +17,14 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NBXplorer;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Plugins
|
||||
{
|
||||
public static class PluginManager
|
||||
{
|
||||
public const string BTCPayPluginSuffix = ".btcpay";
|
||||
private static readonly List<Assembly> _pluginAssemblies = new List<Assembly>();
|
||||
private static readonly List<Assembly> _pluginAssemblies = new ();
|
||||
|
||||
public static bool IsExceptionByPlugin(Exception exception, [MaybeNullWhen(false)] out string pluginName)
|
||||
{
|
||||
|
@ -65,6 +63,31 @@ namespace BTCPayServer.Plugins
|
|||
public static IMvcBuilder AddPlugins(this IMvcBuilder mvcBuilder, IServiceCollection serviceCollection,
|
||||
IConfiguration config, ILoggerFactory loggerFactory, ServiceProvider bootstrapServiceProvider)
|
||||
{
|
||||
void LoadPluginsFromAssemblies(Assembly systemAssembly1, HashSet<string> hashSet, HashSet<string> loadedPluginIdentifiers1,
|
||||
List<IBTCPayServerPlugin> btcPayServerPlugins)
|
||||
{
|
||||
// Load the referenced assembly plugins
|
||||
// All referenced plugins should have at least one plugin with exact same plugin identifier
|
||||
// as the assembly. Except for the system assembly (btcpayserver assembly) which are fake plugins
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
var assemblyName = assembly.GetName().Name;
|
||||
bool isSystemPlugin = assembly == systemAssembly1;
|
||||
if (!isSystemPlugin && hashSet.Contains(assemblyName))
|
||||
continue;
|
||||
|
||||
foreach (var plugin in GetPluginInstancesFromAssembly(assembly))
|
||||
{
|
||||
if (!isSystemPlugin && plugin.Identifier != assemblyName)
|
||||
continue;
|
||||
if (!loadedPluginIdentifiers1.Add(plugin.Identifier))
|
||||
continue;
|
||||
btcPayServerPlugins.Add(plugin);
|
||||
plugin.SystemPlugin = isSystemPlugin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var logger = loggerFactory.CreateLogger(typeof(PluginManager));
|
||||
var pluginsFolder = new DataDirectories().Configure(config).PluginDir;
|
||||
var plugins = new List<IBTCPayServerPlugin>();
|
||||
|
@ -80,25 +103,12 @@ namespace BTCPayServer.Plugins
|
|||
|
||||
var disabledPlugins = GetDisabledPlugins(pluginsFolder);
|
||||
var systemAssembly = typeof(Program).Assembly;
|
||||
// Load the referenced assembly plugins
|
||||
// All referenced plugins should have at least one plugin with exact same plugin identifier
|
||||
// as the assembly. Except for the system assembly (btcpayserver assembly) which are fake plugins
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
var assemblyName = assembly.GetName().Name;
|
||||
bool isSystemPlugin = assembly == systemAssembly;
|
||||
if (!isSystemPlugin && disabledPlugins.Contains(assemblyName))
|
||||
continue;
|
||||
LoadPluginsFromAssemblies(systemAssembly, disabledPlugins, loadedPluginIdentifiers, plugins);
|
||||
|
||||
foreach (var plugin in GetPluginInstancesFromAssembly(assembly))
|
||||
{
|
||||
if (!isSystemPlugin && plugin.Identifier != assemblyName)
|
||||
continue;
|
||||
if (!loadedPluginIdentifiers.Add(plugin.Identifier))
|
||||
continue;
|
||||
plugins.Add(plugin);
|
||||
plugin.SystemPlugin = isSystemPlugin;
|
||||
}
|
||||
if (ExecuteCommands(pluginsFolder, plugins.ToDictionary(plugin => plugin.Identifier, plugin => plugin.Version)))
|
||||
{
|
||||
plugins.Clear();
|
||||
LoadPluginsFromAssemblies(systemAssembly, disabledPlugins, loadedPluginIdentifiers, plugins);
|
||||
}
|
||||
|
||||
var pluginsToLoad = new List<(string PluginIdentifier, string PluginFilePath)>();
|
||||
|
@ -244,37 +254,56 @@ namespace BTCPayServer.Plugins
|
|||
!type.IsAbstract).
|
||||
Select(type => (IBTCPayServerPlugin)Activator.CreateInstance(type, Array.Empty<object>()));
|
||||
}
|
||||
|
||||
private static IBTCPayServerPlugin GetPluginInstanceFromAssembly(string pluginIdentifier, Assembly assembly)
|
||||
{
|
||||
return GetPluginInstancesFromAssembly(assembly)
|
||||
.Where(plugin => plugin.Identifier == pluginIdentifier)
|
||||
.FirstOrDefault();
|
||||
return GetPluginInstancesFromAssembly(assembly).FirstOrDefault(plugin => plugin.Identifier == pluginIdentifier);
|
||||
}
|
||||
|
||||
private static void ExecuteCommands(string pluginsFolder)
|
||||
private static bool ExecuteCommands(string pluginsFolder, Dictionary<string, Version> installed = null)
|
||||
{
|
||||
var pendingCommands = GetPendingCommands(pluginsFolder);
|
||||
foreach (var command in pendingCommands)
|
||||
if (!pendingCommands.Any())
|
||||
{
|
||||
ExecuteCommand(command, pluginsFolder);
|
||||
return false;
|
||||
}
|
||||
|
||||
File.Delete(Path.Combine(pluginsFolder, "commands"));
|
||||
var remainingCommands = (from command in pendingCommands where !ExecuteCommand(command, pluginsFolder, false, installed) select $"{command.command}:{command.plugin}").ToList();
|
||||
if (remainingCommands.Any())
|
||||
{
|
||||
File.WriteAllLines(Path.Combine(pluginsFolder, "commands"), remainingCommands);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Delete(Path.Combine(pluginsFolder, "commands"));
|
||||
}
|
||||
|
||||
return remainingCommands.Count != pendingCommands.Length;
|
||||
}
|
||||
|
||||
private static void ExecuteCommand((string command, string extension) command, string pluginsFolder,
|
||||
bool ignoreOrder = false)
|
||||
private static bool DependenciesMet(string pluginsFolder, string plugin, Dictionary<string, Version> installed)
|
||||
{
|
||||
var dirName = Path.Combine(pluginsFolder, plugin);
|
||||
var manifestFileName = dirName + ".json";
|
||||
if (!File.Exists(manifestFileName)) return true;
|
||||
var pluginManifest = JObject.Parse(File.ReadAllText(manifestFileName)).ToObject<PluginService.AvailablePlugin>();
|
||||
return DependenciesMet(pluginManifest.Dependencies, installed);
|
||||
}
|
||||
|
||||
private static bool ExecuteCommand((string command, string extension) command, string pluginsFolder,
|
||||
bool ignoreOrder = false, Dictionary<string, Version> installed = null)
|
||||
{
|
||||
var dirName = Path.Combine(pluginsFolder, command.extension);
|
||||
switch (command.command)
|
||||
{
|
||||
case "update":
|
||||
ExecuteCommand(("enable", command.extension), pluginsFolder, true);
|
||||
if (!DependenciesMet(pluginsFolder, command.extension, installed))
|
||||
return false;
|
||||
ExecuteCommand(("delete", command.extension), pluginsFolder, true);
|
||||
ExecuteCommand(("install", command.extension), pluginsFolder, true);
|
||||
break;
|
||||
case "delete":
|
||||
|
||||
case "delete":
|
||||
ExecuteCommand(("enable", command.extension), pluginsFolder, true);
|
||||
if (File.Exists(dirName))
|
||||
{
|
||||
|
@ -290,11 +319,15 @@ namespace BTCPayServer.Plugins
|
|||
orders.Where(s => s != command.extension));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "install":
|
||||
ExecuteCommand(("enable", command.extension), pluginsFolder, true);
|
||||
var fileName = dirName + BTCPayPluginSuffix;
|
||||
var manifestFileName = dirName + ".json";
|
||||
if (!DependenciesMet(pluginsFolder, command.extension, installed))
|
||||
return false;
|
||||
|
||||
ExecuteCommand(("enable", command.extension), pluginsFolder, true);
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
ZipFile.ExtractToDirectory(fileName, dirName, true);
|
||||
|
@ -302,10 +335,12 @@ namespace BTCPayServer.Plugins
|
|||
{
|
||||
File.AppendAllLines(Path.Combine(pluginsFolder, "order"), new[] { command.extension });
|
||||
}
|
||||
|
||||
File.Delete(fileName);
|
||||
if (File.Exists(manifestFileName))
|
||||
{
|
||||
File.Move(manifestFileName, Path.Combine(dirName, Path.GetFileName(manifestFileName)));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "disable":
|
||||
|
@ -339,6 +374,8 @@ namespace BTCPayServer.Plugins
|
|||
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static (string command, string plugin)[] GetPendingCommands(string pluginsFolder)
|
||||
|
@ -364,25 +401,84 @@ namespace BTCPayServer.Plugins
|
|||
var cmds = GetPendingCommands(pluginDir).Where(tuple =>
|
||||
!tuple.plugin.Equals(plugin, StringComparison.InvariantCultureIgnoreCase)).ToArray();
|
||||
|
||||
if (File.Exists(Path.Combine(pluginDir, plugin, BTCPayPluginSuffix)))
|
||||
{
|
||||
File.Delete(Path.Combine(pluginDir, plugin, BTCPayPluginSuffix));
|
||||
}
|
||||
if (File.Exists(Path.Combine(pluginDir, plugin, ".json")))
|
||||
{
|
||||
File.Delete(Path.Combine(pluginDir, plugin, ".json"));
|
||||
}
|
||||
File.Delete(Path.Combine(pluginDir, "commands"));
|
||||
QueueCommands(pluginDir, cmds);
|
||||
}
|
||||
|
||||
public static void DisablePlugin(string pluginDir, string plugin)
|
||||
{
|
||||
|
||||
QueueCommands(pluginDir, ("disable", plugin));
|
||||
}
|
||||
|
||||
public static HashSet<string> GetDisabledPlugins(string pluginsFolder)
|
||||
{
|
||||
var disabledFilePath = Path.Combine(pluginsFolder, "disabled");
|
||||
if (File.Exists(disabledFilePath))
|
||||
return File.Exists(disabledFilePath)
|
||||
? File.ReadLines(disabledFilePath).ToHashSet()
|
||||
: [];
|
||||
}
|
||||
|
||||
public static bool DependencyMet(IBTCPayServerPlugin.PluginDependency dependency,
|
||||
Dictionary<string, Version> installed = null)
|
||||
{
|
||||
var plugin = dependency.Identifier.ToLowerInvariant();
|
||||
var versionReq = dependency.Condition;
|
||||
// ensure installed is not null and has lowercased keys for comparison
|
||||
installed = installed == null
|
||||
? new Dictionary<string, Version>()
|
||||
: installed.ToDictionary(x => x.Key.ToLowerInvariant(), x => x.Value);
|
||||
if (!installed.ContainsKey(plugin) && !versionReq.Equals("!"))
|
||||
{
|
||||
return File.ReadLines(disabledFilePath).ToHashSet();
|
||||
return false;
|
||||
}
|
||||
|
||||
return new HashSet<string>();
|
||||
var versionConditions = versionReq.Split("||", StringSplitOptions.RemoveEmptyEntries);
|
||||
return versionConditions.Any(s =>
|
||||
{
|
||||
s = s.Trim();
|
||||
var v = s.Substring(1);
|
||||
if (s[1] == '=')
|
||||
{
|
||||
v = s.Substring(2);
|
||||
}
|
||||
|
||||
var parsedV = Version.Parse(v);
|
||||
switch (s)
|
||||
{
|
||||
case { } xx when xx.StartsWith(">="):
|
||||
return installed[plugin] >= parsedV;
|
||||
case { } xx when xx.StartsWith("<="):
|
||||
return installed[plugin] <= parsedV;
|
||||
case { } xx when xx.StartsWith(">"):
|
||||
return installed[plugin] > parsedV;
|
||||
case { } xx when xx.StartsWith("<"):
|
||||
return installed[plugin] >= parsedV;
|
||||
case { } xx when xx.StartsWith("^"):
|
||||
return installed[plugin] >= parsedV && installed[plugin].Major == parsedV.Major;
|
||||
case { } xx when xx.StartsWith("~"):
|
||||
return installed[plugin] >= parsedV && installed[plugin].Major == parsedV.Major &&
|
||||
installed[plugin].Minor == parsedV.Minor;
|
||||
case { } xx when xx.StartsWith("!="):
|
||||
return installed[plugin] != parsedV;
|
||||
case { } xx when xx.StartsWith("=="):
|
||||
default:
|
||||
return installed[plugin] == parsedV;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static bool DependenciesMet(IEnumerable<IBTCPayServerPlugin.PluginDependency> dependencies,
|
||||
Dictionary<string, Version> installed = null)
|
||||
{
|
||||
return dependencies.All(dependency => DependencyMet(dependency, installed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,22 +2,14 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Contracts;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileSystemGlobbing;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin.DataEncoders;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
|
@ -27,10 +19,8 @@ namespace BTCPayServer.Plugins
|
|||
{
|
||||
private readonly IOptions<DataDirectories> _dataDirectories;
|
||||
private readonly PoliciesSettings _policiesSettings;
|
||||
private readonly ISettingsRepository _settingsRepository;
|
||||
private readonly PluginBuilderClient _pluginBuilderClient;
|
||||
public PluginService(
|
||||
ISettingsRepository settingsRepository,
|
||||
IEnumerable<IBTCPayServerPlugin> btcPayServerPlugins,
|
||||
PluginBuilderClient pluginBuilderClient,
|
||||
IOptions<DataDirectories> dataDirectories,
|
||||
|
@ -39,7 +29,6 @@ namespace BTCPayServer.Plugins
|
|||
{
|
||||
LoadedPlugins = btcPayServerPlugins;
|
||||
_pluginBuilderClient = pluginBuilderClient;
|
||||
_settingsRepository = settingsRepository;
|
||||
_dataDirectories = dataDirectories;
|
||||
_policiesSettings = policiesSettings;
|
||||
Env = env;
|
||||
|
@ -47,6 +36,15 @@ namespace BTCPayServer.Plugins
|
|||
|
||||
public IEnumerable<IBTCPayServerPlugin> LoadedPlugins { get; }
|
||||
public BTCPayServerEnvironment Env { get; }
|
||||
|
||||
public Version? GetVersionOfPendingInstall(string plugin)
|
||||
{
|
||||
var dirName = Path.Combine(_dataDirectories.Value.PluginDir, plugin);
|
||||
var manifestFileName = dirName + ".json";
|
||||
if (!File.Exists(manifestFileName)) return null;
|
||||
var pluginManifest = JObject.Parse(File.ReadAllText(manifestFileName)).ToObject<AvailablePlugin>();
|
||||
return pluginManifest.Version;
|
||||
}
|
||||
|
||||
public async Task<AvailablePlugin[]> GetRemotePlugins()
|
||||
{
|
||||
|
@ -66,14 +64,18 @@ namespace BTCPayServer.Plugins
|
|||
return p;
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
public async Task DownloadRemotePlugin(string pluginIdentifier, string version)
|
||||
{
|
||||
var dest = _dataDirectories.Value.PluginDir;
|
||||
var filedest = Path.Join(dest, pluginIdentifier + ".btcpay");
|
||||
var filemanifestdest = Path.Join(dest, pluginIdentifier + ".json");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(filedest));
|
||||
var url = $"api/v1/plugins/[{Uri.EscapeDataString(pluginIdentifier)}]/versions/{Uri.EscapeDataString(version)}/download";
|
||||
var manifest = (await _pluginBuilderClient.GetPublishedVersions(null, true)).Select(v => v.ManifestInfo.ToObject<AvailablePlugin>()).FirstOrDefault(p => p.Identifier == pluginIdentifier);
|
||||
await File.WriteAllTextAsync(filemanifestdest, JsonConvert.SerializeObject(manifest, Formatting.Indented));
|
||||
using var resp2 = await _pluginBuilderClient.HttpClient.GetAsync(url);
|
||||
using var fs = new FileStream(filedest, FileMode.Create, FileAccess.ReadWrite);
|
||||
await using var fs = new FileStream(filedest, FileMode.Create, FileAccess.ReadWrite);
|
||||
await resp2.Content.CopyToAsync(fs);
|
||||
await fs.FlushAsync();
|
||||
}
|
||||
|
@ -84,6 +86,7 @@ namespace BTCPayServer.Plugins
|
|||
UninstallPlugin(plugin);
|
||||
PluginManager.QueueCommands(dest, ("install", plugin));
|
||||
}
|
||||
|
||||
public void UpdatePlugin(string plugin)
|
||||
{
|
||||
var dest = _dataDirectories.Value.PluginDir;
|
||||
|
@ -122,8 +125,7 @@ namespace BTCPayServer.Plugins
|
|||
public string Author { get; set; }
|
||||
public string AuthorLink { get; set; }
|
||||
|
||||
public void Execute(IApplicationBuilder applicationBuilder,
|
||||
IServiceProvider applicationBuilderApplicationServices)
|
||||
public void Execute(IApplicationBuilder applicationBuilder, IServiceProvider applicationBuilderApplicationServices)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
@using BTCPayServer.Configuration
|
||||
@using BTCPayServer.Plugins
|
||||
@using BTCPayServer.Abstractions.Contracts
|
||||
@model BTCPayServer.Controllers.UIServerController.ListPluginsViewModel
|
||||
@inject BTCPayServerOptions BTCPayServerOptions
|
||||
@inject PluginService PluginService
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
ViewData.SetActivePage(ServerNavPages.Plugins);
|
||||
var installed = Model.Installed.ToDictionary(plugin => plugin.Identifier.ToLowerInvariant(), plugin => plugin.Version);
|
||||
var installed = Model.Installed.ToDictionary(plugin => plugin.Identifier, plugin => plugin.Version);
|
||||
|
||||
var availableAndNotInstalledx = Model.Available
|
||||
.Where(plugin => !installed.ContainsKey(plugin.Identifier.ToLowerInvariant()))
|
||||
.Where(plugin => !installed.ContainsKey(plugin.Identifier))
|
||||
.GroupBy(plugin => plugin.Identifier)
|
||||
.ToList();
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
|||
foreach (var availableAndNotInstalledItem in availableAndNotInstalledx)
|
||||
{
|
||||
var ordered = availableAndNotInstalledItem.OrderByDescending(plugin => plugin.Version).ToArray();
|
||||
availableAndNotInstalled.Add(ordered.FirstOrDefault(availablePlugin => DependenciesMet(availablePlugin.Dependencies)) ?? ordered.FirstOrDefault());
|
||||
availableAndNotInstalled.Add(ordered.FirstOrDefault(availablePlugin => PluginManager.DependenciesMet(availablePlugin.Dependencies, installed)) ?? ordered.FirstOrDefault());
|
||||
}
|
||||
|
||||
bool DependentOn(string plugin)
|
||||
|
@ -41,74 +41,11 @@
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DependencyMet(IBTCPayServerPlugin.PluginDependency dependency)
|
||||
{
|
||||
var plugin = dependency.Identifier.ToLowerInvariant();
|
||||
var versionReq = dependency.Condition;
|
||||
if (!installed.ContainsKey(plugin) && !versionReq.Equals("!"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (installed.ContainsKey(plugin) && versionReq.Equals("!"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var versionConditions = versionReq.Split("||", StringSplitOptions.RemoveEmptyEntries);
|
||||
return versionConditions.Any(s =>
|
||||
{
|
||||
s = s.Trim();
|
||||
var v = s.Substring(1);
|
||||
if (s[1] == '=')
|
||||
{
|
||||
v = s.Substring(2);
|
||||
}
|
||||
var parsedV = Version.Parse(v);
|
||||
switch (s)
|
||||
{
|
||||
case { } xx when xx.StartsWith(">="):
|
||||
return installed[plugin] >= parsedV;
|
||||
case { } xx when xx.StartsWith("<="):
|
||||
return installed[plugin] <= parsedV;
|
||||
case { } xx when xx.StartsWith(">"):
|
||||
return installed[plugin] > parsedV;
|
||||
case { } xx when xx.StartsWith("<"):
|
||||
return installed[plugin] >= parsedV;
|
||||
case { } xx when xx.StartsWith("^"):
|
||||
return installed[plugin] >= parsedV && installed[plugin].Major == parsedV.Major;
|
||||
case { } xx when xx.StartsWith("~"):
|
||||
return installed[plugin] >= parsedV && installed[plugin].Major == parsedV.Major && installed[plugin].Minor == parsedV.Minor;
|
||||
case { } xx when xx.StartsWith("!="):
|
||||
return installed[plugin] != parsedV;
|
||||
case { } xx when xx.StartsWith("=="):
|
||||
default:
|
||||
return installed[plugin] == parsedV;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool DependenciesMet(IBTCPayServerPlugin.PluginDependency[] dependencies)
|
||||
{
|
||||
foreach (var dependency in dependencies)
|
||||
{
|
||||
if (!DependencyMet(dependency))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
<style>
|
||||
.version-switch .nav-link {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.version-switch .nav-link.active {
|
||||
display: none;
|
||||
}
|
||||
.version-switch .nav-link { display: inline; }
|
||||
.version-switch .nav-link.active { display: none; }
|
||||
</style>
|
||||
|
||||
<partial name="_StatusMessage" />
|
||||
|
@ -157,7 +94,7 @@
|
|||
{
|
||||
Model.DownloadedPluginsByIdentifier.TryGetValue(plugin.Identifier, out var downloadInfo);
|
||||
var matchedAvailable = Model.Available.Where(availablePlugin => availablePlugin.Identifier == plugin.Identifier && availablePlugin.Version > plugin.Version).OrderByDescending(availablePlugin => availablePlugin.Version).ToArray();
|
||||
var x = matchedAvailable.FirstOrDefault(availablePlugin => DependenciesMet(availablePlugin.Dependencies)) ?? matchedAvailable.FirstOrDefault();
|
||||
var x = matchedAvailable.FirstOrDefault(availablePlugin => PluginManager.DependenciesMet(availablePlugin.Dependencies, installed)) ?? matchedAvailable.FirstOrDefault();
|
||||
var updateAvailable = matchedAvailable.Any();
|
||||
var tabId = plugin.Identifier.ToLowerInvariant().Replace(".", "_");
|
||||
<div class="col col-12 col-md-6 col-lg-12 col-xl-6 col-xxl-4 mb-4">
|
||||
|
@ -205,7 +142,7 @@
|
|||
{
|
||||
<li class="list-group-item p-2 d-inline-flex align-items-center gap-2">
|
||||
@dependency
|
||||
@if (!DependencyMet(dependency))
|
||||
@if (!PluginManager.DependencyMet(dependency, installed))
|
||||
{
|
||||
<span title="Dependency not met." data-bs-toggle="tooltip" class="text-danger">
|
||||
<vc:icon symbol="warning" />
|
||||
|
@ -229,7 +166,7 @@
|
|||
{
|
||||
<li class="list-group-item p-2 d-inline-flex align-items-center gap-2">
|
||||
@dependency
|
||||
@if (!DependencyMet(dependency))
|
||||
@if (!PluginManager.DependencyMet(dependency, installed))
|
||||
{
|
||||
<span title="Dependency not met." data-bs-toggle="tooltip" class="text-danger">
|
||||
<vc:icon symbol="warning" />
|
||||
|
@ -280,22 +217,41 @@
|
|||
@{
|
||||
var pendingAction = Model.Commands.Any(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
@if (pendingAction || (updateAvailable && x != null && DependenciesMet(x.Dependencies)) || !DependentOn(plugin.Identifier))
|
||||
@if (pendingAction || (updateAvailable && x != null && !DependentOn(plugin.Identifier)))
|
||||
{
|
||||
<div class="card-footer border-0 pb-3 d-flex">
|
||||
var exclusivePendingAction = true;
|
||||
<div class="card-footer border-0 pb-3 d-flex gap-2">
|
||||
@if (pendingAction && updateAvailable)
|
||||
{
|
||||
var isUpdateAction = Model.Commands.Last(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase)).command == "update";
|
||||
if (isUpdateAction)
|
||||
{
|
||||
var version = PluginService.GetVersionOfPendingInstall(plugin.Identifier);
|
||||
exclusivePendingAction = version == x.Version;
|
||||
}
|
||||
}
|
||||
@if (pendingAction)
|
||||
{
|
||||
<form asp-action="CancelPluginCommands" asp-route-plugin="@plugin.Identifier">
|
||||
<button type="submit" class="btn btn-outline-secondary">Cancel pending action</button>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
@if(!pendingAction || !exclusivePendingAction)
|
||||
{
|
||||
@if (updateAvailable && x != null && DependenciesMet(x.Dependencies))
|
||||
@if (updateAvailable && x != null)
|
||||
{
|
||||
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-version="@x.Version" asp-route-update="true" class="me-3">
|
||||
<button type="submit" class="btn btn-secondary">Update</button>
|
||||
</form>
|
||||
if (PluginManager.DependenciesMet(x.Dependencies, installed))
|
||||
{
|
||||
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-version="@x.Version" asp-route-update="true" class="me-3">
|
||||
<button type="submit" class="btn btn-secondary">Update</button>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-version="@x.Version" asp-route-update="true" class="me-3">
|
||||
<button title="Schedule upgrade for when the dependencies have been met to ensure a smooth update" data-bs-toggle="tooltip" type="submit" class="btn btn-secondary">Schedule update</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@if (DependentOn(plugin.Identifier))
|
||||
{
|
||||
|
@ -322,7 +278,7 @@
|
|||
<div class="row mb-4">
|
||||
@foreach (var plugin in availableAndNotInstalled)
|
||||
{
|
||||
var recommended = BTCPayServerOptions.RecommendedPlugins.Contains(plugin.Identifier.ToLowerInvariant());
|
||||
var recommended = BTCPayServerOptions.RecommendedPlugins.Any(id => string.Equals(id, plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
var disabled = Model.Disabled?.Contains(plugin.Identifier) ?? false;
|
||||
|
||||
<div class="col col-12 col-md-6 col-lg-12 col-xl-6 col-xxl-4 mb-4">
|
||||
|
@ -360,7 +316,7 @@
|
|||
{
|
||||
<li class="list-group-item p-2 d-inline-flex align-items-center gap-2">
|
||||
@dependency
|
||||
@if (!DependencyMet(dependency))
|
||||
@if (!PluginManager.DependencyMet(dependency, installed))
|
||||
{
|
||||
<span title="Dependency not met." data-bs-toggle="tooltip" class="text-danger">
|
||||
<vc:icon symbol="warning" />
|
||||
|
@ -369,6 +325,12 @@
|
|||
</li>
|
||||
}
|
||||
</ul>
|
||||
if(!PluginManager.DependenciesMet(plugin.Dependencies, installed))
|
||||
{
|
||||
<div class="text-warning py-2 ">
|
||||
Dependencies not met.
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@if (plugin != null)
|
||||
|
@ -405,32 +367,37 @@
|
|||
</ul>
|
||||
}
|
||||
</div>
|
||||
<div class="card-footer border-0 pb-3">
|
||||
<div class="card-footer border-0 pb-3 d-flex gap-2">
|
||||
@{
|
||||
var pending = Model.Commands.LastOrDefault(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
var pendingAction = Model.Commands.LastOrDefault(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
var version = PluginService.GetVersionOfPendingInstall(plugin.Identifier);
|
||||
var exclusivePendingAction = version == plugin.Version;
|
||||
}
|
||||
@if (!pending.Equals(default))
|
||||
@if (!pendingAction.Equals(default))
|
||||
{
|
||||
<form asp-action="CancelPluginCommands" asp-route-plugin="@plugin.Identifier">
|
||||
<button type="submit" class="btn btn-outline-secondary">Cancel pending @pending.command</button>
|
||||
<button type="submit" class="btn btn-outline-secondary">Cancel pending @pendingAction.command</button>
|
||||
</form>
|
||||
}
|
||||
else if (DependenciesMet(plugin.Dependencies))
|
||||
@if (pendingAction.Equals(default) || !exclusivePendingAction)
|
||||
{
|
||||
@* Don't show the "Install" button if plugin has been disabled *@
|
||||
@if (!disabled)
|
||||
if (PluginManager.DependenciesMet(plugin.Dependencies, installed))
|
||||
{
|
||||
@* Don't show the "Install" button if plugin has been disabled *@
|
||||
@if (!disabled)
|
||||
{
|
||||
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-version="@plugin.Version">
|
||||
<button type="submit" class="btn btn-primary">Install</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-version="@plugin.Version">
|
||||
<button type="submit" class="btn btn-primary">Install</button>
|
||||
<button title="Schedule install for when the dependencies have been met to ensure a smooth update" data-bs-toggle="tooltip" type="submit" class="btn btn-primary">Schedule install</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="text-danger">
|
||||
Cannot install until dependencies are met.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Reference in a new issue