1
0
mirror of https://github.com/RaidMax/IW4M-Admin.git synced 2025-06-11 07:40:54 -05:00

implement new eventing system

This commit is contained in:
RaidMax
2023-04-05 09:54:57 -05:00
parent 53a6ef2ec3
commit ebe69a94ad
39 changed files with 1410 additions and 526 deletions

View File

@ -21,7 +21,6 @@
<Win32Resource />
<RootNamespace>IW4MAdmin.Application</RootNamespace>
<PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>

View File

@ -16,6 +16,7 @@ using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
@ -23,12 +24,18 @@ using System.Threading;
using System.Threading.Tasks;
using Data.Abstractions;
using Data.Context;
using Data.Models;
using IW4MAdmin.Application.Configuration;
using IW4MAdmin.Application.Migration;
using IW4MAdmin.Application.Plugin.Script;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using SharedLibraryCore.Events;
using SharedLibraryCore.Events.Management;
using SharedLibraryCore.Events.Server;
using SharedLibraryCore.Formatting;
using SharedLibraryCore.Interfaces.Events;
using static SharedLibraryCore.GameEvent;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using ObsoleteLogger = SharedLibraryCore.Interfaces.ILogger;
@ -50,7 +57,7 @@ namespace IW4MAdmin.Application
public IList<Func<GameEvent, bool>> CommandInterceptors { get; set; } =
new List<Func<GameEvent, bool>>();
public ITokenAuthentication TokenAuthenticator { get; }
public CancellationToken CancellationToken => _tokenSource.Token;
public CancellationToken CancellationToken => _isRunningTokenSource.Token;
public string ExternalIPAddress { get; private set; }
public bool IsRestartRequested { get; private set; }
public IMiddlewareActionHandler MiddlewareActionHandler { get; }
@ -64,29 +71,30 @@ namespace IW4MAdmin.Application
public IConfigurationHandler<ApplicationConfiguration> ConfigHandler;
readonly IPageList PageList;
private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0);
private CancellationTokenSource _tokenSource;
private CancellationTokenSource _isRunningTokenSource;
private CancellationTokenSource _eventHandlerTokenSource;
private readonly Dictionary<string, Task<IList>> _operationLookup = new Dictionary<string, Task<IList>>();
private readonly ITranslationLookup _translationLookup;
private readonly IConfigurationHandler<CommandConfiguration> _commandConfiguration;
private readonly IGameServerInstanceFactory _serverInstanceFactory;
private readonly IParserRegexFactory _parserRegexFactory;
private readonly IEnumerable<IRegisterEvent> _customParserEvents;
private readonly IEventHandler _eventHandler;
private readonly ICoreEventHandler _coreEventHandler;
private readonly IScriptCommandFactory _scriptCommandFactory;
private readonly IMetaRegistration _metaRegistration;
private readonly IScriptPluginServiceResolver _scriptPluginServiceResolver;
private readonly IServiceProvider _serviceProvider;
private readonly ChangeHistoryService _changeHistoryService;
private readonly ApplicationConfiguration _appConfig;
public ConcurrentDictionary<long, GameEvent> ProcessingEvents { get; } = new ConcurrentDictionary<long, GameEvent>();
public ConcurrentDictionary<long, GameEvent> ProcessingEvents { get; } = new();
public ApplicationManager(ILogger<ApplicationManager> logger, IMiddlewareActionHandler actionHandler, IEnumerable<IManagerCommand> commands,
ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration,
IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory,
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents,
IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory,
ICoreEventHandler coreEventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory,
IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver, ClientService clientService, IServiceProvider serviceProvider,
ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService, IAlertManager alertManager, IInteractionRegistration interactionRegistration)
ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService, IAlertManager alertManager, IInteractionRegistration interactionRegistration, IEnumerable<IPluginV2> v2PLugins)
{
MiddlewareActionHandler = actionHandler;
_servers = new ConcurrentBag<Server>();
@ -101,14 +109,14 @@ namespace IW4MAdmin.Application
AdditionalRConParsers = new List<IRConParser> { new BaseRConParser(serviceProvider.GetRequiredService<ILogger<BaseRConParser>>(), parserRegexFactory) };
TokenAuthenticator = new TokenAuthentication();
_logger = logger;
_tokenSource = new CancellationTokenSource();
_isRunningTokenSource = new CancellationTokenSource();
_commands = commands.ToList();
_translationLookup = translationLookup;
_commandConfiguration = commandConfiguration;
_serverInstanceFactory = serverInstanceFactory;
_parserRegexFactory = parserRegexFactory;
_customParserEvents = customParserEvents;
_eventHandler = eventHandler;
_coreEventHandler = coreEventHandler;
_scriptCommandFactory = scriptCommandFactory;
_metaRegistration = metaRegistration;
_scriptPluginServiceResolver = scriptPluginServiceResolver;
@ -117,6 +125,8 @@ namespace IW4MAdmin.Application
_appConfig = appConfig;
Plugins = plugins;
InteractionRegistration = interactionRegistration;
IManagementEventSubscriptions.ClientPersistentIdReceived += OnClientPersistentIdReceived;
}
public IEnumerable<IPlugin> Plugins { get; }
@ -124,7 +134,7 @@ namespace IW4MAdmin.Application
public async Task ExecuteEvent(GameEvent newEvent)
{
ProcessingEvents.TryAdd(newEvent.Id, newEvent);
ProcessingEvents.TryAdd(newEvent.IncrementalId, newEvent);
// the event has failed already
if (newEvent.Failed)
@ -142,12 +152,12 @@ namespace IW4MAdmin.Application
catch (TaskCanceledException)
{
_logger.LogDebug("Received quit signal for event id {eventId}, so we are aborting early", newEvent.Id);
_logger.LogDebug("Received quit signal for event id {EventId}, so we are aborting early", newEvent.IncrementalId);
}
catch (OperationCanceledException)
{
_logger.LogDebug("Received quit signal for event id {eventId}, so we are aborting early", newEvent.Id);
_logger.LogDebug("Received quit signal for event id {EventId}, so we are aborting early", newEvent.IncrementalId);
}
// this happens if a plugin requires login
@ -186,11 +196,11 @@ namespace IW4MAdmin.Application
}
skip:
if (newEvent.Type == EventType.Command && newEvent.ImpersonationOrigin == null)
if (newEvent.Type == EventType.Command && newEvent.ImpersonationOrigin == null && newEvent.CorrelationId is not null)
{
var correlatedEvents =
ProcessingEvents.Values.Where(ev =>
ev.CorrelationId == newEvent.CorrelationId && ev.Id != newEvent.Id)
ev.CorrelationId == newEvent.CorrelationId && ev.IncrementalId != newEvent.IncrementalId)
.ToList();
await Task.WhenAll(correlatedEvents.Select(ev =>
@ -199,14 +209,16 @@ namespace IW4MAdmin.Application
foreach (var correlatedEvent in correlatedEvents)
{
ProcessingEvents.Remove(correlatedEvent.Id, out _);
ProcessingEvents.Remove(correlatedEvent.IncrementalId, out _);
}
}
// we don't want to remove events that are correlated to command
if (ProcessingEvents.Values.ToList()?.Count(gameEvent => gameEvent.CorrelationId == newEvent.CorrelationId) == 1)
if (ProcessingEvents.Values.Count(gameEvent =>
newEvent.CorrelationId is not null && newEvent.CorrelationId == gameEvent.CorrelationId) == 1 ||
newEvent.CorrelationId is null)
{
ProcessingEvents.Remove(newEvent.Id, out _);
ProcessingEvents.Remove(newEvent.IncrementalId, out _);
}
// tell anyone waiting for the output that we're done
@ -226,75 +238,58 @@ namespace IW4MAdmin.Application
public IReadOnlyList<IManagerCommand> Commands => _commands.ToImmutableList();
public async Task UpdateServerStates()
private Task UpdateServerStates()
{
// store the server hash code and task for it
var runningUpdateTasks = new Dictionary<long, (Task task, CancellationTokenSource tokenSource, DateTime startTime)>();
var timeout = TimeSpan.FromSeconds(60);
while (!_tokenSource.IsCancellationRequested) // main shutdown requested
var index = 0;
return Task.WhenAll(_servers.Select(server =>
{
// select the server ids that have completed the update task
var serverTasksToRemove = runningUpdateTasks
.Where(ut => ut.Value.task.IsCompleted)
.Select(ut => ut.Key)
.ToList();
// remove the update tasks as they have completed
foreach (var serverId in serverTasksToRemove.Where(serverId => runningUpdateTasks.ContainsKey(serverId)))
{
if (!runningUpdateTasks[serverId].tokenSource.Token.IsCancellationRequested)
{
runningUpdateTasks[serverId].tokenSource.Cancel();
}
runningUpdateTasks.Remove(serverId);
}
// select the servers where the tasks have completed
var newTaskServers = Servers.Select(s => s.EndPoint).Except(runningUpdateTasks.Select(r => r.Key)).ToList();
foreach (var server in Servers.Where(s => newTaskServers.Contains(s.EndPoint)))
{
var firstTokenSource = new CancellationTokenSource();
firstTokenSource.CancelAfter(timeout);
var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(firstTokenSource.Token, _tokenSource.Token);
runningUpdateTasks.Add(server.EndPoint, (ProcessUpdateHandler(server, linkedTokenSource.Token), linkedTokenSource, DateTime.Now));
}
try
{
await Task.Delay(ConfigHandler.Configuration().RConPollRate, _tokenSource.Token);
}
// if a cancellation is received, we want to return immediately after shutting down
catch
{
foreach (var server in Servers.Where(s => newTaskServers.Contains(s.EndPoint)))
{
await server.ProcessUpdatesAsync(_tokenSource.Token);
}
break;
}
}
var thisIndex = index;
Interlocked.Increment(ref index);
return ProcessUpdateHandler(server, thisIndex);
}));
}
private async Task ProcessUpdateHandler(Server server, CancellationToken token)
private async Task ProcessUpdateHandler(Server server, int index)
{
try
const int delayScalar = 50; // Task.Delay is inconsistent enough there's no reason to try to prevent collisions
var timeout = TimeSpan.FromMinutes(2);
while (!_isRunningTokenSource.IsCancellationRequested)
{
await server.ProcessUpdatesAsync(token);
}
catch (Exception ex)
{
using (LogContext.PushProperty("Server", server.ToString()))
try
{
_logger.LogError(ex, "Failed to update status");
var delayFactor = Math.Min(_appConfig.RConPollRate, delayScalar * index);
await Task.Delay(delayFactor, _isRunningTokenSource.Token);
using var timeoutTokenSource = new CancellationTokenSource();
timeoutTokenSource.CancelAfter(timeout);
using var linkedTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token,
_isRunningTokenSource.Token);
await server.ProcessUpdatesAsync(linkedTokenSource.Token);
await Task.Delay(Math.Max(1000, _appConfig.RConPollRate - delayFactor),
_isRunningTokenSource.Token);
}
catch (OperationCanceledException)
{
// ignored
}
catch (Exception ex)
{
using (LogContext.PushProperty("Server", server.Id))
{
_logger.LogError(ex, "Failed to update status");
}
}
finally
{
server.IsInitialized = true;
}
}
finally
{
server.IsInitialized = true;
}
// run the final updates to clean up server
await server.ProcessUpdatesAsync(_isRunningTokenSource.Token);
}
public async Task Init()
@ -305,18 +300,24 @@ namespace IW4MAdmin.Application
#region DATABASE
_logger.LogInformation("Beginning database migration sync");
Console.WriteLine(_translationLookup["MANAGER_MIGRATION_START"]);
await ContextSeed.Seed(_serviceProvider.GetRequiredService<IDatabaseContextFactory>(), _tokenSource.Token);
await DatabaseHousekeeping.RemoveOldRatings(_serviceProvider.GetRequiredService<IDatabaseContextFactory>(), _tokenSource.Token);
await ContextSeed.Seed(_serviceProvider.GetRequiredService<IDatabaseContextFactory>(), _isRunningTokenSource.Token);
await DatabaseHousekeeping.RemoveOldRatings(_serviceProvider.GetRequiredService<IDatabaseContextFactory>(), _isRunningTokenSource.Token);
_logger.LogInformation("Finished database migration sync");
Console.WriteLine(_translationLookup["MANAGER_MIGRATION_END"]);
#endregion
#region EVENTS
IGameServerEventSubscriptions.ServerValueRequested += OnServerValueRequested;
IGameServerEventSubscriptions.ServerValueSetRequested += OnServerValueSetRequested;
await IManagementEventSubscriptions.InvokeLoadAsync(this, CancellationToken);
# endregion
#region PLUGINS
foreach (var plugin in Plugins)
{
try
{
if (plugin is ScriptPlugin scriptPlugin)
if (plugin is ScriptPlugin scriptPlugin && !plugin.IsParser)
{
await scriptPlugin.Initialize(this, _scriptCommandFactory, _scriptPluginServiceResolver,
_serviceProvider.GetService<IConfigurationHandlerV2<ScriptPluginConfiguration>>());
@ -391,13 +392,11 @@ namespace IW4MAdmin.Application
if (string.IsNullOrEmpty(_appConfig.Id))
{
_appConfig.Id = Guid.NewGuid().ToString();
await ConfigHandler.Save();
}
if (string.IsNullOrEmpty(_appConfig.WebfrontBindUrl))
{
_appConfig.WebfrontBindUrl = "http://0.0.0.0:1624";
await ConfigHandler.Save();
}
#pragma warning disable 618
@ -442,8 +441,8 @@ namespace IW4MAdmin.Application
serverConfig.ModifyParsers();
}
await ConfigHandler.Save();
}
await ConfigHandler.Save();
}
if (_appConfig.Servers.Length == 0)
@ -468,7 +467,7 @@ namespace IW4MAdmin.Application
#endregion
#region COMMANDS
if (await ClientSvc.HasOwnerAsync(_tokenSource.Token))
if (await ClientSvc.HasOwnerAsync(_isRunningTokenSource.Token))
{
_commands.RemoveAll(_cmd => _cmd.GetType() == typeof(OwnerCommand));
}
@ -526,7 +525,7 @@ namespace IW4MAdmin.Application
}
}
#endregion
Console.WriteLine(_translationLookup["MANAGER_COMMUNICATION_INFO"]);
await InitializeServers();
IsInitialized = true;
@ -543,26 +542,23 @@ namespace IW4MAdmin.Application
try
{
// todo: this might not always be an IW4MServer
var ServerInstance = _serverInstanceFactory.CreateServer(Conf, this) as IW4MServer;
using (LogContext.PushProperty("Server", ServerInstance.ToString()))
var serverInstance = _serverInstanceFactory.CreateServer(Conf, this) as IW4MServer;
using (LogContext.PushProperty("Server", serverInstance!.ToString()))
{
_logger.LogInformation("Beginning server communication initialization");
await ServerInstance.Initialize();
await serverInstance.Initialize();
_servers.Add(ServerInstance);
Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"].FormatExt(ServerInstance.Hostname.StripColors()));
_logger.LogInformation("Finishing initialization and now monitoring [{Server}]", ServerInstance.Hostname);
_servers.Add(serverInstance);
Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"].FormatExt(serverInstance.Hostname.StripColors()));
_logger.LogInformation("Finishing initialization and now monitoring [{Server}]", serverInstance.Hostname);
}
// add the start event for this server
var e = new GameEvent()
QueueEvent(new MonitorStartEvent
{
Type = EventType.Start,
Data = $"{ServerInstance.GameName} started",
Owner = ServerInstance
};
AddEvent(e);
Server = serverInstance,
Source = this
});
successServers++;
}
@ -593,11 +589,27 @@ namespace IW4MAdmin.Application
}
}
public async Task Start() => await UpdateServerStates();
public async Task Start()
{
_eventHandlerTokenSource = new CancellationTokenSource();
var eventHandlerThread = new Thread(() =>
{
_coreEventHandler.StartProcessing(_eventHandlerTokenSource.Token);
})
{
Name = nameof(CoreEventHandler)
};
eventHandlerThread.Start();
await UpdateServerStates();
_eventHandlerTokenSource.Cancel();
eventHandlerThread.Join();
}
public async Task Stop()
{
foreach (var plugin in Plugins)
foreach (var plugin in Plugins.Where(plugin => !plugin.IsParser))
{
try
{
@ -607,19 +619,32 @@ namespace IW4MAdmin.Application
{
_logger.LogError(ex, "Could not cleanly unload plugin {PluginName}", plugin.Name);
}
}
_tokenSource.Cancel();
}
_isRunningTokenSource.Cancel();
IsRunning = false;
}
public void Restart()
public async Task Restart()
{
IsRestartRequested = true;
Stop().GetAwaiter().GetResult();
_tokenSource.Dispose();
_tokenSource = new CancellationTokenSource();
await Stop();
using var subscriptionTimeoutToken = new CancellationTokenSource();
subscriptionTimeoutToken.CancelAfter(Utilities.DefaultCommandTimeout);
await IManagementEventSubscriptions.InvokeUnloadAsync(this, subscriptionTimeoutToken.Token);
IGameEventSubscriptions.ClearEventInvocations();
IGameServerEventSubscriptions.ClearEventInvocations();
IManagementEventSubscriptions.ClearEventInvocations();
_isRunningTokenSource.Dispose();
_isRunningTokenSource = new CancellationTokenSource();
_eventHandlerTokenSource.Dispose();
_eventHandlerTokenSource = new CancellationTokenSource();
}
[Obsolete]
@ -661,9 +686,14 @@ namespace IW4MAdmin.Application
public void AddEvent(GameEvent gameEvent)
{
_eventHandler.HandleEvent(this, gameEvent);
_coreEventHandler.QueueEvent(this, gameEvent);
}
public void QueueEvent(CoreEvent coreEvent)
{
_coreEventHandler.QueueEvent(this, coreEvent);
}
public IPageList GetPageList()
{
return PageList;
@ -698,15 +728,132 @@ namespace IW4MAdmin.Application
public void AddAdditionalCommand(IManagerCommand command)
{
if (_commands.Any(_command => _command.Name == command.Name || _command.Alias == command.Alias))
lock (_commands)
{
throw new InvalidOperationException($"Duplicate command name or alias ({command.Name}, {command.Alias})");
}
if (_commands.Any(cmd => cmd.Name == command.Name || cmd.Alias == command.Alias))
{
throw new InvalidOperationException(
$"Duplicate command name or alias ({command.Name}, {command.Alias})");
}
_commands.Add(command);
_commands.Add(command);
}
}
public void RemoveCommandByName(string commandName) => _commands.RemoveAll(_command => _command.Name == commandName);
public IAlertManager AlertManager => _alertManager;
private async Task OnServerValueRequested(ServerValueRequestEvent requestEvent, CancellationToken token)
{
if (requestEvent.Server is not IW4MServer server)
{
return;
}
Dvar<string> serverValue = null;
try
{
if (requestEvent.DelayMs.HasValue)
{
await Task.Delay(requestEvent.DelayMs.Value, token);
}
var waitToken = token;
using var timeoutTokenSource = new CancellationTokenSource();
using var linkedTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, token);
if (requestEvent.TimeoutMs is not null)
{
timeoutTokenSource.CancelAfter(requestEvent.TimeoutMs.Value);
waitToken = linkedTokenSource.Token;
}
serverValue =
await server.GetDvarAsync(requestEvent.ValueName, requestEvent.FallbackValue, waitToken);
}
catch
{
// ignored
}
finally
{
QueueEvent(new ServerValueReceiveEvent
{
Server = server,
Source = server,
Response = serverValue ?? new Dvar<string> { Name = requestEvent.ValueName },
Success = serverValue is not null
});
}
}
private async Task OnServerValueSetRequested(ServerValueSetRequestEvent requestEvent, CancellationToken token)
{
if (requestEvent.Server is not IW4MServer server)
{
return;
}
var completed = false;
try
{
if (requestEvent.DelayMs.HasValue)
{
await Task.Delay(requestEvent.DelayMs.Value, token);
}
if (requestEvent.TimeoutMs is not null)
{
using var timeoutTokenSource = new CancellationTokenSource(requestEvent.TimeoutMs.Value);
using var linkedTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, token);
token = linkedTokenSource.Token;
}
await server.SetDvarAsync(requestEvent.ValueName, requestEvent.Value, token);
completed = true;
}
catch
{
// ignored
}
finally
{
QueueEvent(new ServerValueSetCompleteEvent
{
Server = server,
Source = server,
Success = completed,
Value = requestEvent.Value,
ValueName = requestEvent.ValueName
});
}
}
private async Task OnClientPersistentIdReceived(ClientPersistentIdReceiveEvent receiveEvent, CancellationToken token)
{
var parts = receiveEvent.PersistentId.Split(",");
if (parts.Length == 2 && int.TryParse(parts[0], out var high) &&
int.TryParse(parts[1], out var low))
{
var guid = long.Parse(high.ToString("X") + low.ToString("X"), NumberStyles.HexNumber);
var penalties = await PenaltySvc
.GetActivePenaltiesByIdentifier(null, guid, receiveEvent.Client.GameName);
var banPenalty =
penalties.FirstOrDefault(penalty => penalty.Type == EFPenalty.PenaltyType.Ban);
if (banPenalty is not null && receiveEvent.Client.Level != Data.Models.Client.EFClient.Permission.Banned)
{
_logger.LogInformation(
"Banning {Client} as they have have provided a persistent clientId of {PersistentClientId}, which is banned",
receiveEvent.Client, guid);
receiveEvent.Client.Ban(_translationLookup["SERVER_BAN_EVADE"].FormatExt(guid),
receiveEvent.Client.CurrentServer.AsConsoleClient(), true);
}
}
}
}
}

View File

@ -0,0 +1,145 @@
using System;
using System.Collections.Concurrent;
using SharedLibraryCore;
using SharedLibraryCore.Events;
using SharedLibraryCore.Interfaces;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SharedLibraryCore.Events.Management;
using SharedLibraryCore.Events.Server;
using SharedLibraryCore.Interfaces.Events;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application
{
public class CoreEventHandler : ICoreEventHandler
{
private const int MaxCurrentEvents = 25;
private readonly ILogger _logger;
private readonly SemaphoreSlim _onProcessingEvents = new(MaxCurrentEvents, MaxCurrentEvents);
private readonly ManualResetEventSlim _onEventReady = new(false);
private readonly ConcurrentQueue<(IManager, CoreEvent)> _runningEventTasks = new();
private CancellationToken _cancellationToken;
private int _activeTasks;
private static readonly GameEvent.EventType[] OverrideEvents =
{
GameEvent.EventType.Connect,
GameEvent.EventType.Disconnect,
GameEvent.EventType.Quit,
GameEvent.EventType.Stop
};
public CoreEventHandler(ILogger<CoreEventHandler> logger)
{
_logger = logger;
}
public void QueueEvent(IManager manager, CoreEvent coreEvent)
{
_runningEventTasks.Enqueue((manager, coreEvent));
_onEventReady.Set();
}
public void StartProcessing(CancellationToken token)
{
_cancellationToken = token;
while (!_cancellationToken.IsCancellationRequested)
{
_onEventReady.Reset();
try
{
_onProcessingEvents.Wait(_cancellationToken);
if (!_runningEventTasks.TryDequeue(out var coreEvent))
{
if (_onProcessingEvents.CurrentCount < MaxCurrentEvents)
{
_onProcessingEvents.Release(1);
}
_onEventReady.Wait(_cancellationToken);
continue;
}
_logger.LogDebug("Start processing event {Name} {SemaphoreCount} - {QueuedTasks}",
coreEvent.Item2.GetType().Name, _onProcessingEvents.CurrentCount, _runningEventTasks.Count);
_ = Task.Factory.StartNew(() =>
{
Interlocked.Increment(ref _activeTasks);
_logger.LogDebug("[Start] Active Tasks = {TaskCount}", _activeTasks);
return HandleEventTaskExecute(coreEvent);
});
}
catch (OperationCanceledException)
{
// ignored
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not enqueue event for processing");
}
}
}
private async Task HandleEventTaskExecute((IManager, CoreEvent) coreEvent)
{
try
{
await GetEventTask(coreEvent.Item1, coreEvent.Item2);
}
catch (OperationCanceledException)
{
_logger.LogWarning("Event timed out {Type}", coreEvent.Item2.GetType().Name);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not complete invoke for {EventType}",
coreEvent.Item2.GetType().Name);
}
finally
{
if (_onProcessingEvents.CurrentCount < MaxCurrentEvents)
{
_logger.LogDebug("Freeing up event semaphore for next event {SemaphoreCount}",
_onProcessingEvents.CurrentCount);
_onProcessingEvents.Release(1);
}
Interlocked.Decrement(ref _activeTasks);
_logger.LogDebug("[Complete] {Type}, Active Tasks = {TaskCount} - {Queue}", coreEvent.Item2.GetType(),
_activeTasks, _runningEventTasks.Count);
}
}
private Task GetEventTask(IManager manager, CoreEvent coreEvent)
{
return coreEvent switch
{
GameEvent gameEvent => BuildLegacyEventTask(manager, coreEvent, gameEvent),
GameServerEvent gameServerEvent => IGameServerEventSubscriptions.InvokeEventAsync(gameServerEvent,
manager.CancellationToken),
ManagementEvent managementEvent => IManagementEventSubscriptions.InvokeEventAsync(managementEvent,
manager.CancellationToken),
_ => Task.CompletedTask
};
}
private async Task BuildLegacyEventTask(IManager manager, CoreEvent coreEvent, GameEvent gameEvent)
{
if (manager.IsRunning || OverrideEvents.Contains(gameEvent.Type))
{
await manager.ExecuteEvent(gameEvent);
await IGameEventSubscriptions.InvokeEventAsync(coreEvent, manager.CancellationToken);
return;
}
_logger.LogDebug("Skipping event as we're shutting down {EventId}", gameEvent.IncrementalId);
}
}
}

View File

@ -9,7 +9,7 @@ using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net;
@ -31,6 +31,9 @@ using IW4MAdmin.Application.Plugin.Script;
using IW4MAdmin.Plugins.Stats.Helpers;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Alerts;
using SharedLibraryCore.Events.Management;
using SharedLibraryCore.Events.Server;
using SharedLibraryCore.Interfaces.Events;
using static Data.Models.Client.EFClient;
namespace IW4MAdmin
@ -44,11 +47,12 @@ namespace IW4MAdmin
private const int REPORT_FLAG_COUNT = 4;
private long lastGameTime = 0;
public int Id { get; private set; }
private readonly IServiceProvider _serviceProvider;
private readonly IClientNoticeMessageFormatter _messageFormatter;
private readonly ILookupCache<EFServer> _serverCache;
private readonly CommandConfiguration _commandConfiguration;
private EFServer _cachedDatabaseServer;
private readonly StatManager _statManager;
public IW4MServer(
ServerConfiguration serverConfiguration,
@ -72,6 +76,18 @@ namespace IW4MAdmin
_messageFormatter = messageFormatter;
_serverCache = serverCache;
_commandConfiguration = commandConfiguration;
_statManager = serviceProvider.GetRequiredService<StatManager>();
IGameServerEventSubscriptions.MonitoringStarted += async (gameEvent, token) =>
{
if (gameEvent.Server.Id != Id)
{
return;
}
await EnsureServerAdded();
await _statManager.EnsureServerAdded(gameEvent.Server, token);
};
}
public override async Task<EFClient> OnClientConnected(EFClient clientFromLog)
@ -108,7 +124,7 @@ namespace IW4MAdmin
Clients[client.ClientNumber] = client;
ServerLogger.LogDebug("End PreConnect for {client}", client.ToString());
var e = new GameEvent()
var e = new GameEvent
{
Origin = client,
Owner = this,
@ -116,6 +132,11 @@ namespace IW4MAdmin
};
Manager.AddEvent(e);
Manager.QueueEvent(new ClientStateInitializeEvent
{
Client = client,
Source = this,
});
return client;
}
@ -210,10 +231,17 @@ namespace IW4MAdmin
ServerLogger.LogInformation("Executing command {Command} for {Client}", cmd.Name,
E.Origin.ToString());
await cmd.ExecuteAsync(E);
Manager.QueueEvent(new ClientExecuteCommandEvent
{
Command = cmd,
Client = E.Origin,
Source = this,
CommandText = E.Data
});
}
var pluginTasks = Manager.Plugins
.Select(async plugin => await CreatePluginTask(plugin, E));
var pluginTasks = Manager.Plugins.Where(plugin => !plugin.IsParser)
.Select(plugin => CreatePluginTask(plugin, E));
await Task.WhenAll(pluginTasks);
}
@ -250,7 +278,7 @@ namespace IW4MAdmin
try
{
await plugin.OnEventAsync(gameEvent, this).WithWaitCancellation(tokenSource.Token);
await plugin.OnEventAsync(gameEvent, this);
}
catch (OperationCanceledException)
{
@ -277,29 +305,7 @@ namespace IW4MAdmin
{
ServerLogger.LogDebug("processing event of type {type}", E.Type);
if (E.Type == GameEvent.EventType.Start)
{
var existingServer = (await _serverCache
.FirstAsync(server => server.Id == EndPoint));
var serverId = await GetIdForServer(E.Owner);
if (existingServer == null)
{
var server = new EFServer()
{
Port = Port,
EndPoint = ToString(),
ServerId = serverId,
GameName = (Reference.Game?)GameName,
HostName = Hostname
};
await _serverCache.AddAsync(server);
}
}
else if (E.Type == GameEvent.EventType.ConnectionLost)
if (E.Type == GameEvent.EventType.ConnectionLost)
{
var exception = E.Extra as Exception;
ServerLogger.LogError(exception,
@ -350,9 +356,18 @@ namespace IW4MAdmin
else if (E.Type == GameEvent.EventType.ChangePermission)
{
var newPermission = (Permission) E.Extra;
var oldPermission = E.Target.Level;
ServerLogger.LogInformation("{origin} is setting {target} to permission level {newPermission}",
E.Origin.ToString(), E.Target.ToString(), newPermission);
await Manager.GetClientService().UpdateLevel(newPermission, E.Target, E.Origin);
Manager.QueueEvent(new ClientPermissionChangeEvent
{
Client = E.Origin,
Source = this,
OldPermission = oldPermission,
NewPermission = newPermission
});
}
else if (E.Type == GameEvent.EventType.Connect)
@ -500,6 +515,12 @@ namespace IW4MAdmin
await Manager.GetPenaltyService().Create(newPenalty);
E.Target.SetLevel(Permission.Flagged, E.Origin);
Manager.QueueEvent(new ClientPenaltyEvent
{
Client = E.Target,
Penalty = newPenalty
});
}
else if (E.Type == GameEvent.EventType.Unflag)
@ -519,6 +540,12 @@ namespace IW4MAdmin
await Manager.GetPenaltyService().RemoveActivePenalties(E.Target.AliasLinkId, E.Target.NetworkId,
E.Target.GameName, E.Target.CurrentAlias?.IPAddress);
await Manager.GetPenaltyService().Create(unflagPenalty);
Manager.QueueEvent(new ClientPenaltyRevokeEvent
{
Client = E.Target,
Penalty = unflagPenalty
});
}
else if (E.Type == GameEvent.EventType.Report)
@ -554,6 +581,13 @@ namespace IW4MAdmin
Utilities.CurrentLocalization.LocalizationIndex["SERVER_AUTO_FLAG_REPORT"]
.FormatExt(reportNum), Utilities.IW4MAdminClient(E.Owner));
}
Manager.QueueEvent(new ClientPenaltyEvent
{
Client = E.Target,
Penalty = newReport,
Source = this
});
}
else if (E.Type == GameEvent.EventType.TempBan)
@ -728,6 +762,11 @@ namespace IW4MAdmin
{
MaxClients = int.Parse(dict["com_maxclients"]);
}
else if (dict.ContainsKey("com_maxplayers"))
{
MaxClients = int.Parse(dict["com_maxplayers"]);
}
if (dict.ContainsKey("mapname"))
{
@ -772,34 +811,6 @@ namespace IW4MAdmin
{
E.Origin.UpdateTeam(E.Extra as string);
}
else if (E.Type == GameEvent.EventType.MetaUpdated)
{
if (E.Extra is "PersistentClientGuid")
{
var parts = E.Data.Split(",");
if (parts.Length == 2 && int.TryParse(parts[0], out var high) &&
int.TryParse(parts[1], out var low))
{
var guid = long.Parse(high.ToString("X") + low.ToString("X"), NumberStyles.HexNumber);
var penalties = await Manager.GetPenaltyService()
.GetActivePenaltiesByIdentifier(null, guid, (Reference.Game)GameName);
var banPenalty =
penalties.FirstOrDefault(penalty => penalty.Type == EFPenalty.PenaltyType.Ban);
if (banPenalty is not null && E.Origin.Level != Permission.Banned)
{
ServerLogger.LogInformation(
"Banning {Client} as they have have provided a persistent clientId of {PersistentClientId}, which is banned",
E.Origin.ToString(), guid);
E.Origin.Ban(loc["SERVER_BAN_EVADE"].FormatExt(guid),
Utilities.IW4MAdminClient(this), true);
}
}
}
}
lock (ChatHistory)
{
@ -820,6 +831,53 @@ namespace IW4MAdmin
}
}
public async Task EnsureServerAdded()
{
var gameServer = await _serverCache
.FirstAsync(server => server.EndPoint == base.Id);
if (gameServer == null)
{
gameServer = new EFServer
{
Port = ListenPort,
EndPoint = base.Id,
ServerId = BuildLegacyDatabaseId(),
GameName = (Reference.Game?)GameName,
HostName = ServerName
};
await _serverCache.AddAsync(gameServer);
}
await using var context = _serviceProvider.GetRequiredService<IDatabaseContextFactory>()
.CreateContext(enableTracking: false);
context.Servers.Attach(gameServer);
// we want to set the gamename up if it's never been set, or it changed
if (!gameServer.GameName.HasValue || gameServer.GameName.Value != GameCode)
{
gameServer.GameName = GameCode;
context.Entry(gameServer).Property(property => property.GameName).IsModified = true;
}
if (gameServer.HostName == null || gameServer.HostName != ServerName)
{
gameServer.HostName = ServerName;
context.Entry(gameServer).Property(property => property.HostName).IsModified = true;
}
if (gameServer.IsPasswordProtected != !string.IsNullOrEmpty(GamePassword))
{
gameServer.IsPasswordProtected = !string.IsNullOrEmpty(GamePassword);
context.Entry(gameServer).Property(property => property.IsPasswordProtected).IsModified = true;
}
await context.SaveChangesAsync();
_cachedDatabaseServer = gameServer;
}
private async Task OnClientUpdate(EFClient origin)
{
var client = GetClientsAsList().FirstOrDefault(c => c.NetworkId == origin.NetworkId);
@ -909,22 +967,15 @@ namespace IW4MAdmin
public override async Task<long> GetIdForServer(Server server = null)
{
server ??= this;
if ($"{server.IP}:{server.Port.ToString()}" == "66.150.121.184:28965")
{
return 886229536;
}
// todo: this is not stable and will need to be migrated again...
long id = HashCode.Combine(server.IP, server.Port);
id = id < 0 ? Math.Abs(id) : id;
return (await _serverCache.FirstAsync(cachedServer =>
cachedServer.EndPoint == server.Id || cachedServer.ServerId == server.EndPoint)).ServerId;
}
var serverId = (await _serverCache
.FirstAsync(_server => _server.ServerId == server.EndPoint ||
_server.EndPoint == server.ToString() ||
_server.ServerId == id))?.ServerId;
return !serverId.HasValue ? id : serverId.Value;
private long BuildLegacyDatabaseId()
{
long id = HashCode.Combine(ListenAddress, ListenPort);
return id < 0 ? Math.Abs(id) : id;
}
private void UpdateMap(string mapname)
@ -983,7 +1034,7 @@ namespace IW4MAdmin
{
await client.OnDisconnect();
var e = new GameEvent()
var e = new GameEvent
{
Type = GameEvent.EventType.Disconnect,
Owner = this,
@ -994,6 +1045,14 @@ namespace IW4MAdmin
await e.WaitAsync(Utilities.DefaultCommandTimeout, new CancellationTokenRegistration().Token);
}
using var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(Utilities.DefaultCommandTimeout);
Manager.QueueEvent(new MonitorStopEvent
{
Server = this
});
}
private DateTime _lastMessageSent = DateTime.Now;
@ -1075,6 +1134,16 @@ namespace IW4MAdmin
Manager.AddEvent(gameEvent);
}
if (polledClients[2].Any())
{
Manager.QueueEvent(new ClientDataUpdateEvent
{
Clients = new ReadOnlyCollection<EFClient>(polledClients[2]),
Server = this,
Source = this,
});
}
if (Throttled)
{
var gameEvent = new GameEvent
@ -1086,6 +1155,12 @@ namespace IW4MAdmin
};
Manager.AddEvent(gameEvent);
Manager.QueueEvent(new ConnectionRestoreEvent
{
Server = this,
Source = this
});
}
LastPoll = DateTime.Now;
@ -1109,6 +1184,12 @@ namespace IW4MAdmin
};
Manager.AddEvent(gameEvent);
Manager.QueueEvent(new ConnectionInterruptEvent
{
Server = this,
Source = this
});
return true;
}
finally
@ -1469,6 +1550,12 @@ namespace IW4MAdmin
.FormatExt(activeClient.Warnings, activeClient.Name, reason);
activeClient.CurrentServer.Broadcast(message);
}
Manager.QueueEvent(new ClientPenaltyEvent
{
Client = targetClient,
Penalty = newPenalty
});
}
public override async Task Kick(string reason, EFClient targetClient, EFClient originClient, EFPenalty previousPenalty)
@ -1507,6 +1594,12 @@ namespace IW4MAdmin
ServerLogger.LogDebug("Executing tempban kick command for {ActiveClient}", activeClient.ToString());
await activeClient.CurrentServer.ExecuteCommandAsync(formattedKick);
}
Manager.QueueEvent(new ClientPenaltyEvent
{
Client = targetClient,
Penalty = newPenalty
});
}
public override async Task TempBan(string reason, TimeSpan length, EFClient targetClient, EFClient originClient)
@ -1540,6 +1633,12 @@ namespace IW4MAdmin
ServerLogger.LogDebug("Executing tempban kick command for {ActiveClient}", activeClient.ToString());
await activeClient.CurrentServer.ExecuteCommandAsync(formattedKick);
}
Manager.QueueEvent(new ClientPenaltyEvent
{
Client = targetClient,
Penalty = newPenalty
});
}
public override async Task Ban(string reason, EFClient targetClient, EFClient originClient, bool isEvade = false)
@ -1575,6 +1674,12 @@ namespace IW4MAdmin
_messageFormatter.BuildFormattedMessage(RconParser.Configuration, newPenalty));
await activeClient.CurrentServer.ExecuteCommandAsync(formattedString);
}
Manager.QueueEvent(new ClientPenaltyEvent
{
Client = targetClient,
Penalty = newPenalty
});
}
public override async Task Unban(string reason, EFClient targetClient, EFClient originClient)
@ -1596,6 +1701,12 @@ namespace IW4MAdmin
await Manager.GetPenaltyService().RemoveActivePenalties(targetClient.AliasLink.AliasLinkId,
targetClient.NetworkId, targetClient.GameName, targetClient.CurrentAlias?.IPAddress);
await Manager.GetPenaltyService().Create(unbanPenalty);
Manager.QueueEvent(new ClientPenaltyRevokeEvent
{
Client = targetClient,
Penalty = unbanPenalty
});
}
public override void InitializeTokens()
@ -1605,5 +1716,7 @@ namespace IW4MAdmin
Manager.GetMessageTokens().Add(new MessageToken("NEXTMAP", (Server s) => SharedLibraryCore.Commands.NextMapCommand.GetNextMap(s, _translationLookup)));
Manager.GetMessageTokens().Add(new MessageToken("ADMINS", (Server s) => Task.FromResult(ListAdminsCommand.OnlineAdmins(s, _translationLookup))));
}
public override long LegacyDatabaseId => _cachedDatabaseServer.ServerId;
}
}

View File

@ -51,7 +51,7 @@ namespace IW4MAdmin.Application
public static BuildNumber Version { get; } = BuildNumber.Parse(Utilities.GetVersionAsString());
private static ApplicationManager _serverManager;
private static Task _applicationTask;
private static ServiceProvider _serviceProvider;
private static IServiceProvider _serviceProvider;
/// <summary>
/// entrypoint of the application
@ -112,23 +112,24 @@ namespace IW4MAdmin.Application
ConfigurationMigration.MoveConfigFolder10518(null);
ConfigurationMigration.CheckDirectories();
ConfigurationMigration.RemoveObsoletePlugins20210322();
logger.LogDebug("Configuring services...");
var services = await ConfigureServices(args);
_serviceProvider = services.BuildServiceProvider();
var versionChecker = _serviceProvider.GetRequiredService<IMasterCommunication>();
_serverManager = (ApplicationManager) _serviceProvider.GetRequiredService<IManager>();
translationLookup = _serviceProvider.GetRequiredService<ITranslationLookup>();
_applicationTask = RunApplicationTasksAsync(logger, services);
var tasks = new[]
{
versionChecker.CheckVersion(),
_applicationTask
};
var configHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
await configHandler.BuildAsync();
_serviceProvider = WebfrontCore.Program.InitializeServices(ConfigureServices,
(configHandler.Configuration() ?? new ApplicationConfiguration()).WebfrontBindUrl);
_serverManager = (ApplicationManager)_serviceProvider.GetRequiredService<IManager>();
translationLookup = _serviceProvider.GetRequiredService<ITranslationLookup>();
await _serverManager.Init();
await Task.WhenAll(tasks);
_applicationTask = Task.WhenAll(RunApplicationTasksAsync(logger, _serviceProvider),
_serverManager.Start());
await _applicationTask;
logger.LogInformation("Shutdown completed successfully");
}
catch (Exception e)
@ -178,21 +179,20 @@ namespace IW4MAdmin.Application
{
goto restart;
}
await _serviceProvider.DisposeAsync();
}
/// <summary>
/// runs the core application tasks
/// </summary>
/// <returns></returns>
private static async Task RunApplicationTasksAsync(ILogger logger, IServiceCollection services)
private static Task RunApplicationTasksAsync(ILogger logger, IServiceProvider serviceProvider)
{
var webfrontTask = _serverManager.GetApplicationSettings().Configuration().EnableWebFront
? WebfrontCore.Program.Init(_serverManager, _serviceProvider, services, _serverManager.CancellationToken)
? WebfrontCore.Program.GetWebHostTask(_serverManager.CancellationToken)
: Task.CompletedTask;
var collectionService = _serviceProvider.GetRequiredService<IServerDataCollector>();
var collectionService = serviceProvider.GetRequiredService<IServerDataCollector>();
var versionChecker = serviceProvider.GetRequiredService<IMasterCommunication>();
// we want to run this one on a manual thread instead of letting the thread pool handle it,
// because we can't exit early from waiting on console input, and it prevents us from restarting
@ -203,18 +203,15 @@ namespace IW4MAdmin.Application
var tasks = new[]
{
versionChecker.CheckVersion(),
webfrontTask,
_serverManager.Start(),
_serviceProvider.GetRequiredService<IMasterCommunication>()
serviceProvider.GetRequiredService<IMasterCommunication>()
.RunUploadStatus(_serverManager.CancellationToken),
collectionService.BeginCollectionAsync(cancellationToken: _serverManager.CancellationToken)
};
logger.LogDebug("Starting webfront and input tasks");
await Task.WhenAll(tasks);
logger.LogInformation("Shutdown completed successfully");
Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_SHUTDOWN_SUCCESS"]);
return Task.WhenAll(tasks);
}
/// <summary>
@ -302,8 +299,21 @@ namespace IW4MAdmin.Application
var (plugins, commands, configurations) = pluginImporter.DiscoverAssemblyPluginImplementations();
foreach (var pluginType in plugins)
{
defaultLogger.LogDebug("Registered plugin type {Name}", pluginType.FullName);
serviceCollection.AddSingleton(typeof(IPlugin), pluginType);
var isV2 = pluginType.GetInterface(nameof(IPluginV2), false) != null;
defaultLogger.LogDebug("Registering plugin type {Name}", pluginType.FullName);
serviceCollection.AddSingleton(!isV2 ? typeof(IPlugin) : typeof(IPluginV2), pluginType);
try
{
var registrationMethod = pluginType.GetMethod(nameof(IPluginV2.RegisterDependencies));
registrationMethod?.Invoke(null, new object[] { serviceCollection });
}
catch (Exception ex)
{
defaultLogger.LogError(ex, "Could not register plugin of type {Type}", pluginType.Name);
}
}
// register the plugin commands
@ -351,13 +361,11 @@ namespace IW4MAdmin.Application
/// <summary>
/// Configures the dependency injection services
/// </summary>
private static async Task<IServiceCollection> ConfigureServices(string[] args)
private static void ConfigureServices(IServiceCollection serviceCollection)
{
// todo: this is a quick fix
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
// setup the static resources (config/master api/translations)
var serviceCollection = new ServiceCollection();
serviceCollection.AddConfiguration<ApplicationConfiguration>("IW4MAdminSettings")
.AddConfiguration<DefaultSettings>()
.AddConfiguration<CommandConfiguration>()
@ -365,14 +373,10 @@ namespace IW4MAdmin.Application
// for legacy purposes. update at some point
var appConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
await appConfigHandler.BuildAsync();
var defaultConfigHandler = new BaseConfigurationHandler<DefaultSettings>("DefaultSettings");
await defaultConfigHandler.BuildAsync();
appConfigHandler.BuildAsync().GetAwaiter().GetResult();
var commandConfigHandler = new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration");
await commandConfigHandler.BuildAsync();
var statsCommandHandler = new BaseConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
await statsCommandHandler.BuildAsync();
var defaultConfig = defaultConfigHandler.Configuration();
commandConfigHandler.BuildAsync().GetAwaiter().GetResult();
var appConfig = appConfigHandler.Configuration();
var masterUri = Utilities.IsDevelopment
? new Uri("http://127.0.0.1:8080")
@ -385,13 +389,6 @@ namespace IW4MAdmin.Application
var masterRestClient = RestClient.For<IMasterApi>(httpClient);
var translationLookup = Configure.Initialize(Utilities.DefaultLogger, masterRestClient, appConfig);
if (appConfig == null)
{
appConfig = (ApplicationConfiguration) new ApplicationConfiguration().Generate();
appConfigHandler.Set(appConfig);
await appConfigHandler.Save();
}
// register override level names
foreach (var (key, value) in appConfig.OverridePermissionLevelNames)
{
@ -402,17 +399,10 @@ namespace IW4MAdmin.Application
}
// build the dependency list
HandlePluginRegistration(appConfig, serviceCollection, masterRestClient);
serviceCollection
.AddBaseLogger(appConfig)
.AddSingleton(defaultConfig)
.AddSingleton<IServiceCollection>(serviceCollection)
.AddSingleton<IConfigurationHandler<DefaultSettings>, BaseConfigurationHandler<DefaultSettings>>()
.AddSingleton((IConfigurationHandler<ApplicationConfiguration>) appConfigHandler)
.AddSingleton<IConfigurationHandler<CommandConfiguration>>(commandConfigHandler)
.AddSingleton(appConfig)
.AddSingleton(statsCommandHandler.Configuration() ?? new StatsConfiguration())
.AddSingleton(serviceProvider =>
serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>()
.Configuration() ?? new CommandConfiguration())
@ -464,7 +454,9 @@ namespace IW4MAdmin.Application
.AddSingleton<IServerDataCollector, ServerDataCollector>()
.AddSingleton<IGeoLocationService>(new GeoLocationService(Path.Join(".", "Resources", "GeoLite2-Country.mmdb")))
.AddSingleton<IAlertManager, AlertManager>()
#pragma warning disable CS0618
.AddTransient<IScriptPluginTimerHelper, ScriptPluginTimerHelper>()
#pragma warning restore CS0618
.AddSingleton<IInteractionRegistration, InteractionRegistration>()
.AddSingleton<IRemoteCommandService, RemoteCommandService>()
.AddSingleton(new ConfigurationWatcher())
@ -472,19 +464,10 @@ namespace IW4MAdmin.Application
.AddSingleton<IScriptPluginFactory, ScriptPluginFactory>()
.AddSingleton(translationLookup)
.AddDatabaseContextOptions(appConfig);
if (args.Contains("serialevents"))
{
serviceCollection.AddSingleton<IEventHandler, SerialGameEventHandler>();
}
else
{
serviceCollection.AddSingleton<IEventHandler, GameEventHandler>();
}
serviceCollection.AddSingleton<ICoreEventHandler, CoreEventHandler>();
serviceCollection.AddSource();
return serviceCollection;
HandlePluginRegistration(appConfig, serviceCollection, masterRestClient);
}
private static ILogger BuildDefaultLogger<T>(ApplicationConfiguration appConfig)

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
@ -27,7 +28,7 @@ public class RemoteCommandService : IRemoteCommandService
public async Task<IEnumerable<CommandResponseInfo>> Execute(int originId, int? targetId, string command,
IEnumerable<string> arguments, Server server)
{
var (success, result) = await ExecuteWithResult(originId, targetId, command, arguments, server);
var (_, result) = await ExecuteWithResult(originId, targetId, command, arguments, server);
return result;
}
@ -56,7 +57,8 @@ public class RemoteCommandService : IRemoteCommandService
: $"{_appConfig.CommandPrefix}{command}",
Origin = client,
Owner = server,
IsRemote = true
IsRemote = true,
CorrelationId = Guid.NewGuid()
};
server.Manager.AddEvent(remoteEvent);
@ -72,7 +74,7 @@ public class RemoteCommandService : IRemoteCommandService
{
response = new[]
{
new CommandResponseInfo()
new CommandResponseInfo
{
ClientId = client.ClientId,
Response = Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_COMMAND_TIMEOUT"]
@ -90,7 +92,7 @@ public class RemoteCommandService : IRemoteCommandService
}
}
catch (System.OperationCanceledException)
catch (OperationCanceledException)
{
response = new[]
{

View File

@ -12,8 +12,10 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Events.Management;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Interfaces.Events;
namespace IW4MAdmin.Application.Misc
{
@ -24,28 +26,20 @@ namespace IW4MAdmin.Application.Misc
private readonly IManager _manager;
private readonly IDatabaseContextFactory _contextFactory;
private readonly ApplicationConfiguration _appConfig;
private readonly IEventPublisher _eventPublisher;
private bool _inProgress;
private TimeSpan _period;
public ServerDataCollector(ILogger<ServerDataCollector> logger, ApplicationConfiguration appConfig,
IManager manager, IDatabaseContextFactory contextFactory, IEventPublisher eventPublisher)
IManager manager, IDatabaseContextFactory contextFactory)
{
_logger = logger;
_appConfig = appConfig;
_manager = manager;
_contextFactory = contextFactory;
_eventPublisher = eventPublisher;
_eventPublisher.OnClientConnect += SaveConnectionInfo;
_eventPublisher.OnClientDisconnect += SaveConnectionInfo;
}
~ServerDataCollector()
{
_eventPublisher.OnClientConnect -= SaveConnectionInfo;
_eventPublisher.OnClientDisconnect -= SaveConnectionInfo;
IManagementEventSubscriptions.ClientStateAuthorized += SaveConnectionInfo;
IManagementEventSubscriptions.ClientStateDisposed += SaveConnectionInfo;
}
public async Task BeginCollectionAsync(TimeSpan? period = null, CancellationToken cancellationToken = default)
@ -131,18 +125,19 @@ namespace IW4MAdmin.Application.Misc
await context.SaveChangesAsync(token);
}
private void SaveConnectionInfo(object sender, GameEvent gameEvent)
private async Task SaveConnectionInfo(ClientStateEvent stateEvent, CancellationToken token)
{
using var context = _contextFactory.CreateContext(enableTracking: false);
await using var context = _contextFactory.CreateContext(enableTracking: false);
context.ConnectionHistory.Add(new EFClientConnectionHistory
{
ClientId = gameEvent.Origin.ClientId,
ServerId = gameEvent.Owner.GetIdForServer().Result,
ConnectionType = gameEvent.Type == GameEvent.EventType.Connect
ClientId = stateEvent.Client.ClientId,
ServerId = await stateEvent.Client.CurrentServer.GetIdForServer(),
ConnectionType = stateEvent is ClientStateAuthorizeEvent
? Reference.ConnectionType.Connect
: Reference.ConnectionType.Disconnect
});
context.SaveChanges();
await context.SaveChangesAsync();
}
}
}

View File

@ -176,7 +176,7 @@ namespace IW4MAdmin.Application.Misc
.Where(rating => rating.Client.Level != EFClient.Permission.Banned)
.Where(rating => rating.Ranking != null)
.CountAsync(cancellationToken);
}, nameof(_rankedClientsCache), serverId is null ? null: new[] { (object)serverId }, _cacheTimeSpan);
}, nameof(_rankedClientsCache), serverId is null ? null: new[] { (object)serverId }, _cacheTimeSpan);
try
{

View File

@ -151,10 +151,12 @@ public class ScriptPluginV2 : IPluginV2
}
ScriptEngine.Execute(pluginScript);
#pragma warning disable CS8974
var initResult = ScriptEngine.Call("init", JsValue.FromObject(ScriptEngine, EventCallbackWrapper),
JsValue.FromObject(ScriptEngine, _pluginServiceResolver),
JsValue.FromObject(ScriptEngine, _scriptPluginConfigurationWrapper),
JsValue.FromObject(ScriptEngine, new ScriptPluginHelper(manager, this)));
#pragma warning restore CS8974
if (initResult.IsNull() || initResult.IsUndefined())
{

View File

@ -142,12 +142,12 @@ public class ClientResourceQueryHelper : IResourceQueryHelper<ClientResourceRequ
});
}
private static Func<IGrouping<int, ClientResourceResponse>, DateTime> SearchByAliasLocal(string? clientName,
string? ipAddress)
private static Func<IGrouping<int, ClientResourceResponse>, DateTime> SearchByAliasLocal(string clientName,
string ipAddress)
{
return group =>
{
ClientResourceResponse? match = null;
ClientResourceResponse match = null;
var lowercaseClientName = clientName?.ToLower();
if (!string.IsNullOrWhiteSpace(lowercaseClientName))