Refactor plugin loading. All active plugins must now be listed in torch.cfg

This commit is contained in:
Brant Martin
2019-05-03 21:39:56 -04:00
parent c7651c9949
commit 9813d6946a
6 changed files with 109 additions and 60 deletions

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace Torch
{
@@ -12,7 +13,8 @@ namespace Torch
string InstancePath { get; set; }
bool NoGui { get; set; }
bool NoUpdate { get; set; }
List<string> Plugins { get; set; }
List<Guid> Plugins { get; set; }
bool LocalPlugins { get; set; }
bool RestartOnCrash { get; set; }
bool ShouldUpdatePlugins { get; }
bool ShouldUpdateTorch { get; }

View File

@@ -69,6 +69,7 @@ quit";
_config = InitConfig();
if (!_config.Parse(args))
return false;
_config.Save();
if (!string.IsNullOrEmpty(_config.WaitForPID))
{

View File

@@ -59,7 +59,11 @@ namespace Torch.Server
public int TickTimeout { get; set; } = 60;
/// <inheritdoc />
public List<string> Plugins { get; set; } = new List<string>();
[Arg("plugins", "Starts Torch with the given plugin GUIDs (space delimited).")]
public List<Guid> Plugins { get; set; } = new List<Guid>();
[Arg("localplugins", "Loads all pluhins from disk, ignores the plugins defined in config.")]
public bool LocalPlugins { get; set; }
public string ChatName { get; set; } = "Server";

View File

@@ -90,6 +90,7 @@ namespace Torch.Server.Views
private void DownloadButton_OnClick(object sender, RoutedEventArgs e)
{
var item = CurrentItem;
TorchBase.Instance.Config.Plugins.Add(new Guid(item.ID));
Task.Run(async () =>
{
var result = await PluginQuery.Instance.DownloadPlugin(item.ID);

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using NLog;
namespace Torch
{
@@ -12,6 +13,7 @@ namespace Torch
{
private readonly string _argPrefix;
private readonly Dictionary<ArgAttribute, PropertyInfo> _args = new Dictionary<ArgAttribute, PropertyInfo>();
private readonly Logger _log = LogManager.GetCurrentClassLogger();
protected CommandLine(string argPrefix = "-")
{
@@ -89,6 +91,24 @@ namespace Torch
if (property.Value.PropertyType == typeof(string))
property.Value.SetValue(this, args[++i]);
if (property.Value.PropertyType == typeof(List<Guid>))
{
i++;
var l = new List<Guid>(16);
while (i < args.Length && !args[i].StartsWith(_argPrefix))
{
if (Guid.TryParse(args[i], out Guid g))
{
l.Add(g);
_log.Info($"added plugin {g}");
}
else
_log.Warn($"Failed to parse GUID {args[i]}");
i++;
}
property.Value.SetValue(this, l);
}
}
}
catch

View File

@@ -96,6 +96,8 @@ namespace Torch.Managers
public void LoadPlugins()
{
bool firstLoad = Torch.Config.Plugins.Count == 0;
List<Guid> foundPlugins = new List<Guid>();
if (Torch.Config.ShouldUpdatePlugins)
DownloadPluginUpdates();
@@ -106,22 +108,55 @@ namespace Torch.Managers
var path = Path.Combine(PluginDir, item);
var isZip = item.EndsWith(".zip", StringComparison.CurrentCultureIgnoreCase);
var manifest = isZip ? GetManifestFromZip(path) : GetManifestFromDirectory(path);
if (manifest == null)
if (!Torch.Config.LocalPlugins)
{
_log.Warn($"Item '{item}' is missing a manifest, skipping.");
continue;
if (isZip && !Torch.Config.Plugins.Contains(manifest.Guid))
{
if (!firstLoad)
{
_log.Warn($"Plugin {manifest.Name} ({item}) exists in the plugin directory, but is not listed in torch.cfg. Skipping load!");
return;
}
_log.Info($"First-time load: Plugin {manifest.Name} added to torch.cfg.");
Torch.Config.Plugins.Add(manifest.Guid);
}
if(isZip)
foundPlugins.Add(manifest.Guid);
}
if (_plugins.ContainsKey(manifest.Guid))
{
_log.Error($"The GUID provided by {manifest.Name} ({item}) is already in use by {_plugins[manifest.Guid].Name}");
continue;
}
LoadPlugin(item);
}
if (isZip)
LoadPluginFromZip(path);
else
LoadPluginFromFolder(path);
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);
}
}
foreach (var plugin in _plugins.Values)
@@ -132,11 +167,34 @@ namespace Torch.Managers
PluginsLoaded?.Invoke(_plugins.Values.AsReadOnly());
}
private void LoadPlugin(string item)
{
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...");
var count = 0;
var pluginItems = Directory.EnumerateFiles(PluginDir, "*.zip").Union(Directory.EnumerateDirectories(PluginDir));
var pluginItems = Directory.EnumerateFiles(PluginDir, "*.zip");
Parallel.ForEach(pluginItems, async item =>
{
PluginManifest manifest = null;
@@ -144,7 +202,12 @@ namespace Torch.Managers
{
var path = Path.Combine(PluginDir, item);
var isZip = item.EndsWith(".zip", StringComparison.CurrentCultureIgnoreCase);
manifest = isZip ? GetManifestFromZip(path) : GetManifestFromDirectory(path);
if (!isZip)
{
_log.Warn($"Unzipped plugins cannot be auto-updated. Skipping plugin {item}");
return;
}
manifest = GetManifestFromZip(path);
if (manifest == null)
{
_log.Warn($"Item '{item}' is missing a manifest, skipping update check.");
@@ -188,48 +251,6 @@ namespace Torch.Managers
_log.Info($"Updated {count} plugins.");
}
private async Task<Tuple<Version, string>> GetLatestArchiveAsync(string repository)
{
try
{
//var split = repository.Split('/');
//var latest = await _gitClient.Repository.Release.GetLatest(split[0], split[1]).ConfigureAwait(false);
//if (!latest.TagName.TryExtractVersion(out Version latestVersion))
//{
// _log.Error($"Unable to parse version tag for the latest release of '{repository}.'");
//}
//var zipAsset = latest.Assets.FirstOrDefault(x => x.Name.Contains(".zip", StringComparison.CurrentCultureIgnoreCase));
//if (zipAsset == null)
//{
// _log.Error($"Unable to find archive for the latest release of '{repository}.'");
//}
//return new Tuple<Version, string>(latestVersion, zipAsset?.BrowserDownloadUrl);
return null;
}
catch (Exception e)
{
_log.Error($"Unable to get the latest release of '{repository}.'");
_log.Error(e);
return default(Tuple<Version, string>);
}
}
private Task UpdatePluginAsync(string localPath, string downloadUrl)
{
if (File.Exists(localPath))
File.Delete(localPath);
if (Directory.Exists(localPath))
Directory.Delete(localPath, true);
var fileName = downloadUrl.Split('/').Last();
var filePath = Path.Combine(PluginDir, fileName);
return new WebClient().DownloadFileTaskAsync(downloadUrl, filePath);
}
private void LoadPluginFromFolder(string directory)
{
var assemblies = new List<Assembly>();