mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-01-18 13:26:47 +01:00
Various plugin fixes (#5577)
* Fix: Plugin updates do not work * Offer install on disabled plugins when different version This will: * Clear any previous pending actions of a plugin if you click uninstall * Show the plugin version that was disabled * Show an update button on disabled plugins instead of install * if a plugin is scheduled to be installed/updated, it will show which version was scheduled to be updated. If a newer version if available than the scheduled one, it will show an option to switch to that * Ensure disabled plugins don't get loaded * View fixes --------- Co-authored-by: d11n <mail@dennisreimann.de>
This commit is contained in:
parent
3eec9cb0bb
commit
a753698ae7
@ -55,7 +55,7 @@ namespace BTCPayServer.Controllers
|
||||
public IEnumerable<PluginService.AvailablePlugin> Available { get; set; }
|
||||
public (string command, string plugin)[] Commands { get; set; }
|
||||
public bool CanShowRestart { get; set; }
|
||||
public string[] Disabled { get; set; }
|
||||
public Dictionary<string, Version> Disabled { get; set; }
|
||||
public Dictionary<string, AvailablePlugin> DownloadedPluginsByIdentifier { get; set; } = new Dictionary<string, AvailablePlugin>();
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ namespace BTCPayServer.HostedServices
|
||||
var remotePluginsList = remotePlugins
|
||||
.GroupBy(plugin => plugin.Identifier)
|
||||
.Select(group => group.OrderByDescending(plugin => plugin.Version).First())
|
||||
.Where(pair => installedPlugins.ContainsKey(pair.Identifier) || disabledPlugins.Contains(pair.Name))
|
||||
.Where(pair => installedPlugins.ContainsKey(pair.Identifier) || disabledPlugins.ContainsKey(pair.Name))
|
||||
.ToDictionary(plugin => plugin.Identifier, plugin => plugin.Version);
|
||||
var notify = new HashSet<string>();
|
||||
foreach (var pair in remotePluginsList)
|
||||
@ -95,8 +95,10 @@ namespace BTCPayServer.HostedServices
|
||||
if (dh.LastVersions.TryGetValue(pair.Key, out var lastVersion) && lastVersion >= pair.Value)
|
||||
continue;
|
||||
if (installedPlugins.TryGetValue(pair.Key, out var installedVersion) && installedVersion < pair.Value)
|
||||
{
|
||||
notify.Add(pair.Key);
|
||||
if (disabledPlugins.Contains(pair.Key))
|
||||
}
|
||||
else if (disabledPlugins.TryGetValue(pair.Key, out var disabledVersion) && disabledVersion < pair.Value)
|
||||
{
|
||||
notify.Add(pair.Key);
|
||||
}
|
||||
|
@ -60,10 +60,11 @@ namespace BTCPayServer.Plugins
|
||||
pluginName = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
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,
|
||||
void LoadPluginsFromAssemblies(Assembly systemAssembly1, HashSet<string> exclude, HashSet<string> loadedPluginIdentifiers1,
|
||||
List<IBTCPayServerPlugin> btcPayServerPlugins)
|
||||
{
|
||||
// Load the referenced assembly plugins
|
||||
@ -73,7 +74,7 @@ namespace BTCPayServer.Plugins
|
||||
{
|
||||
var assemblyName = assembly.GetName().Name;
|
||||
bool isSystemPlugin = assembly == systemAssembly1;
|
||||
if (!isSystemPlugin && hashSet.Contains(assemblyName))
|
||||
if (!isSystemPlugin && exclude.Contains(assemblyName))
|
||||
continue;
|
||||
|
||||
foreach (var plugin in GetPluginInstancesFromAssembly(assembly))
|
||||
@ -101,15 +102,15 @@ namespace BTCPayServer.Plugins
|
||||
Directory.CreateDirectory(pluginsFolder);
|
||||
ExecuteCommands(pluginsFolder);
|
||||
|
||||
var disabledPlugins = GetDisabledPlugins(pluginsFolder);
|
||||
var disabledPluginIdentifiers = GetDisabledPluginIdentifiers(pluginsFolder);
|
||||
var systemAssembly = typeof(Program).Assembly;
|
||||
LoadPluginsFromAssemblies(systemAssembly, disabledPlugins, loadedPluginIdentifiers, plugins);
|
||||
LoadPluginsFromAssemblies(systemAssembly, disabledPluginIdentifiers, loadedPluginIdentifiers, plugins);
|
||||
|
||||
if (ExecuteCommands(pluginsFolder, plugins.ToDictionary(plugin => plugin.Identifier, plugin => plugin.Version)))
|
||||
{
|
||||
plugins.Clear();
|
||||
loadedPluginIdentifiers.Clear();
|
||||
LoadPluginsFromAssemblies(systemAssembly, disabledPlugins, loadedPluginIdentifiers, plugins);
|
||||
LoadPluginsFromAssemblies(systemAssembly, disabledPluginIdentifiers, loadedPluginIdentifiers, plugins);
|
||||
}
|
||||
|
||||
var pluginsToLoad = new List<(string PluginIdentifier, string PluginFilePath)>();
|
||||
@ -135,7 +136,7 @@ namespace BTCPayServer.Plugins
|
||||
var pluginFilePath = Path.Combine(directory, pluginIdentifier + ".dll");
|
||||
if (!File.Exists(pluginFilePath))
|
||||
continue;
|
||||
if (disabledPlugins.Contains(pluginIdentifier))
|
||||
if (disabledPluginIdentifiers.Contains(pluginIdentifier))
|
||||
continue;
|
||||
pluginsToLoad.Add((pluginIdentifier, pluginFilePath));
|
||||
}
|
||||
@ -288,6 +289,31 @@ namespace BTCPayServer.Plugins
|
||||
|
||||
return remainingCommands.Count != pendingCommands.Length;
|
||||
}
|
||||
private static Dictionary<string, (Version, IBTCPayServerPlugin.PluginDependency[] Dependencies, bool Disabled)> TryGetInstalledInfo(
|
||||
string pluginsFolder)
|
||||
{
|
||||
var disabled = GetDisabledPluginIdentifiers(pluginsFolder);
|
||||
var installed = new Dictionary<string, (Version, IBTCPayServerPlugin.PluginDependency[] Dependencies, bool Disabled)>();
|
||||
foreach (string pluginDir in Directory.EnumerateDirectories(pluginsFolder))
|
||||
{
|
||||
var plugin = Path.GetFileName(pluginDir);
|
||||
var dirName = Path.Combine(pluginsFolder, plugin);
|
||||
var isDisabled = disabled.Contains(plugin);
|
||||
var manifestFileName = Path.Combine(dirName, plugin + ".json");
|
||||
if (File.Exists(manifestFileName))
|
||||
{
|
||||
var pluginManifest = JObject.Parse(File.ReadAllText(manifestFileName)).ToObject<PluginService.AvailablePlugin>();
|
||||
installed.TryAdd(pluginManifest.Identifier, (pluginManifest.Version, pluginManifest.Dependencies, isDisabled));
|
||||
}
|
||||
else if (isDisabled)
|
||||
{
|
||||
// Disabled plugin might not have a manifest, but we still need to include
|
||||
// it in the list, so that it can be shown on the Manage Plugins page
|
||||
installed.TryAdd(plugin, (null, null, true));
|
||||
}
|
||||
}
|
||||
return installed;
|
||||
}
|
||||
|
||||
private static bool DependenciesMet(string pluginsFolder, string plugin, Dictionary<string, Version> installed)
|
||||
{
|
||||
@ -299,7 +325,7 @@ namespace BTCPayServer.Plugins
|
||||
}
|
||||
|
||||
private static bool ExecuteCommand((string command, string extension) command, string pluginsFolder,
|
||||
bool ignoreOrder = false, Dictionary<string, Version> installed = null)
|
||||
bool ignoreOrder, Dictionary<string, Version> installed)
|
||||
{
|
||||
var dirName = Path.Combine(pluginsFolder, command.extension);
|
||||
switch (command.command)
|
||||
@ -307,12 +333,12 @@ namespace BTCPayServer.Plugins
|
||||
case "update":
|
||||
if (!DependenciesMet(pluginsFolder, command.extension, installed))
|
||||
return false;
|
||||
ExecuteCommand(("delete", command.extension), pluginsFolder, true);
|
||||
ExecuteCommand(("install", command.extension), pluginsFolder, true);
|
||||
ExecuteCommand(("delete", command.extension), pluginsFolder, true, installed);
|
||||
ExecuteCommand(("install", command.extension), pluginsFolder, true, installed);
|
||||
break;
|
||||
|
||||
case "delete":
|
||||
ExecuteCommand(("enable", command.extension), pluginsFolder, true);
|
||||
ExecuteCommand(("enable", command.extension), pluginsFolder, true, installed);
|
||||
if (File.Exists(dirName))
|
||||
{
|
||||
File.Delete(dirName);
|
||||
@ -335,7 +361,7 @@ namespace BTCPayServer.Plugins
|
||||
if (!DependenciesMet(pluginsFolder, command.extension, installed))
|
||||
return false;
|
||||
|
||||
ExecuteCommand(("enable", command.extension), pluginsFolder, true);
|
||||
ExecuteCommand(("enable", command.extension), pluginsFolder, true, installed);
|
||||
if (File.Exists(fileName))
|
||||
{
|
||||
ZipFile.ExtractToDirectory(fileName, dirName, true);
|
||||
@ -426,12 +452,18 @@ namespace BTCPayServer.Plugins
|
||||
QueueCommands(pluginDir, ("disable", plugin));
|
||||
}
|
||||
|
||||
public static HashSet<string> GetDisabledPlugins(string pluginsFolder)
|
||||
// Loads the list of disabled plugins from the file
|
||||
private static HashSet<string> GetDisabledPluginIdentifiers(string pluginsFolder)
|
||||
{
|
||||
var disabledFilePath = Path.Combine(pluginsFolder, "disabled");
|
||||
return File.Exists(disabledFilePath)
|
||||
? File.ReadLines(disabledFilePath).ToHashSet()
|
||||
: [];
|
||||
var disabledPath = Path.Combine(pluginsFolder, "disabled");
|
||||
return File.Exists(disabledPath) ? File.ReadAllLines(disabledPath).ToHashSet() : [];
|
||||
}
|
||||
|
||||
// List of disabled plugins with additional info, like the disabled version and its dependencies
|
||||
public static Dictionary<string, Version> GetDisabledPlugins(string pluginsFolder)
|
||||
{
|
||||
return TryGetInstalledInfo(pluginsFolder).Where(pair => pair.Value.Disabled)
|
||||
.ToDictionary(pair => pair.Key, pair => pair.Value.Item1);
|
||||
}
|
||||
|
||||
public static bool DependencyMet(IBTCPayServerPlugin.PluginDependency dependency,
|
||||
|
@ -108,6 +108,7 @@ namespace BTCPayServer.Plugins
|
||||
public void UninstallPlugin(string plugin)
|
||||
{
|
||||
var dest = _dataDirectories.Value.PluginDir;
|
||||
PluginManager.CancelCommands(dest, plugin);
|
||||
PluginManager.QueueCommands(dest, ("delete", plugin));
|
||||
}
|
||||
|
||||
@ -155,9 +156,9 @@ namespace BTCPayServer.Plugins
|
||||
PluginManager.CancelCommands(_dataDirectories.Value.PluginDir, plugin);
|
||||
}
|
||||
|
||||
public string[] GetDisabledPlugins()
|
||||
public Dictionary<string, Version> GetDisabledPlugins()
|
||||
{
|
||||
return PluginManager.GetDisabledPlugins(_dataDirectories.Value.PluginDir).ToArray();
|
||||
return PluginManager.GetDisabledPlugins(_dataDirectories.Value.PluginDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,13 +7,13 @@
|
||||
Layout = "_Layout";
|
||||
ViewData.SetActivePage(ServerNavPages.Plugins);
|
||||
var installed = Model.Installed.ToDictionary(plugin => plugin.Identifier, plugin => plugin.Version);
|
||||
|
||||
var installedWithoutSystemPlugins = Model.Installed.Where(i => !i.SystemPlugin).ToList();
|
||||
var availableAndNotInstalled = new List<PluginService.AvailablePlugin>();
|
||||
var availableAndNotInstalledx = Model.Available
|
||||
.Where(plugin => !installed.ContainsKey(plugin.Identifier))
|
||||
.GroupBy(plugin => plugin.Identifier)
|
||||
.ToList();
|
||||
|
||||
var availableAndNotInstalled = new List<PluginService.AvailablePlugin>();
|
||||
|
||||
foreach (var availableAndNotInstalledItem in availableAndNotInstalledx)
|
||||
{
|
||||
var ordered = availableAndNotInstalledItem.OrderByDescending(plugin => plugin.Version).ToArray();
|
||||
@ -76,12 +76,18 @@
|
||||
<div class="mb-5">
|
||||
<h3 class="mb-4">Disabled Plugins</h3>
|
||||
<ul class="list-group list-group-flush d-inline-block">
|
||||
@foreach (var d in Model.Disabled)
|
||||
@foreach (var (plugin, version) in Model.Disabled)
|
||||
{
|
||||
<li class="list-group-item px-0">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3">
|
||||
<span>@d</span>
|
||||
<form asp-action="UnInstallPlugin" asp-route-plugin="@d">
|
||||
<span>
|
||||
@plugin
|
||||
@if (version != null)
|
||||
{
|
||||
<span>({version})</span>
|
||||
}
|
||||
</span>
|
||||
<form asp-action="UnInstallPlugin" asp-route-plugin="@plugin">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">Uninstall</button>
|
||||
</form>
|
||||
</div>
|
||||
@ -233,26 +239,27 @@
|
||||
</div>
|
||||
</div>
|
||||
@{
|
||||
var pendingAction = Model.Commands.Any(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
var pendingAction = Model.Commands.LastOrDefault(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase)).command;
|
||||
var exclusivePendingAction = true;
|
||||
|
||||
var versionOfPendingInstall = PluginService.GetVersionOfPendingInstall(plugin.Identifier);
|
||||
}
|
||||
<div class="card-footer border-0 pb-3 d-flex gap-2">
|
||||
@if (pendingAction && updateAvailable)
|
||||
@if (pendingAction is not null && 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;
|
||||
exclusivePendingAction = versionOfPendingInstall == x.Version;
|
||||
}
|
||||
}
|
||||
@if (pendingAction)
|
||||
@if (pendingAction is not null)
|
||||
{
|
||||
<form asp-action="CancelPluginCommands" asp-route-plugin="@plugin.Identifier">
|
||||
<button type="submit" class="btn btn-outline-secondary">Cancel pending action</button>
|
||||
<button type="submit" class="btn btn-outline-secondary">Cancel pending @pendingAction @(versionOfPendingInstall is null? "": $"of {versionOfPendingInstall}")</button>
|
||||
</form>
|
||||
}
|
||||
@if (!pendingAction || !exclusivePendingAction)
|
||||
@if (pendingAction is null || !exclusivePendingAction)
|
||||
{
|
||||
@if (updateAvailable && x != null)
|
||||
{
|
||||
@ -294,7 +301,7 @@
|
||||
@foreach (var plugin in availableAndNotInstalled)
|
||||
{
|
||||
var recommended = BTCPayServerOptions.RecommendedPlugins.Any(id => string.Equals(id, plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
var disabled = Model.Disabled?.Contains(plugin.Identifier) ?? false;
|
||||
Model.Disabled.TryGetValue(plugin.Identifier, out var disabled);
|
||||
|
||||
<div class="col col-12 col-md-6 col-lg-12 col-xl-6 col-xxl-4 mb-4">
|
||||
<div class="card h-100" id="@plugin.Identifier">
|
||||
@ -313,7 +320,11 @@
|
||||
</div>
|
||||
<h5 class="text-muted d-flex align-items-center mt-1 gap-2">
|
||||
@plugin.Version
|
||||
@if (disabled)
|
||||
@if (disabled is { } && disabled != plugin.Version)
|
||||
{
|
||||
<div class="badge bg-light">Disabled (@disabled)</div>
|
||||
}
|
||||
else if (disabled is { } && disabled == plugin.Version)
|
||||
{
|
||||
<div class="badge bg-light">Disabled</div>
|
||||
}
|
||||
@ -384,27 +395,33 @@
|
||||
</div>
|
||||
<div class="card-footer border-0 pb-3 d-flex gap-2">
|
||||
@{
|
||||
var pendingAction = Model.Commands.LastOrDefault(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
var version = PluginService.GetVersionOfPendingInstall(plugin.Identifier);
|
||||
var exclusivePendingAction = version == plugin.Version;
|
||||
var res = Model.Commands.LastOrDefault(tuple => tuple.plugin.Equals(plugin.Identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
var pendingAction = res != default ? res.command : null;
|
||||
var versionOfPendingInstall = PluginService.GetVersionOfPendingInstall(plugin.Identifier);
|
||||
var exclusivePendingAction = pendingAction is not null && (pendingAction == "delete" || versionOfPendingInstall == plugin.Version);
|
||||
}
|
||||
@if (!pendingAction.Equals(default))
|
||||
@if (pendingAction is not null)
|
||||
{
|
||||
<form asp-action="CancelPluginCommands" asp-route-plugin="@plugin.Identifier">
|
||||
<button type="submit" class="btn btn-outline-secondary">Cancel pending @pendingAction.command</button>
|
||||
<button type="submit" class="btn btn-outline-secondary">Cancel pending @pendingAction @(versionOfPendingInstall is null? "": $"of {versionOfPendingInstall}")</button>
|
||||
</form>
|
||||
}
|
||||
@if (pendingAction.Equals(default) || !exclusivePendingAction)
|
||||
@if (pendingAction is null|| !exclusivePendingAction)
|
||||
{
|
||||
if (PluginManager.DependenciesMet(plugin.Dependencies, installed))
|
||||
{
|
||||
@* Don't show the "Install" button if plugin has been disabled *@
|
||||
@if (!disabled)
|
||||
@if (disabled is null)
|
||||
{
|
||||
<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 if (disabled != plugin.Version)
|
||||
{
|
||||
<form asp-action="InstallPlugin" asp-route-plugin="@plugin.Identifier" asp-route-version="@plugin.Version" asp-route-update="true">
|
||||
<button type="submit" class="btn btn-primary">Update</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -413,7 +430,7 @@
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@if (disabled)
|
||||
@if (disabled is not null && pendingAction is null)
|
||||
{
|
||||
<form asp-action="UnInstallPlugin" asp-route-plugin="@plugin.Identifier">
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger">Uninstall</button>
|
||||
|
Loading…
Reference in New Issue
Block a user