Skip to content

Commit

Permalink
Update rdmpplugins.txt on plugin addition, deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
jas88 committed Jan 5, 2024
1 parent 0960852 commit e78d168
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace Rdmp.Core.CommandExecution.AtomicCommands;
/// dll at runtime and so these dlls will just bloat your plugin. Use this command to prune out
/// those files.
/// </summary>
public partial class ExecuteCommandPrunePlugin : BasicCommandExecution
public sealed partial class ExecuteCommandPrunePlugin : BasicCommandExecution
{
private string _file;

Expand Down
31 changes: 10 additions & 21 deletions Rdmp.Core/CommandLine/Runners/PackPluginRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Xml.Linq;
using Rdmp.Core.CommandExecution.AtomicCommands;
using Rdmp.Core.CommandLine.Options;
using Rdmp.Core.Curation.Data;
using Rdmp.Core.DataFlowPipeline;
using Rdmp.Core.Repositories;
using Rdmp.Core.ReusableLibraryCode.Checks;
Expand All @@ -23,13 +24,13 @@ namespace Rdmp.Core.CommandLine.Runners;
/// <summary>
/// Uploads a packed plugin (.nupkg) into a consumable plugin for RDMP
/// </summary>
public class PackPluginRunner : IRunner
public sealed partial class PackPluginRunner : IRunner
{
private readonly PackOptions _packOpts;
public const string PluginPackageSuffix = ".nupkg";
public const string PluginPackageManifest = ".nuspec";
private const string PluginPackageManifest = ".nuspec";

private static readonly Regex VersionSuffix = new("-.*$");
private static readonly Regex VersionSuffix = VersionSuffixRe();

public PackPluginRunner(PackOptions packOpts)
{
Expand All @@ -44,7 +45,7 @@ public int Run(IRDMPPlatformRepositoryServiceLocator repositoryLocator, IDataLoa
if (!toCommit.Exists)
throw new FileNotFoundException($"Could not find file '{toCommit}'");

if (toCommit.Extension.ToLowerInvariant() != PluginPackageSuffix)
if (!toCommit.Name.EndsWith(PluginPackageSuffix, StringComparison.OrdinalIgnoreCase))
throw new NotSupportedException($"Plugins must be packaged as {PluginPackageSuffix}");

//the version of the plugin e.g. MyPlugin.nupkg version 1.0.0.0
Expand All @@ -62,7 +63,7 @@ public int Run(IRDMPPlatformRepositoryServiceLocator repositoryLocator, IDataLoa
//find the manifest that lists name, version etc
using (var zf = ZipFile.OpenRead(toCommit.FullName))
{
var manifests = zf.Entries.Where(e => e.FullName.EndsWith(PluginPackageManifest)).ToArray();
var manifests = zf.Entries.Where(static e => e.FullName.EndsWith(PluginPackageManifest, StringComparison.OrdinalIgnoreCase)).ToArray();

if (manifests.Length != 1)
throw new Exception(
Expand All @@ -79,7 +80,7 @@ public int Run(IRDMPPlatformRepositoryServiceLocator repositoryLocator, IDataLoa

var rdmpDependencyNode =
doc.Descendants(ns + "dependency")
.FirstOrDefault(e => e?.Attribute("id")?.Value == "HIC.RDMP.Plugin") ?? throw new Exception(
.FirstOrDefault(static e => e?.Attribute("id")?.Value == "HIC.RDMP.Plugin") ?? throw new Exception(
"Expected a single <dependency> tag with id = HIC.RDMP.Plugin (in order to determine plugin compatibility). Ensure your nuspec file includes a dependency on this package.");
rdmpDependencyVersion =
new Version(VersionSuffix.Replace(rdmpDependencyNode?.Attribute("version")?.Value ?? "", ""));
Expand All @@ -90,23 +91,11 @@ public int Run(IRDMPPlatformRepositoryServiceLocator repositoryLocator, IDataLoa
throw new NotSupportedException(
$"Plugin version {pluginVersion} is incompatible with current running version of RDMP ({runningSoftwareVersion}).");

UploadFile(repositoryLocator, checkNotifier, toCommit, pluginVersion, rdmpDependencyVersion);
LoadModuleAssembly.UploadFile(checkNotifier, toCommit);

return 0;
}

private static void UploadFile(IRDMPPlatformRepositoryServiceLocator repositoryLocator,
ICheckNotifier checkNotifier, FileInfo toCommit, Version pluginVersion, Version rdmpDependencyVersion)
{
try
{
toCommit.CopyTo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, toCommit.Name), true);
}
catch (Exception e)
{
checkNotifier.OnCheckPerformed(new CheckEventArgs($"Failed copying plugin {toCommit.Name}",
CheckResult.Fail, e));
throw;
}
}
[GeneratedRegex("-.*$")]
private static partial Regex VersionSuffixRe();
}
84 changes: 70 additions & 14 deletions Rdmp.Core/Curation/Data/LoadModuleAssembly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.IO;
using System.Linq;
using ICSharpCode.SharpZipLib.Zip;
using Rdmp.Core.ReusableLibraryCode.Checks;

namespace Rdmp.Core.Curation.Data;

Expand All @@ -17,35 +18,49 @@ namespace Rdmp.Core.Curation.Data;
/// </summary>
public sealed class LoadModuleAssembly
{
internal static readonly List<LoadModuleAssembly> Assemblies=new();
private static readonly bool IsWin = AppDomain.CurrentDomain.GetAssemblies()
.Any(static a => a.FullName?.StartsWith("Rdmp.UI", StringComparison.Ordinal) == true);

private static readonly string PluginsList = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "rdmpplugins.txt");

internal static readonly List<LoadModuleAssembly> Assemblies = [];
private readonly FileInfo _file;

private LoadModuleAssembly(FileInfo file)
{
_file = file;
}

/// <summary>
/// List the plugin files to load
/// </summary>
/// <returns></returns>
internal static IEnumerable<string> PluginFiles()
{
return File.Exists(PluginsList)
? File.ReadAllLines(PluginsList)
.Select(static name => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, name))
: Directory.EnumerateFiles(AppDomain.CurrentDomain.BaseDirectory, "*.nupkg");
}

/// <summary>
/// Unpack the plugin DLL files, excluding any Windows UI specific dlls when not running a Windows GUI
/// </summary>
internal static IEnumerable<ValueTuple<string, MemoryStream>> GetContents(string path)
internal static IEnumerable<(string, MemoryStream)> GetContents(string path)
{
var info = new FileInfo(path);
if (!info.Exists || info.Length < 100) yield break; // Ignore missing or empty files

var pluginStream = info.OpenRead();
Assemblies.Add(new LoadModuleAssembly(info));

var isWin = AppDomain.CurrentDomain.GetAssemblies()
.Any(static a => a.FullName?.StartsWith("Rdmp.UI", StringComparison.Ordinal) == true);

if (!pluginStream.CanSeek)
throw new ArgumentException("Seek needed", nameof(path));

Assemblies.Add(new LoadModuleAssembly(info));

using var zip = new ZipFile(pluginStream);
foreach (var e in zip.Cast<ZipEntry>()
.Where(static e => e.IsFile && e.Name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
.Where(e => isWin || !e.Name.Contains("/windows/")))
.Where(static e => e.IsFile && e.Name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase) &&
(IsWin || !e.Name.Contains("/windows/"))))
{
using var s = zip.GetInputStream(e);
using var ms2 = new MemoryStream();
Expand All @@ -59,15 +74,56 @@ internal static IEnumerable<ValueTuple<string, MemoryStream>> GetContents(string
/// Copy the plugin nupkg to the given directory
/// </summary>
/// <param name="downloadDirectory"></param>
public string DownloadAssembly(DirectoryInfo downloadDirectory)
public void DownloadAssembly(DirectoryInfo downloadDirectory)
{
if (!downloadDirectory.Exists)
downloadDirectory.Create();
var targetFile=Path.Combine(downloadDirectory.FullName, _file.Name);
_file.CopyTo(targetFile,true);
return targetFile;
var targetFile = Path.Combine(downloadDirectory.FullName, _file.Name);
_file.CopyTo(targetFile, true);
}

/// <summary>
/// Delete the plugin file from disk, and remove it from rdmpplugins.txt if in use
/// </summary>
public void Delete()
{
_file.Delete();
if (!File.Exists(PluginsList)) return;

var tmp = $"{PluginsList}.tmp";
File.WriteAllLines(tmp, File.ReadAllLines(PluginsList).Where(l => !l.Contains(_file.Name)));
File.Move(tmp, PluginsList, true);
}

public void Delete() => _file.Delete();
public override string ToString() => _file.Name;

public static void UploadFile(ICheckNotifier checkNotifier, FileInfo toCommit)
{
try
{
toCommit.CopyTo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, toCommit.Name), true);
if (!File.Exists(PluginsList)) return;

// Now the tricky bit: add this new file, and remove any other versions of the same
// e.g. adding DummyPlugin-1.2.3 should delete DummyPlugin-2.0 and DummyPlugin-1.0 if present
var list = File.ReadAllLines(PluginsList);
var tmp = $"{PluginsList}.tmp";

var versionPos = toCommit.Name.IndexOf('-');
if (versionPos != -1)
{
var stub = toCommit.Name[..(versionPos + 1)];
list = list.Where(l => !l.StartsWith(stub, StringComparison.OrdinalIgnoreCase)).ToArray();

Check warning

Code scanning / CodeQL

Useless assignment to local variable Warning

This assignment to
list
is useless, since its value is never read.
}

File.WriteAllLines(tmp, list.Union([toCommit.Name]));
File.Move(tmp, PluginsList, true);
}
catch (Exception e)
{
checkNotifier.OnCheckPerformed(new CheckEventArgs($"Failed copying plugin {toCommit.Name}",
CheckResult.Fail, e));
throw;
}
}
}
1 change: 1 addition & 0 deletions Rdmp.Core/Rdmp.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
</PropertyGroup>
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Databases\CatalogueDatabase\up\views\**" />
Expand Down
12 changes: 4 additions & 8 deletions Rdmp.Core/Startup/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public void DoStartup(ICheckNotifier notifier)

//only load data export manager if catalogue worked
if (!foundCatalogue) return;

LoadMEF(RepositoryLocator.CatalogueRepository, notifier);

//find tier 2 databases
Expand All @@ -138,10 +139,10 @@ public void DoStartup(ICheckNotifier notifier)
e));
}

FindTier3Databases(RepositoryLocator.CatalogueRepository, notifier);
FindTier3Databases(notifier);
}

private void FindTier3Databases(ICatalogueRepository catalogueRepository, ICheckNotifier notifier)
private void FindTier3Databases(ICheckNotifier notifier)
{
foreach (var patcher in _patcherManager.GetTier3Patchers(PluginPatcherFound))
FindWithPatcher(patcher, notifier);
Expand Down Expand Up @@ -241,12 +242,7 @@ private void FindWithPatcher(IPatcher patcher, ICheckNotifier notifier)
/// <param name="notifier"></param>
private static void LoadMEF(ICatalogueRepository catalogueRepository, ICheckNotifier notifier)
{
var pluginsList = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "rdmpplugins.txt");
var packageFiles = File.Exists(pluginsList)
? File.ReadAllLines(pluginsList)
.Select(static name => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, name))
: Directory.EnumerateFiles(AppDomain.CurrentDomain.BaseDirectory, "*.nupkg");
foreach (var (name, body) in packageFiles.SelectMany(LoadModuleAssembly.GetContents))
foreach (var (name, body) in LoadModuleAssembly.PluginFiles().SelectMany(LoadModuleAssembly.GetContents))

Check notice

Code scanning / CodeQL

Missed 'using' opportunity Note

This variable is manually
disposed
in a
finally block
- consider a C# using statement as a preferable resource management technique.
try
{
AssemblyLoadContext.Default.LoadFromStream(body);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using System;

namespace Rdmp.UI.CommandExecution.AtomicCommands;

public class ExecuteCommandDeletePlugin : BasicUICommandExecution
public sealed class ExecuteCommandDeletePlugin : BasicUICommandExecution
{
private readonly LoadModuleAssembly _assembly;

public ExecuteCommandDeletePlugin(IActivateItems activator, LoadModuleAssembly assembly) : base(activator)
{
_assembly = assembly;
Expand All @@ -27,18 +29,16 @@ public override Image<Rgba32> GetImage(IIconProvider iconProvider) =>
public override void Execute()
{
base.Execute();
if (YesNo($"Are you sure you want to delete {_assembly}?", "Delete Plugin"))
if (!YesNo($"Are you sure you want to delete {_assembly}?", "Delete Plugin")) return;

try
{
_assembly.Delete();
try
{
_assembly.Delete();
Show("Changes will take effect on restart");
}
catch (SystemException ex)
{
Show($"Could not delete the {_assembly} plugin.", ex);
}
Show("Changes will take effect on restart");
}
catch (SystemException ex)
{
Show($"Could not delete the {_assembly} plugin.", ex);
}
}
}

0 comments on commit e78d168

Please sign in to comment.