Skip to content

Commit

Permalink
allow using in applications not published as a single file (#128)
Browse files Browse the repository at this point in the history
* breaking change: plugins run from the "plugins" subfolder

reason: this allows to run without publishing the app as a single file, which at the time of writing allows to workaround dotnet/runtime#59961

* Also fixed regression in #127 preventing the full help from showing in the help command.
  • Loading branch information
freddyrios committed Oct 5, 2021
1 parent 9e1fae4 commit 250ab1f
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 32 deletions.
2 changes: 1 addition & 1 deletion CA_DataUploaderLib/CommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ private List<bool> RunCommandFunctions(string cmdString, bool addToCommandHistor
isFirstAccepted = false;
OnCommandAccepted(cmdString, addToCommandHistory); // track it in the history if at least one execution accepted the command
}
else
else if (!accepted)
break; // avoid running the command on another subsystem when it was already rejected
}
catch (ArgumentException ex)
Expand Down
65 changes: 34 additions & 31 deletions CA_DataUploaderLib/PluginsLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,34 @@ namespace CA_DataUploaderLib
{
public class PluginsLoader
{ // see https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support
private readonly string targetFolder = "plugins";
readonly Dictionary<string, (AssemblyLoadContext ctx, IEnumerable<LoopControlCommand> instances)> _runningPlugins =
new Dictionary<string, (AssemblyLoadContext ctx, IEnumerable<LoopControlCommand> instances)>();
SingleFireFileWatcher _pluginChangesWatcher;
readonly object[] plugingArgs = {};
readonly CommandHandler handler;
UpdatePluginsCommand updatePluginsCommand;

public PluginsLoader(CommandHandler handler, Func<(string pluginName, string targetFolder), Task> pluginDownloader = null)
public PluginsLoader(CommandHandler handler, Func<(string pluginName, string targetFolder), Task> pluginDownloader = null, string targetFolder = "plugins")
{
this.handler = handler;
this.targetFolder = targetFolder;
if (pluginDownloader != null)
this.updatePluginsCommand = new UpdatePluginsCommand(handler, pluginDownloader, this);
this.updatePluginsCommand = new UpdatePluginsCommand(handler, pluginDownloader, this, targetFolder);
}

public IEnumerable<string> GetRunningPluginsNames() => _runningPlugins.Keys.Select(v => Path.GetFileNameWithoutExtension(v));

public void LoadPlugins(bool automaticallyLoadPluginChanges = true)
{
Directory.CreateDirectory(targetFolder);
// load all
foreach (var assembly in Directory.GetFiles(".", "*.dll"))
LoadPlugin(assembly);
foreach (var assemblyFullPath in Directory.GetFiles(targetFolder, "*.dll"))
LoadPlugin(assemblyFullPath);

if (automaticallyLoadPluginChanges)
{
_pluginChangesWatcher = new SingleFireFileWatcher(".", "*.dll");
_pluginChangesWatcher = new SingleFireFileWatcher(targetFolder, "*.dll");
_pluginChangesWatcher.Deleted += UnloadPlugin;
_pluginChangesWatcher.Changed += ReloadPlugin;
}
Expand All @@ -49,10 +52,9 @@ public void UnloadPlugins()
GC.Collect(); // triggers the unload of the assembly (after DoUnloadExtension we no longer have references to the instances)
}

void LoadPlugin(string assemblyPath)
void LoadPlugin(string assemblyFullPath)
{
assemblyPath = Path.GetFullPath(assemblyPath);
var (context, plugins) = Load(assemblyPath, plugingArgs);
var (context, plugins) = Load(assemblyFullPath, plugingArgs);
var initializedPlugins = plugins.ToList();
if (initializedPlugins.Count == 0)
{
Expand All @@ -63,26 +65,25 @@ void LoadPlugin(string assemblyPath)
foreach (var plugin in initializedPlugins)
plugin.Initialize(new PluginsCommandHandler(handler), new PluginsLogger(plugin.Name));

_runningPlugins[assemblyPath] = (context, initializedPlugins);
CALog.LogData(LogID.A, $"loaded plugins from {assemblyPath} - {string.Join(",", initializedPlugins.Select(e => e.GetType().Name))}");
_runningPlugins[assemblyFullPath] = (context, initializedPlugins);
CALog.LogData(LogID.A, $"loaded plugins from {assemblyFullPath} - {string.Join(",", initializedPlugins.Select(e => e.GetType().Name))}");
}

void UnloadPlugin(string assemblyPath)
void UnloadPlugin(string assemblyFullPath)
{
assemblyPath = Path.GetFullPath(assemblyPath);
if (!_runningPlugins.TryGetValue(assemblyPath, out var runningPluginEntry))
CALog.LogData(LogID.A, $"running plugin not found: {Path.GetFileNameWithoutExtension(assemblyPath)}");
if (!_runningPlugins.TryGetValue(assemblyFullPath, out var runningPluginEntry))
CALog.LogData(LogID.A, $"running plugin not found: {Path.GetFileNameWithoutExtension(assemblyFullPath)}");
else
UnloadPlugin(assemblyPath, runningPluginEntry);
UnloadPlugin(assemblyFullPath, runningPluginEntry);
}

void UnloadPlugin(string assemblyPath, (AssemblyLoadContext ctx, IEnumerable<LoopControlCommand> instances) entry)
void UnloadPlugin(string assemblyFullPath, (AssemblyLoadContext ctx, IEnumerable<LoopControlCommand> instances) entry)
{
foreach (var instance in entry.instances)
instance.Dispose();
_runningPlugins.Remove(assemblyPath);
_runningPlugins.Remove(assemblyFullPath);
entry.ctx.Unload();
CALog.LogData(LogID.A, $"unloaded plugins from {assemblyPath}");
CALog.LogData(LogID.A, $"unloaded plugins from {assemblyFullPath}");
}

/// <remarks>
Expand All @@ -94,16 +95,16 @@ void ReloadPlugin(string fullpath)
LoadPlugin(fullpath);
}

static (AssemblyLoadContext context, IEnumerable<LoopControlCommand> plugins) Load(string assemblyPath, params object[] args)
static (AssemblyLoadContext context, IEnumerable<LoopControlCommand> plugins) Load(string assemblyFullPath, params object[] args)
{
var (context, assembly) = LoadAssembly(assemblyPath);
var (context, assembly) = LoadAssembly(assemblyFullPath);
return (context, CreateInstances<LoopControlCommand>(assembly, args));
}

static (AssemblyLoadContext context, Assembly assembly) LoadAssembly(string assemblyPath)
static (AssemblyLoadContext context, Assembly assembly) LoadAssembly(string assemblyFullPath)
{
PluginLoadContext context = new PluginLoadContext(assemblyPath);
using var fs = new FileStream(assemblyPath, FileMode.Open, FileAccess.Read); // force no file lock
PluginLoadContext context = new PluginLoadContext(assemblyFullPath);
using var fs = new FileStream(assemblyFullPath, FileMode.Open, FileAccess.Read); // force no file lock
return (context, context.LoadFromStream(fs));
}

Expand All @@ -121,8 +122,8 @@ private class PluginLoadContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver _resolver;

public PluginLoadContext(string pluginPath) : base (true) =>
_resolver = new AssemblyDependencyResolver(pluginPath);
public PluginLoadContext(string pluginFullPath) : base (true) =>
_resolver = new AssemblyDependencyResolver(pluginFullPath);

protected override Assembly Load(AssemblyName assemblyName)
{
Expand Down Expand Up @@ -180,18 +181,20 @@ private class UpdatePluginsCommand : LoopControlCommand
{
private readonly Func<(string pluginName, string targetFolder), Task> pluginDownloader;
private readonly PluginsLoader loader;
private readonly string targetFolder;

public override string Name => "updateplugins";

public override string Description => "when no name is given it updates all existing plugins, otherwise adds/updates the specified plugin";

public override bool IsHiddenCommand => true;

public UpdatePluginsCommand(CommandHandler cmd, Func<(string pluginName, string targetFolder), Task> pluginDownloader, PluginsLoader loader)
public UpdatePluginsCommand(CommandHandler cmd, Func<(string pluginName, string targetFolder), Task> pluginDownloader, PluginsLoader loader, string targetFolder)
{
Initialize(new PluginsCommandHandler(cmd), new PluginsLogger("PluginsUpdater"));
this.pluginDownloader = pluginDownloader;
this.loader = loader;
this.targetFolder = targetFolder;
}

protected override Task Command(List<string> args) => args.Count > 1 ? UpdatePlugin(args[1]) : UpdateAllPlugins();
Expand All @@ -201,7 +204,7 @@ private async Task UpdateAllPlugins()
foreach (var pluginName in loader.GetRunningPluginsNames())
{
CALog.LogInfoAndConsoleLn(LogID.A, $"downloading plugin: {pluginName}");
await pluginDownloader((pluginName, "."));
await pluginDownloader((pluginName, targetFolder));
}

CALog.LogInfoAndConsoleLn(LogID.A, $"unloading running plugins");
Expand All @@ -214,12 +217,12 @@ private async Task UpdateAllPlugins()
private async Task UpdatePlugin(string pluginName)
{
CALog.LogInfoAndConsoleLn(LogID.A, $"downloading plugin: {pluginName}");
await pluginDownloader((pluginName, "."));
var assemblyPath = Path.GetFullPath(pluginName + ".dll");
await pluginDownloader((pluginName, targetFolder));
var assemblyFullPath = Path.Combine(targetFolder, Path.GetFullPath(pluginName + ".dll"));
CALog.LogInfoAndConsoleLn(LogID.A, $"unloading plugin: {pluginName}");
loader.UnloadPlugin(assemblyPath);
loader.UnloadPlugin(assemblyFullPath);
CALog.LogInfoAndConsoleLn(LogID.A, $"loading plugin: {pluginName}");
loader.LoadPlugin(assemblyPath);
loader.LoadPlugin(assemblyFullPath);
CALog.LogInfoAndConsoleLn(LogID.A, $"plugins updated");
}
}
Expand Down

0 comments on commit 250ab1f

Please sign in to comment.