Plugins flexibility PR (#2129)

* Plugins flexibility PR

* Makes the BTCPayServerOptions.LoadArgs async to support Plugin hooks and actions
* relax the private set modifiers in the BTCPayNetwork
* Separate IPluginHookService from PluginService to reduce dependencies on DI
* fix some small bugs around image path
* Fix bug with new dbreeze migration where data dir was incorrect

* Update BTCPayServer/Plugins/PluginHookService.cs

Co-authored-by: rockstardev <5191402+rockstardev@users.noreply.github.com>

* Update BTCPayServer/Plugins/PluginHookService.cs

Co-authored-by: rockstardev <5191402+rockstardev@users.noreply.github.com>

Co-authored-by: rockstardev <5191402+rockstardev@users.noreply.github.com>
This commit is contained in:
Andrew Camilleri 2021-01-07 14:49:53 +01:00 committed by GitHub
parent e2e37a0db4
commit b8da6847b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 92 additions and 61 deletions

View File

@ -1,6 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build and pack plugins" type="CompoundRunConfigurationType">
<toRun name="Pack Test Plugin" type="DotNetProject" />
<method v="2" />
</configuration>
</component>

View File

@ -21,7 +21,6 @@ namespace BTCPayServer.Abstractions.Models
public abstract string Description { get; }
public bool SystemPlugin { get; set; }
public bool SystemExtension { get; set; }
public virtual IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>();
public virtual void Execute(IApplicationBuilder applicationBuilder,

View File

@ -5,7 +5,7 @@ namespace BTCPayServer.Abstractions.Services
{
public abstract class PluginAction<T>:IPluginHookAction
{
public string Hook { get; }
public abstract string Hook { get; }
public Task Execute(object args)
{
return Execute(args is T args1 ? args1 : default);

View File

@ -5,7 +5,8 @@ namespace BTCPayServer.Abstractions.Services
{
public abstract class PluginHookFilter<T>:IPluginHookFilter
{
public string Hook { get; }
public abstract string Hook { get; }
public Task<object> Execute(object args)
{
return Execute(args is T args1 ? args1 : default).ContinueWith(task => task.Result as object);

View File

@ -53,7 +53,7 @@ namespace BTCPayServer
public bool SupportRBF { get; internal set; }
public string LightningImagePath { get; set; }
public BTCPayDefaultSettings DefaultSettings { get; set; }
public KeyPath CoinType { get; internal set; }
public KeyPath CoinType { get; set; }
public Dictionary<uint, DerivationType> ElectrumMapping = new Dictionary<uint, DerivationType>();
@ -132,7 +132,7 @@ namespace BTCPayServer
{
private string _blockExplorerLink;
public bool ShowSyncSummary { get; set; } = true;
public string CryptoCode { get; internal set; }
public string CryptoCode { get; set; }
public string BlockExplorerLink
{
@ -161,7 +161,7 @@ namespace BTCPayServer
}
public string CryptoImagePath { get; set; }
public string[] DefaultRateRules { get; internal set; } = Array.Empty<string>();
public string[] DefaultRateRules { get; set; } = Array.Empty<string>();
public override string ToString()
{
return CryptoCode;

View File

@ -8,8 +8,7 @@ namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
readonly Dictionary<string, BTCPayNetworkBase> _Networks = new Dictionary<string, BTCPayNetworkBase>();
protected readonly Dictionary<string, BTCPayNetworkBase> _Networks = new Dictionary<string, BTCPayNetworkBase>();
private readonly NBXplorerNetworkProvider _NBXplorerNetworkProvider;
public NBXplorerNetworkProvider NBXplorerNetworkProvider

View File

@ -43,6 +43,7 @@ namespace BTCPayServer.PluginPacker
}
ZipFile.CreateFromDirectory(directory, outputFile + ".btcpay", CompressionLevel.Optimal, false);
File.WriteAllText(outputFile + ".btcpay.json", json);
Console.WriteLine($"Created {outputFile}.btcpay at {directory}");
}
private static Type[] GetAllExtensionTypesFromAssembly(Assembly assembly)

View File

@ -1186,7 +1186,7 @@ namespace BTCPayServer.Controllers
var res = paymentMethodId.PaymentType == PaymentTypes.BTCLike
? Url.Content(network.CryptoImagePath)
: Url.Content(network.LightningImagePath);
return "/" + res;
return Request.GetRelativePathOrAbsolute(res);
}
}

View File

@ -7,6 +7,7 @@ using BTCPayServer.Configuration;
using BTCPayServer.Data;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using Microsoft.Extensions.Options;
namespace BTCPayServer.HostedServices
{
@ -18,9 +19,9 @@ namespace BTCPayServer.HostedServices
private readonly InvoiceRepository _invoiceRepository;
private readonly SettingsRepository _settingsRepository;
private readonly ApplicationDbContextFactory _dbContextFactory;
private readonly DataDirectories _datadirs;
private readonly IOptions<DataDirectories> _datadirs;
public DbMigrationsHostedService(InvoiceRepository invoiceRepository, SettingsRepository settingsRepository, ApplicationDbContextFactory dbContextFactory, DataDirectories datadirs)
public DbMigrationsHostedService(InvoiceRepository invoiceRepository, SettingsRepository settingsRepository, ApplicationDbContextFactory dbContextFactory, IOptions<DataDirectories> datadirs)
{
_invoiceRepository = invoiceRepository;
_settingsRepository = settingsRepository;
@ -48,7 +49,7 @@ namespace BTCPayServer.HostedServices
private async Task MigratedInvoiceTextSearchToDb(int startFromPage)
{
// deleting legacy DBriize database if present
var dbpath = Path.Combine(_datadirs.DataDir, "InvoiceDB");
var dbpath = Path.Combine(_datadirs.Value.DataDir, "InvoiceDB");
if (Directory.Exists(dbpath))
{
Directory.Delete(dbpath, true);

View File

@ -70,7 +70,6 @@ namespace BTCPayServer.Hosting
}
public static IServiceCollection AddBTCPayServer(this IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<DataDirectories>();
services.AddSingleton<MvcNewtonsoftJsonOptions>(o => o.GetRequiredService<IOptions<MvcNewtonsoftJsonOptions>>().Value);
services.AddDbContext<ApplicationDbContext>((provider, o) =>
{
@ -244,7 +243,7 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<AppService>();
services.AddSingleton<PluginService>();
services.AddSingleton<IPluginHookService>(provider => provider.GetService<PluginService>());
services.AddSingleton<IPluginHookService, PluginHookService>();
services.TryAddTransient<Safe>();
services.TryAddSingleton<Ganss.XSS.HtmlSanitizer>(o =>
{

View File

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using Microsoft.Extensions.Logging;
namespace BTCPayServer.Plugins
{
public class PluginHookService : IPluginHookService
{
private readonly IEnumerable<IPluginHookAction> _actions;
private readonly IEnumerable<IPluginHookFilter> _filters;
private readonly ILogger<PluginHookService> _logger;
public PluginHookService(IEnumerable<IPluginHookAction> actions, IEnumerable<IPluginHookFilter> filters,
ILogger<PluginHookService> logger)
{
_actions = actions;
_filters = filters;
_logger = logger;
}
// Trigger simple action hook for registered plugins
public async Task ApplyAction(string hook, object args)
{
var filters = _actions
.Where(filter => filter.Hook.Equals(hook, StringComparison.InvariantCultureIgnoreCase)).ToList();
foreach (IPluginHookAction pluginHookFilter in filters)
{
try
{
await pluginHookFilter.Execute(args);
}
catch (Exception e)
{
_logger.LogError(e, $"Action on hook {hook} failed");
}
}
}
// Trigger hook on which registered plugins can optionally return modified args or new object back
public async Task<object> ApplyFilter(string hook, object args)
{
var filters = _filters
.Where(filter => filter.Hook.Equals(hook, StringComparison.InvariantCultureIgnoreCase)).ToList();
foreach (IPluginHookFilter pluginHookFilter in filters)
{
try
{
args = await pluginHookFilter.Execute(args);
}
catch (Exception e)
{
_logger.LogError(e, $"Filter on hook {hook} failed");
}
}
return args;
}
}
}

View File

@ -16,23 +16,19 @@ using Newtonsoft.Json;
namespace BTCPayServer.Plugins
{
public class PluginService: IPluginHookService
public class PluginService
{
private readonly IOptions<DataDirectories> _datadirs;
private readonly BTCPayServerOptions _options;
private readonly IOptions<DataDirectories> _dataDirectories;
private readonly BTCPayServerOptions _btcPayServerOptions;
private readonly HttpClient _githubClient;
private readonly IEnumerable<IPluginHookAction> _actions;
private readonly IEnumerable<IPluginHookFilter> _filters;
public PluginService(IEnumerable<IBTCPayServerPlugin> btcPayServerPlugins,
IHttpClientFactory httpClientFactory, IOptions<DataDirectories> datadirs, BTCPayServerOptions options, IEnumerable<IPluginHookAction> actions, IEnumerable<IPluginHookFilter> filters)
IHttpClientFactory httpClientFactory, BTCPayServerOptions btcPayServerOptions, IOptions<DataDirectories> dataDirectories)
{
LoadedPlugins = btcPayServerPlugins;
_githubClient = httpClientFactory.CreateClient();
_githubClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("btcpayserver", "1"));
_datadirs = datadirs;
_options = options;
_actions = actions;
_filters = filters;
_btcPayServerOptions = btcPayServerOptions;
_dataDirectories = dataDirectories;
}
public IEnumerable<IBTCPayServerPlugin> LoadedPlugins { get; }
@ -40,7 +36,7 @@ namespace BTCPayServer.Plugins
public async Task<IEnumerable<AvailablePlugin>> GetRemotePlugins()
{
var resp = await _githubClient
.GetStringAsync(new Uri($"https://api.github.com/repos/{_options.PluginRemote}/contents"));
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents"));
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
return await Task.WhenAll(files.Where(file => file.Name.EndsWith($"{PluginManager.BTCPayPluginSuffix}.json", StringComparison.InvariantCulture)).Select(async file =>
{
@ -51,9 +47,9 @@ namespace BTCPayServer.Plugins
public async Task DownloadRemotePlugin(string plugin)
{
var dest = _datadirs.Value.PluginDir;
var dest = _dataDirectories.Value.PluginDir;
var resp = await _githubClient
.GetStringAsync(new Uri($"https://api.github.com/repos/{_options.PluginRemote}/contents"));
.GetStringAsync(new Uri($"https://api.github.com/repos/{_btcPayServerOptions.PluginRemote}/contents"));
var files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
var ext = files.SingleOrDefault(file => file.Name == $"{plugin}{PluginManager.BTCPayPluginSuffix}");
if (ext is null)
@ -68,19 +64,19 @@ namespace BTCPayServer.Plugins
public void InstallPlugin(string plugin)
{
var dest = _datadirs.Value.PluginDir;
var dest = _dataDirectories.Value.PluginDir;
UninstallPlugin(plugin);
PluginManager.QueueCommands(dest, ("install", plugin));
}
public void UpdatePlugin(string plugin)
{
var dest = _datadirs.Value.PluginDir;
var dest = _dataDirectories.Value.PluginDir;
PluginManager.QueueCommands(dest, ("update", plugin));
}
public async Task UploadPlugin(IFormFile plugin)
{
var dest = _datadirs.Value.PluginDir;
var dest = _dataDirectories.Value.PluginDir;
var filedest = Path.Combine(dest, plugin.FileName);
Directory.CreateDirectory(Path.GetDirectoryName(filedest));
if (Path.GetExtension(filedest) == PluginManager.BTCPayPluginSuffix)
@ -92,7 +88,7 @@ namespace BTCPayServer.Plugins
public void UninstallPlugin(string plugin)
{
var dest = _datadirs.Value.PluginDir;
var dest = _dataDirectories.Value.PluginDir;
PluginManager.QueueCommands(dest, ("delete", plugin));
}
@ -127,34 +123,12 @@ namespace BTCPayServer.Plugins
public (string command, string plugin)[] GetPendingCommands()
{
return PluginManager.GetPendingCommands(_datadirs.Value.PluginDir);
return PluginManager.GetPendingCommands(_dataDirectories.Value.PluginDir);
}
public void CancelCommands(string plugin)
{
PluginManager.CancelCommands(_datadirs.Value.PluginDir, plugin);
}
public async Task ApplyAction(string hook, object args)
{
var filters = _actions
.Where(filter => filter.Hook.Equals(hook, StringComparison.InvariantCultureIgnoreCase)).ToList();
foreach (IPluginHookAction pluginHookFilter in filters)
{
await pluginHookFilter.Execute(args);
}
}
public async Task<object> ApplyFilter(string hook, object args)
{
var filters = _filters
.Where(filter => filter.Hook.Equals(hook, StringComparison.InvariantCultureIgnoreCase)).ToList();
foreach (IPluginHookFilter pluginHookFilter in filters)
{
args = await pluginHookFilter.Execute(args);
}
return args;
PluginManager.CancelCommands(_dataDirectories.Value.PluginDir, plugin);
}
}
}

View File

@ -278,5 +278,6 @@ Global
GlobalSection(NestedProjects) = preSolution
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295} = {1FC7F660-ADF1-4D55-B61A-85C6AB083C33}
{7DC94B25-1CFC-4170-AA41-7BA983E4C0B8} = {1FC7F660-ADF1-4D55-B61A-85C6AB083C33}
{3E7D4925-0CA6-4A86-87BA-5BD00E5AA4A5} = {1FC7F660-ADF1-4D55-B61A-85C6AB083C33}
EndGlobalSection
EndGlobal