allow Kekno to run with sv_running not returning anything :upside_down:

make sure script plugins output correct errors instead of being swallowed
prevent webfront error when webfront tab is left open on a server no longer being modified
This commit is contained in:
RaidMax 2020-02-01 12:27:14 -06:00
parent c6d6bebeab
commit 06cdaef8a4
9 changed files with 131 additions and 146 deletions

View File

@ -5,7 +5,7 @@
<TargetFramework>netcoreapp3.1</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish> <MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<PackageId>RaidMax.IW4MAdmin.Application</PackageId> <PackageId>RaidMax.IW4MAdmin.Application</PackageId>
<Version>2.3.1.0</Version> <Version>2.3.2.0</Version>
<Authors>RaidMax</Authors> <Authors>RaidMax</Authors>
<Company>Forever None</Company> <Company>Forever None</Company>
<Product>IW4MAdmin</Product> <Product>IW4MAdmin</Product>

View File

@ -256,9 +256,25 @@ namespace IW4MAdmin.Application
if (plugin is ScriptPlugin scriptPlugin) if (plugin is ScriptPlugin scriptPlugin)
{ {
await scriptPlugin.Initialize(this); await scriptPlugin.Initialize(this);
scriptPlugin.Watcher.Changed += async (sender, e) =>
{
try
{
await scriptPlugin.Initialize(this);
}
catch (Exception ex)
{
Logger.WriteError(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"].FormatExt(scriptPlugin.Name));
Logger.WriteDebug(ex.Message);
}
};
} }
await plugin.OnLoadAsync(this); else
{
await plugin.OnLoadAsync(this);
}
} }
catch (Exception ex) catch (Exception ex)

View File

@ -881,9 +881,9 @@ namespace IW4MAdmin
Version = RconParser.Version; Version = RconParser.Version;
} }
var svRunning = await this.GetDvarAsync<int>("sv_running"); var svRunning = await this.GetDvarAsync<string>("sv_running");
if (svRunning.Value == 0) if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1")
{ {
throw new ServerException(loc["SERVER_ERROR_NOT_RUNNING"]); throw new ServerException(loc["SERVER_ERROR_NOT_RUNNING"]);
} }

View File

@ -8,6 +8,7 @@ using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Helpers; using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@ -68,30 +69,9 @@ namespace IW4MAdmin.Application
try try
{ {
var services = ConfigureServices(); var services = ConfigureServices();
using (var builder = services.BuildServiceProvider())
{
translationLookup = builder.GetRequiredService<ITranslationLookup>();
var importer = builder.GetRequiredService<IPluginImporter>();
importer.Load();
foreach (var type in importer.CommandTypes)
{
services.AddTransient(typeof(IManagerCommand), type);
}
foreach (var commandDefinition in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes()
.Where(_command => _command.BaseType == typeof(Command)))
{
services.AddTransient(typeof(IManagerCommand), commandDefinition);
}
}
serviceProvider = services.BuildServiceProvider(); serviceProvider = services.BuildServiceProvider();
var pluginImporter = serviceProvider.GetRequiredService<IPluginImporter>();
pluginImporter.Load();
ServerManager = (ApplicationManager)serviceProvider.GetRequiredService<IManager>(); ServerManager = (ApplicationManager)serviceProvider.GetRequiredService<IManager>();
translationLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
// do any needed housekeeping file/folder migrations // do any needed housekeeping file/folder migrations
ConfigurationMigration.MoveConfigFolder10518(null); ConfigurationMigration.MoveConfigFolder10518(null);
@ -283,8 +263,8 @@ namespace IW4MAdmin.Application
/// </summary> /// </summary>
private static IServiceCollection ConfigureServices() private static IServiceCollection ConfigureServices()
{ {
var serviceProvider = new ServiceCollection(); var serviceCollection = new ServiceCollection();
serviceProvider.AddSingleton<IManager, ApplicationManager>() serviceCollection.AddSingleton<IServiceCollection>(_serviceProvider => serviceCollection)
.AddSingleton(new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings") as IConfigurationHandler<ApplicationConfiguration>) .AddSingleton(new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings") as IConfigurationHandler<ApplicationConfiguration>)
.AddSingleton(new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration") as IConfigurationHandler<CommandConfiguration>) .AddSingleton(new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration") as IConfigurationHandler<CommandConfiguration>)
.AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration()) .AddSingleton(_serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration())
@ -292,14 +272,27 @@ namespace IW4MAdmin.Application
.AddSingleton<ILogger>(_serviceProvider => new Logger("IW4MAdmin-Manager")) .AddSingleton<ILogger>(_serviceProvider => new Logger("IW4MAdmin-Manager"))
.AddSingleton<IPluginImporter, PluginImporter>() .AddSingleton<IPluginImporter, PluginImporter>()
.AddSingleton<IMiddlewareActionHandler, MiddlewareActionHandler>() .AddSingleton<IMiddlewareActionHandler, MiddlewareActionHandler>()
.AddTransient(_serviceProvider =>
{
var importer = _serviceProvider.GetRequiredService<IPluginImporter>();
var config = _serviceProvider.GetRequiredService<CommandConfiguration>();
var layout = _serviceProvider.GetRequiredService<ITranslationLookup>();
// todo: this is disgusting, but I need it until I can figure out a way to dynamically load the plugins without creating an instance.
return importer.CommandTypes.
Union(typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes()
.Where(_command => _command.BaseType == typeof(Command)))
.Select(_cmdType => Activator.CreateInstance(_cmdType, config, layout) as IManagerCommand);
})
.AddSingleton(_serviceProvider => .AddSingleton(_serviceProvider =>
{ {
var config = _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration(); var config = _serviceProvider.GetRequiredService<IConfigurationHandler<ApplicationConfiguration>>().Configuration();
return Localization.Configure.Initialize(useLocalTranslation: config?.UseLocalTranslations ?? false, return Localization.Configure.Initialize(useLocalTranslation: config?.UseLocalTranslations ?? false,
customLocale: config?.EnableCustomLocale ?? false ? (config.CustomLocale ?? "en-US") : "en-US"); customLocale: config?.EnableCustomLocale ?? false ? (config.CustomLocale ?? "en-US") : "en-US");
}); })
.AddSingleton<IManager, ApplicationManager>();
return serviceProvider; return serviceCollection;
} }
} }
} }

View File

@ -22,12 +22,14 @@ namespace IW4MAdmin.Application.Helpers
{ {
_logger = logger; _logger = logger;
_translationLookup = translationLookup; _translationLookup = translationLookup;
Load();
} }
/// <summary> /// <summary>
/// Loads all the assembly and javascript plugins /// Loads all the assembly and javascript plugins
/// </summary> /// </summary>
public void Load() private void Load()
{ {
string pluginDir = $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}"; string pluginDir = $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}";
string[] dllFileNames = null; string[] dllFileNames = null;

View File

@ -1315,7 +1315,7 @@ namespace SharedLibraryCore.Commands
/// <summary> /// <summary>
/// Lists the loaded plugins /// Lists the loaded plugins
/// </summary> /// </summary>
public class ListPluginsCommand : Command /*public class ListPluginsCommand : Command
{ {
private readonly IPluginImporter _pluginImporter; private readonly IPluginImporter _pluginImporter;
public ListPluginsCommand(CommandConfiguration config, ITranslationLookup translationLookup, IPluginImporter pluginImporter) : base(config, translationLookup) public ListPluginsCommand(CommandConfiguration config, ITranslationLookup translationLookup, IPluginImporter pluginImporter) : base(config, translationLookup)
@ -1337,7 +1337,7 @@ namespace SharedLibraryCore.Commands
} }
return Task.CompletedTask; return Task.CompletedTask;
} }
} }*/
/// <summary> /// <summary>
/// Lists external IP /// Lists external IP

View File

@ -28,10 +28,5 @@ namespace SharedLibraryCore.Interfaces
/// All assemblies in the plugin folder /// All assemblies in the plugin folder
/// </summary> /// </summary>
IList<Assembly> Assemblies { get; } IList<Assembly> Assemblies { get; }
/// <summary>
/// Loads in plugin assemblies and script plugins
/// </summary>
void Load();
} }
} }

View File

@ -18,46 +18,110 @@ namespace SharedLibraryCore
public string Author { get; set; } public string Author { get; set; }
public FileSystemWatcher Watcher { get; private set; }
private Engine ScriptEngine; private Engine ScriptEngine;
private readonly string FileName; private readonly string _fileName;
private IManager Manager;
private readonly FileSystemWatcher _watcher;
private readonly SemaphoreSlim _fileChanging; private readonly SemaphoreSlim _fileChanging;
private bool successfullyLoaded; private bool successfullyLoaded;
public ScriptPlugin(string fileName) public ScriptPlugin(string filename)
{ {
FileName = fileName; _fileName = filename;
_fileChanging = new SemaphoreSlim(1, 1); Watcher = new FileSystemWatcher()
_watcher = new FileSystemWatcher()
{ {
Path = $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}", Path = $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}",
NotifyFilter = NotifyFilters.Size, NotifyFilter = NotifyFilters.Size,
Filter = fileName.Split(Path.DirectorySeparatorChar).Last() Filter = _fileName.Split(Path.DirectorySeparatorChar).Last()
}; };
_watcher.Changed += Watcher_Changed; Watcher.EnableRaisingEvents = true;
_watcher.EnableRaisingEvents = true; _fileChanging = new SemaphoreSlim(1, 1);
} }
~ScriptPlugin() ~ScriptPlugin()
{ {
_watcher.Dispose(); Watcher.Dispose();
_fileChanging.Dispose(); _fileChanging.Dispose();
} }
private async void Watcher_Changed(object sender, FileSystemEventArgs e)
public async Task Initialize(IManager manager)
{ {
await _fileChanging.WaitAsync();
try try
{ {
await _fileChanging.WaitAsync(); // for some reason we get an event trigger when the file is not finished being modified.
await Initialize(Manager); // this must have been a change in .NET CORE 3.x
// so if the new file is empty we can't process it yet
if (new FileInfo(_fileName).Length == 0L)
{
return;
}
bool firstRun = ScriptEngine == null;
// it's been loaded before so we need to call the unload event
if (!firstRun)
{
await OnUnloadAsync();
}
successfullyLoaded = false;
string script;
using (var stream = new FileStream(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (var reader = new StreamReader(stream, Encoding.Default))
{
script = await reader.ReadToEndAsync();
}
}
ScriptEngine = new Engine(cfg =>
cfg.AllowClr(new[]
{
typeof(System.Net.Http.HttpClient).Assembly,
typeof(EFClient).Assembly,
typeof(Utilities).Assembly,
typeof(Encoding).Assembly
})
.CatchClrExceptions());
ScriptEngine.Execute(script);
ScriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
dynamic pluginObject = ScriptEngine.GetValue("plugin").ToObject();
Author = pluginObject.author;
Name = pluginObject.name;
Version = (float)pluginObject.version;
try
{
if (pluginObject.isParser)
{
await OnLoadAsync(manager);
IEventParser eventParser = (IEventParser)ScriptEngine.GetValue("eventParser").ToObject();
IRConParser rconParser = (IRConParser)ScriptEngine.GetValue("rconParser").ToObject();
manager.AdditionalEventParsers.Add(eventParser);
manager.AdditionalRConParsers.Add(rconParser);
}
}
catch { }
if (!firstRun)
{
await OnLoadAsync(manager);
}
successfullyLoaded = true;
} }
catch (Exception ex) catch
{ {
Manager.GetLogger(0).WriteError(Utilities.CurrentLocalization.LocalizationIndex["PLUGIN_IMPORTER_ERROR"].FormatExt(Name)); throw;
Manager.GetLogger(0).WriteDebug(ex.Message);
} }
finally finally
@ -69,104 +133,20 @@ namespace SharedLibraryCore
} }
} }
public async Task Initialize(IManager mgr)
{
// for some reason we get an event trigger when the file is not finished being modified.
// this must have been a change in .NET CORE 3.x
// so if the new file is empty we can't process it yet
if (new FileInfo(FileName).Length == 0L)
{
return;
}
bool firstRun = ScriptEngine == null;
// it's been loaded before so we need to call the unload event
if (!firstRun)
{
await OnUnloadAsync();
}
successfullyLoaded = false;
Manager = mgr;
string script;
using (var stream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (var reader = new StreamReader(stream, Encoding.Default))
{
script = await reader.ReadToEndAsync();
}
}
ScriptEngine = new Engine(cfg =>
cfg.AllowClr(new[]
{
typeof(System.Net.Http.HttpClient).Assembly,
typeof(EFClient).Assembly,
typeof(Utilities).Assembly,
typeof(Encoding).Assembly
})
.CatchClrExceptions());
ScriptEngine.Execute(script);
ScriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
dynamic pluginObject = ScriptEngine.GetValue("plugin").ToObject();
Author = pluginObject.author;
Name = pluginObject.name;
Version = (float)pluginObject.version;
try
{
if(pluginObject.isParser)
{
await OnLoadAsync(mgr);
IEventParser eventParser = (IEventParser)ScriptEngine.GetValue("eventParser").ToObject();
IRConParser rconParser = (IRConParser)ScriptEngine.GetValue("rconParser").ToObject();
Manager.AdditionalEventParsers.Add(eventParser);
Manager.AdditionalRConParsers.Add(rconParser);
}
}
catch { }
if (!firstRun)
{
await OnLoadAsync(mgr);
}
successfullyLoaded = true;
}
public async Task OnEventAsync(GameEvent E, Server S) public async Task OnEventAsync(GameEvent E, Server S)
{ {
if (successfullyLoaded) if (successfullyLoaded)
{ {
try ScriptEngine.SetValue("_gameEvent", E);
{ ScriptEngine.SetValue("_server", S);
await _fileChanging.WaitAsync(); ScriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient(S));
ScriptEngine.SetValue("_gameEvent", E); await Task.FromResult(ScriptEngine.Execute("plugin.onEventAsync(_gameEvent, _server)").GetCompletionValue());
ScriptEngine.SetValue("_server", S);
ScriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient(S));
ScriptEngine.Execute("plugin.onEventAsync(_gameEvent, _server)").GetCompletionValue();
}
catch { }
finally
{
if (_fileChanging.CurrentCount == 0)
{
_fileChanging.Release(1);
}
}
} }
} }
public Task OnLoadAsync(IManager manager) public Task OnLoadAsync(IManager manager)
{ {
Manager.GetLogger(0).WriteDebug($"OnLoad executing for {Name}"); manager.GetLogger(0).WriteDebug($"OnLoad executing for {Name}");
ScriptEngine.SetValue("_manager", manager); ScriptEngine.SetValue("_manager", manager);
return Task.FromResult(ScriptEngine.Execute("plugin.onLoadAsync(_manager)").GetCompletionValue()); return Task.FromResult(ScriptEngine.Execute("plugin.onLoadAsync(_manager)").GetCompletionValue());
} }
@ -181,7 +161,6 @@ namespace SharedLibraryCore
{ {
if (successfullyLoaded) if (successfullyLoaded)
{ {
Manager.GetLogger(0).WriteDebug($"OnUnLoad executing for {Name}");
await Task.FromResult(ScriptEngine.Execute("plugin.onUnloadAsync()").GetCompletionValue()); await Task.FromResult(ScriptEngine.Execute("plugin.onUnloadAsync()").GetCompletionValue());
} }
} }

View File

@ -21,7 +21,7 @@ namespace WebfrontCore.Controllers
if (s == null) if (s == null)
{ {
return View("Error", "Invalid server!"); return NotFound();
} }
var serverInfo = new ServerInfo() var serverInfo = new ServerInfo()