2020-10-21 14:02:20 +02:00
using System ;
using System.Collections.Generic ;
2023-01-18 14:15:27 +09:00
using System.Diagnostics.CodeAnalysis ;
2020-10-21 14:02:20 +02:00
using System.Globalization ;
using System.IO ;
using System.IO.Compression ;
using System.Linq ;
using System.Reflection ;
2023-11-28 15:19:47 +01:00
using System.Text.RegularExpressions ;
2020-11-17 13:46:23 +01:00
using BTCPayServer.Abstractions.Contracts ;
2020-10-21 14:02:20 +02:00
using BTCPayServer.Configuration ;
using McMaster.NETCore.Plugins ;
using Microsoft.AspNetCore.Builder ;
using Microsoft.AspNetCore.Hosting ;
2021-02-15 13:42:08 +01:00
using Microsoft.AspNetCore.Server.Kestrel.Core ;
2020-10-21 14:02:20 +02:00
using Microsoft.Extensions.Configuration ;
using Microsoft.Extensions.DependencyInjection ;
using Microsoft.Extensions.FileProviders ;
using Microsoft.Extensions.Logging ;
2023-12-13 12:36:23 +01:00
using Newtonsoft.Json.Linq ;
2020-10-21 14:02:20 +02:00
namespace BTCPayServer.Plugins
{
public static class PluginManager
{
public const string BTCPayPluginSuffix = ".btcpay" ;
2023-12-13 12:36:23 +01:00
private static readonly List < Assembly > _pluginAssemblies = new ( ) ;
2020-10-21 14:02:20 +02:00
2023-01-18 14:15:27 +09:00
public static bool IsExceptionByPlugin ( Exception exception , [ MaybeNullWhen ( false ) ] out string pluginName )
2021-04-01 05:27:22 +02:00
{
2023-11-28 15:19:47 +01:00
var fromAssembly = exception is TypeLoadException
? Regex . Match ( exception . Message , "from assembly '(.*?)," ) . Groups [ 1 ] . Value
: null ;
2023-01-18 14:15:27 +09:00
foreach ( var assembly in _pluginAssemblies )
{
var assemblyName = assembly . GetName ( ) . Name ;
if ( assemblyName is null )
continue ;
2023-11-28 15:19:47 +01:00
// Comparison is case sensitive as it is theoretically possible to have a different plugin
2023-01-18 14:15:27 +09:00
// with same name but different casing.
if ( exception . Source is not null & &
assemblyName . Equals ( exception . Source , StringComparison . Ordinal ) )
{
pluginName = assemblyName ;
return true ;
}
if ( exception . Message . Contains ( assemblyName , StringComparison . Ordinal ) )
{
pluginName = assemblyName ;
return true ;
}
2023-11-28 15:19:47 +01:00
// For TypeLoadException, check if it might come from areferenced assembly
if ( ! string . IsNullOrEmpty ( fromAssembly ) & & assembly . GetReferencedAssemblies ( ) . Select ( a = > a . Name ) . Contains ( fromAssembly ) )
{
pluginName = assemblyName ;
return true ;
}
2023-01-18 14:15:27 +09:00
}
pluginName = null ;
return false ;
2021-04-01 05:27:22 +02:00
}
2024-01-18 09:15:16 +01:00
2020-10-21 14:02:20 +02:00
public static IMvcBuilder AddPlugins ( this IMvcBuilder mvcBuilder , IServiceCollection serviceCollection ,
2023-11-29 18:51:40 +09:00
IConfiguration config , ILoggerFactory loggerFactory , ServiceProvider bootstrapServiceProvider )
2020-10-21 14:02:20 +02:00
{
2024-01-18 09:15:16 +01:00
void LoadPluginsFromAssemblies ( Assembly systemAssembly1 , HashSet < string > exclude , HashSet < string > loadedPluginIdentifiers1 ,
2023-12-13 12:36:23 +01:00
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 ;
2024-01-18 09:15:16 +01:00
if ( ! isSystemPlugin & & exclude . Contains ( assemblyName ) )
2023-12-13 12:36:23 +01:00
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 ;
}
}
}
2023-01-16 10:37:17 +09:00
var logger = loggerFactory . CreateLogger ( typeof ( PluginManager ) ) ;
2021-01-06 15:51:13 +01:00
var pluginsFolder = new DataDirectories ( ) . Configure ( config ) . PluginDir ;
2020-10-21 14:02:20 +02:00
var plugins = new List < IBTCPayServerPlugin > ( ) ;
2023-01-16 10:37:17 +09:00
var loadedPluginIdentifiers = new HashSet < string > ( ) ;
2020-10-21 14:02:20 +02:00
2021-02-15 13:42:08 +01:00
serviceCollection . Configure < KestrelServerOptions > ( options = >
{
options . Limits . MaxRequestBodySize = int . MaxValue ; // if don't set default value is: 30 MB
} ) ;
2023-01-16 10:37:17 +09:00
logger . LogInformation ( $"Loading plugins from {pluginsFolder}" ) ;
2020-10-21 14:02:20 +02:00
Directory . CreateDirectory ( pluginsFolder ) ;
ExecuteCommands ( pluginsFolder ) ;
2021-07-08 12:53:34 +02:00
2024-01-18 09:15:16 +01:00
var disabledPluginIdentifiers = GetDisabledPluginIdentifiers ( pluginsFolder ) ;
2023-01-16 10:37:17 +09:00
var systemAssembly = typeof ( Program ) . Assembly ;
2024-01-18 09:15:16 +01:00
LoadPluginsFromAssemblies ( systemAssembly , disabledPluginIdentifiers , loadedPluginIdentifiers , plugins ) ;
2021-12-31 16:59:02 +09:00
2023-12-13 12:36:23 +01:00
if ( ExecuteCommands ( pluginsFolder , plugins . ToDictionary ( plugin = > plugin . Identifier , plugin = > plugin . Version ) ) )
{
plugins . Clear ( ) ;
2024-01-17 19:26:22 +09:00
loadedPluginIdentifiers . Clear ( ) ;
2024-01-18 09:15:16 +01:00
LoadPluginsFromAssemblies ( systemAssembly , disabledPluginIdentifiers , loadedPluginIdentifiers , plugins ) ;
2020-10-21 14:02:20 +02:00
}
2021-12-31 16:59:02 +09:00
2023-01-16 10:37:17 +09:00
var pluginsToLoad = new List < ( string PluginIdentifier , string PluginFilePath ) > ( ) ;
2020-11-05 15:43:14 +01:00
2023-01-16 10:37:17 +09:00
#if DEBUG
// Load from DEBUG_PLUGINS, in an optional appsettings.dev.json
var debugPlugins = config [ "DEBUG_PLUGINS" ] ? ? "" ;
foreach ( var plugin in debugPlugins . Split ( ';' , StringSplitOptions . RemoveEmptyEntries ) )
2020-11-05 15:43:14 +01:00
{
2023-01-16 10:37:17 +09:00
// Formatted either as "<PLUGIN_IDENTIFIER>::<PathToDll>" or "<PathToDll>"
var idx = plugin . IndexOf ( "::" ) ;
if ( idx ! = - 1 )
2023-04-10 11:07:03 +09:00
pluginsToLoad . Add ( ( plugin [ 0. . idx ] , plugin [ ( idx + 1 ) . . ] ) ) ;
2023-01-16 10:37:17 +09:00
else
pluginsToLoad . Add ( ( Path . GetFileNameWithoutExtension ( plugin ) , plugin ) ) ;
2020-11-05 15:43:14 +01:00
}
2023-01-16 10:37:17 +09:00
#endif
2020-11-05 15:43:14 +01:00
2023-01-16 10:37:17 +09:00
// Load from the plugins folder
foreach ( var directory in Directory . GetDirectories ( pluginsFolder ) )
2020-10-21 14:02:20 +02:00
{
2023-01-16 16:31:26 +09:00
var pluginIdentifier = Path . GetFileName ( directory ) ;
2023-01-16 10:37:17 +09:00
var pluginFilePath = Path . Combine ( directory , pluginIdentifier + ".dll" ) ;
2021-04-20 08:38:37 +02:00
if ( ! File . Exists ( pluginFilePath ) )
continue ;
2024-01-18 09:15:16 +01:00
if ( disabledPluginIdentifiers . Contains ( pluginIdentifier ) )
2023-01-16 10:37:17 +09:00
continue ;
pluginsToLoad . Add ( ( pluginIdentifier , pluginFilePath ) ) ;
}
2021-04-20 08:38:37 +02:00
2023-01-16 10:37:17 +09:00
ReorderPlugins ( pluginsFolder , pluginsToLoad ) ;
foreach ( var toLoad in pluginsToLoad )
{
2023-02-08 07:47:38 +01:00
// This used to be a standalone plugin but due to popular demand has been made as part of core. If we detect an install, we remove the redundant plugin.
if ( toLoad . PluginIdentifier = = "BTCPayServer.Plugins.NFC" )
{
QueueCommands ( pluginsFolder , ( "delete" , toLoad . PluginIdentifier ) ) ;
continue ;
}
2023-01-16 10:37:17 +09:00
if ( ! loadedPluginIdentifiers . Add ( toLoad . PluginIdentifier ) )
continue ;
2021-05-03 08:35:54 +02:00
try
{
var plugin = PluginLoader . CreateFromAssemblyFile (
2023-01-16 10:37:17 +09:00
toLoad . PluginFilePath , // create a plugin from for the .dll file
2021-05-03 08:35:54 +02:00
config = >
{
// this ensures that the version of MVC is shared between this app and the plugin
config . PreferSharedTypes = true ;
2023-01-18 16:46:08 +09:00
config . IsUnloadable = false ;
2021-05-03 08:35:54 +02:00
} ) ;
var pluginAssembly = plugin . LoadDefaultAssembly ( ) ;
2023-01-16 10:37:17 +09:00
var p = GetPluginInstanceFromAssembly ( toLoad . PluginIdentifier , pluginAssembly ) ;
if ( p = = null )
{
logger . LogError ( $"The plugin assembly doesn't contain the plugin {toLoad.PluginIdentifier}" ) ;
}
else
2022-12-13 18:54:41 +09:00
{
2023-01-16 10:37:17 +09:00
mvcBuilder . AddPluginLoader ( plugin ) ;
_pluginAssemblies . Add ( pluginAssembly ) ;
2022-12-13 18:54:41 +09:00
p . SystemPlugin = false ;
plugins . Add ( p ) ;
}
2021-05-03 08:35:54 +02:00
}
catch ( Exception e )
{
2023-01-16 10:37:17 +09:00
logger . LogError ( e ,
$"Error when loading plugin {toLoad.PluginIdentifier}" ) ;
2021-05-03 08:35:54 +02:00
}
2020-10-21 14:02:20 +02:00
}
foreach ( var plugin in plugins )
{
2023-09-22 23:43:06 +09:00
if ( plugin . Identifier = = "BTCPayServer.Plugins.Prism" & & plugin . Version < = new Version ( "1.1.18" ) )
{
2023-12-19 10:34:45 +01:00
QueueCommands ( pluginsFolder , ( "disable" , plugin . Identifier ) ) ;
2023-09-22 23:43:06 +09:00
logger . LogWarning ( "Please update your prism plugin, this version is incompatible" ) ;
continue ;
2023-12-19 10:34:45 +01:00
}
if ( plugin . Identifier = = "BTCPayServer.Plugins.Wabisabi" & & plugin . Version < = new Version ( "1.0.66" ) )
{
QueueCommands ( pluginsFolder , ( "disable" , plugin . Identifier ) ) ;
continue ;
2023-09-22 23:43:06 +09:00
}
2020-10-21 14:02:20 +02:00
try
{
2023-01-16 10:37:17 +09:00
logger . LogInformation (
2020-10-21 14:02:20 +02:00
$"Adding and executing plugin {plugin.Identifier} - {plugin.Version}" ) ;
2023-11-29 18:51:40 +09:00
var pluginServiceCollection = new PluginServiceCollection ( serviceCollection , bootstrapServiceProvider ) ;
plugin . Execute ( pluginServiceCollection ) ;
2020-10-21 14:02:20 +02:00
serviceCollection . AddSingleton ( plugin ) ;
}
catch ( Exception e )
{
2023-01-16 10:37:17 +09:00
logger . LogError (
2020-11-05 15:43:14 +01:00
$"Error when loading plugin {plugin.Identifier} - {plugin.Version}{Environment.NewLine}{e.Message}" ) ;
2020-10-21 14:02:20 +02:00
}
}
return mvcBuilder ;
}
2023-01-16 10:37:17 +09:00
private static void ReorderPlugins ( string pluginsFolder , List < ( string PluginIdentifier , string PluginFilePath ) > pluginsToLoad )
{
Dictionary < string , int > ordersByPlugin = new Dictionary < string , int > ( ) ;
var orderFilePath = Path . Combine ( pluginsFolder , "order" ) ;
int order = 0 ;
if ( File . Exists ( orderFilePath ) )
{
foreach ( var o in File . ReadLines ( orderFilePath ) )
{
if ( ordersByPlugin . TryAdd ( o , order ) )
order + + ;
}
}
foreach ( var p in pluginsToLoad )
{
if ( ordersByPlugin . TryAdd ( p . PluginIdentifier , order ) )
order + + ;
}
2023-04-10 11:07:03 +09:00
pluginsToLoad . Sort ( ( a , b ) = > ordersByPlugin [ a . PluginIdentifier ] - ordersByPlugin [ b . PluginIdentifier ] ) ;
2023-01-16 10:37:17 +09:00
}
2020-10-21 14:02:20 +02:00
public static void UsePlugins ( this IApplicationBuilder applicationBuilder )
{
2023-01-16 10:37:17 +09:00
HashSet < Assembly > assemblies = new HashSet < Assembly > ( ) ;
2020-10-21 14:02:20 +02:00
foreach ( var extension in applicationBuilder . ApplicationServices
. GetServices < IBTCPayServerPlugin > ( ) )
{
extension . Execute ( applicationBuilder ,
applicationBuilder . ApplicationServices ) ;
2023-01-16 10:37:17 +09:00
assemblies . Add ( extension . GetType ( ) . Assembly ) ;
2020-10-21 14:02:20 +02:00
}
var webHostEnvironment = applicationBuilder . ApplicationServices . GetService < IWebHostEnvironment > ( ) ;
2021-12-31 16:59:02 +09:00
List < IFileProvider > providers = new List < IFileProvider > ( ) { webHostEnvironment . WebRootFileProvider } ;
2023-01-16 10:37:17 +09:00
providers . AddRange ( assemblies . Select ( a = > new EmbeddedFileProvider ( a ) ) ) ;
2020-10-21 14:02:20 +02:00
webHostEnvironment . WebRootFileProvider = new CompositeFileProvider ( providers ) ;
}
2020-11-05 15:43:14 +01:00
2023-01-16 10:37:17 +09:00
private static IEnumerable < IBTCPayServerPlugin > GetPluginInstancesFromAssembly ( Assembly assembly )
2020-10-21 14:02:20 +02:00
{
return assembly . GetTypes ( ) . Where ( type = >
2020-11-05 15:43:14 +01:00
typeof ( IBTCPayServerPlugin ) . IsAssignableFrom ( type ) & & type ! = typeof ( PluginService . AvailablePlugin ) & &
2023-01-16 10:37:17 +09:00
! type . IsAbstract ) .
Select ( type = > ( IBTCPayServerPlugin ) Activator . CreateInstance ( type , Array . Empty < object > ( ) ) ) ;
2020-10-21 14:02:20 +02:00
}
2023-12-13 12:36:23 +01:00
2023-01-16 10:37:17 +09:00
private static IBTCPayServerPlugin GetPluginInstanceFromAssembly ( string pluginIdentifier , Assembly assembly )
2020-10-21 14:02:20 +02:00
{
2023-12-13 12:36:23 +01:00
return GetPluginInstancesFromAssembly ( assembly ) . FirstOrDefault ( plugin = > plugin . Identifier = = pluginIdentifier ) ;
2020-10-21 14:02:20 +02:00
}
2023-12-13 12:36:23 +01:00
private static bool ExecuteCommands ( string pluginsFolder , Dictionary < string , Version > installed = null )
2020-10-21 14:02:20 +02:00
{
var pendingCommands = GetPendingCommands ( pluginsFolder ) ;
2023-12-13 12:36:23 +01:00
if ( ! pendingCommands . Any ( ) )
{
return false ;
}
var remainingCommands = ( from command in pendingCommands where ! ExecuteCommand ( command , pluginsFolder , false , installed ) select $"{command.command}:{command.plugin}" ) . ToList ( ) ;
if ( remainingCommands . Any ( ) )
2020-10-21 14:02:20 +02:00
{
2023-12-13 12:36:23 +01:00
File . WriteAllLines ( Path . Combine ( pluginsFolder , "commands" ) , remainingCommands ) ;
}
else
{
File . Delete ( Path . Combine ( pluginsFolder , "commands" ) ) ;
2020-10-21 14:02:20 +02:00
}
2023-12-13 12:36:23 +01:00
return remainingCommands . Count ! = pendingCommands . Length ;
2020-10-21 14:02:20 +02:00
}
2024-01-18 09:15:16 +01:00
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 ;
}
2020-10-21 14:02:20 +02:00
2023-12-13 12:36:23 +01:00
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 ,
2024-01-18 09:15:16 +01:00
bool ignoreOrder , Dictionary < string , Version > installed )
2020-10-21 14:02:20 +02:00
{
var dirName = Path . Combine ( pluginsFolder , command . extension ) ;
switch ( command . command )
{
2020-11-05 15:43:14 +01:00
case "update" :
2023-12-13 12:36:23 +01:00
if ( ! DependenciesMet ( pluginsFolder , command . extension , installed ) )
return false ;
2024-01-18 09:15:16 +01:00
ExecuteCommand ( ( "delete" , command . extension ) , pluginsFolder , true , installed ) ;
ExecuteCommand ( ( "install" , command . extension ) , pluginsFolder , true , installed ) ;
2020-11-05 15:43:14 +01:00
break ;
2021-12-31 16:59:02 +09:00
2023-12-13 12:36:23 +01:00
case "delete" :
2024-01-18 09:15:16 +01:00
ExecuteCommand ( ( "enable" , command . extension ) , pluginsFolder , true , installed ) ;
2022-04-01 13:20:19 +02:00
if ( File . Exists ( dirName ) )
{
File . Delete ( dirName ) ;
}
2020-10-21 14:02:20 +02:00
if ( Directory . Exists ( dirName ) )
{
Directory . Delete ( dirName , true ) ;
2020-11-05 15:43:14 +01:00
if ( ! ignoreOrder & & File . Exists ( Path . Combine ( pluginsFolder , "order" ) ) )
{
var orders = File . ReadAllLines ( Path . Combine ( pluginsFolder , "order" ) ) ;
2021-04-01 05:27:22 +02:00
File . WriteAllLines ( Path . Combine ( pluginsFolder , "order" ) ,
2020-11-05 15:43:14 +01:00
orders . Where ( s = > s ! = command . extension ) ) ;
}
2020-10-21 14:02:20 +02:00
}
break ;
2023-12-13 12:36:23 +01:00
2021-12-31 16:59:02 +09:00
case "install" :
2020-10-21 14:02:20 +02:00
var fileName = dirName + BTCPayPluginSuffix ;
2023-12-13 12:36:23 +01:00
var manifestFileName = dirName + ".json" ;
if ( ! DependenciesMet ( pluginsFolder , command . extension , installed ) )
return false ;
2024-01-18 09:15:16 +01:00
ExecuteCommand ( ( "enable" , command . extension ) , pluginsFolder , true , installed ) ;
2020-10-21 14:02:20 +02:00
if ( File . Exists ( fileName ) )
{
ZipFile . ExtractToDirectory ( fileName , dirName , true ) ;
2020-11-05 15:43:14 +01:00
if ( ! ignoreOrder )
{
2021-12-31 16:59:02 +09:00
File . AppendAllLines ( Path . Combine ( pluginsFolder , "order" ) , new [ ] { command . extension } ) ;
2020-11-05 15:43:14 +01:00
}
2020-10-21 14:02:20 +02:00
File . Delete ( fileName ) ;
2023-12-13 12:36:23 +01:00
if ( File . Exists ( manifestFileName ) )
{
File . Move ( manifestFileName , Path . Combine ( dirName , Path . GetFileName ( manifestFileName ) ) ) ;
}
2020-10-21 14:02:20 +02:00
}
2021-04-01 05:27:22 +02:00
break ;
2021-12-31 16:59:02 +09:00
2021-04-01 05:27:22 +02:00
case "disable" :
if ( Directory . Exists ( dirName ) )
{
if ( File . Exists ( Path . Combine ( pluginsFolder , "disabled" ) ) )
{
var disabled = File . ReadAllLines ( Path . Combine ( pluginsFolder , "disabled" ) ) ;
if ( ! disabled . Contains ( command . extension ) )
{
2021-12-31 16:59:02 +09:00
File . AppendAllLines ( Path . Combine ( pluginsFolder , "disabled" ) , new [ ] { command . extension } ) ;
2021-04-01 05:27:22 +02:00
}
}
else
{
2021-12-31 16:59:02 +09:00
File . AppendAllLines ( Path . Combine ( pluginsFolder , "disabled" ) , new [ ] { command . extension } ) ;
2021-04-01 05:27:22 +02:00
}
}
break ;
2021-12-31 16:59:02 +09:00
2021-04-01 05:27:22 +02:00
case "enable" :
2021-09-27 09:03:59 +02:00
if ( File . Exists ( Path . Combine ( pluginsFolder , "disabled" ) ) )
2021-04-01 05:27:22 +02:00
{
2021-09-27 09:03:59 +02:00
var disabled = File . ReadAllLines ( Path . Combine ( pluginsFolder , "disabled" ) ) ;
if ( disabled . Contains ( command . extension ) )
2021-04-01 05:27:22 +02:00
{
2021-12-31 16:59:02 +09:00
File . WriteAllLines ( Path . Combine ( pluginsFolder , "disabled" ) , disabled . Where ( s = > s ! = command . extension ) ) ;
2021-04-01 05:27:22 +02:00
}
}
2020-10-21 14:02:20 +02:00
break ;
}
2023-12-13 12:36:23 +01:00
return true ;
2020-10-21 14:02:20 +02:00
}
public static ( string command , string plugin ) [ ] GetPendingCommands ( string pluginsFolder )
{
if ( ! File . Exists ( Path . Combine ( pluginsFolder , "commands" ) ) )
return Array . Empty < ( string command , string plugin ) > ( ) ;
var commands = File . ReadAllLines ( Path . Combine ( pluginsFolder , "commands" ) ) ;
return commands . Select ( s = >
{
var split = s . Split ( ':' ) ;
return ( split [ 0 ] . ToLower ( CultureInfo . InvariantCulture ) , split [ 1 ] ) ;
} ) . ToArray ( ) ;
}
2021-12-31 16:59:02 +09:00
public static void QueueCommands ( string pluginsFolder , params ( string action , string plugin ) [ ] commands )
2020-10-21 14:02:20 +02:00
{
File . AppendAllLines ( Path . Combine ( pluginsFolder , "commands" ) ,
commands . Select ( ( tuple ) = > $"{tuple.action}:{tuple.plugin}" ) ) ;
}
public static void CancelCommands ( string pluginDir , string plugin )
{
var cmds = GetPendingCommands ( pluginDir ) . Where ( tuple = >
! tuple . plugin . Equals ( plugin , StringComparison . InvariantCultureIgnoreCase ) ) . ToArray ( ) ;
2023-12-13 12:36:23 +01:00
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" ) ) ;
}
2020-10-21 14:02:20 +02:00
File . Delete ( Path . Combine ( pluginDir , "commands" ) ) ;
QueueCommands ( pluginDir , cmds ) ;
}
2021-04-01 05:27:22 +02:00
public static void DisablePlugin ( string pluginDir , string plugin )
{
2021-12-31 16:59:02 +09:00
QueueCommands ( pluginDir , ( "disable" , plugin ) ) ;
2021-04-01 05:27:22 +02:00
}
2024-01-18 09:15:16 +01:00
// Loads the list of disabled plugins from the file
private static HashSet < string > GetDisabledPluginIdentifiers ( string pluginsFolder )
{
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 )
2021-04-01 05:27:22 +02:00
{
2024-01-18 09:15:16 +01:00
return TryGetInstalledInfo ( pluginsFolder ) . Where ( pair = > pair . Value . Disabled )
. ToDictionary ( pair = > pair . Key , pair = > pair . Value . Item1 ) ;
2023-12-13 12:36:23 +01:00
}
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 ( "!" ) )
2021-04-01 05:27:22 +02:00
{
2023-12-13 12:36:23 +01:00
return false ;
2021-04-01 05:27:22 +02:00
}
2023-12-13 12:36:23 +01:00
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 ) ) ;
2021-04-01 05:27:22 +02:00
}
2020-10-21 14:02:20 +02:00
}
}