Refactored PluginManager to accept load hints.
This commit is contained in:
41
Torch/Extensions/LinqExtensions.cs
Normal file
41
Torch/Extensions/LinqExtensions.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Torch
|
||||||
|
{
|
||||||
|
public static class LinqExtensions
|
||||||
|
{
|
||||||
|
public static IEnumerable<T> TSort<T>( this IEnumerable<T> source, Func<T, IEnumerable<T>> dependencies, bool throwOnCycle = false )
|
||||||
|
{
|
||||||
|
var sorted = new List<T>();
|
||||||
|
var visited = new HashSet<T>();
|
||||||
|
|
||||||
|
foreach( var item in source )
|
||||||
|
Visit( item, visited, sorted, dependencies, throwOnCycle );
|
||||||
|
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Visit<T>( T item, HashSet<T> visited, List<T> sorted, Func<T, IEnumerable<T>> dependencies, bool throwOnCycle )
|
||||||
|
{
|
||||||
|
if( !visited.Contains( item ) )
|
||||||
|
{
|
||||||
|
visited.Add( item );
|
||||||
|
|
||||||
|
var resolvedDependencies = dependencies(item);
|
||||||
|
if (resolvedDependencies != null)
|
||||||
|
{
|
||||||
|
foreach (var dep in resolvedDependencies)
|
||||||
|
Visit(dep, visited, sorted, dependencies, throwOnCycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
sorted.Add( item );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if( throwOnCycle && !sorted.Contains( item ) )
|
||||||
|
throw new Exception( "Cyclic dependency found" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
Torch/Plugins/PluginDependency.cs
Normal file
17
Torch/Plugins/PluginDependency.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Torch
|
||||||
|
{
|
||||||
|
public class PluginDependency
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A unique identifier for the plugin that identifies the dependency.
|
||||||
|
/// </summary>
|
||||||
|
public Guid Plugin { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The plugin minimum version. This must include a string in the format of #[.#[.#]] for update checking purposes.
|
||||||
|
/// </summary>
|
||||||
|
public string MinVersion { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@@ -25,10 +25,23 @@ namespace Torch.Managers
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public class PluginManager : Manager, IPluginManager
|
public class PluginManager : Manager, IPluginManager
|
||||||
{
|
{
|
||||||
|
private class PluginItem
|
||||||
|
{
|
||||||
|
public string Filename { get; set; }
|
||||||
|
public string Path { get; set; }
|
||||||
|
public PluginManifest Manifest { get; set; }
|
||||||
|
public bool IsZip { get; set; }
|
||||||
|
public List<PluginItem> ResolvedDependencies { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
private static Logger _log = LogManager.GetCurrentClassLogger();
|
private static Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
private const string MANIFEST_NAME = "manifest.xml";
|
private const string MANIFEST_NAME = "manifest.xml";
|
||||||
|
|
||||||
public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
|
public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
|
||||||
private readonly MtObservableSortedDictionary<Guid, ITorchPlugin> _plugins = new MtObservableSortedDictionary<Guid, ITorchPlugin>();
|
private readonly MtObservableSortedDictionary<Guid, ITorchPlugin> _plugins = new MtObservableSortedDictionary<Guid, ITorchPlugin>();
|
||||||
|
private CommandManager _mgr;
|
||||||
|
|
||||||
#pragma warning disable 649
|
#pragma warning disable 649
|
||||||
[Dependency]
|
[Dependency]
|
||||||
private ITorchSessionManager _sessionManager;
|
private ITorchSessionManager _sessionManager;
|
||||||
@@ -70,8 +83,6 @@ namespace Torch.Managers
|
|||||||
_sessionManager.SessionStateChanged += SessionManagerOnSessionStateChanged;
|
_sessionManager.SessionStateChanged += SessionManagerOnSessionStateChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommandManager _mgr;
|
|
||||||
|
|
||||||
private void SessionManagerOnSessionStateChanged(ITorchSession session, TorchSessionState newState)
|
private void SessionManagerOnSessionStateChanged(ITorchSession session, TorchSessionState newState)
|
||||||
{
|
{
|
||||||
_mgr = session.Managers.GetManager<CommandManager>();
|
_mgr = session.Managers.GetManager<CommandManager>();
|
||||||
@@ -108,18 +119,95 @@ namespace Torch.Managers
|
|||||||
|
|
||||||
public void LoadPlugins()
|
public void LoadPlugins()
|
||||||
{
|
{
|
||||||
bool firstLoad = Torch.Config.Plugins.Count == 0;
|
|
||||||
List<Guid> foundPlugins = new List<Guid>();
|
|
||||||
if (Torch.Config.ShouldUpdatePlugins)
|
|
||||||
DownloadPluginUpdates();
|
|
||||||
|
|
||||||
_log.Info("Loading plugins...");
|
_log.Info("Loading plugins...");
|
||||||
var pluginItems = Directory.EnumerateFiles(PluginDir, "*.zip").Union(Directory.EnumerateDirectories(PluginDir));
|
|
||||||
|
var pluginItems = GetLocalPlugins(PluginDir);
|
||||||
|
var pluginsToLoad = new List<PluginItem>();
|
||||||
|
foreach (var item in pluginItems)
|
||||||
|
{
|
||||||
|
var pluginItem = item;
|
||||||
|
if (!TryValidatePluginDependencies(pluginItems, ref pluginItem, out var missingPlugins))
|
||||||
|
{
|
||||||
|
// We have some missing dependencies.
|
||||||
|
// Future fix would be to download them, but instead for now let's
|
||||||
|
// just warn the user it's missing
|
||||||
|
foreach(var missingPlugin in missingPlugins)
|
||||||
|
_log.Warn($"{item.Manifest.Name} is missing dependency {missingPlugin}. Skipping plugin.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginsToLoad.Add(pluginItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download any plugin updates.
|
||||||
|
bool updatesGotten = DownloadPluginUpdates(pluginsToLoad);
|
||||||
|
|
||||||
|
if (updatesGotten)
|
||||||
|
{
|
||||||
|
// Resort the plugins just in case updates changed load hints.
|
||||||
|
pluginItems = GetLocalPlugins(PluginDir);
|
||||||
|
pluginsToLoad.Clear();
|
||||||
|
foreach (var item in pluginItems)
|
||||||
|
{
|
||||||
|
var pluginItem = item;
|
||||||
|
if (!TryValidatePluginDependencies(pluginItems, ref pluginItem, out var missingPlugins))
|
||||||
|
{
|
||||||
|
foreach (var missingPlugin in missingPlugins)
|
||||||
|
_log.Warn($"{item.Manifest.Name} is missing dependency {missingPlugin}. Skipping plugin.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginsToLoad.Add(pluginItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort based on dependencies.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pluginsToLoad = pluginsToLoad.TSort(item => item.ResolvedDependencies)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Actually load the plugins now.
|
||||||
|
foreach (var item in pluginsToLoad)
|
||||||
|
{
|
||||||
|
LoadPlugin(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// This will happen on cylic dependencies.
|
||||||
|
_log.Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PluginItem> GetLocalPlugins(string pluginDir)
|
||||||
|
{
|
||||||
|
var firstLoad = Torch.Config.Plugins.Count == 0;
|
||||||
|
|
||||||
|
var pluginItems = Directory.EnumerateFiles(pluginDir, "*.zip")
|
||||||
|
.Union(Directory.EnumerateDirectories(PluginDir));
|
||||||
|
var results = new List<PluginItem>();
|
||||||
|
|
||||||
foreach (var item in pluginItems)
|
foreach (var item in pluginItems)
|
||||||
{
|
{
|
||||||
var path = Path.Combine(PluginDir, item);
|
var path = Path.Combine(PluginDir, item);
|
||||||
var isZip = item.EndsWith(".zip", StringComparison.CurrentCultureIgnoreCase);
|
var isZip = item.EndsWith(".zip", StringComparison.CurrentCultureIgnoreCase);
|
||||||
var manifest = isZip ? GetManifestFromZip(path) : GetManifestFromDirectory(path);
|
var manifest = isZip ? GetManifestFromZip(path) : GetManifestFromDirectory(path);
|
||||||
|
|
||||||
|
if (manifest == null)
|
||||||
|
{
|
||||||
|
_log.Warn($"Item '{item}' is missing a manifest, skipping.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var duplicatePlugin = results.FirstOrDefault(r => r.Manifest.Guid == manifest.Guid);
|
||||||
|
if (duplicatePlugin != null)
|
||||||
|
{
|
||||||
|
_log.Warn(
|
||||||
|
$"The GUID provided by {manifest.Name} ({item}) is already in use by {duplicatePlugin.Manifest.Name}.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!Torch.Config.LocalPlugins)
|
if (!Torch.Config.LocalPlugins)
|
||||||
{
|
{
|
||||||
if (isZip && !Torch.Config.Plugins.Contains(manifest.Guid))
|
if (isZip && !Torch.Config.Plugins.Contains(manifest.Guid))
|
||||||
@@ -132,126 +220,39 @@ namespace Torch.Managers
|
|||||||
_log.Info($"First-time load: Plugin {manifest.Name} added to torch.cfg.");
|
_log.Info($"First-time load: Plugin {manifest.Name} added to torch.cfg.");
|
||||||
Torch.Config.Plugins.Add(manifest.Guid);
|
Torch.Config.Plugins.Add(manifest.Guid);
|
||||||
}
|
}
|
||||||
if(isZip)
|
|
||||||
foundPlugins.Add(manifest.Guid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadPlugin(item);
|
results.Add(new PluginItem
|
||||||
}
|
|
||||||
if (!Torch.Config.LocalPlugins && firstLoad)
|
|
||||||
Torch.Config.Save();
|
|
||||||
|
|
||||||
if (!Torch.Config.LocalPlugins)
|
|
||||||
{
|
|
||||||
List<string> toLoad = new List<string>();
|
|
||||||
|
|
||||||
//This is actually the easiest way to batch process async tasks and block until completion (????)
|
|
||||||
Task.WhenAll(Torch.Config.Plugins.Select(async g =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (foundPlugins.Contains(g))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var item = await PluginQuery.Instance.QueryOne(g);
|
|
||||||
string s = Path.Combine(PluginDir, item.Name + ".zip");
|
|
||||||
await PluginQuery.Instance.DownloadPlugin(item, s);
|
|
||||||
lock (toLoad)
|
|
||||||
toLoad.Add(s);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_log.Error(ex);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
foreach (var l in toLoad)
|
|
||||||
{
|
{
|
||||||
LoadPlugin(l);
|
Filename = item,
|
||||||
}
|
IsZip = isZip,
|
||||||
|
Manifest = manifest,
|
||||||
|
Path = path
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//just reuse the list from earlier
|
return results;
|
||||||
foundPlugins.Clear();
|
|
||||||
foreach (var plugin in _plugins.Values)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
plugin.Init(Torch);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_log.Error(e, $"Plugin {plugin.Name} threw an exception during init! Unloading plugin!");
|
|
||||||
foundPlugins.Add(plugin.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var id in foundPlugins)
|
|
||||||
{
|
|
||||||
var p = _plugins[id];
|
|
||||||
_plugins.Remove(id);
|
|
||||||
_mgr.UnregisterPluginCommands(p);
|
|
||||||
p.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_log.Info($"Loaded {_plugins.Count} plugins.");
|
|
||||||
PluginsLoaded?.Invoke(_plugins.Values.AsReadOnly());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadPlugin(string item)
|
private bool DownloadPluginUpdates(List<PluginItem> plugins)
|
||||||
{
|
|
||||||
var path = Path.Combine(PluginDir, item);
|
|
||||||
var isZip = item.EndsWith(".zip", StringComparison.CurrentCultureIgnoreCase);
|
|
||||||
var manifest = isZip ? GetManifestFromZip(path) : GetManifestFromDirectory(path);
|
|
||||||
if (manifest == null)
|
|
||||||
{
|
|
||||||
_log.Warn($"Item '{item}' is missing a manifest, skipping.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_plugins.ContainsKey(manifest.Guid))
|
|
||||||
{
|
|
||||||
_log.Error($"The GUID provided by {manifest.Name} ({item}) is already in use by {_plugins[manifest.Guid].Name}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isZip)
|
|
||||||
LoadPluginFromZip(path);
|
|
||||||
else
|
|
||||||
LoadPluginFromFolder(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DownloadPluginUpdates()
|
|
||||||
{
|
{
|
||||||
_log.Info("Checking for plugin updates...");
|
_log.Info("Checking for plugin updates...");
|
||||||
var count = 0;
|
var count = 0;
|
||||||
var pluginItems = Directory.EnumerateFiles(PluginDir, "*.zip");
|
Task.WhenAll(plugins.Select(async item =>
|
||||||
Task.WhenAll(pluginItems.Select(async item =>
|
|
||||||
{
|
{
|
||||||
PluginManifest manifest = null;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var path = Path.Combine(PluginDir, item);
|
if (!item.IsZip)
|
||||||
var isZip = item.EndsWith(".zip", StringComparison.CurrentCultureIgnoreCase);
|
|
||||||
if (!isZip)
|
|
||||||
{
|
{
|
||||||
_log.Warn($"Unzipped plugins cannot be auto-updated. Skipping plugin {item}");
|
_log.Warn($"Unzipped plugins cannot be auto-updated. Skipping plugin {item}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
manifest = GetManifestFromZip(path);
|
item.Manifest.Version.TryExtractVersion(out Version currentVersion);
|
||||||
if (manifest == null)
|
var latest = await PluginQuery.Instance.QueryOne(item.Manifest.Guid);
|
||||||
{
|
|
||||||
_log.Warn($"Item '{item}' is missing a manifest, skipping update check.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest.Version.TryExtractVersion(out Version currentVersion);
|
|
||||||
var latest = await PluginQuery.Instance.QueryOne(manifest.Guid);
|
|
||||||
|
|
||||||
if (latest?.LatestVersion == null)
|
if (latest?.LatestVersion == null)
|
||||||
{
|
{
|
||||||
_log.Warn($"Plugin {manifest.Name} does not have any releases on torchapi.net. Cannot update.");
|
_log.Warn($"Plugin {item.Manifest.Name} does not have any releases on torchapi.net. Cannot update.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,113 +260,105 @@ namespace Torch.Managers
|
|||||||
|
|
||||||
if (currentVersion == null || newVersion == null)
|
if (currentVersion == null || newVersion == null)
|
||||||
{
|
{
|
||||||
_log.Error($"Error parsing version from manifest or website for plugin '{manifest.Name}.'");
|
_log.Error($"Error parsing version from manifest or website for plugin '{item.Manifest.Name}.'");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newVersion <= currentVersion)
|
if (newVersion <= currentVersion)
|
||||||
{
|
{
|
||||||
_log.Debug($"{manifest.Name} {manifest.Version} is up to date.");
|
_log.Debug($"{item.Manifest.Name} {item.Manifest.Version} is up to date.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_log.Info($"Updating plugin '{manifest.Name}' from {currentVersion} to {newVersion}.");
|
_log.Info($"Updating plugin '{item.Manifest.Name}' from {currentVersion} to {newVersion}.");
|
||||||
await PluginQuery.Instance.DownloadPlugin(latest, path);
|
await PluginQuery.Instance.DownloadPlugin(latest, item.Path);
|
||||||
Interlocked.Increment(ref count);
|
Interlocked.Increment(ref count);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
_log.Warn($"An error occurred updating the plugin {manifest?.Name ?? item}.");
|
_log.Warn($"An error occurred updating the plugin {item.Manifest.Name}.");
|
||||||
_log.Warn(e);
|
_log.Warn(e);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
_log.Info($"Updated {count} plugins.");
|
_log.Info($"Updated {count} plugins.");
|
||||||
|
return count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadPluginFromFolder(string directory)
|
private void LoadPlugin(PluginItem item)
|
||||||
{
|
{
|
||||||
var assemblies = new List<Assembly>();
|
var assemblies = new List<Assembly>();
|
||||||
var files = Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories).ToList();
|
|
||||||
|
|
||||||
var manifest = GetManifestFromDirectory(directory);
|
if (item.IsZip)
|
||||||
if (manifest == null)
|
|
||||||
{
|
{
|
||||||
_log.Warn($"Directory {directory} is missing a manifest, skipping load.");
|
using (var zipFile = ZipFile.OpenRead(item.Path))
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var file in files)
|
|
||||||
{
|
|
||||||
if (!file.EndsWith(".dll", StringComparison.CurrentCultureIgnoreCase))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
using (var stream = File.OpenRead(file))
|
|
||||||
{
|
{
|
||||||
var data = stream.ReadToEnd();
|
foreach (var entry in zipFile.Entries)
|
||||||
byte[] symbol = null;
|
|
||||||
var symbolPath = Path.Combine(Path.GetDirectoryName(file) ?? ".",
|
|
||||||
Path.GetFileNameWithoutExtension(file) + ".pdb");
|
|
||||||
if (File.Exists(symbolPath))
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (var symbolStream = File.OpenRead(symbolPath))
|
|
||||||
symbol = symbolStream.ReadToEnd();
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_log.Warn(e, $"Failed to read debugging symbols from {symbolPath}");
|
|
||||||
}
|
|
||||||
assemblies.Add(symbol != null ? Assembly.Load(data, symbol) : Assembly.Load(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RegisterAllAssemblies(assemblies);
|
|
||||||
InstantiatePlugin(manifest, assemblies);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadPluginFromZip(string path)
|
|
||||||
{
|
|
||||||
PluginManifest manifest;
|
|
||||||
var assemblies = new List<Assembly>();
|
|
||||||
using (var zipFile = ZipFile.OpenRead(path))
|
|
||||||
{
|
|
||||||
manifest = GetManifestFromZip(path);
|
|
||||||
if (manifest == null)
|
|
||||||
{
|
|
||||||
_log.Warn($"Zip file {path} is missing a manifest, skipping.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var entry in zipFile.Entries)
|
|
||||||
{
|
|
||||||
if (!entry.Name.EndsWith(".dll", StringComparison.CurrentCultureIgnoreCase))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
|
|
||||||
using (var stream = entry.Open())
|
|
||||||
{
|
{
|
||||||
var data = stream.ReadToEnd((int)entry.Length);
|
if (!entry.Name.EndsWith(".dll", StringComparison.CurrentCultureIgnoreCase))
|
||||||
byte[] symbol = null;
|
continue;
|
||||||
var symbolEntryName = entry.FullName.Substring(0, entry.FullName.Length - "dll".Length) + "pdb";
|
|
||||||
var symbolEntry = zipFile.GetEntry(symbolEntryName);
|
|
||||||
if (symbolEntry != null)
|
using (var stream = entry.Open())
|
||||||
try
|
{
|
||||||
{
|
var data = stream.ReadToEnd((int) entry.Length);
|
||||||
using (var symbolStream = symbolEntry.Open())
|
byte[] symbol = null;
|
||||||
symbol = symbolStream.ReadToEnd((int)symbolEntry.Length);
|
var symbolEntryName =
|
||||||
}
|
entry.FullName.Substring(0, entry.FullName.Length - "dll".Length) + "pdb";
|
||||||
catch (Exception e)
|
var symbolEntry = zipFile.GetEntry(symbolEntryName);
|
||||||
{
|
if (symbolEntry != null)
|
||||||
_log.Warn(e, $"Failed to read debugging symbols from {path}:{symbolEntryName}");
|
try
|
||||||
}
|
{
|
||||||
assemblies.Add(symbol != null ? Assembly.Load(data, symbol) : Assembly.Load(data));
|
using (var symbolStream = symbolEntry.Open())
|
||||||
|
symbol = symbolStream.ReadToEnd((int) symbolEntry.Length);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.Warn(e, $"Failed to read debugging symbols from {item.Filename}:{symbolEntryName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
assemblies.Add(symbol != null ? Assembly.Load(data, symbol) : Assembly.Load(data));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var files = Directory
|
||||||
|
.EnumerateFiles(item.Path, "*.*", SearchOption.AllDirectories)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
if (!file.EndsWith(".dll", StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
using (var stream = File.OpenRead(file))
|
||||||
|
{
|
||||||
|
var data = stream.ReadToEnd();
|
||||||
|
byte[] symbol = null;
|
||||||
|
var symbolPath = Path.Combine(Path.GetDirectoryName(file) ?? ".",
|
||||||
|
Path.GetFileNameWithoutExtension(file) + ".pdb");
|
||||||
|
if (File.Exists(symbolPath))
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var symbolStream = File.OpenRead(symbolPath))
|
||||||
|
symbol = symbolStream.ReadToEnd();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.Warn(e, $"Failed to read debugging symbols from {symbolPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
assemblies.Add(symbol != null ? Assembly.Load(data, symbol) : Assembly.Load(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
RegisterAllAssemblies(assemblies);
|
RegisterAllAssemblies(assemblies);
|
||||||
InstantiatePlugin(manifest, assemblies);
|
InstantiatePlugin(item.Manifest, assemblies);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegisterAllAssemblies(IReadOnlyCollection<Assembly> assemblies)
|
private void RegisterAllAssemblies(IReadOnlyCollection<Assembly> assemblies)
|
||||||
@@ -401,32 +394,6 @@ namespace Torch.Managers
|
|||||||
return a.Name == b.Name && a.Version.Major == b.Version.Major && a.Version.Minor == b.Version.Minor;
|
return a.Name == b.Name && a.Version.Major == b.Version.Major && a.Version.Minor == b.Version.Minor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private PluginManifest GetManifestFromZip(string path)
|
|
||||||
{
|
|
||||||
using (var zipFile = ZipFile.OpenRead(path))
|
|
||||||
{
|
|
||||||
foreach (var entry in zipFile.Entries)
|
|
||||||
{
|
|
||||||
if (!entry.Name.Equals(MANIFEST_NAME, StringComparison.CurrentCultureIgnoreCase))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
using (var stream = new StreamReader(entry.Open()))
|
|
||||||
{
|
|
||||||
return PluginManifest.Load(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PluginManifest GetManifestFromDirectory(string directory)
|
|
||||||
{
|
|
||||||
var path = Path.Combine(directory, MANIFEST_NAME);
|
|
||||||
return !File.Exists(path) ? null : PluginManifest.Load(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InstantiatePlugin(PluginManifest manifest, IEnumerable<Assembly> assemblies)
|
private void InstantiatePlugin(PluginManifest manifest, IEnumerable<Assembly> assemblies)
|
||||||
{
|
{
|
||||||
Type pluginType = null;
|
Type pluginType = null;
|
||||||
@@ -496,6 +463,74 @@ namespace Torch.Managers
|
|||||||
_plugins.Add(manifest.Guid, plugin);
|
_plugins.Add(manifest.Guid, plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PluginManifest GetManifestFromZip(string path)
|
||||||
|
{
|
||||||
|
using (var zipFile = ZipFile.OpenRead(path))
|
||||||
|
{
|
||||||
|
foreach (var entry in zipFile.Entries)
|
||||||
|
{
|
||||||
|
if (!entry.Name.Equals(MANIFEST_NAME, StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
using (var stream = new StreamReader(entry.Open()))
|
||||||
|
{
|
||||||
|
return PluginManifest.Load(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryValidatePluginDependencies(List<PluginItem> items, ref PluginItem item, out List<Guid> missingDependencies)
|
||||||
|
{
|
||||||
|
var dependencies = new List<PluginItem>();
|
||||||
|
missingDependencies = new List<Guid>();
|
||||||
|
|
||||||
|
foreach (var pluginDependency in item.Manifest.Dependencies)
|
||||||
|
{
|
||||||
|
var dependency = items
|
||||||
|
.FirstOrDefault(pi => pi?.Manifest.Guid == pluginDependency.Plugin);
|
||||||
|
if (dependency == null)
|
||||||
|
{
|
||||||
|
missingDependencies.Add(pluginDependency.Plugin);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(pluginDependency.MinVersion)
|
||||||
|
&& dependency.Manifest.Version.TryExtractVersion(out var dependencyVersion)
|
||||||
|
&& pluginDependency.MinVersion.TryExtractVersion(out var minVersion))
|
||||||
|
{
|
||||||
|
// really only care about version if it is defined.
|
||||||
|
if (dependencyVersion < minVersion)
|
||||||
|
{
|
||||||
|
// If dependency version is too low, we can try to update. Otherwise
|
||||||
|
// it's a missing dependency.
|
||||||
|
|
||||||
|
// For now let's just warn the user. bitMuse is lazy.
|
||||||
|
_log.Warn($"{dependency.Manifest.Name} is below the requested version for {item.Manifest.Name}."
|
||||||
|
+ Environment.NewLine
|
||||||
|
+ $" Desired version: {pluginDependency.MinVersion}, Available version: {dependency.Manifest.Version}");
|
||||||
|
missingDependencies.Add(pluginDependency.Plugin);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies.Add(dependency);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.ResolvedDependencies = dependencies;
|
||||||
|
if (missingDependencies.Count > 0)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PluginManifest GetManifestFromDirectory(string directory)
|
||||||
|
{
|
||||||
|
var path = Path.Combine(directory, MANIFEST_NAME);
|
||||||
|
return !File.Exists(path) ? null : PluginManifest.Load(path);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="IEnumerable.GetEnumerator"/>
|
/// <inheritdoc cref="IEnumerable.GetEnumerator"/>
|
||||||
public IEnumerator<ITorchPlugin> GetEnumerator()
|
public IEnumerator<ITorchPlugin> GetEnumerator()
|
||||||
{
|
{
|
||||||
|
@@ -37,7 +37,7 @@ namespace Torch
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A list of dependent plugin repositories. This may be updated to include GUIDs in the future.
|
/// A list of dependent plugin repositories. This may be updated to include GUIDs in the future.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public List<string> Dependencies { get; } = new List<string>();
|
public List<PluginDependency> Dependencies { get; } = new List<PluginDependency>();
|
||||||
|
|
||||||
public void Save(string path)
|
public void Save(string path)
|
||||||
{
|
{
|
||||||
|
@@ -168,9 +168,12 @@
|
|||||||
<HintPath>..\GameBinaries\VRage.OpenVRWrapper.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.OpenVRWrapper.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="VRage.Platform.Windows, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
<Reference Include="VRage.Platform.Windows, Culture=neutral, PublicKeyToken=null">
|
||||||
<HintPath>..\bin\x64\Release\DedicatedServer64\VRage.Platform.Windows.dll</HintPath>
|
<HintPath>..\bin\x64\Release\DedicatedServer64\VRage.Platform.Windows.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="VRage.Platform.Windows, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
|
||||||
|
<HintPath>..\GameBinaries\VRage.Platform.Windows.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="VRage.Render">
|
<Reference Include="VRage.Render">
|
||||||
<HintPath>..\GameBinaries\VRage.Render.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Render.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
@@ -203,6 +206,7 @@
|
|||||||
<Compile Include="Collections\TransformEnumerator.cs" />
|
<Compile Include="Collections\TransformEnumerator.cs" />
|
||||||
<Compile Include="Commands\ConsoleCommandContext.cs" />
|
<Compile Include="Commands\ConsoleCommandContext.cs" />
|
||||||
<Compile Include="Event\EventShimAttribute.cs" />
|
<Compile Include="Event\EventShimAttribute.cs" />
|
||||||
|
<Compile Include="Extensions\LinqExtensions.cs" />
|
||||||
<Compile Include="Managers\ChatManager\ChatManagerClient.cs" />
|
<Compile Include="Managers\ChatManager\ChatManagerClient.cs" />
|
||||||
<Compile Include="Managers\ChatManager\ChatManagerServer.cs" />
|
<Compile Include="Managers\ChatManager\ChatManagerServer.cs" />
|
||||||
<Compile Include="Extensions\DispatcherExtensions.cs" />
|
<Compile Include="Extensions\DispatcherExtensions.cs" />
|
||||||
@@ -242,6 +246,7 @@
|
|||||||
<Compile Include="Patches\PhysicsMemoryPatch.cs" />
|
<Compile Include="Patches\PhysicsMemoryPatch.cs" />
|
||||||
<Compile Include="Patches\SessionDownloadPatch.cs" />
|
<Compile Include="Patches\SessionDownloadPatch.cs" />
|
||||||
<Compile Include="Patches\TorchAsyncSaving.cs" />
|
<Compile Include="Patches\TorchAsyncSaving.cs" />
|
||||||
|
<Compile Include="Plugins\PluginDependency.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="Collections\KeyTree.cs" />
|
<Compile Include="Collections\KeyTree.cs" />
|
||||||
<Compile Include="Collections\RollingAverage.cs" />
|
<Compile Include="Collections\RollingAverage.cs" />
|
||||||
|
Reference in New Issue
Block a user