Refactor plugin loading. All active plugins must now be listed in torch.cfg
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Torch
|
namespace Torch
|
||||||
{
|
{
|
||||||
@@ -12,7 +13,8 @@ namespace Torch
|
|||||||
string InstancePath { get; set; }
|
string InstancePath { get; set; }
|
||||||
bool NoGui { get; set; }
|
bool NoGui { get; set; }
|
||||||
bool NoUpdate { 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 RestartOnCrash { get; set; }
|
||||||
bool ShouldUpdatePlugins { get; }
|
bool ShouldUpdatePlugins { get; }
|
||||||
bool ShouldUpdateTorch { get; }
|
bool ShouldUpdateTorch { get; }
|
||||||
|
@@ -69,6 +69,7 @@ quit";
|
|||||||
_config = InitConfig();
|
_config = InitConfig();
|
||||||
if (!_config.Parse(args))
|
if (!_config.Parse(args))
|
||||||
return false;
|
return false;
|
||||||
|
_config.Save();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_config.WaitForPID))
|
if (!string.IsNullOrEmpty(_config.WaitForPID))
|
||||||
{
|
{
|
||||||
|
@@ -59,7 +59,11 @@ namespace Torch.Server
|
|||||||
public int TickTimeout { get; set; } = 60;
|
public int TickTimeout { get; set; } = 60;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <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";
|
public string ChatName { get; set; } = "Server";
|
||||||
|
|
||||||
|
@@ -90,6 +90,7 @@ namespace Torch.Server.Views
|
|||||||
private void DownloadButton_OnClick(object sender, RoutedEventArgs e)
|
private void DownloadButton_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var item = CurrentItem;
|
var item = CurrentItem;
|
||||||
|
TorchBase.Instance.Config.Plugins.Add(new Guid(item.ID));
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var result = await PluginQuery.Instance.DownloadPlugin(item.ID);
|
var result = await PluginQuery.Instance.DownloadPlugin(item.ID);
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using NLog;
|
||||||
|
|
||||||
namespace Torch
|
namespace Torch
|
||||||
{
|
{
|
||||||
@@ -12,6 +13,7 @@ namespace Torch
|
|||||||
{
|
{
|
||||||
private readonly string _argPrefix;
|
private readonly string _argPrefix;
|
||||||
private readonly Dictionary<ArgAttribute, PropertyInfo> _args = new Dictionary<ArgAttribute, PropertyInfo>();
|
private readonly Dictionary<ArgAttribute, PropertyInfo> _args = new Dictionary<ArgAttribute, PropertyInfo>();
|
||||||
|
private readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
protected CommandLine(string argPrefix = "-")
|
protected CommandLine(string argPrefix = "-")
|
||||||
{
|
{
|
||||||
@@ -89,6 +91,24 @@ namespace Torch
|
|||||||
|
|
||||||
if (property.Value.PropertyType == typeof(string))
|
if (property.Value.PropertyType == typeof(string))
|
||||||
property.Value.SetValue(this, args[++i]);
|
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
|
catch
|
||||||
|
@@ -96,6 +96,8 @@ 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)
|
if (Torch.Config.ShouldUpdatePlugins)
|
||||||
DownloadPluginUpdates();
|
DownloadPluginUpdates();
|
||||||
|
|
||||||
@@ -106,22 +108,55 @@ namespace Torch.Managers
|
|||||||
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)
|
if (!Torch.Config.LocalPlugins)
|
||||||
{
|
{
|
||||||
_log.Warn($"Item '{item}' is missing a manifest, skipping.");
|
if (isZip && !Torch.Config.Plugins.Contains(manifest.Guid))
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_plugins.ContainsKey(manifest.Guid))
|
|
||||||
{
|
{
|
||||||
_log.Error($"The GUID provided by {manifest.Name} ({item}) is already in use by {_plugins[manifest.Guid].Name}");
|
if (!firstLoad)
|
||||||
continue;
|
{
|
||||||
|
_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)
|
if(isZip)
|
||||||
LoadPluginFromZip(path);
|
foundPlugins.Add(manifest.Guid);
|
||||||
else
|
}
|
||||||
LoadPluginFromFolder(path);
|
|
||||||
|
LoadPlugin(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
foreach (var plugin in _plugins.Values)
|
||||||
@@ -132,11 +167,34 @@ namespace Torch.Managers
|
|||||||
PluginsLoaded?.Invoke(_plugins.Values.AsReadOnly());
|
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()
|
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").Union(Directory.EnumerateDirectories(PluginDir));
|
var pluginItems = Directory.EnumerateFiles(PluginDir, "*.zip");
|
||||||
Parallel.ForEach(pluginItems, async item =>
|
Parallel.ForEach(pluginItems, async item =>
|
||||||
{
|
{
|
||||||
PluginManifest manifest = null;
|
PluginManifest manifest = null;
|
||||||
@@ -144,7 +202,12 @@ namespace Torch.Managers
|
|||||||
{
|
{
|
||||||
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);
|
||||||
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)
|
if (manifest == null)
|
||||||
{
|
{
|
||||||
_log.Warn($"Item '{item}' is missing a manifest, skipping update check.");
|
_log.Warn($"Item '{item}' is missing a manifest, skipping update check.");
|
||||||
@@ -188,48 +251,6 @@ namespace Torch.Managers
|
|||||||
_log.Info($"Updated {count} plugins.");
|
_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)
|
private void LoadPluginFromFolder(string directory)
|
||||||
{
|
{
|
||||||
var assemblies = new List<Assembly>();
|
var assemblies = new List<Assembly>();
|
||||||
|
Reference in New Issue
Block a user