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 abstract string Description { get; }
public bool SystemPlugin { get; set; } public bool SystemPlugin { get; set; }
public bool SystemExtension { get; set; }
public virtual IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>(); public virtual IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>();
public virtual void Execute(IApplicationBuilder applicationBuilder, public virtual void Execute(IApplicationBuilder applicationBuilder,

View File

@ -5,7 +5,7 @@ namespace BTCPayServer.Abstractions.Services
{ {
public abstract class PluginAction<T>:IPluginHookAction public abstract class PluginAction<T>:IPluginHookAction
{ {
public string Hook { get; } public abstract string Hook { get; }
public Task Execute(object args) public Task Execute(object args)
{ {
return Execute(args is T args1 ? args1 : default); 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 abstract class PluginHookFilter<T>:IPluginHookFilter
{ {
public string Hook { get; } public abstract string Hook { get; }
public Task<object> Execute(object args) public Task<object> Execute(object args)
{ {
return Execute(args is T args1 ? args1 : default).ContinueWith(task => task.Result as object); 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 bool SupportRBF { get; internal set; }
public string LightningImagePath { get; set; } public string LightningImagePath { get; set; }
public BTCPayDefaultSettings DefaultSettings { 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>(); public Dictionary<uint, DerivationType> ElectrumMapping = new Dictionary<uint, DerivationType>();
@ -132,7 +132,7 @@ namespace BTCPayServer
{ {
private string _blockExplorerLink; private string _blockExplorerLink;
public bool ShowSyncSummary { get; set; } = true; public bool ShowSyncSummary { get; set; } = true;
public string CryptoCode { get; internal set; } public string CryptoCode { get; set; }
public string BlockExplorerLink public string BlockExplorerLink
{ {
@ -161,7 +161,7 @@ namespace BTCPayServer
} }
public string CryptoImagePath { get; set; } 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() public override string ToString()
{ {
return CryptoCode; return CryptoCode;

View File

@ -8,8 +8,7 @@ namespace BTCPayServer
{ {
public partial class BTCPayNetworkProvider 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; private readonly NBXplorerNetworkProvider _NBXplorerNetworkProvider;
public NBXplorerNetworkProvider NBXplorerNetworkProvider public NBXplorerNetworkProvider NBXplorerNetworkProvider

View File

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

View File

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

View File

@ -7,6 +7,7 @@ using BTCPayServer.Configuration;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Invoices;
using Microsoft.Extensions.Options;
namespace BTCPayServer.HostedServices namespace BTCPayServer.HostedServices
{ {
@ -18,9 +19,9 @@ namespace BTCPayServer.HostedServices
private readonly InvoiceRepository _invoiceRepository; private readonly InvoiceRepository _invoiceRepository;
private readonly SettingsRepository _settingsRepository; private readonly SettingsRepository _settingsRepository;
private readonly ApplicationDbContextFactory _dbContextFactory; 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; _invoiceRepository = invoiceRepository;
_settingsRepository = settingsRepository; _settingsRepository = settingsRepository;
@ -48,7 +49,7 @@ namespace BTCPayServer.HostedServices
private async Task MigratedInvoiceTextSearchToDb(int startFromPage) private async Task MigratedInvoiceTextSearchToDb(int startFromPage)
{ {
// deleting legacy DBriize database if present // 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)) if (Directory.Exists(dbpath))
{ {
Directory.Delete(dbpath, true); Directory.Delete(dbpath, true);

View File

@ -70,7 +70,6 @@ namespace BTCPayServer.Hosting
} }
public static IServiceCollection AddBTCPayServer(this IServiceCollection services, IConfiguration configuration) public static IServiceCollection AddBTCPayServer(this IServiceCollection services, IConfiguration configuration)
{ {
services.AddSingleton<DataDirectories>();
services.AddSingleton<MvcNewtonsoftJsonOptions>(o => o.GetRequiredService<IOptions<MvcNewtonsoftJsonOptions>>().Value); services.AddSingleton<MvcNewtonsoftJsonOptions>(o => o.GetRequiredService<IOptions<MvcNewtonsoftJsonOptions>>().Value);
services.AddDbContext<ApplicationDbContext>((provider, o) => services.AddDbContext<ApplicationDbContext>((provider, o) =>
{ {
@ -244,7 +243,7 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<AppService>(); services.TryAddSingleton<AppService>();
services.AddSingleton<PluginService>(); services.AddSingleton<PluginService>();
services.AddSingleton<IPluginHookService>(provider => provider.GetService<PluginService>()); services.AddSingleton<IPluginHookService, PluginHookService>();
services.TryAddTransient<Safe>(); services.TryAddTransient<Safe>();
services.TryAddSingleton<Ganss.XSS.HtmlSanitizer>(o => 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 namespace BTCPayServer.Plugins
{ {
public class PluginService: IPluginHookService public class PluginService
{ {
private readonly IOptions<DataDirectories> _datadirs; private readonly IOptions<DataDirectories> _dataDirectories;
private readonly BTCPayServerOptions _options; private readonly BTCPayServerOptions _btcPayServerOptions;
private readonly HttpClient _githubClient; private readonly HttpClient _githubClient;
private readonly IEnumerable<IPluginHookAction> _actions;
private readonly IEnumerable<IPluginHookFilter> _filters;
public PluginService(IEnumerable<IBTCPayServerPlugin> btcPayServerPlugins, 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; LoadedPlugins = btcPayServerPlugins;
_githubClient = httpClientFactory.CreateClient(); _githubClient = httpClientFactory.CreateClient();
_githubClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("btcpayserver", "1")); _githubClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("btcpayserver", "1"));
_datadirs = datadirs; _btcPayServerOptions = btcPayServerOptions;
_options = options; _dataDirectories = dataDirectories;
_actions = actions;
_filters = filters;
} }
public IEnumerable<IBTCPayServerPlugin> LoadedPlugins { get; } public IEnumerable<IBTCPayServerPlugin> LoadedPlugins { get; }
@ -40,7 +36,7 @@ namespace BTCPayServer.Plugins
public async Task<IEnumerable<AvailablePlugin>> GetRemotePlugins() public async Task<IEnumerable<AvailablePlugin>> GetRemotePlugins()
{ {
var resp = await _githubClient 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 files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
return await Task.WhenAll(files.Where(file => file.Name.EndsWith($"{PluginManager.BTCPayPluginSuffix}.json", StringComparison.InvariantCulture)).Select(async file => 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) public async Task DownloadRemotePlugin(string plugin)
{ {
var dest = _datadirs.Value.PluginDir; var dest = _dataDirectories.Value.PluginDir;
var resp = await _githubClient 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 files = JsonConvert.DeserializeObject<GithubFile[]>(resp);
var ext = files.SingleOrDefault(file => file.Name == $"{plugin}{PluginManager.BTCPayPluginSuffix}"); var ext = files.SingleOrDefault(file => file.Name == $"{plugin}{PluginManager.BTCPayPluginSuffix}");
if (ext is null) if (ext is null)
@ -68,19 +64,19 @@ namespace BTCPayServer.Plugins
public void InstallPlugin(string plugin) public void InstallPlugin(string plugin)
{ {
var dest = _datadirs.Value.PluginDir; var dest = _dataDirectories.Value.PluginDir;
UninstallPlugin(plugin); UninstallPlugin(plugin);
PluginManager.QueueCommands(dest, ("install", plugin)); PluginManager.QueueCommands(dest, ("install", plugin));
} }
public void UpdatePlugin(string plugin) public void UpdatePlugin(string plugin)
{ {
var dest = _datadirs.Value.PluginDir; var dest = _dataDirectories.Value.PluginDir;
PluginManager.QueueCommands(dest, ("update", plugin)); PluginManager.QueueCommands(dest, ("update", plugin));
} }
public async Task UploadPlugin(IFormFile plugin) public async Task UploadPlugin(IFormFile plugin)
{ {
var dest = _datadirs.Value.PluginDir; var dest = _dataDirectories.Value.PluginDir;
var filedest = Path.Combine(dest, plugin.FileName); var filedest = Path.Combine(dest, plugin.FileName);
Directory.CreateDirectory(Path.GetDirectoryName(filedest)); Directory.CreateDirectory(Path.GetDirectoryName(filedest));
if (Path.GetExtension(filedest) == PluginManager.BTCPayPluginSuffix) if (Path.GetExtension(filedest) == PluginManager.BTCPayPluginSuffix)
@ -92,7 +88,7 @@ namespace BTCPayServer.Plugins
public void UninstallPlugin(string plugin) public void UninstallPlugin(string plugin)
{ {
var dest = _datadirs.Value.PluginDir; var dest = _dataDirectories.Value.PluginDir;
PluginManager.QueueCommands(dest, ("delete", plugin)); PluginManager.QueueCommands(dest, ("delete", plugin));
} }
@ -127,34 +123,12 @@ namespace BTCPayServer.Plugins
public (string command, string plugin)[] GetPendingCommands() public (string command, string plugin)[] GetPendingCommands()
{ {
return PluginManager.GetPendingCommands(_datadirs.Value.PluginDir); return PluginManager.GetPendingCommands(_dataDirectories.Value.PluginDir);
} }
public void CancelCommands(string plugin) public void CancelCommands(string plugin)
{ {
PluginManager.CancelCommands(_datadirs.Value.PluginDir, plugin); PluginManager.CancelCommands(_dataDirectories.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;
} }
} }
} }

View File

@ -278,5 +278,6 @@ Global
GlobalSection(NestedProjects) = preSolution GlobalSection(NestedProjects) = preSolution
{545AFC8E-7BC2-43D9-84CA-F9468F4FF295} = {1FC7F660-ADF1-4D55-B61A-85C6AB083C33} {545AFC8E-7BC2-43D9-84CA-F9468F4FF295} = {1FC7F660-ADF1-4D55-B61A-85C6AB083C33}
{7DC94B25-1CFC-4170-AA41-7BA983E4C0B8} = {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 EndGlobalSection
EndGlobal EndGlobal