Compare commits

..

6 Commits

441 changed files with 8238 additions and 55772 deletions

1
.gitignore vendored
View File

@ -224,6 +224,7 @@ bootstrap-custom.min.css
bootstrap-custom.css bootstrap-custom.css
**/Master/static **/Master/static
**/Master/dev_env **/Master/dev_env
/WebfrontCore/Views/Plugins/*
/WebfrontCore/wwwroot/**/dds /WebfrontCore/wwwroot/**/dds
/WebfrontCore/wwwroot/images/radar/* /WebfrontCore/wwwroot/images/radar/*

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish> <MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<PackageId>RaidMax.IW4MAdmin.Application</PackageId> <PackageId>RaidMax.IW4MAdmin.Application</PackageId>
<Version>2020.0.0.0</Version> <Version>2020.0.0.0</Version>
@ -24,15 +24,14 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Jint" Version="3.0.0-beta-2037" /> <PackageReference Include="Jint" Version="3.0.0-beta-1632" />
<PackageReference Include="MaxMind.GeoIP2" Version="5.1.0" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.10">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.10" />
<PackageReference Include="RestEase" Version="1.5.5" /> <PackageReference Include="RestEase" Version="1.5.1" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
@ -64,9 +63,6 @@
<None Update="Configuration\LoggingConfiguration.json"> <None Update="Configuration\LoggingConfiguration.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None> </None>
<None Update="Resources\GeoLite2-Country.mmdb">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup> </ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent"> <Target Name="PreBuild" BeforeTargets="PreBuildEvent">

View File

@ -15,7 +15,6 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
@ -27,7 +26,6 @@ using IW4MAdmin.Application.Migration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Serilog.Context; using Serilog.Context;
using SharedLibraryCore.Formatting;
using static SharedLibraryCore.GameEvent; using static SharedLibraryCore.GameEvent;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
using ObsoleteLogger = SharedLibraryCore.Interfaces.ILogger; using ObsoleteLogger = SharedLibraryCore.Interfaces.ILogger;
@ -59,6 +57,7 @@ namespace IW4MAdmin.Application
readonly PenaltyService PenaltySvc; readonly PenaltyService PenaltySvc;
public IConfigurationHandler<ApplicationConfiguration> ConfigHandler; public IConfigurationHandler<ApplicationConfiguration> ConfigHandler;
readonly IPageList PageList; readonly IPageList PageList;
private readonly IMetaService _metaService;
private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0); private readonly TimeSpan _throttleTimeout = new TimeSpan(0, 1, 0);
private readonly CancellationTokenSource _tokenSource; private readonly CancellationTokenSource _tokenSource;
private readonly Dictionary<string, Task<IList>> _operationLookup = new Dictionary<string, Task<IList>>(); private readonly Dictionary<string, Task<IList>> _operationLookup = new Dictionary<string, Task<IList>>();
@ -80,7 +79,7 @@ namespace IW4MAdmin.Application
ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration, ITranslationLookup translationLookup, IConfigurationHandler<CommandConfiguration> commandConfiguration,
IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory, IConfigurationHandler<ApplicationConfiguration> appConfigHandler, IGameServerInstanceFactory serverInstanceFactory,
IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents, IEnumerable<IPlugin> plugins, IParserRegexFactory parserRegexFactory, IEnumerable<IRegisterEvent> customParserEvents,
IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory, IEventHandler eventHandler, IScriptCommandFactory scriptCommandFactory, IDatabaseContextFactory contextFactory, IMetaService metaService,
IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver, ClientService clientService, IServiceProvider serviceProvider, IMetaRegistration metaRegistration, IScriptPluginServiceResolver scriptPluginServiceResolver, ClientService clientService, IServiceProvider serviceProvider,
ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService) ChangeHistoryService changeHistoryService, ApplicationConfiguration appConfig, PenaltyService penaltyService)
{ {
@ -96,6 +95,7 @@ namespace IW4MAdmin.Application
AdditionalRConParsers = new List<IRConParser>() { new BaseRConParser(serviceProvider.GetRequiredService<ILogger<BaseRConParser>>(), parserRegexFactory) }; AdditionalRConParsers = new List<IRConParser>() { new BaseRConParser(serviceProvider.GetRequiredService<ILogger<BaseRConParser>>(), parserRegexFactory) };
TokenAuthenticator = new TokenAuthentication(); TokenAuthenticator = new TokenAuthentication();
_logger = logger; _logger = logger;
_metaService = metaService;
_tokenSource = new CancellationTokenSource(); _tokenSource = new CancellationTokenSource();
_commands = commands.ToList(); _commands = commands.ToList();
_translationLookup = translationLookup; _translationLookup = translationLookup;
@ -217,8 +217,6 @@ namespace IW4MAdmin.Application
return _commands; return _commands;
} }
public IReadOnlyList<IManagerCommand> Commands => _commands.ToImmutableList();
public async Task UpdateServerStates() public async Task UpdateServerStates()
{ {
// store the server hash code and task for it // store the server hash code and task for it
@ -234,6 +232,13 @@ namespace IW4MAdmin.Application
.Select(ut => ut.Key) .Select(ut => ut.Key)
.ToList(); .ToList();
// this is to prevent the log reader from starting before the initial
// query of players on the server
if (serverTasksToRemove.Count > 0)
{
IsInitialized = true;
}
// remove the update tasks as they have completed // remove the update tasks as they have completed
foreach (var serverId in serverTasksToRemove.Where(serverId => runningUpdateTasks.ContainsKey(serverId))) foreach (var serverId in serverTasksToRemove.Where(serverId => runningUpdateTasks.ContainsKey(serverId)))
{ {
@ -346,10 +351,10 @@ namespace IW4MAdmin.Application
// copy over default config if it doesn't exist // copy over default config if it doesn't exist
if (!_appConfig.Servers?.Any() ?? true) if (!_appConfig.Servers?.Any() ?? true)
{ {
var defaultHandler = new BaseConfigurationHandler<DefaultSettings>("DefaultSettings"); var defaultConfig = new BaseConfigurationHandler<DefaultSettings>("DefaultSettings").Configuration();
await defaultHandler.BuildAsync(); //ConfigHandler.Set((ApplicationConfiguration)new ApplicationConfiguration().Generate());
var defaultConfig = defaultHandler.Configuration(); //var newConfig = ConfigHandler.Configuration();
_appConfig.AutoMessages = defaultConfig.AutoMessages; _appConfig.AutoMessages = defaultConfig.AutoMessages;
_appConfig.GlobalRules = defaultConfig.GlobalRules; _appConfig.GlobalRules = defaultConfig.GlobalRules;
_appConfig.DisallowedClientNames = defaultConfig.DisallowedClientNames; _appConfig.DisallowedClientNames = defaultConfig.DisallowedClientNames;
@ -410,7 +415,7 @@ namespace IW4MAdmin.Application
if (!validationResult.IsValid) if (!validationResult.IsValid)
{ {
throw new ConfigurationException("Could not validate configuration") throw new ConfigurationException("MANAGER_CONFIGURATION_ERROR")
{ {
Errors = validationResult.Errors.Select(_error => _error.ErrorMessage).ToArray(), Errors = validationResult.Errors.Select(_error => _error.ErrorMessage).ToArray(),
ConfigurationFileName = ConfigHandler.FileName ConfigurationFileName = ConfigHandler.FileName
@ -447,17 +452,6 @@ namespace IW4MAdmin.Application
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(_appConfig.CustomParserEncoding) ? _appConfig.CustomParserEncoding : "windows-1252"); Utilities.EncodingType = Encoding.GetEncoding(!string.IsNullOrEmpty(_appConfig.CustomParserEncoding) ? _appConfig.CustomParserEncoding : "windows-1252");
foreach (var parser in AdditionalRConParsers)
{
if (!parser.Configuration.ColorCodeMapping.ContainsKey(ColorCodes.Accent.ToString()))
{
parser.Configuration.ColorCodeMapping.Add(ColorCodes.Accent.ToString(),
parser.Configuration.ColorCodeMapping.TryGetValue(_appConfig.IngameAccentColorKey, out var colorCode)
? colorCode
: "");
}
}
#endregion #endregion
#region COMMANDS #region COMMANDS
@ -483,17 +477,13 @@ namespace IW4MAdmin.Application
// this is because I want to store the command prefix in IW4MAdminSettings, but can't easily // this is because I want to store the command prefix in IW4MAdminSettings, but can't easily
// inject it to all the places that need it // inject it to all the places that need it
cmdConfig.CommandPrefix = _appConfig?.CommandPrefix ?? "!"; cmdConfig.CommandPrefix = _appConfig.CommandPrefix;
cmdConfig.BroadcastCommandPrefix = _appConfig?.BroadcastCommandPrefix ?? "@"; cmdConfig.BroadcastCommandPrefix = _appConfig.BroadcastCommandPrefix;
foreach (var cmd in commandsToAddToConfig) foreach (var cmd in commandsToAddToConfig)
{ {
if (cmdConfig.Commands.ContainsKey(cmd.CommandConfigNameForType()))
{
continue;
}
cmdConfig.Commands.Add(cmd.CommandConfigNameForType(), cmdConfig.Commands.Add(cmd.CommandConfigNameForType(),
new CommandProperties new CommandProperties()
{ {
Name = cmd.Name, Name = cmd.Name,
Alias = cmd.Alias, Alias = cmd.Alias,
@ -521,7 +511,6 @@ namespace IW4MAdmin.Application
Console.WriteLine(_translationLookup["MANAGER_COMMUNICATION_INFO"]); Console.WriteLine(_translationLookup["MANAGER_COMMUNICATION_INFO"]);
await InitializeServers(); await InitializeServers();
IsInitialized = true;
} }
private async Task InitializeServers() private async Task InitializeServers()
@ -543,7 +532,7 @@ namespace IW4MAdmin.Application
_servers.Add(ServerInstance); _servers.Add(ServerInstance);
Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"].FormatExt(ServerInstance.Hostname.StripColors())); Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_MONITORING_TEXT"].FormatExt(ServerInstance.Hostname.StripColors()));
_logger.LogInformation("Finishing initialization and now monitoring [{Server}]", ServerInstance.Hostname); _logger.LogInformation("Finishing initialization and now monitoring [{server}]", ServerInstance.Hostname, ServerInstance.ToString());
} }
// add the start event for this server // add the start event for this server
@ -587,29 +576,16 @@ namespace IW4MAdmin.Application
public async Task Start() => await UpdateServerStates(); public async Task Start() => await UpdateServerStates();
public async Task Stop() public void Stop()
{ {
foreach (var plugin in Plugins)
{
try
{
await plugin.OnUnloadAsync().WithTimeout(Utilities.DefaultCommandTimeout);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not cleanly unload plugin {PluginName}", plugin.Name);
}
}
_tokenSource.Cancel(); _tokenSource.Cancel();
IsRunning = false; IsRunning = false;
} }
public void Restart() public void Restart()
{ {
IsRestartRequested = true; IsRestartRequested = true;
Stop().GetAwaiter().GetResult(); Stop();
} }
[Obsolete] [Obsolete]

View File

@ -13,5 +13,4 @@ if not exist "%TargetDir%Plugins" (
md "%TargetDir%Plugins" md "%TargetDir%Plugins"
) )
xcopy /y "%SolutionDir%Build\Plugins" "%TargetDir%Plugins\" xcopy /y "%SolutionDir%Build\Plugins" "%TargetDir%Plugins\"
del "%TargetDir%Plugins\SQLite*"

View File

@ -23,7 +23,6 @@ echo setting up default folders
if not exist "%PublishDir%\Configuration" md "%PublishDir%\Configuration" if not exist "%PublishDir%\Configuration" md "%PublishDir%\Configuration"
move "%PublishDir%\DefaultSettings.json" "%PublishDir%\Configuration\" move "%PublishDir%\DefaultSettings.json" "%PublishDir%\Configuration\"
if not exist "%PublishDir%\Lib\" md "%PublishDir%\Lib\" if not exist "%PublishDir%\Lib\" md "%PublishDir%\Lib\"
del "%PublishDir%\Microsoft.CodeAnalysis*.dll" /F /Q
move "%PublishDir%\*.dll" "%PublishDir%\Lib\" move "%PublishDir%\*.dll" "%PublishDir%\Lib\"
move "%PublishDir%\*.json" "%PublishDir%\Lib\" move "%PublishDir%\*.json" "%PublishDir%\Lib\"
move "%PublishDir%\runtimes" "%PublishDir%\Lib\runtimes" move "%PublishDir%\runtimes" "%PublishDir%\Lib\runtimes"
@ -31,37 +30,16 @@ move "%PublishDir%\ru" "%PublishDir%\Lib\ru"
move "%PublishDir%\de" "%PublishDir%\Lib\de" move "%PublishDir%\de" "%PublishDir%\Lib\de"
move "%PublishDir%\pt" "%PublishDir%\Lib\pt" move "%PublishDir%\pt" "%PublishDir%\Lib\pt"
move "%PublishDir%\es" "%PublishDir%\Lib\es" move "%PublishDir%\es" "%PublishDir%\Lib\es"
rmdir /Q /S "%PublishDir%\cs"
rmdir /Q /S "%PublishDir%\fr"
rmdir /Q /S "%PublishDir%\it"
rmdir /Q /S "%PublishDir%\ja"
rmdir /Q /S "%PublishDir%\ko"
rmdir /Q /S "%PublishDir%\pl"
rmdir /Q /S "%PublishDir%\pt-BR"
rmdir /Q /S "%PublishDir%\tr"
rmdir /Q /S "%PublishDir%\zh-Hans"
rmdir /Q /S "%PublishDir%\zh-Hant"
if exist "%PublishDir%\refs" move "%PublishDir%\refs" "%PublishDir%\Lib\refs" if exist "%PublishDir%\refs" move "%PublishDir%\refs" "%PublishDir%\Lib\refs"
echo making start scripts echo making start scripts
@(echo @echo off && echo @title IW4MAdmin && echo set DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%PublishDir%\StartIW4MAdmin.cmd" @(echo @echo off && echo @title IW4MAdmin && echo set DOTNET_CLI_TELEMETRY_OPTOUT=1 && echo dotnet Lib\IW4MAdmin.dll && echo pause) > "%PublishDir%\StartIW4MAdmin.cmd"
@(echo #!/bin/bash&& echo export DOTNET_CLI_TELEMETRY_OPTOUT=1&& echo dotnet Lib/IW4MAdmin.dll) > "%PublishDir%\StartIW4MAdmin.sh" @(echo #!/bin/bash&& echo export DOTNET_CLI_TELEMETRY_OPTOUT=1&& echo dotnet Lib/IW4MAdmin.dll) > "%PublishDir%\StartIW4MAdmin.sh"
echo copying update scripts
copy "%SourceDir%\DeploymentFiles\UpdateIW4MAdmin.ps1" "%PublishDir%\UpdateIW4MAdmin.ps1"
copy "%SourceDir%\DeploymentFiles\UpdateIW4MAdmin.sh" "%PublishDir%\UpdateIW4MAdmin.sh"
echo moving front-end library dependencies echo moving front-end library dependencies
if not exist "%PublishDir%\wwwroot\font" mkdir "%PublishDir%\wwwroot\font" if not exist "%PublishDir%\wwwroot\font" mkdir "%PublishDir%\wwwroot\font"
move "WebfrontCore\wwwroot\lib\open-iconic\font\fonts\*.*" "%PublishDir%\wwwroot\font\" move "WebfrontCore\wwwroot\lib\open-iconic\font\fonts\*.*" "%PublishDir%\wwwroot\font\"
if exist "%PublishDir%\wwwroot\lib" rd /s /q "%PublishDir%\wwwroot\lib" if exist "%PublishDir%\wwwroot\lib" rd /s /q "%PublishDir%\wwwroot\lib"
if not exist "%PublishDir%\wwwroot\css" mkdir "%PublishDir%\wwwroot\css"
move "WebfrontCore\wwwroot\css\global.min.css" "%PublishDir%\wwwroot\css\global.min.css"
if not exist "%PublishDir%\wwwroot\js" mkdir "%PublishDir%\wwwroot\js"
move "%SourceDir%\WebfrontCore\wwwroot\js\global.min.js" "%PublishDir%\wwwroot\js\global.min.js"
if not exist "%PublishDir%\wwwroot\images" mkdir "%PublishDir%\wwwroot\images"
xcopy "%SourceDir%\WebfrontCore\wwwroot\images" "%PublishDir%\wwwroot\images" /E /H /C /I
echo setting permissions... echo setting permissions...
cacls "%PublishDir%" /t /e /p Everyone:F cacls "%PublishDir%" /t /e /p Everyone:F

View File

@ -1,64 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Data.Models;
using Data.Models.Client;
using Microsoft.Extensions.Logging;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands.ClientTags
{
public class AddClientTagCommand : Command
{
private readonly IMetaServiceV2 _metaService;
public AddClientTagCommand(ILogger<AddClientTagCommand> commandLogger, CommandConfiguration config,
ITranslationLookup layout, IMetaServiceV2 metaService) :
base(config, layout)
{
Name = "addclienttag";
Description = layout["COMMANDS_ADD_CLIENT_TAG_DESC"];
Alias = "act";
Permission = EFClient.Permission.Owner;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGUMENT_TAG"],
Required = true
}
};
_metaService = metaService;
logger = commandLogger;
}
public override async Task ExecuteAsync(GameEvent gameEvent)
{
var existingTags = await _metaService.GetPersistentMetaValue<List<TagMeta>>(EFMeta.ClientTagNameV2) ??
new List<TagMeta>();
var tagName = gameEvent.Data.Trim();
if (existingTags.Any(tag => tag.TagName == tagName))
{
logger.LogWarning("Tag with name {TagName} already exists", tagName);
return;
}
existingTags.Add(new TagMeta
{
Id = (existingTags.LastOrDefault()?.TagId ?? 0) + 1,
Value = tagName
});
await _metaService.SetPersistentMetaValue(EFMeta.ClientTagNameV2, existingTags,
gameEvent.Owner.Manager.CancellationToken);
gameEvent.Origin.Tell(_translationLookup["COMMANDS_ADD_CLIENT_TAG_SUCCESS"].FormatExt(gameEvent.Data));
}
}
}

View File

@ -1,38 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Data.Models;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands.ClientTags
{
public class ListClientTags : Command
{
private readonly IMetaServiceV2 _metaService;
public ListClientTags(CommandConfiguration config, ITranslationLookup layout, IMetaServiceV2 metaService) : base(
config, layout)
{
Name = "listclienttags";
Description = layout["COMMANDS_LIST_CLIENT_TAGS_DESC"];
Alias = "lct";
Permission = EFClient.Permission.Owner;
RequiresTarget = false;
_metaService = metaService;
}
public override async Task ExecuteAsync(GameEvent gameEvent)
{
var tags = await _metaService.GetPersistentMetaValue<List<TagMeta>>(EFMeta.ClientTagNameV2);
if (tags is not null)
{
await gameEvent.Origin.TellAsync(tags.Select(tag => tag.TagName),
gameEvent.Owner.Manager.CancellationToken);
}
}
}
}

View File

@ -1,13 +0,0 @@
using System.Text.Json.Serialization;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands.ClientTags;
public class TagMeta : ILookupValue<string>
{
[JsonIgnore] public int TagId => Id;
[JsonIgnore] public string TagName => Value;
public int Id { get; set; }
public string Value { get; set; }
}

View File

@ -1,59 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Finds player by name
/// </summary>
public class FindPlayerCommand : Command
{
public FindPlayerCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "find";
Description = _translationLookup["COMMANDS_FIND_DESC"];
Alias = "f";
Permission = EFClient.Permission.Administrator;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument()
{
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
Required = true
}
};
}
public override async Task ExecuteAsync(GameEvent gameEvent)
{
if (gameEvent.Data.Length < 3)
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_FIND_MIN"]);
return;
}
var players = await gameEvent.Owner.Manager.GetClientService().FindClientsByIdentifier(gameEvent.Data);
if (!players.Any())
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_FIND_EMPTY"]);
return;
}
foreach (var client in players)
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_FIND_FORMAT_V2"].FormatExt(client.Name,
client.ClientId, Utilities.ConvertLevelToColor((EFClient.Permission) client.LevelInt, client.Level),
client.IPAddress, (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture()));
}
}
}
}

View File

@ -1,93 +0,0 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Prints help information
/// </summary>
public class HelpCommand : Command
{
public HelpCommand(CommandConfiguration config, ITranslationLookup translationLookup) :
base(config, translationLookup)
{
Name = "help";
Description = translationLookup["COMMANDS_HELP_DESC"];
Alias = "h";
Permission = EFClient.Permission.User;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument
{
Name = translationLookup["COMMANDS_ARGS_COMMANDS"],
Required = false
}
};
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
var searchTerm = gameEvent.Data.Trim();
var availableCommands = gameEvent.Owner.Manager.Commands.Distinct().Where(command =>
command.SupportedGames == null || !command.SupportedGames.Any() ||
command.SupportedGames.Contains(gameEvent.Owner.GameName))
.Where(command => gameEvent.Origin.Level >= command.Permission);
if (searchTerm.Length > 2)
{
var matchingCommand = availableCommands.FirstOrDefault(command =>
command.Name.Equals(searchTerm, StringComparison.InvariantCultureIgnoreCase) ||
command.Alias.Equals(searchTerm, StringComparison.InvariantCultureIgnoreCase));
if (matchingCommand != null)
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_HELP_SEARCH_RESULT"]
.FormatExt(matchingCommand.Name, matchingCommand.Alias));
gameEvent.Origin.Tell(matchingCommand.Syntax);
}
else
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_HELP_NOTFOUND"]);
}
}
else
{
var commandStrings = availableCommands.Select((command, index) =>
new
{
response = $" {_translationLookup["COMMANDS_HELP_LIST_FORMAT"].FormatExt(command.Name)} ",
index
});
var helpResponse = new StringBuilder();
foreach (var item in commandStrings)
{
helpResponse.Append(item.response);
if (item.index == 0 || item.index % 4 != 0)
{
continue;
}
gameEvent.Origin.Tell(helpResponse.ToString());
helpResponse = new StringBuilder();
}
gameEvent.Origin.Tell(helpResponse.ToString());
gameEvent.Origin.Tell(_translationLookup["COMMANDS_HELP_MOREINFO"]);
}
return Task.CompletedTask;
}
}
}

View File

@ -1,50 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Lists all unmasked admins
/// </summary>
public class ListAdminsCommand : Command
{
public ListAdminsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "admins";
Description = _translationLookup["COMMANDS_ADMINS_DESC"];
Alias = "a";
Permission = EFClient.Permission.User;
RequiresTarget = false;
}
public static string OnlineAdmins(Server server, ITranslationLookup lookup)
{
var onlineAdmins = server.GetClientsAsList()
.Where(p => p.Level > EFClient.Permission.Flagged)
.Where(p => !p.Masked)
.Select(p =>
$"[(Color::Yellow){Utilities.ConvertLevelToColor(p.Level, p.ClientPermission.Name)}(Color::White)] {p.Name}")
.ToList();
return onlineAdmins.Any() ? string.Join(Environment.NewLine, onlineAdmins) : lookup["COMMANDS_ADMINS_NONE"];
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
foreach (var line in OnlineAdmins(gameEvent.Owner, _translationLookup).Split(Environment.NewLine))
{
var _ = gameEvent.Message.IsBroadcastCommand(_config.BroadcastCommandPrefix)
? gameEvent.Owner.Broadcast(line)
: gameEvent.Origin.Tell(line);
}
return Task.CompletedTask;
}
}
}

View File

@ -1,57 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Lists alises of specified client
/// </summary>
public class ListAliasesCommand : Command
{
public ListAliasesCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "alias";
Description = _translationLookup["COMMANDS_ALIAS_DESC"];
Alias = "known";
Permission = EFClient.Permission.Moderator;
RequiresTarget = true;
Arguments = new[]
{
new CommandArgument()
{
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
Required = true,
}
};
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
var message = new StringBuilder();
var names = new List<string>(gameEvent.Target.AliasLink.Children.Select(a => a.Name));
var ips = new List<string>(gameEvent.Target.AliasLink.Children.Select(a => a.IPAddress.ConvertIPtoString())
.Distinct());
gameEvent.Origin.Tell($"[(Color::Accent){gameEvent.Target}(Color::White)]");
message.Append($"{_translationLookup["COMMANDS_ALIAS_ALIASES"]}: ");
message.Append(string.Join(" | ", names));
gameEvent.Origin.Tell(message.ToString());
message.Clear();
message.Append($"{_translationLookup["COMMANDS_ALIAS_IPS"]}: ");
message.Append(string.Join(" | ", ips));
gameEvent.Origin.Tell(message.ToString());
return Task.CompletedTask;
}
}
}

View File

@ -1,37 +0,0 @@
using System.Linq;
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// List online clients
/// </summary>
public class ListClientsCommand : Command
{
public ListClientsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "list";
Description = _translationLookup["COMMANDS_LIST_DESC"];
Alias = "l";
Permission = EFClient.Permission.Moderator;
RequiresTarget = false;
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
var clientList = gameEvent.Owner.GetClientsAsList()
.Select(client =>
$"[(Color::Accent){client.ClientPermission.Name}(Color::White){(string.IsNullOrEmpty(client.Tag) ? "" : $" {client.Tag}")}(Color::White)][(Color::Yellow)#{client.ClientNumber}(Color::White)] {client.Name}")
.ToArray();
gameEvent.Origin.TellAsync(clientList, gameEvent.Owner.Manager.CancellationToken);
return Task.CompletedTask;
}
}
}

View File

@ -1,41 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Lists the loaded plugins
/// </summary>
public class ListPluginsCommand : Command
{
private readonly IEnumerable<IPlugin> _plugins;
public ListPluginsCommand(CommandConfiguration config, ITranslationLookup translationLookup,
IEnumerable<IPlugin> plugins) : base(config, translationLookup)
{
Name = "plugins";
Description = _translationLookup["COMMANDS_PLUGINS_DESC"];
Alias = "p";
Permission = EFClient.Permission.Administrator;
RequiresTarget = false;
_plugins = plugins;
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_PLUGINS_LOADED"]);
foreach (var plugin in _plugins.Where(plugin => !plugin.IsParser))
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_LIST_PLUGINS_FORMAT"]
.FormatExt(plugin.Name, plugin.Version, plugin.Author));
}
return Task.CompletedTask;
}
}
}

View File

@ -1,59 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// List all reports on the server
/// </summary>
public class ListReportsCommand : Command
{
public ListReportsCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "reports";
Description = _translationLookup["COMMANDS_REPORTS_DESC"];
Alias = "reps";
Permission = EFClient.Permission.Moderator;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_CLEAR"],
Required = false
}
};
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
if (gameEvent.Data != null && gameEvent.Data.ToLower().Contains(_translationLookup["COMMANDS_ARGS_CLEAR"]))
{
gameEvent.Owner.Reports = new List<Report>();
gameEvent.Origin.Tell(_translationLookup["COMMANDS_REPORTS_CLEAR_SUCCESS"]);
return Task.CompletedTask;
}
if (gameEvent.Owner.Reports.Count < 1)
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_REPORTS_NONE"]);
return Task.CompletedTask;
}
foreach (var report in gameEvent.Owner.Reports)
{
gameEvent.Origin.Tell(
$"(Color::Accent){report.Origin.Name}(Color::White) -> (Color::Red){report.Target.Name}(Color::White): {report.Reason}");
}
return Task.CompletedTask;
}
}
}

View File

@ -1,116 +0,0 @@
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Data.Models.Client;
using IW4MAdmin.Application.Extensions;
using Microsoft.Extensions.Logging;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application.Commands
{
public class MapAndGameTypeCommand : Command
{
private const string ArgumentRegexPattern = "(?:\"([^\"]+)\"|([^\\s]+)) (?:\"([^\"]+)\"|([^\\s]+))";
private readonly ILogger _logger;
private readonly DefaultSettings _defaultSettings;
public MapAndGameTypeCommand(ILogger<MapAndGameTypeCommand> logger, CommandConfiguration config,
DefaultSettings defaultSettings, ITranslationLookup layout) : base(config, layout)
{
Name = "mapandgametype";
Description = _translationLookup["COMMANDS_MAG_DESCRIPTION"];
Alias = "mag";
Permission = EFClient.Permission.Administrator;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument
{
Name = _translationLookup["COMMADS_MAG_ARG_1"],
Required = true
},
new CommandArgument
{
Name = _translationLookup["COMMADS_MAG_ARG_2"],
Required = true
}
};
_logger = logger;
_defaultSettings = defaultSettings;
}
public override async Task ExecuteAsync(GameEvent gameEvent)
{
var match = Regex.Match(gameEvent.Data.Trim(), ArgumentRegexPattern,
RegexOptions.Compiled | RegexOptions.IgnoreCase);
if (!match.Success)
{
gameEvent.Origin.Tell(Syntax);
return;
}
var map = match.Groups[1].Length > 0 ? match.Groups[1].ToString() : match.Groups[2].ToString();
var gametype = match.Groups[3].Length > 0 ? match.Groups[3].ToString() : match.Groups[4].ToString();
var matchingMaps = gameEvent.Owner.FindMap(map);
var matchingGametypes = _defaultSettings.FindGametype(gametype, gameEvent.Owner.GameName);
if (matchingMaps.Count > 1)
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_MAG_MULTIPLE_MAPS"]);
foreach (var matchingMap in matchingMaps)
{
gameEvent.Origin.Tell(
$"[(Color::Yellow){matchingMap.Alias}(Color::White)] [(Color::Yellow){matchingMap.Name}(Color::White)]");
}
return;
}
if (matchingGametypes.Count > 1)
{
gameEvent.Origin.Tell(_translationLookup["COMMANDS_MAG_MULTIPLE_GAMETYPES"]);
foreach (var matchingGametype in matchingGametypes)
{
gameEvent.Origin.Tell(
$"[(Color::Yellow){matchingGametype.Alias}(Color::White)] [(Color::Yellow){matchingGametype.Name}(Color::White)]");
}
return;
}
map = matchingMaps.FirstOrDefault()?.Name ?? map;
gametype = matchingGametypes.FirstOrDefault()?.Name ?? gametype;
var hasMatchingGametype = matchingGametypes.Any();
_logger.LogDebug("Changing map to {Map} and gametype {Gametype}", map, gametype);
await gameEvent.Owner.SetDvarAsync("g_gametype", gametype, gameEvent.Owner.Manager.CancellationToken);
gameEvent.Owner.Broadcast(_translationLookup["COMMANDS_MAP_SUCCESS"].FormatExt(map));
await Task.Delay(gameEvent.Owner.Manager.GetApplicationSettings().Configuration().MapChangeDelaySeconds);
switch (gameEvent.Owner.GameName)
{
case Server.Game.IW5:
await gameEvent.Owner.ExecuteCommandAsync(
$"load_dsr {(hasMatchingGametype ? gametype.ToUpper() + "_default" : gametype)}");
await gameEvent.Owner.ExecuteCommandAsync($"map {map}");
break;
case Server.Game.T6:
await gameEvent.Owner.ExecuteCommandAsync($"exec {gametype}.cfg");
await gameEvent.Owner.ExecuteCommandAsync($"map {map}");
break;
default:
await gameEvent.Owner.ExecuteCommandAsync($"map {map}");
break;
}
}
}
}

View File

@ -1,45 +0,0 @@
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Sends a private message to another player
/// </summary>
public class PrivateMessageCommand : Command
{
public PrivateMessageCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config, translationLookup)
{
Name = "privatemessage";
Description = _translationLookup["COMMANDS_PM_DESC"];
Alias = "pm";
Permission = EFClient.Permission.User;
RequiresTarget = true;
Arguments = new[]
{
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
Required = true
},
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
Required = true
}
};
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
gameEvent.Target.Tell(_translationLookup["COMMANDS_PRIVATE_MESSAGE_FORMAT"].FormatExt(gameEvent.Origin.Name, gameEvent.Data));
gameEvent.Origin.Tell(_translationLookup["COMMANDS_PRIVATE_MESSAGE_RESULT"]
.FormatExt(gameEvent.Target.Name, gameEvent.Data));
return Task.CompletedTask;
}
}
}

View File

@ -1,77 +0,0 @@
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Report client for given reason
/// </summary>
public class ReportClientCommand : Command
{
public ReportClientCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "report";
Description = _translationLookup["COMMANDS_REPORT_DESC"];
Alias = "rep";
Permission = EFClient.Permission.User;
RequiresTarget = true;
Arguments = new[]
{
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_PLAYER"],
Required = true
},
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_REASON"],
Required = true
}
};
}
public override async Task ExecuteAsync(GameEvent commandEvent)
{
if (commandEvent.Data.ToLower().Contains("camp"))
{
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_CAMP"]);
return;
}
var success = false;
switch ((await commandEvent.Target.Report(commandEvent.Data, commandEvent.Origin)
.WaitAsync(Utilities.DefaultCommandTimeout, commandEvent.Owner.Manager.CancellationToken)).FailReason)
{
case GameEvent.EventFailReason.None:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_SUCCESS"]);
success = true;
break;
case GameEvent.EventFailReason.Exception:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_DUPLICATE"]);
break;
case GameEvent.EventFailReason.Permission:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL"]
.FormatExt(commandEvent.Target.Name));
break;
case GameEvent.EventFailReason.Invalid:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_SELF"]);
break;
case GameEvent.EventFailReason.Throttle:
commandEvent.Origin.Tell(_translationLookup["COMMANDS_REPORT_FAIL_TOOMANY"]);
break;
}
if (success)
{
commandEvent.Owner.ToAdmins(
$"(Color::Accent){commandEvent.Origin.Name}(Color::White) -> (Color::Red){commandEvent.Target.Name}(Color::White): {commandEvent.Data}");
}
}
}
}

View File

@ -1,46 +0,0 @@
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Prints out a message to all clients on all servers
/// </summary>
public class SayAllCommand : Command
{
public SayAllCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "sayall";
Description = _translationLookup["COMMANDS_SAY_ALL_DESC"];
Alias = "sa";
Permission = EFClient.Permission.Moderator;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
Required = true
}
};
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
var message = $"(Color::Accent){gameEvent.Origin.Name}(Color::White) - (Color::Red){gameEvent.Data}";
foreach (var server in gameEvent.Owner.Manager.GetServers())
{
server.Broadcast(message, gameEvent.Origin);
}
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SAY_SUCCESS"]);
return Task.CompletedTask;
}
}
}

View File

@ -1,42 +0,0 @@
using System.Threading.Tasks;
using SharedLibraryCore;
using SharedLibraryCore.Commands;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
using EFClient = Data.Models.Client.EFClient;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Prints out a message to all clients on the server
/// </summary>
public class SayCommand : Command
{
public SayCommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "say";
Description = _translationLookup["COMMANDS_SAY_DESC"];
Alias = "s";
Permission = EFClient.Permission.Moderator;
RequiresTarget = false;
Arguments = new[]
{
new CommandArgument
{
Name = _translationLookup["COMMANDS_ARGS_MESSAGE"],
Required = true
}
};
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
gameEvent.Owner.Broadcast(
_translationLookup["COMMANDS_SAY_FORMAT"].FormatExt(gameEvent.Origin.Name, gameEvent.Data),
gameEvent.Origin);
gameEvent.Origin.Tell(_translationLookup["COMMANDS_SAY_SUCCESS"]);
return Task.CompletedTask;
}
}
}

View File

@ -1,38 +0,0 @@
using System.Threading.Tasks;
using Data.Models.Client;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Commands
{
/// <summary>
/// Prints client information
/// </summary>
public class WhoAmICommand : Command
{
public WhoAmICommand(CommandConfiguration config, ITranslationLookup translationLookup) : base(config,
translationLookup)
{
Name = "whoami";
Description = _translationLookup["COMMANDS_WHO_DESC"];
Alias = "who";
Permission = EFClient.Permission.User;
RequiresTarget = false;
}
public override Task ExecuteAsync(GameEvent gameEvent)
{
var you =
"[(Color::Yellow)#{{clientNumber}}(Color::White)] [(Color::Yellow)@{{clientId}}(Color::White)] [{{networkId}}] [{{ip}}] [(Color::Cyan){{level}}(Color::White){{tag}}(Color::White)] {{name}}"
.FormatExt(gameEvent.Origin.ClientNumber,
gameEvent.Origin.ClientId, gameEvent.Origin.GuidString,
gameEvent.Origin.IPAddressString, gameEvent.Origin.ClientPermission.Name,
string.IsNullOrEmpty(gameEvent.Origin.Tag) ? "" : $" {gameEvent.Origin.Tag}",
gameEvent.Origin.Name);
gameEvent.Origin.Tell(you);
return Task.CompletedTask;
}
}
}

View File

@ -18,13 +18,6 @@
"rollingInterval": "Day", "rollingInterval": "Day",
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Server} {Level:u3}] {Message:lj}{NewLine}{Exception}" "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Server} {Level:u3}] {Message:lj}{NewLine}{Exception}"
} }
},
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff} {Server} {Level:u3}] {Message:lj}{NewLine}{Exception}",
"RestrictedToMinimumLevel": "Fatal"
}
} }
], ],
"Enrich": [ "Enrich": [

View File

@ -1,11 +1,11 @@
{ {
"AutoMessagePeriod": 60, "AutoMessagePeriod": 60,
"AutoMessages": [ "AutoMessages": [
"This server uses (Color::Accent)IW4M Admin v{{VERSION}} (Color::White)get it at (Color::Accent)raidmax.org/IW4MAdmin", "This server uses ^5IW4M Admin v{{VERSION}} ^7get it at ^5raidmax.org/IW4MAdmin",
"(Color::Accent)IW4M Admin (Color::White)sees (Color::Accent)YOU!", "^5IW4M Admin ^7sees ^5YOU!",
"{{TOPSTATS}}", "{{TOPSTATS}}",
"This server has seen a total of (Color::Accent){{TOTALPLAYERS}} (Color::White)players!", "This server has seen a total of ^5{{TOTALPLAYERS}} ^7players!",
"Cheaters are (Color::Red)unwelcome (Color::White)on this server", "Cheaters are ^1unwelcome ^7 on this server",
"Did you know 8/10 people agree with unverified statistics?" "Did you know 8/10 people agree with unverified statistics?"
], ],
"GlobalRules": [ "GlobalRules": [
@ -68,504 +68,6 @@
} }
} }
], ],
"Gametypes": [
{
"Game": "IW4",
"Gametypes": [
{
"Name": "arena",
"Alias": "Arena"
},
{
"Name": "ctf",
"Alias": "Capture The Flag"
},
{
"Name": "dd",
"Alias": "Demolition"
},
{
"Name": "dm",
"Alias": "Free For All"
},
{
"Name": "dom",
"Alias": "Domination"
},
{
"Name": "gtnw",
"Alias": "Global Thermo-Nuclear War"
},
{
"Name": "koth",
"Alias": "Headquarters"
},
{
"Name": "oneflag",
"Alias": "One-Flag CTF"
},
{
"Name": "sab",
"Alias": "Sabotage"
},
{
"Name": "sd",
"Alias": "Search & Destroy"
},
{
"Name": "war",
"Alias": "Team Deathmatch"
}
]
},
{
"Game": "T4",
"Gametypes": [
{
"Name": "ctf",
"Alias": "Capture The Flag"
},
{
"Name": "dom",
"Alias": "Domination"
},
{
"Name": "dm",
"Alias": "Free For All"
},
{
"Name": "koth",
"Alias": "Headquarters"
},
{
"Name": "tdm",
"Alias": "Team Deathmatch"
},
{
"Name": "sab",
"Alias": "Sabotage"
},
{
"Name": "sd",
"Alias": "Search & Destroy"
},
{
"Name": "twar",
"Alias": "War"
}
]
},
{
"Game": "IW5",
"Gametypes": [
{
"Name": "tdm",
"Alias": "Team Deathmatch"
},
{
"Name": "dom",
"Alias": "Domination"
},
{
"Name": "ctf",
"Alias": "Capture The Flag"
},
{
"Name": "dd",
"Alias": "Demolition"
},
{
"Name": "dz",
"Alias": "Drop Zone"
},
{
"Name": "ffa",
"Alias": "Free For All"
},
{
"Name": "gg",
"Alias": "Gun Game"
},
{
"Name": "hq",
"Alias": "Headquarters"
},
{
"Name": "koth",
"Alias": "Headquarters"
},
{
"Name": "inf",
"Alias": "Infected"
},
{
"Name": "jug",
"Alias": "Juggernaut"
},
{
"Name": "kc",
"Alias": "Kill Confirmed"
},
{
"Name": "oic",
"Alias": "One In The Chamber"
},
{
"Name": "sab",
"Alias": "Sabotage"
},
{
"Name": "sd",
"Alias": "Search & Destroy"
},
{
"Name": "tdef",
"Alias": "Team Defender"
},
{
"Name": "tj",
"Alias": "Team Juggernaut"
}
]
},
{
"Game": "T5",
"Gametypes": [
{
"Name": "ctf",
"Alias": "Capture The Flag"
},
{
"Name": "dem",
"Alias": "Demolition"
},
{
"Name": "dom",
"Alias": "Domination"
},
{
"Name": "dm",
"Alias": "Free For All"
},
{
"Name": "gun",
"Alias": "Gun Game"
},
{
"Name": "hlnd",
"Alias": "Sticks & Stones"
},
{
"Name": "koth",
"Alias": "Headquarters"
},
{
"Name": "oic",
"Alias": "One In The Chamber"
},
{
"Name": "sab",
"Alias": "Sabotage"
},
{
"Name": "sd",
"Alias": "Search & Destroy"
},
{
"Name": "shrp",
"Alias": "Sharpshooter"
},
{
"Name": "tdm",
"Alias": "Team Deathmatch"
}
]
},
{
"Game": "IW6",
"Gametypes": [
{
"Name": "blitz",
"Alias": "Blitz"
},
{
"Name": "conf",
"Alias": "Kill Confirmed"
},
{
"Name": "cranked",
"Alias": "Cranked"
},
{
"Name": "dm",
"Alias": "Free For All"
},
{
"Name": "dom",
"Alias": "Domination"
},
{
"Name": "grind",
"Alias": "Grind"
},
{
"Name": "grnd",
"Alias": "Drop Zone"
},
{
"Name": "gun",
"Alias": "Gun Game"
},
{
"Name": "horde",
"Alias": "Safeguard"
},
{
"Name": "infect",
"Alias": "Infected"
},
{
"Name": "sd",
"Alias": "Search & Destroy"
},
{
"Name": "siege",
"Alias": "Reinforce"
},
{
"Name": "sotf",
"Alias": "Hunted"
},
{
"Name": "sotf_ffa",
"Alias": "Hunted FFA"
},
{
"Name": "sr",
"Alias": "Search & Rescue"
},
{
"Name": "war",
"Alias": "Team Deathmatch"
}
]
},
{
"Game": "T6",
"Gametypes": [
{
"Name": "conf",
"Alias": "Kill Confirmed"
},
{
"Name": "ctf",
"Alias": "Capture The Flag"
},
{
"Name": "dem",
"Alias": "Demolition"
},
{
"Name": "dm",
"Alias": "Free For All"
},
{
"Name": "dom",
"Alias": "Domination"
},
{
"Name": "gun",
"Alias": "Gun Game"
},
{
"Name": "hq",
"Alias": "Headquarters"
},
{
"Name": "koth",
"Alias": "Hardpoint"
},
{
"Name": "oic",
"Alias": "One In The Chamber"
},
{
"Name": "oneflag",
"Alias": "One-Flag CTF"
},
{
"Name": "sas",
"Alias": "Sticks & Stones"
},
{
"Name": "sd",
"Alias": "Search & Destroy"
},
{
"Name": "shrp",
"Alias": "Sharpshooter"
},
{
"Name": "tdm",
"Alias": "Team Deathmatch"
}
]
},
{
"Game": "T7",
"Gametypes": [
{
"Name": "ball",
"Alias": "Uplink"
},
{
"Name": "clean",
"Alias": "Fracture"
},
{
"Name": "conf",
"Alias": "Kill Confirmed"
},
{
"Name": "ctf",
"Alias": "Capture The Flag"
},
{
"Name": "dom",
"Alias": "Domination"
},
{
"Name": "dem",
"Alias": "Demolition"
},
{
"Name": "dm",
"Alias": "Free For All"
},
{
"Name": "escort",
"Alias": "Safeguard"
},
{
"Name": "gun",
"Alias": "Gun Game"
},
{
"Name": "koth",
"Alias": "Hardpoint"
},
{
"Name": "sd",
"Alias": "Search & Destroy"
},
{
"Name": "tdm",
"Alias": "Team Deathmatch"
},
{
"Name": "hc_ball",
"Alias": "Hardcore Uplink"
},
{
"Name": "hc_clean",
"Alias": "Hardcore Fracture"
},
{
"Name": "hc_conf",
"Alias": "Hardcore Kill Confirmed"
},
{
"Name": "hc_ctf",
"Alias": "Hardcore Capture The Flag"
},
{
"Name": "hc_dom",
"Alias": "Hardcore Domination"
},
{
"Name": "hc_dem",
"Alias": "Hardcore Demolition"
},
{
"Name": "hc_dm",
"Alias": "Hardcore Free For All"
},
{
"Name": "hc_escort",
"Alias": "Hardcore Safeguard"
},
{
"Name": "hc_gun",
"Alias": "Hardcore Gun Game"
},
{
"Name": "hc_koth",
"Alias": "Hardcore Hardpoint"
},
{
"Name": "hc_sd",
"Alias": "Hardcore Search & Destroy"
},
{
"Name": "hc_tdm",
"Alias": "Hardcore Team Deathmatch"
}
]
},
{
"Game": "SHG1",
"Gametypes": [
{
"Name": "ball",
"Alias": "Uplink"
},
{
"Name": "conf",
"Alias": "Kill Confirmed"
},
{
"Name": "ctf",
"Alias": "Capture The Flag"
},
{
"Name": "dom",
"Alias": "Domination"
},
{
"Name": "dm",
"Alias": "Free For All"
},
{
"Name": "gun",
"Alias": "Gun Game"
},
{
"Name": "hp",
"Alias": "Hardpoint"
},
{
"Name": "infect",
"Alias": "Infected"
},
{
"Name": "sd",
"Alias": "Search & Destroy"
},
{
"Name": "sr",
"Alias": "Search & Rescue"
},
{
"Name": "war",
"Alias": "Team Deathmatch"
},
{
"Name": "twar",
"Alias": "Momentum"
}
]
}
],
"Maps": [ "Maps": [
{ {
"Game": "IW3", "Game": "IW3",
@ -1201,18 +703,6 @@
{ {
"Alias": "Highrise", "Alias": "Highrise",
"Name": "mp_highrise" "Name": "mp_highrise"
},
{
"Alias": "Favela",
"Name": "mp_favela"
},
{
"Alias": "Nuketown",
"Name": "mp_nuked"
},
{
"Alias": "Skidrow",
"Name": "mp_nightshift"
} }
] ]
}, },
@ -2021,188 +1511,68 @@
"barrett": "Barrett .50cal", "barrett": "Barrett .50cal",
"mp44": "MP44", "mp44": "MP44",
"remington700": "R700", "remington700": "R700",
"rpd": "RPD", "rpd": "RDP",
"saw": " M249 SAW", "saw": " M249 SAW",
"usp": "USP .45", "usp": "USP .45",
"winchester1200": "W1200", "winchester1200": "W1200",
"concussion": "Stun", "concussion": "Stun",
"melee": "Knife", "melee": "Knife",
"Frag": "Grenade", "Frag" : "Grenade",
"airstrike": "Airstrike", "airstrike": "Airstrike",
"helicopter": "Attack Helicopter", "helicopter": "Attack Helicopter",
"player": "", "player": "",
"attach": "" "attach": ""
}, },
"T4": { "T4": {
"torso_upper": "Upper Torso", "torso_upper": "Upper Torso",
"torso_lower": "Lower Torso", "torso_lower": "Lower Torso",
"right_leg_upper": "Upper Right Leg", "right_leg_upper": "Upper Right Leg",
"right_leg_lower": "Lower Right Leg", "right_leg_lower": "Lower Right Leg",
"right_hand": "Right Hand", "right_hand": "Right Hand",
"right_foot": "Right Foot", "right_foot": "Right Foot",
"right_arm_upper": "Upper Right Arm", "right_arm_upper": "Upper Right Arm",
"right_arm_lower": "Lower Right Arm", "right_arm_lower": "Lower Right Arm",
"left_leg_upper": "Upper Left Leg", "left_leg_upper": "Upper Left Leg",
"left_leg_lower": "Lower Left Leg", "left_leg_lower": "Lower Left Leg",
"left_hand": "Left Hand", "left_hand": "Left Hand",
"left_foot": "Left Foot", "left_foot": "Left Foot",
"left_arm_upper": "Upper Left Arm", "left_arm_upper": "Upper Left Arm",
"left_arm_lower": "Lower Left Arm", "left_arm_lower": "Lower Left Arm",
"gl": "Rifle Grenade", "gl": "Rifle Grenade",
"bigammo": "Round Drum", "bigammo": "Round Drum",
"scoped": "Sniper Scope", "scoped": "Sniper Scope",
"telescopic": "Telescopic Sight", "telescopic": "Telescopic Sight",
"aperture": "Aperture Sight", "aperture": "Aperture Sight",
"flash": "Flash Hider", "flash": "Flash Hider",
"silenced": "Silencer", "silenced": "Silencer",
"molotov": "Molotov Cocktail", "molotov": "Molotov Cocktail",
"sticky": "N° 74 ST", "sticky": "N° 74 ST",
"m2": "M2 Flamethrower", "m2": "M2 Flamethrower",
"artillery": "Artillery Strike", "artillery": "Artillery Strike",
"dog": "Attack Dogs", "dog": "Attack Dogs",
"colt": "Colt M1911", "colt": "Colt M1911",
"357magnum": ".357 Magnum", "357magnum": ".357 Magnum",
"walther": "Walther P38", "walther": "Walther P38",
"tokarev": "Tokarev TT-33", "tokarev": "Tokarev TT-33",
"shotgun": "M1897 Trench Gun", "shotgun": "M1897 Trench Gun",
"doublebarreledshotgun": "Double-Barreled Shotgun", "doublebarreledshotgun": "Double-Barreled Shotgun",
"mp40": "MP40", "mp40": "MP40",
"type100smg": "Type 100", "type100smg": "Type 100",
"ppsh": "PPSh-41", "ppsh": "PPSh-41",
"svt40": "SVT-40", "svt40": "SVT-40",
"gewehr43": "Gewehr 43", "gewehr43": "Gewehr 43",
"m1garand": "M1 Garand", "m1garand": "M1 Garand",
"stg44": "STG-44", "stg44": "STG-44",
"m1carbine": "M1A1 Carbine", "m1carbine": "M1A1 Carbine",
"type99lmg": "Type 99", "type99lmg": "Type 99",
"bar": "BAR", "bar": "BAR",
"dp28": "DP-28", "dp28": "DP-28",
"mg42": "MG42", "mg42": "MG42",
"fg42": "FG42", "fg42": "FG42",
"30cal": "Browning M1919", "30cal": "Browning M1919",
"type99rifle": "Arisaka", "type99rifle": "Arisaka",
"mosinrifle": "Mosin-Nagant", "mosinrifle": "Mosin-Nagant",
"ptrs41": "PTRS-41" "ptrs41":"PTRS-41"
}, }
"T6" : {
"mp7": "MP7",
"pdw57": "PDW-57",
"vector": "Vector K10",
"insas": "MSMC",
"qcw05": "Chicom CQB",
"evoskorpion": "Skorpion EVO",
"peacekeeper": "Peacekeeper",
"tar21": "MTAR",
"type95": "Type 25",
"sig556": "SWAT-556",
"sa58": "FAL-OSW",
"hk416": "M27",
"scar": "SCAR-H",
"saritch": "SMR",
"xm8": "M8A1",
"an94": "AN-94",
"870mcs": "Remington-870 MCS",
"saiga12": "S12",
"ksg": "KSG",
"srm1216": "M1216",
"mk48": "MK 48",
"qbb95": "QBB LSW",
"lsat": "LSAT",
"hamr": "HAMR",
"svu": "SVU-AS",
"dsr50": "DSR 50",
"ballista": "Ballista",
"as50": "XPR-50",
"fiveseven": "Five-Seven",
"fnp45": "TAC-45",
"beretta93r": "B23R",
"judge": "Executioner",
"kard": "KAP-40",
"smaw": "SMAW",
"fhj18": "FHJ-18 AA",
"usrpg": "RPG",
"riotshield": "Assault Shield",
"crossbow": "Crossbow",
"knife_ballistic": "Ballistic Knife",
"knife_held": "Knife",
"knife": "Knife",
"frag_grenade": "Grenade",
"hatchet": "Combat Axe",
"sticky_grenade": "Semtex",
"satchel_charge": "C4",
"bouncingbetty": "Bouncing Betty",
"claymore": "Claymore",
"smoke_center": "Smoke Grenade",
"concussion_grenade": "Concussion",
"emp_grenade": "EMP Grenade",
"sensor_grenade": "Sensor Grenade",
"flash_grenade": "Flashbang",
"proximity_grenade": "Shock Charge",
"pda_hack": "Black Hat",
"trophy_system": "Trophy System",
"tactical_insertion": "Tactical Insertion",
"acog": "ACOG",
"stalker": "Stock",
"swayreduc": "Ballistics CPU",
"ir": "Dual Band",
"dw": "Dual Wield",
"extclip": "Extended Clip",
"halo": "EOTech",
"dualclip": "Fast Mag",
"fmj": "FMJ",
"grip": "Fore Grip",
"gl": "Grenade Launcher",
"dualoptic": "Hybrid Optic",
"is": "Iron Sights",
"steadyaim": "Laser Sight",
"extbarrel": "Long Barrel",
"mms": "MMS",
"fastads": "Quickdraw",
"rf": "Rapid Fire",
"reflex": "Reflex Sight",
"sf": "Select Fire",
"silencer": "Suppressor",
"tacknife": "Tactical Knife",
"stackfire": "Tri-Bolt",
"rangefinder": "Target Finder",
"vzoom": "Variable Zoom",
"spyplane": "UAV",
"rcbomb": "RC-XD",
"missile_drone": "Hunter Killer",
"supplydrop": "Care Package",
"counteruav": "Counter-UAV",
"microwave_turret": "Guardian",
"remote_missile": "Hellstorm Missile",
"planemortar": "Lightning Strike",
"auto_turret": "Sentry Gun",
"minigun": "Death Machine",
"m32": "War Machine",
"qrdrone": "Dragonfire",
"ai_tank_drop": "AGR",
"comlink": "Stealth Chopper",
"spyplane_direction": "Orbital VSAT",
"helicopter_guard": "Escort Drone",
"emp": "EMP",
"straferun": "Warthog",
"remote_mortar": "Lodestar",
"player_gunner": "VTOL Warship",
"dogs": "K9 Unit",
"missile_swarm": "Swarm"
}
} }
} }

View File

@ -49,15 +49,8 @@ namespace IW4MAdmin.Application.EventParsers
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2); Configuration.Join.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3); Configuration.Join.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.Join.AddMapping(ParserRegex.GroupType.OriginName, 4); Configuration.Join.AddMapping(ParserRegex.GroupType.OriginName, 4);
Configuration.JoinTeam.Pattern = @"^(JT);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);([0-9]+);(\w+);(.+)$";
Configuration.JoinTeam.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.JoinTeam.AddMapping(ParserRegex.GroupType.OriginNetworkId, 2);
Configuration.JoinTeam.AddMapping(ParserRegex.GroupType.OriginClientNumber, 3);
Configuration.JoinTeam.AddMapping(ParserRegex.GroupType.OriginTeam, 4);
Configuration.JoinTeam.AddMapping(ParserRegex.GroupType.OriginName, 5);
Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,32});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,32})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$"; Configuration.Damage.Pattern = @"^(D);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Damage.AddMapping(ParserRegex.GroupType.EventType, 1); Configuration.Damage.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2); Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2);
Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3); Configuration.Damage.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);
@ -72,7 +65,7 @@ namespace IW4MAdmin.Application.EventParsers
Configuration.Damage.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12); Configuration.Damage.AddMapping(ParserRegex.GroupType.MeansOfDeath, 12);
Configuration.Damage.AddMapping(ParserRegex.GroupType.HitLocation, 13); Configuration.Damage.AddMapping(ParserRegex.GroupType.HitLocation, 13);
Configuration.Kill.Pattern = @"^(K);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,32});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,32})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$"; Configuration.Kill.Pattern = @"^(K);(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0);(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24});(-?[A-Fa-f0-9_]{1,32}|bot[0-9]+|0)?;(-?[0-9]+);(axis|allies|world|none)?;([^;]{1,24})?;((?:[0-9]+|[a-z]+|_|\+)+);([0-9]+);((?:[A-Z]|_)+);((?:[a-z]|_)+)$";
Configuration.Kill.AddMapping(ParserRegex.GroupType.EventType, 1); Configuration.Kill.AddMapping(ParserRegex.GroupType.EventType, 1);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2); Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetNetworkId, 2);
Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3); Configuration.Kill.AddMapping(ParserRegex.GroupType.TargetClientNumber, 3);
@ -97,8 +90,7 @@ namespace IW4MAdmin.Application.EventParsers
{Configuration.Say, GameEvent.EventType.Say}, {Configuration.Say, GameEvent.EventType.Say},
{Configuration.Kill, GameEvent.EventType.Kill}, {Configuration.Kill, GameEvent.EventType.Kill},
{Configuration.MapChange, GameEvent.EventType.MapChange}, {Configuration.MapChange, GameEvent.EventType.MapChange},
{Configuration.MapEnd, GameEvent.EventType.MapEnd}, {Configuration.MapEnd, GameEvent.EventType.MapEnd}
{Configuration.JoinTeam, GameEvent.EventType.JoinTeam}
}; };
_eventTypeMap = new Dictionary<string, GameEvent.EventType> _eventTypeMap = new Dictionary<string, GameEvent.EventType>
@ -108,8 +100,7 @@ namespace IW4MAdmin.Application.EventParsers
{"K", GameEvent.EventType.Kill}, {"K", GameEvent.EventType.Kill},
{"D", GameEvent.EventType.Damage}, {"D", GameEvent.EventType.Damage},
{"J", GameEvent.EventType.PreConnect}, {"J", GameEvent.EventType.PreConnect},
{"JT", GameEvent.EventType.JoinTeam}, {"Q", GameEvent.EventType.PreDisconnect},
{"Q", GameEvent.EventType.PreDisconnect}
}; };
} }
@ -331,47 +322,6 @@ namespace IW4MAdmin.Application.EventParsers
} }
} }
if (eventType == GameEvent.EventType.JoinTeam)
{
var match = Configuration.JoinTeam.PatternMatcher.Match(logLine);
if (match.Success)
{
var originIdString = match.Values[Configuration.JoinTeam.GroupMapping[ParserRegex.GroupType.OriginNetworkId]];
var originName = match.Values[Configuration.JoinTeam.GroupMapping[ParserRegex.GroupType.OriginName]];
var team = match.Values[Configuration.JoinTeam.GroupMapping[ParserRegex.GroupType.OriginTeam]];
if (Configuration.TeamMapping.ContainsKey(team))
{
team = Configuration.TeamMapping[team].ToString();
}
var networkId = originIdString.IsBotGuid() ?
originName.GenerateGuidFromString() :
originIdString.ConvertGuidToLong(Configuration.GuidNumberStyle);
return new GameEvent
{
Type = GameEvent.EventType.JoinTeam,
Data = logLine,
Origin = new EFClient
{
CurrentAlias = new EFAlias
{
Name = match.Values[Configuration.JoinTeam.GroupMapping[ParserRegex.GroupType.OriginName]].TrimNewLine(),
},
NetworkId = networkId,
ClientNumber = Convert.ToInt32(match.Values[Configuration.JoinTeam.GroupMapping[ParserRegex.GroupType.OriginClientNumber]]),
State = EFClient.ClientState.Connected,
},
Extra = team,
RequiredEntity = GameEvent.EventRequiredEntity.Origin,
GameTime = gameTime,
Source = GameEvent.EventSource.Log
};
}
}
if (eventType == GameEvent.EventType.PreDisconnect) if (eventType == GameEvent.EventType.PreDisconnect)
{ {
var match = Configuration.Quit.PatternMatcher.Match(logLine); var match = Configuration.Quit.PatternMatcher.Match(logLine);

View File

@ -1,8 +1,6 @@
using System.Collections.Generic; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.Interfaces;
using System.Globalization; using System.Globalization;
using SharedLibraryCore; using SharedLibraryCore;
using SharedLibraryCore.Database.Models;
namespace IW4MAdmin.Application.EventParsers namespace IW4MAdmin.Application.EventParsers
{ {
@ -10,12 +8,11 @@ namespace IW4MAdmin.Application.EventParsers
/// generic implementation of the IEventParserConfiguration /// generic implementation of the IEventParserConfiguration
/// allows script plugins to generate dynamic configurations /// allows script plugins to generate dynamic configurations
/// </summary> /// </summary>
internal sealed class DynamicEventParserConfiguration : IEventParserConfiguration sealed internal class DynamicEventParserConfiguration : IEventParserConfiguration
{ {
public string GameDirectory { get; set; } public string GameDirectory { get; set; }
public ParserRegex Say { get; set; } public ParserRegex Say { get; set; }
public ParserRegex Join { get; set; } public ParserRegex Join { get; set; }
public ParserRegex JoinTeam { get; set; }
public ParserRegex Quit { get; set; } public ParserRegex Quit { get; set; }
public ParserRegex Kill { get; set; } public ParserRegex Kill { get; set; }
public ParserRegex Damage { get; set; } public ParserRegex Damage { get; set; }
@ -25,13 +22,10 @@ namespace IW4MAdmin.Application.EventParsers
public ParserRegex MapEnd { get; set; } public ParserRegex MapEnd { get; set; }
public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber; public NumberStyles GuidNumberStyle { get; set; } = NumberStyles.HexNumber;
public Dictionary<string, EFClient.TeamType> TeamMapping { get; set; } = new();
public DynamicEventParserConfiguration(IParserRegexFactory parserRegexFactory) public DynamicEventParserConfiguration(IParserRegexFactory parserRegexFactory)
{ {
Say = parserRegexFactory.CreateParserRegex(); Say = parserRegexFactory.CreateParserRegex();
Join = parserRegexFactory.CreateParserRegex(); Join = parserRegexFactory.CreateParserRegex();
JoinTeam = parserRegexFactory.CreateParserRegex();
Quit = parserRegexFactory.CreateParserRegex(); Quit = parserRegexFactory.CreateParserRegex();
Kill = parserRegexFactory.CreateParserRegex(); Kill = parserRegexFactory.CreateParserRegex();
Damage = parserRegexFactory.CreateParserRegex(); Damage = parserRegexFactory.CreateParserRegex();

View File

@ -1,10 +1,6 @@
using System; using IW4MAdmin.Application.Misc;
using System.Collections.Generic;
using IW4MAdmin.Application.Misc;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using System.Linq; using System.Linq;
using SharedLibraryCore;
using SharedLibraryCore.Configuration;
namespace IW4MAdmin.Application.Extensions namespace IW4MAdmin.Application.Extensions
{ {
@ -17,19 +13,9 @@ namespace IW4MAdmin.Application.Extensions
/// <returns></returns> /// <returns></returns>
public static string CommandConfigNameForType(this IManagerCommand command) public static string CommandConfigNameForType(this IManagerCommand command)
{ {
return command.GetType() == typeof(ScriptCommand) return command.GetType() == typeof(ScriptCommand) ?
? $"{char.ToUpper(command.Name[0])}{command.Name.Substring(1)}Command" $"{char.ToUpper(command.Name[0])}{command.Name.Substring(1)}Command" :
: command.GetType().Name; command.GetType().Name;
} }
public static IList<Map> FindMap(this Server server, string mapName) => server.Maps.Where(map =>
map.Name.Equals(mapName, StringComparison.InvariantCultureIgnoreCase) ||
map.Alias.Equals(mapName, StringComparison.InvariantCultureIgnoreCase)).ToList();
public static IList<Gametype> FindGametype(this DefaultSettings settings, string gameType, Server.Game? game = null) =>
settings.Gametypes?.Where(gt => game == null || gt.Game == game)
.SelectMany(gt => gt.Gametypes).Where(gt =>
gt.Alias.Contains(gameType, StringComparison.CurrentCultureIgnoreCase) ||
gt.Name.Contains(gameType, StringComparison.CurrentCultureIgnoreCase)).ToList();
} }
} }

View File

@ -78,10 +78,8 @@ namespace IW4MAdmin.Application.Extensions
case "mysql": case "mysql":
var appendTimeout = !appConfig.ConnectionString.Contains("default command timeout", var appendTimeout = !appConfig.ConnectionString.Contains("default command timeout",
StringComparison.InvariantCultureIgnoreCase); StringComparison.InvariantCultureIgnoreCase);
var connectionString =
appConfig.ConnectionString + (appendTimeout ? ";default command timeout=0" : "");
services.AddSingleton(sp => (DbContextOptions) new DbContextOptionsBuilder<MySqlDatabaseContext>() services.AddSingleton(sp => (DbContextOptions) new DbContextOptionsBuilder<MySqlDatabaseContext>()
.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), .UseMySql(appConfig.ConnectionString + (appendTimeout ? ";default command timeout=0" : ""),
mysqlOptions => mysqlOptions.EnableRetryOnFailure()) mysqlOptions => mysqlOptions.EnableRetryOnFailure())
.UseLoggerFactory(sp.GetRequiredService<ILoggerFactory>()).Options); .UseLoggerFactory(sp.GetRequiredService<ILoggerFactory>()).Options);
return services; return services;
@ -94,7 +92,7 @@ namespace IW4MAdmin.Application.Extensions
postgresqlOptions => postgresqlOptions =>
{ {
postgresqlOptions.EnableRetryOnFailure(); postgresqlOptions.EnableRetryOnFailure();
postgresqlOptions.SetPostgresVersion(new Version("12.9")); postgresqlOptions.SetPostgresVersion(new Version("9.4"));
}) })
.UseLoggerFactory(sp.GetRequiredService<ILoggerFactory>()).Options); .UseLoggerFactory(sp.GetRequiredService<ILoggerFactory>()).Options);
return services; return services;
@ -103,4 +101,4 @@ namespace IW4MAdmin.Application.Extensions
} }
} }
} }
} }

View File

@ -1,5 +1,4 @@
using System.Threading.Tasks; using IW4MAdmin.Application.Misc;
using IW4MAdmin.Application.Misc;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Factories namespace IW4MAdmin.Application.Factories
@ -18,17 +17,7 @@ namespace IW4MAdmin.Application.Factories
/// <returns></returns> /// <returns></returns>
public IConfigurationHandler<T> GetConfigurationHandler<T>(string name) where T : IBaseConfiguration public IConfigurationHandler<T> GetConfigurationHandler<T>(string name) where T : IBaseConfiguration
{ {
var handler = new BaseConfigurationHandler<T>(name); return new BaseConfigurationHandler<T>(name);
handler.BuildAsync().Wait();
return handler;
}
/// <inheritdoc/>
public async Task<IConfigurationHandler<T>> GetConfigurationHandlerAsync<T>(string name) where T : IBaseConfiguration
{
var handler = new BaseConfigurationHandler<T>(name);
await handler.BuildAsync();
return handler;
} }
} }
} }

View File

@ -18,22 +18,14 @@ namespace IW4MAdmin.Application.Factories
public IGameLogReader CreateGameLogReader(Uri[] logUris, IEventParser eventParser) public IGameLogReader CreateGameLogReader(Uri[] logUris, IEventParser eventParser)
{ {
var baseUri = logUris[0]; var baseUri = logUris[0];
if (baseUri.Scheme == Uri.UriSchemeHttp || baseUri.Scheme == Uri.UriSchemeHttps) if (baseUri.Scheme == Uri.UriSchemeHttp)
{ {
return new GameLogReaderHttp(logUris, eventParser, return new GameLogReaderHttp(logUris, eventParser, _serviceProvider.GetRequiredService<ILogger<GameLogReaderHttp>>());
_serviceProvider.GetRequiredService<ILogger<GameLogReaderHttp>>());
} }
if (baseUri.Scheme == Uri.UriSchemeFile) else if (baseUri.Scheme == Uri.UriSchemeFile)
{ {
return new GameLogReader(baseUri.LocalPath, eventParser, return new GameLogReader(baseUri.LocalPath, eventParser, _serviceProvider.GetRequiredService<ILogger<GameLogReader>>());
_serviceProvider.GetRequiredService<ILogger<GameLogReader>>());
}
if (baseUri.Scheme == Uri.UriSchemeNetTcp)
{
return new NetworkGameLogReader(logUris, eventParser,
_serviceProvider.GetRequiredService<ILogger<NetworkGameLogReader>>());
} }
throw new NotImplementedException($"No log reader implemented for Uri scheme \"{baseUri.Scheme}\""); throw new NotImplementedException($"No log reader implemented for Uri scheme \"{baseUri.Scheme}\"");

View File

@ -14,7 +14,7 @@ namespace IW4MAdmin.Application.Factories
internal class GameServerInstanceFactory : IGameServerInstanceFactory internal class GameServerInstanceFactory : IGameServerInstanceFactory
{ {
private readonly ITranslationLookup _translationLookup; private readonly ITranslationLookup _translationLookup;
private readonly IMetaServiceV2 _metaService; private readonly IMetaService _metaService;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
/// <summary> /// <summary>
@ -23,7 +23,7 @@ namespace IW4MAdmin.Application.Factories
/// <param name="translationLookup"></param> /// <param name="translationLookup"></param>
/// <param name="rconConnectionFactory"></param> /// <param name="rconConnectionFactory"></param>
public GameServerInstanceFactory(ITranslationLookup translationLookup, public GameServerInstanceFactory(ITranslationLookup translationLookup,
IMetaServiceV2 metaService, IMetaService metaService,
IServiceProvider serviceProvider) IServiceProvider serviceProvider)
{ {
_translationLookup = translationLookup; _translationLookup = translationLookup;
@ -45,4 +45,4 @@ namespace IW4MAdmin.Application.Factories
_serviceProvider.GetRequiredService<ILookupCache<EFServer>>()); _serviceProvider.GetRequiredService<ILookupCache<EFServer>>());
} }
} }
} }

View File

@ -6,7 +6,6 @@ using SharedLibraryCore.Interfaces;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Data.Models.Client; using Data.Models.Client;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -31,7 +30,7 @@ namespace IW4MAdmin.Application.Factories
/// <inheritdoc/> /// <inheritdoc/>
public IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission, public IManagerCommand CreateScriptCommand(string name, string alias, string description, string permission,
bool isTargetRequired, IEnumerable<(string, bool)> args, Func<GameEvent, Task> executeAction, Server.Game[] supportedGames) bool isTargetRequired, IEnumerable<(string, bool)> args, Action<GameEvent> executeAction)
{ {
var permissionEnum = Enum.Parse<EFClient.Permission>(permission); var permissionEnum = Enum.Parse<EFClient.Permission>(permission);
var argsArray = args.Select(_arg => new CommandArgument var argsArray = args.Select(_arg => new CommandArgument
@ -41,7 +40,7 @@ namespace IW4MAdmin.Application.Factories
}).ToArray(); }).ToArray();
return new ScriptCommand(name, alias, description, isTargetRequired, permissionEnum, argsArray, executeAction, return new ScriptCommand(name, alias, description, isTargetRequired, permissionEnum, argsArray, executeAction,
_config, _transLookup, _serviceProvider.GetRequiredService<ILogger<ScriptCommand>>(), supportedGames); _config, _transLookup, _serviceProvider.GetRequiredService<ILogger<ScriptCommand>>());
} }
} }
} }

View File

@ -21,7 +21,7 @@ namespace IW4MAdmin.Application.IO
{ {
_reader = gameLogReaderFactory.CreateGameLogReader(gameLogUris, server.EventParser); _reader = gameLogReaderFactory.CreateGameLogReader(gameLogUris, server.EventParser);
_server = server; _server = server;
_ignoreBots = server.Manager.GetApplicationSettings().Configuration()?.IgnoreBots ?? false; _ignoreBots = server?.Manager.GetApplicationSettings().Configuration().IgnoreBots ?? false;
_logger = logger; _logger = logger;
} }
@ -69,7 +69,7 @@ namespace IW4MAdmin.Application.IO
return; return;
} }
var events = await _reader.ReadEventsFromLog(fileDiff, previousFileSize, _server); var events = await _reader.ReadEventsFromLog(fileDiff, previousFileSize);
foreach (var gameEvent in events) foreach (var gameEvent in events)
{ {

View File

@ -28,7 +28,7 @@ namespace IW4MAdmin.Application.IO
_logger = logger; _logger = logger;
} }
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition, Server server = null) public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition)
{ {
// allocate the bytes for the new log lines // allocate the bytes for the new log lines
List<string> logLines = new List<string>(); List<string> logLines = new List<string>();

View File

@ -34,7 +34,7 @@ namespace IW4MAdmin.Application.IO
public int UpdateInterval => 500; public int UpdateInterval => 500;
public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition, Server server = null) public async Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition)
{ {
var events = new List<GameEvent>(); var events = new List<GameEvent>();
var response = await _logServerApi.Log(_safeLogPath, lastKey); var response = await _logServerApi.Log(_safeLogPath, lastKey);

View File

@ -1,163 +0,0 @@
using SharedLibraryCore;
using SharedLibraryCore.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Integrations.Cod;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application.IO
{
/// <summary>
/// provides capability of reading log files over udp
/// </summary>
class NetworkGameLogReader : IGameLogReader
{
private readonly IEventParser _eventParser;
private readonly ILogger _logger;
private readonly Uri _uri;
private static readonly NetworkLogState State = new();
private bool _stateRegistered;
private CancellationToken _token;
public NetworkGameLogReader(IReadOnlyList<Uri> uris, IEventParser parser, ILogger<NetworkGameLogReader> logger)
{
_eventParser = parser;
_uri = uris[0];
_logger = logger;
}
public long Length => -1;
public int UpdateInterval => 150;
public Task<IEnumerable<GameEvent>> ReadEventsFromLog(long fileSizeDiff, long startPosition,
Server server = null)
{
// todo: other games might support this
var serverEndpoint = (server?.RemoteConnection as CodRConConnection)?.Endpoint;
if (serverEndpoint is null)
{
return Task.FromResult(Enumerable.Empty<GameEvent>());
}
if (!_stateRegistered && !State.EndPointExists(serverEndpoint))
{
try
{
var client = State.RegisterEndpoint(serverEndpoint, BuildLocalEndpoint()).Client;
_stateRegistered = true;
_token = server.Manager.CancellationToken;
if (client == null)
{
using (LogContext.PushProperty("Server", server.ToString()))
{
_logger.LogInformation("Not registering {Name} socket because it is already bound",
nameof(NetworkGameLogReader));
}
return Task.FromResult(Enumerable.Empty<GameEvent>());
}
Task.Run(async () => await ReadNetworkData(client, _token), _token);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not register {Name} endpoint {Endpoint}",
nameof(NetworkGameLogReader), _uri);
throw;
}
}
var events = new List<GameEvent>();
foreach (var logData in State.GetServerLogData(serverEndpoint)
.Select(log => Utilities.EncodingType.GetString(log)))
{
if (string.IsNullOrWhiteSpace(logData))
{
return Task.FromResult(Enumerable.Empty<GameEvent>());
}
var lines = logData
.Split('\n')
.Where(line => line.Length > 0 && !line.Contains('ÿ'));
foreach (var eventLine in lines)
{
try
{
// this trim end should hopefully fix the nasty runaway regex
var gameEvent = _eventParser.GenerateGameEvent(eventLine.TrimEnd('\r'));
events.Add(gameEvent);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not properly parse event line from http {EventLine}",
eventLine);
}
}
}
return Task.FromResult((IEnumerable<GameEvent>)events);
}
private async Task ReadNetworkData(UdpClient client, CancellationToken token)
{
while (!token.IsCancellationRequested)
{
// get more data
IPEndPoint remoteEndpoint = null;
byte[] bufferedData = null;
if (client == null)
{
// we already have a socket listening on this port for data, so we don't need to run another thread
break;
}
try
{
var result = await client.ReceiveAsync(_token);
remoteEndpoint = result.RemoteEndPoint;
bufferedData = result.Buffer;
}
catch (OperationCanceledException)
{
_logger.LogDebug("Stopping network log receive");
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not receive lines for {LogReader}", nameof(NetworkGameLogReader));
}
if (bufferedData != null)
{
State.QueueServerLogData(remoteEndpoint, bufferedData);
}
}
}
private IPEndPoint BuildLocalEndpoint()
{
try
{
return new IPEndPoint(Dns.GetHostAddresses(_uri.Host).First(), _uri.Port);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not setup {LogReader} endpoint", nameof(NetworkGameLogReader));
throw;
}
}
}
}

View File

@ -1,138 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace IW4MAdmin.Application.IO;
public class NetworkLogState : Dictionary<IPEndPoint, UdpClientState>
{
public UdpClientState RegisterEndpoint(IPEndPoint serverEndpoint, IPEndPoint localEndpoint)
{
try
{
lock (this)
{
if (!ContainsKey(serverEndpoint))
{
Add(serverEndpoint, new UdpClientState { Client = new UdpClient(localEndpoint) });
}
}
}
catch (SocketException ex) when (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
{
lock (this)
{
// we don't add the udp client because it already exists (listening to multiple servers from one socket)
Add(serverEndpoint, new UdpClientState());
}
}
return this[serverEndpoint];
}
public List<byte[]> GetServerLogData(IPEndPoint serverEndpoint)
{
try
{
var state = this[serverEndpoint];
if (state == null)
{
return new List<byte[]>();
}
// it's possible that we could be trying to read and write to the queue simultaneously so we need to wait
this[serverEndpoint].OnAction.Wait();
var data = new List<byte[]>();
while (this[serverEndpoint].AvailableLogData.Count > 0)
{
data.Add(this[serverEndpoint].AvailableLogData.Dequeue());
}
return data;
}
finally
{
if (this[serverEndpoint].OnAction.CurrentCount == 0)
{
this[serverEndpoint].OnAction.Release(1);
}
}
}
public void QueueServerLogData(IPEndPoint serverEndpoint, byte[] data)
{
var endpoint = Keys.FirstOrDefault(key =>
Equals(key.Address, serverEndpoint.Address) && key.Port == serverEndpoint.Port);
try
{
if (endpoint == null)
{
return;
}
// currently our expected start and end characters
var startsWithPrefix = StartsWith(data, "ÿÿÿÿprint\n");
var endsWithDelimiter = data[^1] == '\n';
// we have the data we expected
if (!startsWithPrefix || !endsWithDelimiter)
{
return;
}
// it's possible that we could be trying to read and write to the queue simultaneously so we need to wait
this[endpoint].OnAction.Wait();
this[endpoint].AvailableLogData.Enqueue(data);
}
finally
{
if (endpoint != null && this[endpoint].OnAction.CurrentCount == 0)
{
this[endpoint].OnAction.Release(1);
}
}
}
public bool EndPointExists(IPEndPoint serverEndpoint)
{
lock (this)
{
return ContainsKey(serverEndpoint);
}
}
private static bool StartsWith(byte[] sourceArray, string match)
{
if (sourceArray is null)
{
return false;
}
if (match.Length > sourceArray.Length)
{
return false;
}
return !match.Where((t, i) => sourceArray[i] != (byte)t).Any();
}
}
public class UdpClientState
{
public UdpClient Client { get; set; }
public Queue<byte[]> AvailableLogData { get; } = new();
public SemaphoreSlim OnAction { get; } = new(1, 1);
~UdpClientState()
{
OnAction.Dispose();
Client?.Dispose();
}
}

View File

@ -12,7 +12,6 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
@ -24,7 +23,6 @@ using Serilog.Context;
using static SharedLibraryCore.Database.Models.EFClient; using static SharedLibraryCore.Database.Models.EFClient;
using Data.Models; using Data.Models;
using Data.Models.Server; using Data.Models.Server;
using IW4MAdmin.Application.Commands;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using static Data.Models.Client.EFClient; using static Data.Models.Client.EFClient;
@ -35,7 +33,7 @@ namespace IW4MAdmin
private static readonly SharedLibraryCore.Localization.TranslationLookup loc = Utilities.CurrentLocalization.LocalizationIndex; private static readonly SharedLibraryCore.Localization.TranslationLookup loc = Utilities.CurrentLocalization.LocalizationIndex;
public GameLogEventDetection LogEvent; public GameLogEventDetection LogEvent;
private readonly ITranslationLookup _translationLookup; private readonly ITranslationLookup _translationLookup;
private readonly IMetaServiceV2 _metaService; private readonly IMetaService _metaService;
private const int REPORT_FLAG_COUNT = 4; private const int REPORT_FLAG_COUNT = 4;
private long lastGameTime = 0; private long lastGameTime = 0;
@ -49,17 +47,15 @@ namespace IW4MAdmin
ServerConfiguration serverConfiguration, ServerConfiguration serverConfiguration,
CommandConfiguration commandConfiguration, CommandConfiguration commandConfiguration,
ITranslationLookup lookup, ITranslationLookup lookup,
IMetaServiceV2 metaService, IMetaService metaService,
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
IClientNoticeMessageFormatter messageFormatter, IClientNoticeMessageFormatter messageFormatter,
ILookupCache<EFServer> serverCache) : base(serviceProvider.GetRequiredService<ILogger<Server>>(), ILookupCache<EFServer> serverCache) : base(serviceProvider.GetRequiredService<ILogger<Server>>(),
#pragma warning disable CS0612
serviceProvider.GetRequiredService<SharedLibraryCore.Interfaces.ILogger>(), serviceProvider.GetRequiredService<SharedLibraryCore.Interfaces.ILogger>(),
#pragma warning restore CS0612
serverConfiguration, serverConfiguration,
serviceProvider.GetRequiredService<IManager>(), serviceProvider.GetRequiredService<IManager>(),
serviceProvider.GetRequiredService<IRConConnectionFactory>(), serviceProvider.GetRequiredService<IRConConnectionFactory>(),
serviceProvider.GetRequiredService<IGameLogReaderFactory>(), serviceProvider) serviceProvider.GetRequiredService<IGameLogReaderFactory>())
{ {
_translationLookup = lookup; _translationLookup = lookup;
_metaService = metaService; _metaService = metaService;
@ -96,8 +92,6 @@ namespace IW4MAdmin
client.ClientNumber = clientFromLog.ClientNumber; client.ClientNumber = clientFromLog.ClientNumber;
client.Score = clientFromLog.Score; client.Score = clientFromLog.Score;
client.Ping = clientFromLog.Ping; client.Ping = clientFromLog.Ping;
client.Team = clientFromLog.Team;
client.TeamName = clientFromLog.TeamName;
client.CurrentServer = this; client.CurrentServer = this;
client.State = ClientState.Connecting; client.State = ClientState.Connecting;
@ -238,7 +232,7 @@ namespace IW4MAdmin
private async Task CreatePluginTask(IPlugin plugin, GameEvent gameEvent) private async Task CreatePluginTask(IPlugin plugin, GameEvent gameEvent)
{ {
// we don't want to run the events on parser plugins // we don't want to run the events on parser plugins
if (plugin is ScriptPlugin { IsParser: true }) if (plugin is ScriptPlugin scriptPlugin && scriptPlugin.IsParser)
{ {
return; return;
} }
@ -248,16 +242,11 @@ namespace IW4MAdmin
try try
{ {
await plugin.OnEventAsync(gameEvent, this).WithWaitCancellation(tokenSource.Token); await (plugin.OnEventAsync(gameEvent, this)).WithWaitCancellation(tokenSource.Token);
}
catch (OperationCanceledException)
{
ServerLogger.LogWarning("Timed out executing event {EventType} for {Plugin}", gameEvent.Type,
plugin.Name);
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine(loc["SERVER_PLUGIN_ERROR"].FormatExt(plugin.Name, ex.GetType().Name)); Console.WriteLine(loc["SERVER_PLUGIN_ERROR"]);
ServerLogger.LogError(ex, "Could not execute {methodName} for plugin {plugin}", ServerLogger.LogError(ex, "Could not execute {methodName} for plugin {plugin}",
nameof(plugin.OnEventAsync), plugin.Name); nameof(plugin.OnEventAsync), plugin.Name);
} }
@ -297,7 +286,7 @@ namespace IW4MAdmin
} }
} }
else if (E.Type == GameEvent.EventType.ConnectionLost) if (E.Type == GameEvent.EventType.ConnectionLost)
{ {
var exception = E.Extra as Exception; var exception = E.Extra as Exception;
ServerLogger.LogError(exception, ServerLogger.LogError(exception,
@ -311,7 +300,7 @@ namespace IW4MAdmin
Throttled = true; Throttled = true;
} }
else if (E.Type == GameEvent.EventType.ConnectionRestored) if (E.Type == GameEvent.EventType.ConnectionRestored)
{ {
ServerLogger.LogInformation( ServerLogger.LogInformation(
"Connection restored with {server}", ToString()); "Connection restored with {server}", ToString());
@ -323,13 +312,13 @@ namespace IW4MAdmin
if (!string.IsNullOrEmpty(CustomSayName)) if (!string.IsNullOrEmpty(CustomSayName))
{ {
await this.SetDvarAsync("sv_sayname", CustomSayName, Manager.CancellationToken); await this.SetDvarAsync("sv_sayname", CustomSayName);
} }
Throttled = false; Throttled = false;
} }
else if (E.Type == GameEvent.EventType.ChangePermission) if (E.Type == GameEvent.EventType.ChangePermission)
{ {
var newPermission = (Permission) E.Extra; var newPermission = (Permission) E.Extra;
ServerLogger.LogInformation("{origin} is setting {target} to permission level {newPermission}", ServerLogger.LogInformation("{origin} is setting {target} to permission level {newPermission}",
@ -352,8 +341,7 @@ namespace IW4MAdmin
Time = DateTime.UtcNow Time = DateTime.UtcNow
}); });
var clientTag = await _metaService.GetPersistentMetaByLookup(EFMeta.ClientTagV2, var clientTag = await _metaService.GetPersistentMeta(EFMeta.ClientTag, E.Origin);
EFMeta.ClientTagNameV2, E.Origin.ClientId, Manager.CancellationToken);
if (clientTag?.LinkedMeta != null) if (clientTag?.LinkedMeta != null)
{ {
@ -363,7 +351,7 @@ namespace IW4MAdmin
try try
{ {
var factory = _serviceProvider.GetRequiredService<IDatabaseContextFactory>(); var factory = _serviceProvider.GetRequiredService<IDatabaseContextFactory>();
await using var context = factory.CreateContext(enableTracking: false); await using var context = factory.CreateContext();
var messageCount = await context.InboxMessages var messageCount = await context.InboxMessages
.CountAsync(msg => msg.DestinationClientId == E.Origin.ClientId && !msg.IsDelivered); .CountAsync(msg => msg.DestinationClientId == E.Origin.ClientId && !msg.IsDelivered);
@ -431,7 +419,6 @@ namespace IW4MAdmin
Clients[E.Origin.ClientNumber] = E.Origin; Clients[E.Origin.ClientNumber] = E.Origin;
try try
{ {
E.Origin.GameName = (Reference.Game?)GameName;
E.Origin = await OnClientConnected(E.Origin); E.Origin = await OnClientConnected(E.Origin);
E.Target = E.Origin; E.Target = E.Origin;
} }
@ -446,7 +433,7 @@ namespace IW4MAdmin
if (E.Origin.Level > Permission.Moderator) if (E.Origin.Level > Permission.Moderator)
{ {
E.Origin.Tell(loc["SERVER_REPORT_COUNT_V2"].FormatExt(E.Owner.Reports.Count)); E.Origin.Tell(string.Format(loc["SERVER_REPORT_COUNT"], E.Owner.Reports.Count));
} }
} }
@ -486,7 +473,7 @@ namespace IW4MAdmin
else if (E.Type == GameEvent.EventType.Unflag) else if (E.Type == GameEvent.EventType.Unflag)
{ {
var unflagPenalty = new EFPenalty var unflagPenalty = new EFPenalty()
{ {
Type = EFPenalty.PenaltyType.Unflag, Type = EFPenalty.PenaltyType.Unflag,
Expires = DateTime.UtcNow, Expires = DateTime.UtcNow,
@ -498,8 +485,7 @@ namespace IW4MAdmin
}; };
E.Target.SetLevel(Permission.User, E.Origin); E.Target.SetLevel(Permission.User, E.Origin);
await Manager.GetPenaltyService().RemoveActivePenalties(E.Target.AliasLinkId, E.Target.NetworkId, await Manager.GetPenaltyService().RemoveActivePenalties(E.Target.AliasLinkId);
E.Target.CurrentAlias?.IPAddress);
await Manager.GetPenaltyService().Create(unflagPenalty); await Manager.GetPenaltyService().Create(unflagPenalty);
} }
@ -509,8 +495,7 @@ namespace IW4MAdmin
{ {
Origin = E.Origin, Origin = E.Origin,
Target = E.Target, Target = E.Target,
Reason = E.Data, Reason = E.Data
ReportedOn = DateTime.UtcNow
}); });
var newReport = new EFPenalty() var newReport = new EFPenalty()
@ -573,8 +558,8 @@ namespace IW4MAdmin
Time = DateTime.UtcNow Time = DateTime.UtcNow
}); });
await _metaService.SetPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin.ClientId); await _metaService.AddPersistentMeta("LastMapPlayed", CurrentMap.Alias, E.Origin);
await _metaService.SetPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin.ClientId); await _metaService.AddPersistentMeta("LastServerPlayed", E.Owner.Hostname, E.Origin);
} }
else if (E.Type == GameEvent.EventType.PreDisconnect) else if (E.Type == GameEvent.EventType.PreDisconnect)
@ -625,7 +610,7 @@ namespace IW4MAdmin
await OnClientUpdate(E.Origin); await OnClientUpdate(E.Origin);
} }
else if (E.Type == GameEvent.EventType.Say) if (E.Type == GameEvent.EventType.Say)
{ {
if (E.Data?.Length > 0) if (E.Data?.Length > 0)
{ {
@ -645,7 +630,7 @@ namespace IW4MAdmin
} }
} }
ChatHistory.Add(new ChatInfo ChatHistory.Add(new ChatInfo()
{ {
Name = E.Origin.Name, Name = E.Origin.Name,
Message = message, Message = message,
@ -655,7 +640,7 @@ namespace IW4MAdmin
} }
} }
else if (E.Type == GameEvent.EventType.MapChange) if (E.Type == GameEvent.EventType.MapChange)
{ {
ServerLogger.LogInformation("New map loaded - {clientCount} active players", ClientNum); ServerLogger.LogInformation("New map loaded - {clientCount} active players", ClientNum);
@ -696,7 +681,7 @@ namespace IW4MAdmin
} }
} }
else if (E.Type == GameEvent.EventType.MapEnd) if (E.Type == GameEvent.EventType.MapEnd)
{ {
ServerLogger.LogInformation("Game ending..."); ServerLogger.LogInformation("Game ending...");
@ -706,23 +691,18 @@ namespace IW4MAdmin
} }
} }
else if (E.Type == GameEvent.EventType.Tell) if (E.Type == GameEvent.EventType.Tell)
{ {
await Tell(E.Message, E.Target); await Tell(E.Message, E.Target);
} }
else if (E.Type == GameEvent.EventType.Broadcast) if (E.Type == GameEvent.EventType.Broadcast)
{ {
if (!Utilities.IsDevelopment && E.Data != null) // hides broadcast when in development mode if (!Utilities.IsDevelopment && E.Data != null) // hides broadcast when in development mode
{ {
await E.Owner.ExecuteCommandAsync(E.Data); await E.Owner.ExecuteCommandAsync(E.Data);
} }
} }
else if (E.Type == GameEvent.EventType.JoinTeam)
{
E.Origin.UpdateTeam(E.Extra as string);
}
lock (ChatHistory) lock (ChatHistory)
{ {
@ -793,7 +773,7 @@ namespace IW4MAdmin
async Task<List<EFClient>[]> PollPlayersAsync() async Task<List<EFClient>[]> PollPlayersAsync()
{ {
var currentClients = GetClientsAsList(); var currentClients = GetClientsAsList();
var statusResponse = await this.GetStatusAsync(Manager.CancellationToken); var statusResponse = (await this.GetStatusAsync());
var polledClients = statusResponse.Clients.AsEnumerable(); var polledClients = statusResponse.Clients.AsEnumerable();
if (Manager.GetApplicationSettings().Configuration().IgnoreBots) if (Manager.GetApplicationSettings().Configuration().IgnoreBots)
@ -905,10 +885,16 @@ namespace IW4MAdmin
await e.WaitAsync(Utilities.DefaultCommandTimeout, new CancellationTokenRegistration().Token); await e.WaitAsync(Utilities.DefaultCommandTimeout, new CancellationTokenRegistration().Token);
} }
foreach (var plugin in Manager.Plugins)
{
await plugin.OnUnloadAsync();
}
} }
private DateTime _lastMessageSent = DateTime.Now; DateTime start = DateTime.Now;
private DateTime _lastPlayerCount = DateTime.Now; DateTime playerCountStart = DateTime.Now;
DateTime lastCount = DateTime.Now;
public override async Task<bool> ProcessUpdatesAsync(CancellationToken cts) public override async Task<bool> ProcessUpdatesAsync(CancellationToken cts)
{ {
@ -922,16 +908,14 @@ namespace IW4MAdmin
try try
{ {
if (Manager.GetApplicationSettings().Configuration().RConPollRate == int.MaxValue && if (Manager.GetApplicationSettings().Configuration().RConPollRate == int.MaxValue && Utilities.IsDevelopment)
Utilities.IsDevelopment)
{ {
return true; return true;
} }
var polledClients = await PollPlayersAsync(); var polledClients = await PollPlayersAsync();
foreach (var disconnectingClient in polledClients[1] foreach (var disconnectingClient in polledClients[1].Where(_client => !_client.IsZombieClient /* ignores "fake" zombie clients */))
.Where(client => !client.IsZombieClient /* ignores "fake" zombie clients */))
{ {
disconnectingClient.CurrentServer = this; disconnectingClient.CurrentServer = this;
var e = new GameEvent() var e = new GameEvent()
@ -947,20 +931,23 @@ namespace IW4MAdmin
} }
// this are our new connecting clients // this are our new connecting clients
foreach (var client in polledClients[0].Where(client => foreach (var client in polledClients[0])
!string.IsNullOrEmpty(client.Name) && (client.Ping != 999 || client.IsBot)))
{ {
// note: this prevents players in ZMBI state from being registered with no name
if (string.IsNullOrEmpty(client.Name) || (client.Ping == 999 && !client.IsBot))
{
continue;
}
client.CurrentServer = this; client.CurrentServer = this;
client.GameName = (Reference.Game?)GameName; var e = new GameEvent()
var e = new GameEvent
{ {
Type = GameEvent.EventType.PreConnect, Type = GameEvent.EventType.PreConnect,
Origin = client, Origin = client,
Owner = this, Owner = this,
IsBlocking = true, IsBlocking = true,
Extra = client.GetAdditionalProperty<string>("BotGuid"), Extra = client.GetAdditionalProperty<string>("BotGuid"),
Source = GameEvent.EventSource.Status, Source = GameEvent.EventSource.Status
}; };
Manager.AddEvent(e); Manager.AddEvent(e);
@ -971,19 +958,19 @@ namespace IW4MAdmin
foreach (var client in polledClients[2]) foreach (var client in polledClients[2])
{ {
client.CurrentServer = this; client.CurrentServer = this;
var gameEvent = new GameEvent var e = new GameEvent()
{ {
Type = GameEvent.EventType.Update, Type = GameEvent.EventType.Update,
Origin = client, Origin = client,
Owner = this Owner = this
}; };
Manager.AddEvent(gameEvent); Manager.AddEvent(e);
} }
if (Throttled) if (Throttled)
{ {
var gameEvent = new GameEvent var _event = new GameEvent()
{ {
Type = GameEvent.EventType.ConnectionRestored, Type = GameEvent.EventType.ConnectionRestored,
Owner = this, Owner = this,
@ -991,52 +978,65 @@ namespace IW4MAdmin
Target = Utilities.IW4MAdminClient(this) Target = Utilities.IW4MAdminClient(this)
}; };
Manager.AddEvent(gameEvent); Manager.AddEvent(_event);
} }
LastPoll = DateTime.Now; LastPoll = DateTime.Now;
} }
catch (NetworkException ex) catch (NetworkException e)
{ {
if (Throttled) if (!Throttled)
{ {
return true; var _event = new GameEvent()
{
Type = GameEvent.EventType.ConnectionLost,
Owner = this,
Origin = Utilities.IW4MAdminClient(this),
Target = Utilities.IW4MAdminClient(this),
Extra = e,
Data = ConnectionErrors.ToString()
};
Manager.AddEvent(_event);
} }
var gameEvent = new GameEvent return true;
}
LastMessage = DateTime.Now - start;
lastCount = DateTime.Now;
var appConfig = _serviceProvider.GetService<ApplicationConfiguration>();
// update the player history
if (lastCount - playerCountStart >= appConfig.ServerDataCollectionInterval)
{
var maxItems = Math.Ceiling(appConfig.MaxClientHistoryTime.TotalMinutes /
appConfig.ServerDataCollectionInterval.TotalMinutes);
while ( ClientHistory.Count > maxItems)
{ {
Type = GameEvent.EventType.ConnectionLost, ClientHistory.Dequeue();
Owner = this, }
Origin = Utilities.IW4MAdminClient(this),
Target = Utilities.IW4MAdminClient(this),
Extra = ex,
Data = ConnectionErrors.ToString()
};
Manager.AddEvent(gameEvent); ClientHistory.Enqueue(new PlayerHistory(ClientNum));
return true; playerCountStart = DateTime.Now;
}
finally
{
RunServerCollection();
}
if (DateTime.Now - _lastMessageSent <=
TimeSpan.FromSeconds(Manager.GetApplicationSettings().Configuration().AutoMessagePeriod) ||
BroadcastMessages.Count <= 0 || ClientNum <= 0)
{
return true;
} }
// send out broadcast messages // send out broadcast messages
var messages = if (LastMessage.TotalSeconds > Manager.GetApplicationSettings().Configuration().AutoMessagePeriod
(await this.ProcessMessageToken(Manager.GetMessageTokens(), BroadcastMessages[NextMessage])).Split( && BroadcastMessages.Count > 0
Environment.NewLine); && ClientNum > 0)
await BroadcastAsync(messages, token: Manager.CancellationToken); {
string[] messages = (await this.ProcessMessageToken(Manager.GetMessageTokens(), BroadcastMessages[NextMessage])).Split(Environment.NewLine);
NextMessage = NextMessage == BroadcastMessages.Count - 1 ? 0 : NextMessage + 1; foreach (string message in messages)
_lastMessageSent = DateTime.Now; {
Broadcast(message);
}
NextMessage = NextMessage == (BroadcastMessages.Count - 1) ? 0 : NextMessage + 1;
start = DateTime.Now;
}
return true; return true;
} }
@ -1048,80 +1048,43 @@ namespace IW4MAdmin
} }
// this one is ok // this one is ok
catch (Exception e) when (e is ServerException || e is RConException) catch (Exception e) when(e is ServerException || e is RConException)
{ {
using (LogContext.PushProperty("Server", ToString())) using(LogContext.PushProperty("Server", ToString()))
{ {
ServerLogger.LogWarning(e, "Undesirable exception occured during processing updates"); ServerLogger.LogWarning(e, "Undesirable exception occured during processing updates");
} }
return false; return false;
} }
catch (Exception e) catch (Exception e)
{ {
using (LogContext.PushProperty("Server", ToString())) using(LogContext.PushProperty("Server", ToString()))
{ {
ServerLogger.LogError(e, "Unexpected exception occured during processing updates"); ServerLogger.LogError(e, "Unexpected exception occured during processing updates");
} }
Console.WriteLine(loc["SERVER_ERROR_EXCEPTION"].FormatExt($"[{IP}:{Port}]")); Console.WriteLine(loc["SERVER_ERROR_EXCEPTION"].FormatExt($"[{IP}:{Port}]"));
return false; return false;
} }
} }
private void RunServerCollection()
{
var appConfig = _serviceProvider.GetService<ApplicationConfiguration>();
if (DateTime.Now - _lastPlayerCount < appConfig?.ServerDataCollectionInterval)
{
return;
}
var maxItems = Math.Ceiling(appConfig.MaxClientHistoryTime.TotalMinutes /
appConfig.ServerDataCollectionInterval.TotalMinutes);
while (ClientHistory.ClientCounts.Count > maxItems)
{
ClientHistory.ClientCounts.RemoveAt(0);
}
ClientHistory.ClientCounts.Add(new ClientCountSnapshot
{
ClientCount = ClientNum,
ConnectionInterrupted = Throttled,
Time = DateTime.UtcNow,
Map = CurrentMap.Name
});
_lastPlayerCount = DateTime.Now;
}
public async Task Initialize() public async Task Initialize()
{ {
try try
{ {
ResolvedIpEndPoint = ResolvedIpEndPoint = new IPEndPoint((await Dns.GetHostAddressesAsync(IP)).First(), Port);
new IPEndPoint(
(await Dns.GetHostAddressesAsync(IP)).First(address =>
address.AddressFamily == AddressFamily.InterNetwork), Port);
} }
catch (Exception ex) catch (Exception ex)
{ {
ServerLogger.LogWarning(ex, "Could not resolve hostname or IP for RCon connection {IP}:{Port}", IP, Port); ServerLogger.LogWarning(ex, "Could not resolve hostname or IP for RCon connection {IP}:{Port}", IP, Port);
ResolvedIpEndPoint = new IPEndPoint(IPAddress.Parse(IP), Port); ResolvedIpEndPoint = new IPEndPoint(IPAddress.Parse(IP), Port);
} }
RconParser = Manager.AdditionalRConParsers RconParser = Manager.AdditionalRConParsers
.FirstOrDefault(parser => .FirstOrDefault(_parser => _parser.Version == ServerConfig.RConParserVersion);
parser.Version == ServerConfig.RConParserVersion ||
parser.Name == ServerConfig.RConParserVersion);
EventParser = Manager.AdditionalEventParsers EventParser = Manager.AdditionalEventParsers
.FirstOrDefault(parser => .FirstOrDefault(_parser => _parser.Version == ServerConfig.EventParserVersion);
parser.Version == ServerConfig.EventParserVersion ||
parser.Name == ServerConfig.RConParserVersion);
RconParser ??= Manager.AdditionalRConParsers[0]; RconParser ??= Manager.AdditionalRConParsers[0];
EventParser ??= Manager.AdditionalEventParsers[0]; EventParser ??= Manager.AdditionalEventParsers[0];
@ -1129,7 +1092,7 @@ namespace IW4MAdmin
RemoteConnection = RConConnectionFactory.CreateConnection(ResolvedIpEndPoint, Password, RconParser.RConEngine); RemoteConnection = RConConnectionFactory.CreateConnection(ResolvedIpEndPoint, Password, RconParser.RConEngine);
RemoteConnection.SetConfiguration(RconParser); RemoteConnection.SetConfiguration(RconParser);
var version = await this.GetMappedDvarValueOrDefaultAsync<string>("version", token: Manager.CancellationToken); var version = await this.GetMappedDvarValueOrDefaultAsync<string>("version");
Version = version.Value; Version = version.Value;
GameName = Utilities.GetGame(version.Value ?? RconParser.Version); GameName = Utilities.GetGame(version.Value ?? RconParser.Version);
@ -1138,7 +1101,7 @@ namespace IW4MAdmin
GameName = RconParser.GameName; GameName = RconParser.GameName;
} }
if (version.Value?.Length != 0) if (version?.Value?.Length != 0)
{ {
var matchedRconParser = Manager.AdditionalRConParsers.FirstOrDefault(_parser => _parser.Version == version.Value); var matchedRconParser = Manager.AdditionalRConParsers.FirstOrDefault(_parser => _parser.Version == version.Value);
RconParser.Configuration = matchedRconParser != null ? matchedRconParser.Configuration : RconParser.Configuration; RconParser.Configuration = matchedRconParser != null ? matchedRconParser.Configuration : RconParser.Configuration;
@ -1146,7 +1109,7 @@ namespace IW4MAdmin
Version = RconParser.Version; Version = RconParser.Version;
} }
var svRunning = await this.GetMappedDvarValueOrDefaultAsync<string>("sv_running", token: Manager.CancellationToken); var svRunning = await this.GetMappedDvarValueOrDefaultAsync<string>("sv_running");
if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1") if (!string.IsNullOrEmpty(svRunning.Value) && svRunning.Value != "1")
{ {
@ -1155,28 +1118,27 @@ namespace IW4MAdmin
var infoResponse = RconParser.Configuration.CommandPrefixes.RConGetInfo != null ? await this.GetInfoAsync() : null; var infoResponse = RconParser.Configuration.CommandPrefixes.RConGetInfo != null ? await this.GetInfoAsync() : null;
var hostname = (await this.GetMappedDvarValueOrDefaultAsync<string>("sv_hostname", "hostname", infoResponse, token: Manager.CancellationToken)).Value; string hostname = (await this.GetMappedDvarValueOrDefaultAsync<string>("sv_hostname", "hostname", infoResponse)).Value;
var mapname = (await this.GetMappedDvarValueOrDefaultAsync<string>("mapname", infoResponse: infoResponse, token: Manager.CancellationToken)).Value; string mapname = (await this.GetMappedDvarValueOrDefaultAsync<string>("mapname", infoResponse: infoResponse)).Value;
var maxplayers = (await this.GetMappedDvarValueOrDefaultAsync<int>("sv_maxclients", infoResponse: infoResponse, token: Manager.CancellationToken)).Value; int maxplayers = (await this.GetMappedDvarValueOrDefaultAsync<int>("sv_maxclients", infoResponse: infoResponse)).Value;
var gametype = (await this.GetMappedDvarValueOrDefaultAsync<string>("g_gametype", "gametype", infoResponse, token: Manager.CancellationToken)).Value; string gametype = (await this.GetMappedDvarValueOrDefaultAsync<string>("g_gametype", "gametype", infoResponse)).Value;
var basepath = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basepath", token: Manager.CancellationToken); var basepath = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basepath");
var basegame = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basegame", token: Manager.CancellationToken); var basegame = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_basegame");
var homepath = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_homepath", token: Manager.CancellationToken); var homepath = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_homepath");
var game = await this.GetMappedDvarValueOrDefaultAsync<string>("fs_game", infoResponse: infoResponse, token: Manager.CancellationToken); var game = (await this.GetMappedDvarValueOrDefaultAsync<string>("fs_game", infoResponse: infoResponse));
var logfile = await this.GetMappedDvarValueOrDefaultAsync<string>("g_log", token: Manager.CancellationToken); var logfile = await this.GetMappedDvarValueOrDefaultAsync<string>("g_log");
var logsync = await this.GetMappedDvarValueOrDefaultAsync<int>("g_logsync", token: Manager.CancellationToken); var logsync = await this.GetMappedDvarValueOrDefaultAsync<int>("g_logsync");
var ip = await this.GetMappedDvarValueOrDefaultAsync<string>("net_ip", token: Manager.CancellationToken); var ip = await this.GetMappedDvarValueOrDefaultAsync<string>("net_ip");
var gamePassword = await this.GetMappedDvarValueOrDefaultAsync("g_password", overrideDefault: "", token: Manager.CancellationToken); var gamePassword = await this.GetMappedDvarValueOrDefaultAsync("g_password", overrideDefault: "");
if (Manager.GetApplicationSettings().Configuration().EnableCustomSayName) if (Manager.GetApplicationSettings().Configuration().EnableCustomSayName)
{ {
await this.SetDvarAsync("sv_sayname", Manager.GetApplicationSettings().Configuration().CustomSayName, await this.SetDvarAsync("sv_sayname", Manager.GetApplicationSettings().Configuration().CustomSayName);
Manager.CancellationToken);
} }
try try
{ {
var website = await this.GetMappedDvarValueOrDefaultAsync<string>("_website", token: Manager.CancellationToken); var website = await this.GetMappedDvarValueOrDefaultAsync<string>("_website");
// this occurs for games that don't give us anything back when // this occurs for games that don't give us anything back when
// the dvar is not set // the dvar is not set
@ -1222,14 +1184,14 @@ namespace IW4MAdmin
if (logsync.Value == 0) if (logsync.Value == 0)
{ {
await this.SetDvarAsync("g_logsync", 2, Manager.CancellationToken); // set to 2 for continous in other games, clamps to 1 for IW4 await this.SetDvarAsync("g_logsync", 2); // set to 2 for continous in other games, clamps to 1 for IW4
needsRestart = true; needsRestart = true;
} }
if (string.IsNullOrWhiteSpace(logfile.Value)) if (string.IsNullOrWhiteSpace(logfile.Value))
{ {
logfile.Value = "games_mp.log"; logfile.Value = "games_mp.log";
await this.SetDvarAsync("g_log", logfile.Value, Manager.CancellationToken); await this.SetDvarAsync("g_log", logfile.Value);
needsRestart = true; needsRestart = true;
} }
@ -1241,7 +1203,7 @@ namespace IW4MAdmin
} }
// this DVAR isn't set until the a map is loaded // this DVAR isn't set until the a map is loaded
await this.SetDvarAsync("logfile", 2, Manager.CancellationToken); await this.SetDvarAsync("logfile", 2);
} }
CustomCallback = await ScriptLoaded(); CustomCallback = await ScriptLoaded();
@ -1382,7 +1344,7 @@ namespace IW4MAdmin
return; return;
} }
var message = loc["COMMANDS_WARNING_FORMAT_V2"] var message = loc["COMMANDS_WARNING_FORMAT"]
.FormatExt(activeClient.Warnings, activeClient.Name, reason); .FormatExt(activeClient.Warnings, activeClient.Name, reason);
activeClient.CurrentServer.Broadcast(message); activeClient.CurrentServer.Broadcast(message);
} }
@ -1466,6 +1428,7 @@ namespace IW4MAdmin
Offender = targetClient, Offender = targetClient,
Offense = reason, Offense = reason,
Punisher = originClient, Punisher = originClient,
Link = targetClient.AliasLink,
IsEvadedOffense = isEvade IsEvadedOffense = isEvade
}; };
@ -1500,8 +1463,7 @@ namespace IW4MAdmin
ServerLogger.LogDebug("Creating unban penalty for {targetClient}", targetClient.ToString()); ServerLogger.LogDebug("Creating unban penalty for {targetClient}", targetClient.ToString());
targetClient.SetLevel(Permission.User, originClient); targetClient.SetLevel(Permission.User, originClient);
await Manager.GetPenaltyService().RemoveActivePenalties(targetClient.AliasLink.AliasLinkId, await Manager.GetPenaltyService().RemoveActivePenalties(targetClient.AliasLink.AliasLinkId);
targetClient.NetworkId, targetClient.CurrentAlias?.IPAddress);
await Manager.GetPenaltyService().Create(unbanPenalty); await Manager.GetPenaltyService().Create(unbanPenalty);
} }
@ -1510,7 +1472,7 @@ namespace IW4MAdmin
Manager.GetMessageTokens().Add(new MessageToken("TOTALPLAYERS", (Server s) => Task.Run(async () => (await Manager.GetClientService().GetTotalClientsAsync()).ToString()))); Manager.GetMessageTokens().Add(new MessageToken("TOTALPLAYERS", (Server s) => Task.Run(async () => (await Manager.GetClientService().GetTotalClientsAsync()).ToString())));
Manager.GetMessageTokens().Add(new MessageToken("VERSION", (Server s) => Task.FromResult(Application.Program.Version.ToString()))); Manager.GetMessageTokens().Add(new MessageToken("VERSION", (Server s) => Task.FromResult(Application.Program.Version.ToString())));
Manager.GetMessageTokens().Add(new MessageToken("NEXTMAP", (Server s) => SharedLibraryCore.Commands.NextMapCommand.GetNextMap(s, _translationLookup))); 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)))); Manager.GetMessageTokens().Add(new MessageToken("ADMINS", (Server s) => Task.FromResult(SharedLibraryCore.Commands.ListAdminsCommand.OnlineAdmins(s, _translationLookup))));
} }
} }
} }

View File

@ -18,7 +18,6 @@ using SharedLibraryCore.Repositories;
using SharedLibraryCore.Services; using SharedLibraryCore.Services;
using Stats.Dtos; using Stats.Dtos;
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
@ -35,17 +34,16 @@ using IW4MAdmin.Plugins.Stats.Client.Abstractions;
using IW4MAdmin.Plugins.Stats.Client; using IW4MAdmin.Plugins.Stats.Client;
using Stats.Client.Abstractions; using Stats.Client.Abstractions;
using Stats.Client; using Stats.Client;
using Stats.Config;
using Stats.Helpers; using Stats.Helpers;
namespace IW4MAdmin.Application namespace IW4MAdmin.Application
{ {
public class Program public class Program
{ {
public static BuildNumber Version { get; } = BuildNumber.Parse(Utilities.GetVersionAsString()); public static BuildNumber Version { get; private set; } = BuildNumber.Parse(Utilities.GetVersionAsString());
private static ApplicationManager _serverManager; public static ApplicationManager ServerManager;
private static Task _applicationTask; private static Task ApplicationTask;
private static ServiceProvider _serviceProvider; private static ServiceProvider serviceProvider;
/// <summary> /// <summary>
/// entrypoint of the application /// entrypoint of the application
@ -58,7 +56,7 @@ namespace IW4MAdmin.Application
Console.OutputEncoding = Encoding.UTF8; Console.OutputEncoding = Encoding.UTF8;
Console.ForegroundColor = ConsoleColor.Gray; Console.ForegroundColor = ConsoleColor.Gray;
Console.CancelKeyPress += OnCancelKey; Console.CancelKeyPress += new ConsoleCancelEventHandler(OnCancelKey);
Console.WriteLine("====================================================="); Console.WriteLine("=====================================================");
Console.WriteLine(" IW4MAdmin"); Console.WriteLine(" IW4MAdmin");
@ -77,14 +75,10 @@ namespace IW4MAdmin.Application
/// <param name="e"></param> /// <param name="e"></param>
private static async void OnCancelKey(object sender, ConsoleCancelEventArgs e) private static async void OnCancelKey(object sender, ConsoleCancelEventArgs e)
{ {
if (_serverManager is not null) ServerManager?.Stop();
if (ApplicationTask != null)
{ {
await _serverManager.Stop(); await ApplicationTask;
}
if (_applicationTask is not null)
{
await _applicationTask;
} }
} }
@ -98,8 +92,9 @@ namespace IW4MAdmin.Application
ITranslationLookup translationLookup = null; ITranslationLookup translationLookup = null;
var logger = BuildDefaultLogger<Program>(new ApplicationConfiguration()); var logger = BuildDefaultLogger<Program>(new ApplicationConfiguration());
Utilities.DefaultLogger = logger; Utilities.DefaultLogger = logger;
logger.LogInformation("Begin IW4MAdmin startup. Version is {Version} {@Args}", Version, args); IServiceCollection services = null;
logger.LogInformation("Begin IW4MAdmin startup. Version is {version} {@args}", Version, args);
try try
{ {
// do any needed housekeeping file/folder migrations // do any needed housekeeping file/folder migrations
@ -107,30 +102,22 @@ namespace IW4MAdmin.Application
ConfigurationMigration.CheckDirectories(); ConfigurationMigration.CheckDirectories();
ConfigurationMigration.RemoveObsoletePlugins20210322(); ConfigurationMigration.RemoveObsoletePlugins20210322();
logger.LogDebug("Configuring services..."); logger.LogDebug("Configuring services...");
var services = await ConfigureServices(args); services = ConfigureServices(args);
_serviceProvider = services.BuildServiceProvider(); serviceProvider = services.BuildServiceProvider();
var versionChecker = _serviceProvider.GetRequiredService<IMasterCommunication>(); var versionChecker = serviceProvider.GetRequiredService<IMasterCommunication>();
_serverManager = (ApplicationManager) _serviceProvider.GetRequiredService<IManager>(); ServerManager = (ApplicationManager) serviceProvider.GetRequiredService<IManager>();
translationLookup = _serviceProvider.GetRequiredService<ITranslationLookup>(); translationLookup = serviceProvider.GetRequiredService<ITranslationLookup>();
_applicationTask = RunApplicationTasksAsync(logger, services); await versionChecker.CheckVersion();
var tasks = new[] await ServerManager.Init();
{
versionChecker.CheckVersion(),
_applicationTask
};
await _serverManager.Init();
await Task.WhenAll(tasks);
} }
catch (Exception e) catch (Exception e)
{ {
var failMessage = translationLookup == null string failMessage = translationLookup == null
? "Failed to initialize IW4MAdmin" ? "Failed to initialize IW4MAdmin"
: translationLookup["MANAGER_INIT_FAIL"]; : translationLookup["MANAGER_INIT_FAIL"];
var exitMessage = translationLookup == null string exitMessage = translationLookup == null
? "Press enter to exit..." ? "Press enter to exit..."
: translationLookup["MANAGER_EXIT"]; : translationLookup["MANAGER_EXIT"];
@ -144,10 +131,13 @@ namespace IW4MAdmin.Application
if (e is ConfigurationException configException) if (e is ConfigurationException configException)
{ {
Console.WriteLine("{{fileName}} contains an error." if (translationLookup != null)
.FormatExt(Path.GetFileName(configException.ConfigurationFileName))); {
Console.WriteLine(translationLookup[configException.Message]
.FormatExt(configException.ConfigurationFileName));
}
foreach (var error in configException.Errors) foreach (string error in configException.Errors)
{ {
Console.WriteLine(error); Console.WriteLine(error);
} }
@ -158,22 +148,32 @@ namespace IW4MAdmin.Application
Console.WriteLine(e.Message); Console.WriteLine(e.Message);
} }
if (_serverManager is not null)
{
await _serverManager?.Stop();
}
Console.WriteLine(exitMessage); Console.WriteLine(exitMessage);
await Console.In.ReadAsync(new char[1], 0, 1); await Console.In.ReadAsync(new char[1], 0, 1);
return; return;
} }
if (_serverManager.IsRestartRequested) try
{
ApplicationTask = RunApplicationTasksAsync(logger, services);
await ApplicationTask;
}
catch (Exception e)
{
logger.LogCritical(e, "Failed to launch IW4MAdmin");
string failMessage = translationLookup == null
? "Failed to launch IW4MAdmin"
: translationLookup["MANAGER_INIT_FAIL"];
Console.WriteLine($"{failMessage}: {e.GetExceptionInfo()}");
}
if (ServerManager.IsRestartRequested)
{ {
goto restart; goto restart;
} }
await _serviceProvider.DisposeAsync(); serviceProvider.Dispose();
} }
/// <summary> /// <summary>
@ -182,26 +182,24 @@ namespace IW4MAdmin.Application
/// <returns></returns> /// <returns></returns>
private static async Task RunApplicationTasksAsync(ILogger logger, IServiceCollection services) private static async Task RunApplicationTasksAsync(ILogger logger, IServiceCollection services)
{ {
var webfrontTask = _serverManager.GetApplicationSettings().Configuration().EnableWebFront var webfrontTask = ServerManager.GetApplicationSettings().Configuration().EnableWebFront
? WebfrontCore.Program.Init(_serverManager, _serviceProvider, services, _serverManager.CancellationToken) ? WebfrontCore.Program.Init(ServerManager, serviceProvider, services, ServerManager.CancellationToken)
: Task.CompletedTask; : Task.CompletedTask;
var collectionService = _serviceProvider.GetRequiredService<IServerDataCollector>(); var collectionService = serviceProvider.GetRequiredService<IServerDataCollector>();
// we want to run this one on a manual thread instead of letting the thread pool handle it, // 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 // because we can't exit early from waiting on console input, and it prevents us from restarting
async void ReadInput() => await ReadConsoleInput(logger); var inputThread = new Thread(async () => await ReadConsoleInput(logger));
var inputThread = new Thread(ReadInput);
inputThread.Start(); inputThread.Start();
var tasks = new[] var tasks = new[]
{ {
ServerManager.Start(),
webfrontTask, webfrontTask,
_serverManager.Start(), serviceProvider.GetRequiredService<IMasterCommunication>()
_serviceProvider.GetRequiredService<IMasterCommunication>() .RunUploadStatus(ServerManager.CancellationToken),
.RunUploadStatus(_serverManager.CancellationToken), collectionService.BeginCollectionAsync(cancellationToken: ServerManager.CancellationToken)
collectionService.BeginCollectionAsync(cancellationToken: _serverManager.CancellationToken)
}; };
logger.LogDebug("Starting webfront and input tasks"); logger.LogDebug("Starting webfront and input tasks");
@ -210,7 +208,8 @@ namespace IW4MAdmin.Application
logger.LogInformation("Shutdown completed successfully"); logger.LogInformation("Shutdown completed successfully");
Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_SHUTDOWN_SUCCESS"]); Console.WriteLine(Utilities.CurrentLocalization.LocalizationIndex["MANAGER_SHUTDOWN_SUCCESS"]);
} }
/// <summary> /// <summary>
/// reads input from the console and executes entered commands on the default server /// reads input from the console and executes entered commands on the default server
/// </summary> /// </summary>
@ -223,41 +222,32 @@ namespace IW4MAdmin.Application
return; return;
} }
EFClient origin = null; string lastCommand;
var Origin = Utilities.IW4MAdminClient(ServerManager.Servers[0]);
try try
{ {
while (!_serverManager.CancellationToken.IsCancellationRequested) while (!ServerManager.CancellationToken.IsCancellationRequested)
{ {
if (!_serverManager.IsInitialized) lastCommand = await Console.In.ReadLineAsync();
if (lastCommand?.Length > 0)
{ {
await Task.Delay(1000); if (lastCommand?.Length > 0)
continue; {
GameEvent E = new GameEvent()
{
Type = GameEvent.EventType.Command,
Data = lastCommand,
Origin = Origin,
Owner = ServerManager.Servers[0]
};
ServerManager.AddEvent(E);
await E.WaitAsync(Utilities.DefaultCommandTimeout, ServerManager.CancellationToken);
Console.Write('>');
}
} }
var lastCommand = await Console.In.ReadLineAsync();
if (lastCommand == null)
{
continue;
}
if (!lastCommand.Any())
{
continue;
}
var gameEvent = new GameEvent
{
Type = GameEvent.EventType.Command,
Data = lastCommand,
Origin = origin ??= Utilities.IW4MAdminClient(_serverManager.Servers.FirstOrDefault()),
Owner = _serverManager.Servers[0]
};
_serverManager.AddEvent(gameEvent);
await gameEvent.WaitAsync(Utilities.DefaultCommandTimeout, _serverManager.CancellationToken);
Console.Write('>');
} }
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@ -285,10 +275,10 @@ namespace IW4MAdmin.Application
// register the native commands // register the native commands
foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes() foreach (var commandType in typeof(SharedLibraryCore.Commands.QuitCommand).Assembly.GetTypes()
.Concat(typeof(Program).Assembly.GetTypes().Where(type => type.Namespace?.StartsWith("IW4MAdmin.Application.Commands") ?? false)) .Concat(typeof(Program).Assembly.GetTypes().Where(type => type.Namespace == "IW4MAdmin.Application.Commands"))
.Where(command => command.BaseType == typeof(Command))) .Where(_command => _command.BaseType == typeof(Command)))
{ {
defaultLogger.LogDebug("Registered native command type {Name}", commandType.Name); defaultLogger.LogDebug("Registered native command type {name}", commandType.Name);
serviceCollection.AddSingleton(typeof(IManagerCommand), commandType); serviceCollection.AddSingleton(typeof(IManagerCommand), commandType);
} }
@ -296,40 +286,40 @@ namespace IW4MAdmin.Application
var (plugins, commands, configurations) = pluginImporter.DiscoverAssemblyPluginImplementations(); var (plugins, commands, configurations) = pluginImporter.DiscoverAssemblyPluginImplementations();
foreach (var pluginType in plugins) foreach (var pluginType in plugins)
{ {
defaultLogger.LogDebug("Registered plugin type {Name}", pluginType.FullName); defaultLogger.LogDebug("Registered plugin type {name}", pluginType.FullName);
serviceCollection.AddSingleton(typeof(IPlugin), pluginType); serviceCollection.AddSingleton(typeof(IPlugin), pluginType);
} }
// register the plugin commands // register the plugin commands
foreach (var commandType in commands) foreach (var commandType in commands)
{ {
defaultLogger.LogDebug("Registered plugin command type {Name}", commandType.FullName); defaultLogger.LogDebug("Registered plugin command type {name}", commandType.FullName);
serviceCollection.AddSingleton(typeof(IManagerCommand), commandType); serviceCollection.AddSingleton(typeof(IManagerCommand), commandType);
} }
foreach (var configurationType in configurations) foreach (var configurationType in configurations)
{ {
defaultLogger.LogDebug("Registered plugin config type {Name}", configurationType.Name); defaultLogger.LogDebug("Registered plugin config type {name}", configurationType.Name);
var configInstance = (IBaseConfiguration) Activator.CreateInstance(configurationType); var configInstance = (IBaseConfiguration) Activator.CreateInstance(configurationType);
var handlerType = typeof(BaseConfigurationHandler<>).MakeGenericType(configurationType); var handlerType = typeof(BaseConfigurationHandler<>).MakeGenericType(configurationType);
var handlerInstance = Activator.CreateInstance(handlerType, configInstance.Name()); var handlerInstance = Activator.CreateInstance(handlerType, new[] {configInstance.Name()});
var genericInterfaceType = typeof(IConfigurationHandler<>).MakeGenericType(configurationType); var genericInterfaceType = typeof(IConfigurationHandler<>).MakeGenericType(configurationType);
serviceCollection.AddSingleton(genericInterfaceType, handlerInstance); serviceCollection.AddSingleton(genericInterfaceType, handlerInstance);
} }
// register any script plugins // register any script plugins
foreach (var plugin in pluginImporter.DiscoverScriptPlugins()) foreach (var scriptPlugin in pluginImporter.DiscoverScriptPlugins())
{ {
serviceCollection.AddSingleton(plugin); serviceCollection.AddSingleton(scriptPlugin);
} }
// register any eventable types // register any eventable types
foreach (var assemblyType in typeof(Program).Assembly.GetTypes() foreach (var assemblyType in typeof(Program).Assembly.GetTypes()
.Where(asmType => typeof(IRegisterEvent).IsAssignableFrom(asmType)) .Where(_asmType => typeof(IRegisterEvent).IsAssignableFrom(_asmType))
.Union(plugins.SelectMany(asm => asm.Assembly.GetTypes()) .Union(plugins.SelectMany(_asm => _asm.Assembly.GetTypes())
.Distinct() .Distinct()
.Where(asmType => typeof(IRegisterEvent).IsAssignableFrom(asmType)))) .Where(_asmType => typeof(IRegisterEvent).IsAssignableFrom(_asmType))))
{ {
var instance = Activator.CreateInstance(assemblyType) as IRegisterEvent; var instance = Activator.CreateInstance(assemblyType) as IRegisterEvent;
serviceCollection.AddSingleton(instance); serviceCollection.AddSingleton(instance);
@ -342,21 +332,12 @@ namespace IW4MAdmin.Application
/// <summary> /// <summary>
/// Configures the dependency injection services /// Configures the dependency injection services
/// </summary> /// </summary>
private static async Task<IServiceCollection> ConfigureServices(string[] args) private static IServiceCollection ConfigureServices(string[] args)
{ {
// todo: this is a quick fix
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
// setup the static resources (config/master api/translations) // setup the static resources (config/master api/translations)
var serviceCollection = new ServiceCollection(); var serviceCollection = new ServiceCollection();
var appConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings"); var appConfigHandler = new BaseConfigurationHandler<ApplicationConfiguration>("IW4MAdminSettings");
await appConfigHandler.BuildAsync();
var defaultConfigHandler = new BaseConfigurationHandler<DefaultSettings>("DefaultSettings"); var defaultConfigHandler = new BaseConfigurationHandler<DefaultSettings>("DefaultSettings");
await defaultConfigHandler.BuildAsync();
var commandConfigHandler = new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration");
await commandConfigHandler.BuildAsync();
var statsCommandHandler = new BaseConfigurationHandler<StatsConfiguration>("StatsPluginSettings");
await statsCommandHandler.BuildAsync();
var defaultConfig = defaultConfigHandler.Configuration(); var defaultConfig = defaultConfigHandler.Configuration();
var appConfig = appConfigHandler.Configuration(); var appConfig = appConfigHandler.Configuration();
var masterUri = Utilities.IsDevelopment var masterUri = Utilities.IsDevelopment
@ -374,7 +355,7 @@ namespace IW4MAdmin.Application
{ {
appConfig = (ApplicationConfiguration) new ApplicationConfiguration().Generate(); appConfig = (ApplicationConfiguration) new ApplicationConfiguration().Generate();
appConfigHandler.Set(appConfig); appConfigHandler.Set(appConfig);
await appConfigHandler.Save(); appConfigHandler.Save();
} }
// register override level names // register override level names
@ -392,14 +373,15 @@ namespace IW4MAdmin.Application
serviceCollection serviceCollection
.AddBaseLogger(appConfig) .AddBaseLogger(appConfig)
.AddSingleton(defaultConfig) .AddSingleton(defaultConfig)
.AddSingleton<IServiceCollection>(serviceCollection) .AddSingleton<IServiceCollection>(_serviceProvider => serviceCollection)
.AddSingleton<IConfigurationHandler<DefaultSettings>, BaseConfigurationHandler<DefaultSettings>>() .AddSingleton<IConfigurationHandler<DefaultSettings>, BaseConfigurationHandler<DefaultSettings>>()
.AddSingleton((IConfigurationHandler<ApplicationConfiguration>) appConfigHandler) .AddSingleton((IConfigurationHandler<ApplicationConfiguration>) appConfigHandler)
.AddSingleton<IConfigurationHandler<CommandConfiguration>>(commandConfigHandler) .AddSingleton(
new BaseConfigurationHandler<CommandConfiguration>("CommandConfiguration") as
IConfigurationHandler<CommandConfiguration>)
.AddSingleton(appConfig) .AddSingleton(appConfig)
.AddSingleton(statsCommandHandler.Configuration() ?? new StatsConfiguration()) .AddSingleton(_serviceProvider =>
.AddSingleton(serviceProvider => _serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>()
serviceProvider.GetRequiredService<IConfigurationHandler<CommandConfiguration>>()
.Configuration() ?? new CommandConfiguration()) .Configuration() ?? new CommandConfiguration())
.AddSingleton<IPluginImporter, PluginImporter>() .AddSingleton<IPluginImporter, PluginImporter>()
.AddSingleton<IMiddlewareActionHandler, MiddlewareActionHandler>() .AddSingleton<IMiddlewareActionHandler, MiddlewareActionHandler>()
@ -412,10 +394,7 @@ namespace IW4MAdmin.Application
.AddSingleton<IScriptCommandFactory, ScriptCommandFactory>() .AddSingleton<IScriptCommandFactory, ScriptCommandFactory>()
.AddSingleton<IAuditInformationRepository, AuditInformationRepository>() .AddSingleton<IAuditInformationRepository, AuditInformationRepository>()
.AddSingleton<IEntityService<EFClient>, ClientService>() .AddSingleton<IEntityService<EFClient>, ClientService>()
#pragma warning disable CS0618
.AddSingleton<IMetaService, MetaService>() .AddSingleton<IMetaService, MetaService>()
#pragma warning restore CS0618
.AddSingleton<IMetaServiceV2, MetaServiceV2>()
.AddSingleton<ClientService>() .AddSingleton<ClientService>()
.AddSingleton<PenaltyService>() .AddSingleton<PenaltyService>()
.AddSingleton<ChangeHistoryService>() .AddSingleton<ChangeHistoryService>()
@ -429,14 +408,11 @@ namespace IW4MAdmin.Application
UpdatedAliasResourceQueryHelper>() UpdatedAliasResourceQueryHelper>()
.AddSingleton<IResourceQueryHelper<ChatSearchQuery, MessageResponse>, ChatResourceQueryHelper>() .AddSingleton<IResourceQueryHelper<ChatSearchQuery, MessageResponse>, ChatResourceQueryHelper>()
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse>, ConnectionsResourceQueryHelper>() .AddSingleton<IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse>, ConnectionsResourceQueryHelper>()
.AddSingleton<IResourceQueryHelper<ClientPaginationRequest, PermissionLevelChangedResponse>, PermissionLevelChangedResourceQueryHelper>()
.AddTransient<IParserPatternMatcher, ParserPatternMatcher>() .AddTransient<IParserPatternMatcher, ParserPatternMatcher>()
.AddSingleton<IRemoteAssemblyHandler, RemoteAssemblyHandler>() .AddSingleton<IRemoteAssemblyHandler, RemoteAssemblyHandler>()
.AddSingleton<IMasterCommunication, MasterCommunication>() .AddSingleton<IMasterCommunication, MasterCommunication>()
.AddSingleton<IManager, ApplicationManager>() .AddSingleton<IManager, ApplicationManager>()
#pragma warning disable CS0612
.AddSingleton<SharedLibraryCore.Interfaces.ILogger, Logger>() .AddSingleton<SharedLibraryCore.Interfaces.ILogger, Logger>()
#pragma warning restore CS0612
.AddSingleton<IClientNoticeMessageFormatter, ClientNoticeMessageFormatter>() .AddSingleton<IClientNoticeMessageFormatter, ClientNoticeMessageFormatter>()
.AddSingleton<IClientStatisticCalculator, HitCalculator>() .AddSingleton<IClientStatisticCalculator, HitCalculator>()
.AddSingleton<IServerDistributionCalculator, ServerDistributionCalculator>() .AddSingleton<IServerDistributionCalculator, ServerDistributionCalculator>()
@ -447,8 +423,6 @@ namespace IW4MAdmin.Application
.AddSingleton<IServerDataViewer, ServerDataViewer>() .AddSingleton<IServerDataViewer, ServerDataViewer>()
.AddSingleton<IServerDataCollector, ServerDataCollector>() .AddSingleton<IServerDataCollector, ServerDataCollector>()
.AddSingleton<IEventPublisher, EventPublisher>() .AddSingleton<IEventPublisher, EventPublisher>()
.AddSingleton<IGeoLocationService>(new GeoLocationService(Path.Join(".", "Resources", "GeoLite2-Country.mmdb")))
.AddTransient<IScriptPluginTimerHelper, ScriptPluginTimerHelper>()
.AddSingleton(translationLookup) .AddSingleton(translationLookup)
.AddDatabaseContextOptions(appConfig); .AddDatabaseContextOptions(appConfig);
@ -475,4 +449,4 @@ namespace IW4MAdmin.Application
return collection.GetRequiredService<ILogger<T>>(); return collection.GetRequiredService<ILogger<T>>();
} }
} }
} }

View File

@ -5,7 +5,6 @@ using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper; using SharedLibraryCore.QueryHelper;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
@ -16,28 +15,19 @@ namespace IW4MAdmin.Application.Meta
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private ITranslationLookup _transLookup; private ITranslationLookup _transLookup;
private readonly IMetaServiceV2 _metaService; private readonly IMetaService _metaService;
private readonly IEntityService<EFClient> _clientEntityService; private readonly IEntityService<EFClient> _clientEntityService;
private readonly IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> _receivedPenaltyHelper; private readonly IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> _receivedPenaltyHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> _administeredPenaltyHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse>
_administeredPenaltyHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> _updatedAliasHelper; private readonly IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> _updatedAliasHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse> private readonly IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse>
_connectionHistoryHelper; _connectionHistoryHelper;
private readonly IResourceQueryHelper<ClientPaginationRequest, PermissionLevelChangedResponse> public MetaRegistration(ILogger<MetaRegistration> logger, IMetaService metaService, ITranslationLookup transLookup, IEntityService<EFClient> clientEntityService,
_permissionLevelHelper;
public MetaRegistration(ILogger<MetaRegistration> logger, IMetaServiceV2 metaService,
ITranslationLookup transLookup, IEntityService<EFClient> clientEntityService,
IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> receivedPenaltyHelper, IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse> receivedPenaltyHelper,
IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> administeredPenaltyHelper, IResourceQueryHelper<ClientPaginationRequest, AdministeredPenaltyResponse> administeredPenaltyHelper,
IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> updatedAliasHelper, IResourceQueryHelper<ClientPaginationRequest, UpdatedAliasResponse> updatedAliasHelper,
IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse> connectionHistoryHelper, IResourceQueryHelper<ClientPaginationRequest, ConnectionHistoryResponse> connectionHistoryHelper)
IResourceQueryHelper<ClientPaginationRequest, PermissionLevelChangedResponse> permissionLevelHelper)
{ {
_logger = logger; _logger = logger;
_transLookup = transLookup; _transLookup = transLookup;
@ -47,31 +37,21 @@ namespace IW4MAdmin.Application.Meta
_administeredPenaltyHelper = administeredPenaltyHelper; _administeredPenaltyHelper = administeredPenaltyHelper;
_updatedAliasHelper = updatedAliasHelper; _updatedAliasHelper = updatedAliasHelper;
_connectionHistoryHelper = connectionHistoryHelper; _connectionHistoryHelper = connectionHistoryHelper;
_permissionLevelHelper = permissionLevelHelper;
} }
public void Register() public void Register()
{ {
_metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, _metaService.AddRuntimeMeta<ClientPaginationRequest, InformationResponse>(MetaType.Information, GetProfileMeta);
GetProfileMeta); _metaService.AddRuntimeMeta<ClientPaginationRequest, ReceivedPenaltyResponse>(MetaType.ReceivedPenalty, GetReceivedPenaltiesMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, ReceivedPenaltyResponse>(MetaType.ReceivedPenalty, _metaService.AddRuntimeMeta<ClientPaginationRequest, AdministeredPenaltyResponse>(MetaType.Penalized, GetAdministeredPenaltiesMeta);
GetReceivedPenaltiesMeta); _metaService.AddRuntimeMeta<ClientPaginationRequest, UpdatedAliasResponse>(MetaType.AliasUpdate, GetUpdatedAliasMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, AdministeredPenaltyResponse>(MetaType.Penalized, _metaService.AddRuntimeMeta<ClientPaginationRequest, ConnectionHistoryResponse>(MetaType.ConnectionHistory, GetConnectionHistoryMeta);
GetAdministeredPenaltiesMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, UpdatedAliasResponse>(MetaType.AliasUpdate,
GetUpdatedAliasMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, ConnectionHistoryResponse>(MetaType.ConnectionHistory,
GetConnectionHistoryMeta);
_metaService.AddRuntimeMeta<ClientPaginationRequest, PermissionLevelChangedResponse>(
MetaType.PermissionLevel, GetPermissionLevelMeta);
} }
private async Task<IEnumerable<InformationResponse>> GetProfileMeta(ClientPaginationRequest request, private async Task<IEnumerable<InformationResponse>> GetProfileMeta(ClientPaginationRequest request)
CancellationToken cancellationToken = default)
{ {
var metaList = new List<InformationResponse>(); var metaList = new List<InformationResponse>();
var lastMapMeta = var lastMapMeta = await _metaService.GetPersistentMeta("LastMapPlayed", new EFClient() { ClientId = request.ClientId });
await _metaService.GetPersistentMeta("LastMapPlayed", request.ClientId, cancellationToken);
if (lastMapMeta != null) if (lastMapMeta != null)
{ {
@ -83,12 +63,12 @@ namespace IW4MAdmin.Application.Meta
Value = lastMapMeta.Value, Value = lastMapMeta.Value,
ShouldDisplay = true, ShouldDisplay = true,
Type = MetaType.Information, Type = MetaType.Information,
Column = 1,
Order = 6 Order = 6
}); });
} }
var lastServerMeta = var lastServerMeta = await _metaService.GetPersistentMeta("LastServerPlayed", new EFClient() { ClientId = request.ClientId });
await _metaService.GetPersistentMeta("LastServerPlayed", request.ClientId, cancellationToken);
if (lastServerMeta != null) if (lastServerMeta != null)
{ {
@ -100,7 +80,8 @@ namespace IW4MAdmin.Application.Meta
Value = lastServerMeta.Value, Value = lastServerMeta.Value,
ShouldDisplay = true, ShouldDisplay = true,
Type = MetaType.Information, Type = MetaType.Information,
Order = 7 Column = 0,
Order = 6
}); });
} }
@ -108,7 +89,7 @@ namespace IW4MAdmin.Application.Meta
if (client == null) if (client == null)
{ {
_logger.LogWarning("No client found with id {ClientId} when generating profile meta", request.ClientId); _logger.LogWarning("No client found with id {clientId} when generating profile meta", request.ClientId);
return metaList; return metaList;
} }
@ -118,7 +99,8 @@ namespace IW4MAdmin.Application.Meta
Key = _transLookup["WEBFRONT_PROFILE_META_PLAY_TIME"], Key = _transLookup["WEBFRONT_PROFILE_META_PLAY_TIME"],
Value = TimeSpan.FromHours(client.TotalConnectionTime / 3600.0).HumanizeForCurrentCulture(), Value = TimeSpan.FromHours(client.TotalConnectionTime / 3600.0).HumanizeForCurrentCulture(),
ShouldDisplay = true, ShouldDisplay = true,
Order = 8, Column = 1,
Order = 0,
Type = MetaType.Information Type = MetaType.Information
}); });
@ -128,7 +110,8 @@ namespace IW4MAdmin.Application.Meta
Key = _transLookup["WEBFRONT_PROFILE_META_FIRST_SEEN"], Key = _transLookup["WEBFRONT_PROFILE_META_FIRST_SEEN"],
Value = (DateTime.UtcNow - client.FirstConnection).HumanizeForCurrentCulture(), Value = (DateTime.UtcNow - client.FirstConnection).HumanizeForCurrentCulture(),
ShouldDisplay = true, ShouldDisplay = true,
Order = 9, Column = 1,
Order = 1,
Type = MetaType.Information Type = MetaType.Information
}); });
@ -138,7 +121,8 @@ namespace IW4MAdmin.Application.Meta
Key = _transLookup["WEBFRONT_PROFILE_META_LAST_SEEN"], Key = _transLookup["WEBFRONT_PROFILE_META_LAST_SEEN"],
Value = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(), Value = (DateTime.UtcNow - client.LastConnection).HumanizeForCurrentCulture(),
ShouldDisplay = true, ShouldDisplay = true,
Order = 10, Column = 1,
Order = 2,
Type = MetaType.Information Type = MetaType.Information
}); });
@ -146,10 +130,10 @@ namespace IW4MAdmin.Application.Meta
{ {
ClientId = client.ClientId, ClientId = client.ClientId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_CONNECTIONS"], Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_CONNECTIONS"],
Value = client.Connections.ToString("#,##0", Value = client.Connections.ToString("#,##0", new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
new System.Globalization.CultureInfo(Utilities.CurrentLocalization.LocalizationName)),
ShouldDisplay = true, ShouldDisplay = true,
Order = 11, Column = 1,
Order = 3,
Type = MetaType.Information Type = MetaType.Information
}); });
@ -157,50 +141,38 @@ namespace IW4MAdmin.Application.Meta
{ {
ClientId = client.ClientId, ClientId = client.ClientId,
Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_MASKED"], Key = Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_MASKED"],
Value = client.Masked Value = client.Masked ? Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_TRUE"] : Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_FALSE"],
? Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_TRUE"]
: Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_CLIENT_META_FALSE"],
IsSensitive = true, IsSensitive = true,
Order = 12, Column = 1,
Order = 4,
Type = MetaType.Information Type = MetaType.Information
}); });
return metaList; return metaList;
} }
private async Task<IEnumerable<ReceivedPenaltyResponse>> GetReceivedPenaltiesMeta( private async Task<IEnumerable<ReceivedPenaltyResponse>> GetReceivedPenaltiesMeta(ClientPaginationRequest request)
ClientPaginationRequest request, CancellationToken token = default)
{ {
var penalties = await _receivedPenaltyHelper.QueryResource(request); var penalties = await _receivedPenaltyHelper.QueryResource(request);
return penalties.Results; return penalties.Results;
} }
private async Task<IEnumerable<AdministeredPenaltyResponse>> GetAdministeredPenaltiesMeta( private async Task<IEnumerable<AdministeredPenaltyResponse>> GetAdministeredPenaltiesMeta(ClientPaginationRequest request)
ClientPaginationRequest request, CancellationToken token = default)
{ {
var penalties = await _administeredPenaltyHelper.QueryResource(request); var penalties = await _administeredPenaltyHelper.QueryResource(request);
return penalties.Results; return penalties.Results;
} }
private async Task<IEnumerable<UpdatedAliasResponse>> GetUpdatedAliasMeta(ClientPaginationRequest request, private async Task<IEnumerable<UpdatedAliasResponse>> GetUpdatedAliasMeta(ClientPaginationRequest request)
CancellationToken token = default)
{ {
var aliases = await _updatedAliasHelper.QueryResource(request); var aliases = await _updatedAliasHelper.QueryResource(request);
return aliases.Results; return aliases.Results;
} }
private async Task<IEnumerable<ConnectionHistoryResponse>> GetConnectionHistoryMeta( private async Task<IEnumerable<ConnectionHistoryResponse>> GetConnectionHistoryMeta(ClientPaginationRequest request)
ClientPaginationRequest request, CancellationToken token = default)
{ {
var connections = await _connectionHistoryHelper.QueryResource(request); var connections = await _connectionHistoryHelper.QueryResource(request);
return connections.Results; return connections.Results;
} }
private async Task<IEnumerable<PermissionLevelChangedResponse>> GetPermissionLevelMeta(
ClientPaginationRequest request, CancellationToken token = default)
{
var permissionChanges = await _permissionLevelHelper.QueryResource(request);
return permissionChanges.Results;
}
} }
} }

View File

@ -1,51 +0,0 @@
using System.Linq;
using System.Threading.Tasks;
using Data.Abstractions;
using Data.Models;
using Microsoft.EntityFrameworkCore;
using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
namespace IW4MAdmin.Application.Meta;
public class
PermissionLevelChangedResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest,
PermissionLevelChangedResponse>
{
private readonly IDatabaseContextFactory _contextFactory;
public PermissionLevelChangedResourceQueryHelper(IDatabaseContextFactory contextFactory)
{
_contextFactory = contextFactory;
}
public async Task<ResourceQueryHelperResult<PermissionLevelChangedResponse>> QueryResource(
ClientPaginationRequest query)
{
await using var context = _contextFactory.CreateContext();
var auditEntries = context.EFChangeHistory.Where(change => change.TargetEntityId == query.ClientId)
.Where(change => change.TypeOfChange == EFChangeHistory.ChangeType.Permission);
var audits = from change in auditEntries
join client in context.Clients
on change.OriginEntityId equals client.ClientId
select new PermissionLevelChangedResponse
{
ChangedById = change.OriginEntityId,
ChangedByName = client.CurrentAlias.Name,
PreviousPermissionLevelValue = change.PreviousValue,
CurrentPermissionLevelValue = change.CurrentValue,
When = change.TimeChanged,
ClientId = change.TargetEntityId,
IsSensitive = true
};
return new ResourceQueryHelperResult<PermissionLevelChangedResponse>
{
Results = await audits.Skip(query.Offset).Take(query.Count).ToListAsync()
};
}
}

View File

@ -11,7 +11,6 @@ using SharedLibraryCore.Dtos.Meta.Responses;
using SharedLibraryCore.Helpers; using SharedLibraryCore.Helpers;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper; using SharedLibraryCore.QueryHelper;
using SharedLibraryCore.Services;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application.Meta namespace IW4MAdmin.Application.Meta
@ -20,14 +19,13 @@ namespace IW4MAdmin.Application.Meta
/// implementation of IResourceQueryHelper /// implementation of IResourceQueryHelper
/// used to pull in penalties applied to a given client id /// used to pull in penalties applied to a given client id
/// </summary> /// </summary>
public class public class ReceivedPenaltyResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse>
ReceivedPenaltyResourceQueryHelper : IResourceQueryHelper<ClientPaginationRequest, ReceivedPenaltyResponse>
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IDatabaseContextFactory _contextFactory; private readonly IDatabaseContextFactory _contextFactory;
private readonly ApplicationConfiguration _appConfig; private readonly ApplicationConfiguration _appConfig;
public ReceivedPenaltyResourceQueryHelper(ILogger<ReceivedPenaltyResourceQueryHelper> logger, public ReceivedPenaltyResourceQueryHelper(ILogger<ReceivedPenaltyResourceQueryHelper> logger,
IDatabaseContextFactory contextFactory, ApplicationConfiguration appConfig) IDatabaseContextFactory contextFactory, ApplicationConfiguration appConfig)
{ {
_contextFactory = contextFactory; _contextFactory = contextFactory;
@ -35,58 +33,45 @@ namespace IW4MAdmin.Application.Meta
_appConfig = appConfig; _appConfig = appConfig;
} }
public async Task<ResourceQueryHelperResult<ReceivedPenaltyResponse>> QueryResource( public async Task<ResourceQueryHelperResult<ReceivedPenaltyResponse>> QueryResource(ClientPaginationRequest query)
ClientPaginationRequest query)
{ {
var linkedPenaltyType = Utilities.LinkedPenaltyTypes(); var linkedPenaltyType = Utilities.LinkedPenaltyTypes();
await using var ctx = _contextFactory.CreateContext(enableTracking: false); await using var ctx = _contextFactory.CreateContext(enableTracking: false);
var linkId = await ctx.Clients.AsNoTracking() var linkId = await ctx.Clients.AsNoTracking()
.Where(_client => _client.ClientId == query.ClientId) .Where(_client => _client.ClientId == query.ClientId)
.Select(_client => new { _client.AliasLinkId, _client.CurrentAliasId }) .Select(_client => new {_client.AliasLinkId, _client.CurrentAliasId })
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
var iqPenalties = ctx.Penalties.AsNoTracking() var iqPenalties = ctx.Penalties.AsNoTracking()
.Where(_penalty => _penalty.OffenderId == query.ClientId || .Where(_penalty => _penalty.OffenderId == query.ClientId ||
linkedPenaltyType.Contains(_penalty.Type) && _penalty.LinkId == linkId.AliasLinkId); linkedPenaltyType.Contains(_penalty.Type) && _penalty.LinkId == linkId.AliasLinkId);
IQueryable<EFPenalty> iqIpLinkedPenalties = null; IQueryable<EFPenalty> iqIpLinkedPenalties = null;
IQueryable<EFPenalty> identifierPenalties = null;
if (!_appConfig.EnableImplicitAccountLinking) if (!_appConfig.EnableImplicitAccountLinking)
{ {
var usedIps = await ctx.Aliases.AsNoTracking() var usedIps = await ctx.Aliases.AsNoTracking()
.Where(alias => .Where(alias => (alias.LinkId == linkId.AliasLinkId || alias.AliasId == linkId.CurrentAliasId) && alias.IPAddress != null)
(alias.LinkId == linkId.AliasLinkId || alias.AliasId == linkId.CurrentAliasId) &&
alias.IPAddress != null)
.Select(alias => alias.IPAddress).ToListAsync(); .Select(alias => alias.IPAddress).ToListAsync();
identifierPenalties = ctx.PenaltyIdentifiers.AsNoTracking().Where(identifier =>
identifier.IPv4Address != null && usedIps.Contains(identifier.IPv4Address))
.Select(id => id.Penalty);
var aliasedIds = await ctx.Aliases.AsNoTracking().Where(alias => usedIps.Contains(alias.IPAddress)) var aliasedIds = await ctx.Aliases.AsNoTracking().Where(alias => usedIps.Contains(alias.IPAddress))
.Select(alias => alias.LinkId) .Select(alias => alias.LinkId)
.ToListAsync(); .ToListAsync();
iqIpLinkedPenalties = ctx.Penalties.AsNoTracking() iqIpLinkedPenalties = ctx.Penalties.AsNoTracking()
.Where(penalty => .Where(penalty =>
linkedPenaltyType.Contains(penalty.Type) && aliasedIds.Contains(penalty.LinkId ?? -1)); linkedPenaltyType.Contains(penalty.Type) && aliasedIds.Contains(penalty.LinkId));
} }
var iqAllPenalties = iqPenalties; var iqAllPenalties = iqPenalties;
if (iqIpLinkedPenalties != null) if (iqIpLinkedPenalties != null)
{ {
iqAllPenalties = iqPenalties.Union(iqIpLinkedPenalties); iqAllPenalties = iqPenalties.Union(iqIpLinkedPenalties);
} }
if (identifierPenalties != null) var penalties = await iqAllPenalties
{
iqAllPenalties = iqPenalties.Union(identifierPenalties);
}
var penalties = await iqAllPenalties
.Where(_penalty => _penalty.When < query.Before) .Where(_penalty => _penalty.When < query.Before)
.OrderByDescending(_penalty => _penalty.When) .OrderByDescending(_penalty => _penalty.When)
.Take(query.Count) .Take(query.Count)
@ -112,7 +97,7 @@ namespace IW4MAdmin.Application.Meta
{ {
// todo: maybe actually count // todo: maybe actually count
RetrievedResultCount = penalties.Count, RetrievedResultCount = penalties.Count,
Results = penalties.Distinct() Results = penalties
}; };
} }
} }

View File

@ -88,7 +88,7 @@ namespace IW4MAdmin.Application.Migration
public static void RemoveObsoletePlugins20210322() public static void RemoveObsoletePlugins20210322()
{ {
var files = new[] {"StatsWeb.dll", "StatsWeb.Views.dll", "IW4ScriptCommands.dll"}; var files = new[] {"StatsWeb.dll", "StatsWeb.Views.dll"};
foreach (var file in files) foreach (var file in files)
{ {

View File

@ -1,13 +1,10 @@
using SharedLibraryCore; using Newtonsoft.Json;
using SharedLibraryCore;
using SharedLibraryCore.Exceptions; using SharedLibraryCore.Exceptions;
using SharedLibraryCore.Interfaces; using SharedLibraryCore.Interfaces;
using System; using System;
using System.IO; using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using JsonSerializer = System.Text.Json.JsonSerializer;
namespace IW4MAdmin.Application.Misc namespace IW4MAdmin.Application.Misc
{ {
@ -17,40 +14,27 @@ namespace IW4MAdmin.Application.Misc
/// <typeparam name="T">base configuration type</typeparam> /// <typeparam name="T">base configuration type</typeparam>
public class BaseConfigurationHandler<T> : IConfigurationHandler<T> where T : IBaseConfiguration public class BaseConfigurationHandler<T> : IConfigurationHandler<T> where T : IBaseConfiguration
{ {
private T _configuration; T _configuration;
private readonly SemaphoreSlim _onSaving;
private readonly JsonSerializerOptions _serializerOptions;
public BaseConfigurationHandler(string fn)
public BaseConfigurationHandler(string fileName)
{ {
_serializerOptions = new JsonSerializerOptions FileName = Path.Join(Utilities.OperatingDirectory, "Configuration", $"{fn}.json");
{ Build();
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
_serializerOptions.Converters.Add(new JsonStringEnumConverter());
_onSaving = new SemaphoreSlim(1, 1);
FileName = Path.Join(Utilities.OperatingDirectory, "Configuration", $"{fileName}.json");
} }
public BaseConfigurationHandler() : this(typeof(T).Name) public BaseConfigurationHandler() : this(typeof(T).Name)
{ {
}
~BaseConfigurationHandler()
{
_onSaving.Dispose();
} }
public string FileName { get; } public string FileName { get; }
public async Task BuildAsync() public void Build()
{ {
try try
{ {
await using var fileStream = File.OpenRead(FileName); var configContent = File.ReadAllText(FileName);
_configuration = await JsonSerializer.DeserializeAsync<T>(fileStream, _serializerOptions); _configuration = JsonConvert.DeserializeObject<T>(configContent);
} }
catch (FileNotFoundException) catch (FileNotFoundException)
@ -60,7 +44,7 @@ namespace IW4MAdmin.Application.Misc
catch (Exception e) catch (Exception e)
{ {
throw new ConfigurationException("Could not load configuration") throw new ConfigurationException("MANAGER_CONFIGURATION_ERROR")
{ {
Errors = new[] { e.Message }, Errors = new[] { e.Message },
ConfigurationFileName = FileName ConfigurationFileName = FileName
@ -68,23 +52,16 @@ namespace IW4MAdmin.Application.Misc
} }
} }
public async Task Save() public Task Save()
{ {
try var settings = new JsonSerializerSettings()
{ {
await _onSaving.WaitAsync(); Formatting = Formatting.Indented
};
settings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter());
await using var fileStream = File.Create(FileName); var appConfigJSON = JsonConvert.SerializeObject(_configuration, settings);
await JsonSerializer.SerializeAsync(fileStream, _configuration, _serializerOptions); return File.WriteAllTextAsync(FileName, appConfigJSON);
}
finally
{
if (_onSaving.CurrentCount == 0)
{
_onSaving.Release(1);
}
}
} }
public T Configuration() public T Configuration()

View File

@ -1,13 +0,0 @@
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Misc;
public class GeoLocationResult : IGeoLocationResult
{
public string Country { get; set; }
public string CountryCode { get; set; }
public string Region { get; set; }
public string ASN { get; set; }
public string Timezone { get; set; }
public string Organization { get; set; }
}

View File

@ -1,40 +0,0 @@
using System;
using System.Threading.Tasks;
using MaxMind.GeoIP2;
using MaxMind.GeoIP2.Responses;
using SharedLibraryCore.Interfaces;
namespace IW4MAdmin.Application.Misc;
public class GeoLocationService : IGeoLocationService
{
private readonly string _sourceAddress;
public GeoLocationService(string sourceAddress)
{
_sourceAddress = sourceAddress;
}
public Task<IGeoLocationResult> Locate(string address)
{
CountryResponse country = null;
try
{
using var reader = new DatabaseReader(_sourceAddress);
reader.TryCountry(address, out country);
}
catch
{
// ignored
}
var response = new GeoLocationResult
{
Country = country?.Country.Name ?? "Unknown",
CountryCode = country?.Country.IsoCode ?? ""
};
return Task.FromResult((IGeoLocationResult)response);
}
}

View File

@ -26,8 +26,7 @@ namespace IW4MAdmin.Application.Misc
private readonly ApplicationConfiguration _appConfig; private readonly ApplicationConfiguration _appConfig;
private readonly BuildNumber _fallbackVersion = BuildNumber.Parse("99.99.99.99"); private readonly BuildNumber _fallbackVersion = BuildNumber.Parse("99.99.99.99");
private readonly int _apiVersion = 1; private readonly int _apiVersion = 1;
private bool _firstHeartBeat = true; private bool firstHeartBeat = true;
private static readonly TimeSpan Interval = TimeSpan.FromSeconds(30);
public MasterCommunication(ILogger<MasterCommunication> logger, ApplicationConfiguration appConfig, ITranslationLookup translationLookup, IMasterApi apiInstance, IManager manager) public MasterCommunication(ILogger<MasterCommunication> logger, ApplicationConfiguration appConfig, ITranslationLookup translationLookup, IMasterApi apiInstance, IManager manager)
{ {
@ -94,24 +93,53 @@ namespace IW4MAdmin.Application.Misc
public async Task RunUploadStatus(CancellationToken token) public async Task RunUploadStatus(CancellationToken token)
{ {
// todo: clean up this logic
bool connected;
while (!token.IsCancellationRequested) while (!token.IsCancellationRequested)
{ {
try try
{ {
if (_manager.IsRunning) await UploadStatus();
}
catch (System.Net.Http.HttpRequestException e)
{
_logger.LogWarning(e, "Could not send heartbeat");
}
catch (AggregateException e)
{
_logger.LogWarning(e, "Could not send heartbeat");
var exceptions = e.InnerExceptions.Where(ex => ex.GetType() == typeof(ApiException));
foreach (var ex in exceptions)
{ {
await UploadStatus(); if (((ApiException)ex).StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
connected = false;
}
} }
} }
catch (Exception ex) catch (ApiException e)
{ {
_logger.LogWarning(ex, "Could not send heartbeat"); _logger.LogWarning(e, "Could not send heartbeat");
if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
connected = false;
}
} }
catch (Exception e)
{
_logger.LogWarning(e, "Could not send heartbeat");
}
try try
{ {
await Task.Delay(Interval, token); await Task.Delay(30000, token);
} }
catch catch
@ -123,7 +151,7 @@ namespace IW4MAdmin.Application.Misc
private async Task UploadStatus() private async Task UploadStatus()
{ {
if (_firstHeartBeat) if (firstHeartBeat)
{ {
var token = await _apiInstance.Authenticate(new AuthenticationId var token = await _apiInstance.Authenticate(new AuthenticationId
{ {
@ -157,7 +185,7 @@ namespace IW4MAdmin.Application.Misc
Response<ResultMessage> response = null; Response<ResultMessage> response = null;
if (_firstHeartBeat) if (firstHeartBeat)
{ {
response = await _apiInstance.AddInstance(instance); response = await _apiInstance.AddInstance(instance);
} }
@ -165,7 +193,7 @@ namespace IW4MAdmin.Application.Misc
else else
{ {
response = await _apiInstance.UpdateInstance(instance.Id, instance); response = await _apiInstance.UpdateInstance(instance.Id, instance);
_firstHeartBeat = false; firstHeartBeat = false;
} }
if (response.ResponseMessage.StatusCode != System.Net.HttpStatusCode.OK) if (response.ResponseMessage.StatusCode != System.Net.HttpStatusCode.OK)

View File

@ -18,7 +18,6 @@ namespace IW4MAdmin.Application.Misc
/// implementation of IMetaService /// implementation of IMetaService
/// used to add and retrieve runtime and persistent meta /// used to add and retrieve runtime and persistent meta
/// </summary> /// </summary>
[Obsolete("Use MetaServiceV2")]
public class MetaService : IMetaService public class MetaService : IMetaService
{ {
private readonly IDictionary<MetaType, List<dynamic>> _metaActions; private readonly IDictionary<MetaType, List<dynamic>> _metaActions;
@ -69,29 +68,6 @@ namespace IW4MAdmin.Application.Misc
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
} }
public async Task SetPersistentMeta(string metaKey, string metaValue, int clientId)
{
await AddPersistentMeta(metaKey, metaValue, new EFClient { ClientId = clientId });
}
public async Task IncrementPersistentMeta(string metaKey, int incrementAmount, int clientId)
{
var existingMeta = await GetPersistentMeta(metaKey, new EFClient { ClientId = clientId });
if (!long.TryParse(existingMeta?.Value ?? "0", out var existingValue))
{
existingValue = 0;
}
var newValue = existingValue + incrementAmount;
await SetPersistentMeta(metaKey, newValue.ToString(), clientId);
}
public async Task DecrementPersistentMeta(string metaKey, int decrementAmount, int clientId)
{
await IncrementPersistentMeta(metaKey, -decrementAmount, clientId);
}
public async Task AddPersistentMeta(string metaKey, string metaValue) public async Task AddPersistentMeta(string metaKey, string metaValue)
{ {
await using var ctx = _contextFactory.CreateContext(); await using var ctx = _contextFactory.CreateContext();
@ -231,30 +207,42 @@ namespace IW4MAdmin.Application.Misc
public async Task<IEnumerable<IClientMeta>> GetRuntimeMeta(ClientPaginationRequest request) public async Task<IEnumerable<IClientMeta>> GetRuntimeMeta(ClientPaginationRequest request)
{ {
var metas = await Task.WhenAll(_metaActions.Where(kvp => kvp.Key != MetaType.Information) var meta = new List<IClientMeta>();
.Select(async kvp => await kvp.Value[0](request)));
return metas.SelectMany(m => (IEnumerable<IClientMeta>)m) foreach (var (type, actions) in _metaActions)
.OrderByDescending(m => m.When) {
// information is not listed chronologically
if (type != MetaType.Information)
{
var metaItems = await actions[0](request);
meta.AddRange(metaItems);
}
}
return meta.OrderByDescending(_meta => _meta.When)
.Take(request.Count) .Take(request.Count)
.ToList(); .ToList();
} }
public async Task<IEnumerable<T>> GetRuntimeMeta<T>(ClientPaginationRequest request, MetaType metaType) where T : IClientMeta public async Task<IEnumerable<T>> GetRuntimeMeta<T>(ClientPaginationRequest request, MetaType metaType) where T : IClientMeta
{ {
IEnumerable<T> meta;
if (metaType == MetaType.Information) if (metaType == MetaType.Information)
{ {
var allMeta = new List<T>(); var allMeta = new List<T>();
var completedMeta = await Task.WhenAll(_metaActions[metaType].Select(async individualMetaRegistration => foreach (var individualMetaRegistration in _metaActions[metaType])
(IEnumerable<T>)await individualMetaRegistration(request))); {
allMeta.AddRange(await individualMetaRegistration(request));
allMeta.AddRange(completedMeta.SelectMany(meta => meta)); }
return ProcessInformationMeta(allMeta); return ProcessInformationMeta(allMeta);
} }
var meta = await _metaActions[metaType][0](request) as IEnumerable<T>; else
{
meta = await _metaActions[metaType][0](request) as IEnumerable<T>;
}
return meta; return meta;
} }

View File

@ -1,453 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Data.Abstractions;
using Data.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SharedLibraryCore.Dtos;
using SharedLibraryCore.Interfaces;
using SharedLibraryCore.QueryHelper;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application.Misc;
public class MetaServiceV2 : IMetaServiceV2
{
private readonly IDictionary<MetaType, List<dynamic>> _metaActions;
private readonly IDatabaseContextFactory _contextFactory;
private readonly ILogger _logger;
public MetaServiceV2(ILogger<MetaServiceV2> logger, IDatabaseContextFactory contextFactory)
{
_logger = logger;
_metaActions = new Dictionary<MetaType, List<dynamic>>();
_contextFactory = contextFactory;
}
public async Task SetPersistentMeta(string metaKey, string metaValue, int clientId,
CancellationToken token = default)
{
if (!ValidArgs(metaKey, clientId))
{
return;
}
await using var context = _contextFactory.CreateContext();
var existingMeta = await context.EFMeta
.Where(meta => meta.Key == metaKey)
.Where(meta => meta.ClientId == clientId)
.FirstOrDefaultAsync(token);
if (existingMeta != null)
{
_logger.LogDebug("Updating existing meta with key {Key} and id {Id}", existingMeta.Key,
existingMeta.MetaId);
existingMeta.Value = metaValue;
existingMeta.Updated = DateTime.UtcNow;
}
else
{
_logger.LogDebug("Adding new meta with key {Key}", metaKey);
context.EFMeta.Add(new EFMeta
{
ClientId = clientId,
Created = DateTime.UtcNow,
Key = metaKey,
Value = metaValue,
});
}
await context.SaveChangesAsync(token);
}
public async Task SetPersistentMetaValue<T>(string metaKey, T metaValue, int clientId,
CancellationToken token = default) where T : class
{
if (!ValidArgs(metaKey, clientId))
{
return;
}
string serializedValue;
try
{
serializedValue = JsonSerializer.Serialize(metaValue);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not serialize meta with key {Key}", metaKey);
return;
}
await SetPersistentMeta(metaKey, serializedValue, clientId, token);
}
public async Task SetPersistentMetaForLookupKey(string metaKey, string lookupKey, int lookupId, int clientId,
CancellationToken token = default)
{
if (!ValidArgs(metaKey, clientId))
{
return;
}
await using var context = _contextFactory.CreateContext();
var lookupMeta = await context.EFMeta.FirstOrDefaultAsync(meta => meta.Key == lookupKey, token);
if (lookupMeta is null)
{
_logger.LogWarning("No lookup meta exists for metaKey {MetaKey} and lookupKey {LookupKey}", metaKey,
lookupKey);
return;
}
var lookupValues = JsonSerializer.Deserialize<List<LookupValue<string>>>(lookupMeta.Value);
if (lookupValues is null)
{
return;
}
var foundLookup = lookupValues.FirstOrDefault(value => value.Id == lookupId);
if (foundLookup is null)
{
_logger.LogWarning("No lookup meta found for provided lookup id {MetaKey}, {LookupKey}, {LookupId}",
metaKey, lookupKey, lookupId);
return;
}
_logger.LogDebug("Setting meta for lookup {MetaKey}, {LookupKey}, {LookupId}",
metaKey, lookupKey, lookupId);
await SetPersistentMeta(metaKey, lookupId.ToString(), clientId, token);
}
public async Task IncrementPersistentMeta(string metaKey, int incrementAmount, int clientId,
CancellationToken token = default)
{
if (!ValidArgs(metaKey, clientId))
{
return;
}
var existingMeta = await GetPersistentMeta(metaKey, clientId, token);
if (!long.TryParse(existingMeta?.Value ?? "0", out var existingValue))
{
existingValue = 0;
}
var newValue = existingValue + incrementAmount;
await SetPersistentMeta(metaKey, newValue.ToString(), clientId, token);
}
public async Task DecrementPersistentMeta(string metaKey, int decrementAmount, int clientId,
CancellationToken token = default)
{
await IncrementPersistentMeta(metaKey, -decrementAmount, clientId, token);
}
public async Task<EFMeta> GetPersistentMeta(string metaKey, int clientId, CancellationToken token = default)
{
if (!ValidArgs(metaKey, clientId))
{
return null;
}
await using var ctx = _contextFactory.CreateContext(enableTracking: false);
return await ctx.EFMeta
.Where(meta => meta.Key == metaKey)
.Where(meta => meta.ClientId == clientId)
.Select(meta => new EFMeta
{
MetaId = meta.MetaId,
Key = meta.Key,
ClientId = meta.ClientId,
Value = meta.Value,
})
.FirstOrDefaultAsync(token);
}
public async Task<T> GetPersistentMetaValue<T>(string metaKey, int clientId, CancellationToken token = default)
where T : class
{
var meta = await GetPersistentMeta(metaKey, clientId, token);
if (meta is null)
{
return default;
}
try
{
return JsonSerializer.Deserialize<T>(meta.Value);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not deserialize meta with key {Key} and value {Value}", metaKey, meta.Value);
return default;
}
}
public async Task<EFMeta> GetPersistentMetaByLookup(string metaKey, string lookupKey, int clientId,
CancellationToken token = default)
{
await using var context = _contextFactory.CreateContext();
var metaValue = await GetPersistentMeta(metaKey, clientId, token);
if (metaValue is null)
{
_logger.LogDebug("No meta exists for key {Key}, clientId {ClientId}", metaKey, clientId);
return default;
}
var lookupMeta = await context.EFMeta.FirstOrDefaultAsync(meta => meta.Key == lookupKey, token);
if (lookupMeta is null)
{
_logger.LogWarning("No lookup meta exists for metaKey {MetaKey} and lookupKey {LookupKey}", metaKey,
lookupKey);
return default;
}
var lookupId = int.Parse(metaValue.Value);
var lookupValues = JsonSerializer.Deserialize<List<LookupValue<string>>>(lookupMeta.Value);
if (lookupValues is null)
{
return default;
}
var foundLookup = lookupValues.FirstOrDefault(value => value.Id == lookupId);
if (foundLookup is not null)
{
return new EFMeta
{
Created = metaValue.Created,
Updated = metaValue.Updated,
Extra = metaValue.Extra,
MetaId = metaValue.MetaId,
Value = foundLookup.Value
};
}
_logger.LogWarning("No lookup meta found for provided lookup id {MetaKey}, {LookupKey}, {LookupId}",
metaKey, lookupKey, lookupId);
return default;
}
public async Task RemovePersistentMeta(string metaKey, int clientId, CancellationToken token = default)
{
if (!ValidArgs(metaKey, clientId))
{
return;
}
await using var context = _contextFactory.CreateContext();
var existingMeta = await context.EFMeta
.FirstOrDefaultAsync(meta => meta.Key == metaKey && meta.ClientId == clientId, token);
if (existingMeta == null)
{
_logger.LogDebug("No meta with key {Key} found for client id {Id}", metaKey, clientId);
return;
}
_logger.LogDebug("Removing meta for key {Key} with id {Id}", metaKey, existingMeta.MetaId);
context.EFMeta.Remove(existingMeta);
await context.SaveChangesAsync(token);
}
public async Task SetPersistentMeta(string metaKey, string metaValue, CancellationToken token = default)
{
if (string.IsNullOrWhiteSpace(metaKey))
{
_logger.LogWarning("Cannot save meta with no key");
return;
}
await using var ctx = _contextFactory.CreateContext();
var existingMeta = await ctx.EFMeta
.Where(meta => meta.Key == metaKey)
.Where(meta => meta.ClientId == null)
.FirstOrDefaultAsync(token);
if (existingMeta is not null)
{
_logger.LogDebug("Updating existing meta with key {Key} and id {Id}", existingMeta.Key,
existingMeta.MetaId);
existingMeta.Value = metaValue;
existingMeta.Updated = DateTime.UtcNow;
await ctx.SaveChangesAsync(token);
}
else
{
_logger.LogDebug("Adding new meta with key {Key}", metaKey);
ctx.EFMeta.Add(new EFMeta
{
Created = DateTime.UtcNow,
Key = metaKey,
Value = metaValue
});
await ctx.SaveChangesAsync(token);
}
}
public async Task SetPersistentMetaValue<T>(string metaKey, T metaValue, CancellationToken token = default)
where T : class
{
if (string.IsNullOrWhiteSpace(metaKey))
{
_logger.LogWarning("Meta key is null, not setting");
return;
}
if (metaValue is null)
{
_logger.LogWarning("Meta value is null, not setting");
return;
}
string serializedMeta;
try
{
serializedMeta = JsonSerializer.Serialize(metaValue);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not serialize meta with {Key} and value {Value}", metaKey, metaValue);
return;
}
await SetPersistentMeta(metaKey, serializedMeta, token);
}
public async Task<EFMeta> GetPersistentMeta(string metaKey, CancellationToken token = default)
{
if (string.IsNullOrWhiteSpace(metaKey))
{
return null;
}
await using var context = _contextFactory.CreateContext(false);
return await context.EFMeta.FirstOrDefaultAsync(meta => meta.Key == metaKey, token);
}
public async Task<T> GetPersistentMetaValue<T>(string metaKey, CancellationToken token = default) where T : class
{
if (string.IsNullOrWhiteSpace(metaKey))
{
return default;
}
var meta = await GetPersistentMeta(metaKey, token);
if (meta is null)
{
return default;
}
try
{
return JsonSerializer.Deserialize<T>(meta.Value);
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not serialize meta with key {Key} and value {Value}", metaKey, meta.Value);
return default;
}
}
public async Task RemovePersistentMeta(string metaKey, CancellationToken token = default)
{
if (string.IsNullOrWhiteSpace(metaKey))
{
return;
}
await using var context = _contextFactory.CreateContext(enableTracking: false);
var existingMeta = await context.EFMeta
.Where(meta => meta.Key == metaKey)
.Where(meta => meta.ClientId == null)
.FirstOrDefaultAsync(token);
if (existingMeta != null)
{
_logger.LogDebug("Removing meta for key {Key} with id {Id}", metaKey, existingMeta.MetaId);
context.Remove(existingMeta);
await context.SaveChangesAsync(token);
}
}
public void AddRuntimeMeta<T, TReturnType>(MetaType metaKey,
Func<T, CancellationToken, Task<IEnumerable<TReturnType>>> metaAction)
where T : PaginationRequest where TReturnType : IClientMeta
{
if (!_metaActions.ContainsKey(metaKey))
{
_metaActions.Add(metaKey, new List<dynamic> { metaAction });
}
else
{
_metaActions[metaKey].Add(metaAction);
}
}
public async Task<IEnumerable<IClientMeta>> GetRuntimeMeta(ClientPaginationRequest request, CancellationToken token = default)
{
var metas = await Task.WhenAll(_metaActions.Where(kvp => kvp.Key != MetaType.Information)
.Select(async kvp => await kvp.Value[0](request, token)));
return metas.SelectMany(m => (IEnumerable<IClientMeta>)m)
.OrderByDescending(m => m.When)
.Take(request.Count)
.ToList();
}
public async Task<IEnumerable<T>> GetRuntimeMeta<T>(ClientPaginationRequest request, MetaType metaType, CancellationToken token = default)
where T : IClientMeta
{
if (metaType == MetaType.Information)
{
var allMeta = new List<T>();
var completedMeta = await Task.WhenAll(_metaActions[metaType].Select(async individualMetaRegistration =>
(IEnumerable<T>)await individualMetaRegistration(request, token)));
allMeta.AddRange(completedMeta.SelectMany(meta => meta));
return ProcessInformationMeta(allMeta);
}
var meta = await _metaActions[metaType][0](request, token) as IEnumerable<T>;
return meta;
}
private static IEnumerable<T> ProcessInformationMeta<T>(IEnumerable<T> meta) where T : IClientMeta
{
return meta;
}
private static bool ValidArgs(string key, int clientId) => !string.IsNullOrWhiteSpace(key) && clientId > 0;
}

View File

@ -6,7 +6,6 @@ using SharedLibraryCore.Interfaces;
using System.Linq; using System.Linq;
using SharedLibraryCore; using SharedLibraryCore;
using IW4MAdmin.Application.API.Master; using IW4MAdmin.Application.API.Master;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SharedLibraryCore.Configuration; using SharedLibraryCore.Configuration;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
@ -40,23 +39,24 @@ namespace IW4MAdmin.Application.Misc
/// <returns></returns> /// <returns></returns>
public IEnumerable<IPlugin> DiscoverScriptPlugins() public IEnumerable<IPlugin> DiscoverScriptPlugins()
{ {
var pluginDir = $"{Utilities.OperatingDirectory}{PLUGIN_DIR}{Path.DirectorySeparatorChar}"; string pluginDir = $"{Utilities.OperatingDirectory}{PLUGIN_DIR}{Path.DirectorySeparatorChar}";
if (!Directory.Exists(pluginDir)) if (Directory.Exists(pluginDir))
{ {
return Enumerable.Empty<IPlugin>(); var scriptPluginFiles = Directory.GetFiles(pluginDir, "*.js").AsEnumerable().Union(GetRemoteScripts());
_logger.LogDebug("Discovered {count} potential script plugins", scriptPluginFiles.Count());
if (scriptPluginFiles.Count() > 0)
{
foreach (string fileName in scriptPluginFiles)
{
_logger.LogDebug("Discovered script plugin {fileName}", fileName);
var plugin = new ScriptPlugin(_logger, fileName);
yield return plugin;
}
}
} }
var scriptPluginFiles =
Directory.GetFiles(pluginDir, "*.js").AsEnumerable().Union(GetRemoteScripts()).ToList();
_logger.LogDebug("Discovered {count} potential script plugins", scriptPluginFiles.Count);
return scriptPluginFiles.Select(fileName =>
{
_logger.LogDebug("Discovered script plugin {fileName}", fileName);
return new ScriptPlugin(_logger, fileName);
}).ToList();
} }
/// <summary> /// <summary>
@ -83,47 +83,19 @@ namespace IW4MAdmin.Application.Misc
.GroupBy(_assembly => _assembly.FullName).Select(_assembly => _assembly.OrderByDescending(_assembly => _assembly.GetName().Version).First()); .GroupBy(_assembly => _assembly.FullName).Select(_assembly => _assembly.OrderByDescending(_assembly => _assembly.GetName().Version).First());
pluginTypes = assemblies pluginTypes = assemblies
.SelectMany(_asm => .SelectMany(_asm => _asm.GetTypes())
{
try
{
return _asm.GetTypes();
}
catch
{
return Enumerable.Empty<Type>();
}
})
.Where(_assemblyType => _assemblyType.GetInterface(nameof(IPlugin), false) != null); .Where(_assemblyType => _assemblyType.GetInterface(nameof(IPlugin), false) != null);
_logger.LogDebug("Discovered {count} plugin implementations", pluginTypes.Count()); _logger.LogDebug("Discovered {count} plugin implementations", pluginTypes.Count());
commandTypes = assemblies commandTypes = assemblies
.SelectMany(_asm =>{ .SelectMany(_asm => _asm.GetTypes())
try
{
return _asm.GetTypes();
}
catch
{
return Enumerable.Empty<Type>();
}
})
.Where(_assemblyType => _assemblyType.IsClass && _assemblyType.BaseType == typeof(Command)); .Where(_assemblyType => _assemblyType.IsClass && _assemblyType.BaseType == typeof(Command));
_logger.LogDebug("Discovered {count} plugin commands", commandTypes.Count()); _logger.LogDebug("Discovered {count} plugin commands", commandTypes.Count());
configurationTypes = assemblies configurationTypes = assemblies
.SelectMany(asm => { .SelectMany(asm => asm.GetTypes())
try
{
return asm.GetTypes();
}
catch
{
return Enumerable.Empty<Type>();
}
})
.Where(asmType => .Where(asmType =>
asmType.IsClass && asmType.GetInterface(nameof(IBaseConfiguration), false) != null); asmType.IsClass && asmType.GetInterface(nameof(IBaseConfiguration), false) != null);

View File

@ -6,6 +6,7 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Data.Models.Client; using Data.Models.Client;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using static SharedLibraryCore.Database.Models.EFClient;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application.Misc namespace IW4MAdmin.Application.Misc
@ -15,15 +16,14 @@ namespace IW4MAdmin.Application.Misc
/// </summary> /// </summary>
public class ScriptCommand : Command public class ScriptCommand : Command
{ {
private readonly Func<GameEvent, Task> _executeAction; private readonly Action<GameEvent> _executeAction;
private readonly ILogger _logger; private readonly ILogger _logger;
public ScriptCommand(string name, string alias, string description, bool isTargetRequired, public ScriptCommand(string name, string alias, string description, bool isTargetRequired, EFClient.Permission permission,
EFClient.Permission permission, CommandArgument[] args, Action<GameEvent> executeAction, CommandConfiguration config, ITranslationLookup layout, ILogger<ScriptCommand> logger)
CommandArgument[] args, Func<GameEvent, Task> executeAction, CommandConfiguration config,
ITranslationLookup layout, ILogger<ScriptCommand> logger, Server.Game[] supportedGames)
: base(config, layout) : base(config, layout)
{ {
_executeAction = executeAction; _executeAction = executeAction;
_logger = logger; _logger = logger;
Name = name; Name = name;
@ -32,7 +32,6 @@ namespace IW4MAdmin.Application.Misc
RequiresTarget = isTargetRequired; RequiresTarget = isTargetRequired;
Permission = permission; Permission = permission;
Arguments = args; Arguments = args;
SupportedGames = supportedGames;
} }
public override async Task ExecuteAsync(GameEvent e) public override async Task ExecuteAsync(GameEvent e)
@ -44,7 +43,7 @@ namespace IW4MAdmin.Application.Misc
try try
{ {
await _executeAction(e); await Task.Run(() => _executeAction(e));
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -13,7 +13,6 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jint.Runtime.Interop;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Serilog.Context; using Serilog.Context;
using ILogger = Microsoft.Extensions.Logging.ILogger; using ILogger = Microsoft.Extensions.Logging.ILogger;
@ -37,12 +36,12 @@ namespace IW4MAdmin.Application.Misc
/// </summary> /// </summary>
public bool IsParser { get; private set; } public bool IsParser { get; private set; }
public FileSystemWatcher Watcher { get; } public FileSystemWatcher Watcher { get; private set; }
private Engine _scriptEngine; private Engine _scriptEngine;
private readonly string _fileName; private readonly string _fileName;
private readonly SemaphoreSlim _onProcessing = new(1, 1); private readonly SemaphoreSlim _onProcessing;
private bool _successfullyLoaded; private bool successfullyLoaded;
private readonly List<string> _registeredCommandNames; private readonly List<string> _registeredCommandNames;
private readonly ILogger _logger; private readonly ILogger _logger;
@ -50,14 +49,15 @@ namespace IW4MAdmin.Application.Misc
{ {
_logger = logger; _logger = logger;
_fileName = filename; _fileName = filename;
Watcher = new FileSystemWatcher Watcher = new FileSystemWatcher()
{ {
Path = workingDirectory ?? $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}", Path = workingDirectory == null ? $"{Utilities.OperatingDirectory}Plugins{Path.DirectorySeparatorChar}" : workingDirectory,
NotifyFilter = NotifyFilters.Size, NotifyFilter = NotifyFilters.Size,
Filter = _fileName.Split(Path.DirectorySeparatorChar).Last() Filter = _fileName.Split(Path.DirectorySeparatorChar).Last()
}; };
Watcher.EnableRaisingEvents = true; Watcher.EnableRaisingEvents = true;
_onProcessing = new SemaphoreSlim(1, 1);
_registeredCommandNames = new List<string>(); _registeredCommandNames = new List<string>();
} }
@ -67,13 +67,12 @@ namespace IW4MAdmin.Application.Misc
_onProcessing.Dispose(); _onProcessing.Dispose();
} }
public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory, public async Task Initialize(IManager manager, IScriptCommandFactory scriptCommandFactory, IScriptPluginServiceResolver serviceResolver)
IScriptPluginServiceResolver serviceResolver)
{ {
await _onProcessing.WaitAsync();
try try
{ {
await _onProcessing.WaitAsync();
// for some reason we get an event trigger when the file is not finished being modified. // 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 // this must have been a change in .NET CORE 3.x
// so if the new file is empty we can't process it yet // so if the new file is empty we can't process it yet
@ -82,27 +81,26 @@ namespace IW4MAdmin.Application.Misc
return; return;
} }
var firstRun = _scriptEngine == null; bool firstRun = _scriptEngine == null;
// it's been loaded before so we need to call the unload event // it's been loaded before so we need to call the unload event
if (!firstRun) if (!firstRun)
{ {
await OnUnloadAsync(); await OnUnloadAsync();
foreach (var commandName in _registeredCommandNames) foreach (string commandName in _registeredCommandNames)
{ {
_logger.LogDebug("Removing plugin registered command {Command}", commandName); _logger.LogDebug("Removing plugin registered command {command}", commandName);
manager.RemoveCommandByName(commandName); manager.RemoveCommandByName(commandName);
} }
_registeredCommandNames.Clear(); _registeredCommandNames.Clear();
} }
_successfullyLoaded = false; successfullyLoaded = false;
string script; string script;
await using (var stream = using (var stream = new FileStream(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
new FileStream(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{ {
using (var reader = new StreamReader(stream, Encoding.Default)) using (var reader = new StreamReader(stream, Encoding.Default))
{ {
@ -112,34 +110,45 @@ namespace IW4MAdmin.Application.Misc
_scriptEngine = new Engine(cfg => _scriptEngine = new Engine(cfg =>
cfg.AllowClr(new[] cfg.AllowClr(new[]
{ {
typeof(System.Net.Http.HttpClient).Assembly, typeof(System.Net.Http.HttpClient).Assembly,
typeof(EFClient).Assembly, typeof(EFClient).Assembly,
typeof(Utilities).Assembly, typeof(Utilities).Assembly,
typeof(Encoding).Assembly typeof(Encoding).Assembly
}) })
.CatchClrExceptions() .CatchClrExceptions());
.AddObjectConverter(new PermissionLevelToStringConverter()));
try
{
_scriptEngine.Execute(script);
}
catch (JavaScriptException ex)
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {methodName} for script plugin {plugin} at {@locationInfo}",
nameof(Initialize), _fileName, ex.Location);
throw new PluginException($"A JavaScript parsing error occured while initializing script plugin");
}
catch (Exception e)
{
_logger.LogError(e,
"Encountered unexpected error while running {methodName} for script plugin {plugin}",
nameof(Initialize), _fileName);
throw new PluginException($"An unexpected error occured while initialization script plugin");
}
_scriptEngine.Execute(script);
_scriptEngine.SetValue("_localization", Utilities.CurrentLocalization); _scriptEngine.SetValue("_localization", Utilities.CurrentLocalization);
_scriptEngine.SetValue("_serviceResolver", serviceResolver); _scriptEngine.SetValue("_serviceResolver", serviceResolver);
_scriptEngine.SetValue("_lock", _onProcessing); dynamic pluginObject = _scriptEngine.GetValue("plugin").ToObject();
dynamic pluginObject = _scriptEngine.Evaluate("plugin").ToObject();
Author = pluginObject.author; Author = pluginObject.author;
Name = pluginObject.name; Name = pluginObject.name;
Version = (float)pluginObject.version; Version = (float)pluginObject.version;
var commands = JsValue.Undefined; var commands = _scriptEngine.GetValue("commands");
try
{
commands = _scriptEngine.Evaluate("commands");
}
catch (JavaScriptException)
{
// ignore because commands aren't defined;
}
if (commands != JsValue.Undefined) if (commands != JsValue.Undefined)
{ {
@ -147,7 +156,7 @@ namespace IW4MAdmin.Application.Misc
{ {
foreach (var command in GenerateScriptCommands(commands, scriptCommandFactory)) foreach (var command in GenerateScriptCommands(commands, scriptCommandFactory))
{ {
_logger.LogDebug("Adding plugin registered command {CommandName}", command.Name); _logger.LogDebug("Adding plugin registered command {commandName}", command.Name);
manager.AddAdditionalCommand(command); manager.AddAdditionalCommand(command);
_registeredCommandNames.Add(command.Name); _registeredCommandNames.Add(command.Name);
} }
@ -155,63 +164,53 @@ namespace IW4MAdmin.Application.Misc
catch (RuntimeBinderException e) catch (RuntimeBinderException e)
{ {
throw new PluginException($"Not all required fields were found: {e.Message}") throw new PluginException($"Not all required fields were found: {e.Message}") { PluginFile = _fileName };
{ PluginFile = _fileName };
} }
} }
_scriptEngine.SetValue("_configHandler", new ScriptPluginConfigurationWrapper(Name, _scriptEngine));
await OnLoadAsync(manager);
try try
{ {
if (pluginObject.isParser) if (pluginObject.isParser)
{ {
await OnLoadAsync(manager);
IsParser = true; IsParser = true;
var eventParser = (IEventParser)_scriptEngine.Evaluate("eventParser").ToObject(); IEventParser eventParser = (IEventParser)_scriptEngine.GetValue("eventParser").ToObject();
var rconParser = (IRConParser)_scriptEngine.Evaluate("rconParser").ToObject(); IRConParser rconParser = (IRConParser)_scriptEngine.GetValue("rconParser").ToObject();
manager.AdditionalEventParsers.Add(eventParser); manager.AdditionalEventParsers.Add(eventParser);
manager.AdditionalRConParsers.Add(rconParser); manager.AdditionalRConParsers.Add(rconParser);
} }
} }
catch (RuntimeBinderException) catch (RuntimeBinderException) { }
{
var configWrapper = new ScriptPluginConfigurationWrapper(Name, _scriptEngine);
await configWrapper.InitializeAsync();
_scriptEngine.SetValue("_configHandler", configWrapper);
await OnLoadAsync(manager);
}
if (!firstRun) if (!firstRun)
{ {
await OnLoadAsync(manager); await OnLoadAsync(manager);
} }
_successfullyLoaded = true; successfullyLoaded = true;
} }
catch (JavaScriptException ex) catch (JavaScriptException ex)
{ {
_logger.LogError(ex, _logger.LogError(ex,
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} at {@LocationInfo}", "Encountered JavaScript runtime error while executing {methodName} for script plugin {plugin} initialization {@locationInfo}",
nameof(Initialize), Path.GetFileName(_fileName), ex.Location); nameof(OnLoadAsync), _fileName, ex.Location);
throw new PluginException("An error occured while initializing script plugin");
}
catch (Exception ex) when (ex.InnerException is JavaScriptException jsEx)
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} initialization {@LocationInfo}",
nameof(Initialize), _fileName, jsEx.Location);
throw new PluginException("An error occured while initializing script plugin"); throw new PluginException("An error occured while initializing script plugin");
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, _logger.LogError(ex,
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin}", "Encountered unexpected error while running {methodName} for script plugin {plugin}",
nameof(OnLoadAsync), Path.GetFileName(_fileName)); nameof(OnLoadAsync), _fileName);
throw new PluginException("An error occured while executing action for script plugin"); throw new PluginException("An unexpected error occured while initializing script plugin");
} }
finally finally
{ {
if (_onProcessing.CurrentCount == 0) if (_onProcessing.CurrentCount == 0)
@ -221,120 +220,73 @@ namespace IW4MAdmin.Application.Misc
} }
} }
public async Task OnEventAsync(GameEvent gameEvent, Server server) public async Task OnEventAsync(GameEvent E, Server S)
{ {
if (!_successfullyLoaded) if (successfullyLoaded)
{
return;
}
try
{ {
await _onProcessing.WaitAsync(); await _onProcessing.WaitAsync();
_scriptEngine.SetValue("_gameEvent", gameEvent);
_scriptEngine.SetValue("_server", server);
_scriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient(server));
_scriptEngine.Evaluate("plugin.onEventAsync(_gameEvent, _server)");
}
catch (JavaScriptException ex) try
{
using (LogContext.PushProperty("Server", server.ToString()))
{ {
_logger.LogError(ex, _scriptEngine.SetValue("_gameEvent", E);
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} with event type {EventType} {@LocationInfo}", _scriptEngine.SetValue("_server", S);
nameof(OnEventAsync), Path.GetFileName(_fileName), gameEvent.Type, ex.Location); _scriptEngine.SetValue("_IW4MAdminClient", Utilities.IW4MAdminClient(S));
_scriptEngine.Execute("plugin.onEventAsync(_gameEvent, _server)").GetCompletionValue();
}
catch (JavaScriptException ex)
{
using (LogContext.PushProperty("Server", S.ToString()))
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {methodName} for script plugin {plugin} with event type {eventType} {@locationInfo}",
nameof(OnEventAsync), _fileName, E.Type, ex.Location);
}
throw new PluginException($"An error occured while executing action for script plugin");
} }
throw new PluginException("An error occured while executing action for script plugin"); catch (Exception e)
}
catch (Exception ex)
{
using (LogContext.PushProperty("Server", server.ToString()))
{ {
_logger.LogError(ex, using (LogContext.PushProperty("Server", S.ToString()))
"Encountered error while running {MethodName} for script plugin {Plugin} with event type {EventType}", {
nameof(OnEventAsync), _fileName, gameEvent.Type); _logger.LogError(e,
"Encountered unexpected error while running {methodName} for script plugin {plugin} with event type {eventType}",
nameof(OnEventAsync), _fileName, E.Type);
}
throw new PluginException($"An error occured while executing action for script plugin");
} }
throw new PluginException("An error occured while executing action for script plugin"); finally
}
finally
{
if (_onProcessing.CurrentCount == 0)
{ {
_onProcessing.Release(1); if (_onProcessing.CurrentCount == 0)
{
_onProcessing.Release(1);
}
} }
} }
} }
public Task OnLoadAsync(IManager manager) public Task OnLoadAsync(IManager manager)
{ {
try _logger.LogDebug("OnLoad executing for {name}", Name);
{ _scriptEngine.SetValue("_manager", manager);
_logger.LogDebug("OnLoad executing for {Name}", Name); return Task.FromResult(_scriptEngine.Execute("plugin.onLoadAsync(_manager)").GetCompletionValue());
_scriptEngine.SetValue("_manager", manager);
_scriptEngine.SetValue("getDvar", GetDvarAsync);
_scriptEngine.SetValue("setDvar", SetDvarAsync);
_scriptEngine.Evaluate("plugin.onLoadAsync(_manager)");
return Task.CompletedTask;
}
catch (JavaScriptException ex)
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} at {@LocationInfo}",
nameof(OnLoadAsync), Path.GetFileName(_fileName), ex.Location);
throw new PluginException("A runtime error occured while executing action for script plugin");
}
catch (Exception ex)
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin}",
nameof(OnLoadAsync), Path.GetFileName(_fileName));
throw new PluginException("An error occured while executing action for script plugin");
}
} }
public async Task OnTickAsync(Server server) public Task OnTickAsync(Server S)
{ {
_scriptEngine.SetValue("_server", server); _scriptEngine.SetValue("_server", S);
await Task.FromResult(_scriptEngine.Evaluate("plugin.onTickAsync(_server)")); return Task.FromResult(_scriptEngine.Execute("plugin.onTickAsync(_server)").GetCompletionValue());
} }
public Task OnUnloadAsync() public async Task OnUnloadAsync()
{ {
if (!_successfullyLoaded) if (successfullyLoaded)
{ {
return Task.CompletedTask; await Task.FromResult(_scriptEngine.Execute("plugin.onUnloadAsync()").GetCompletionValue());
} }
try
{
_scriptEngine.Evaluate("plugin.onUnloadAsync()");
}
catch (JavaScriptException ex)
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin} at {@LocationInfo}",
nameof(OnUnloadAsync), Path.GetFileName(_fileName), ex.Location);
throw new PluginException("A runtime error occured while executing action for script plugin");
}
catch (Exception ex)
{
_logger.LogError(ex,
"Encountered JavaScript runtime error while executing {MethodName} for script plugin {Plugin}",
nameof(OnUnloadAsync), Path.GetFileName(_fileName));
throw new PluginException("An error occured while executing action for script plugin");
}
return Task.CompletedTask;
} }
/// <summary> /// <summary>
@ -343,9 +295,9 @@ namespace IW4MAdmin.Application.Misc
/// <param name="commands">commands value from jint parser</param> /// <param name="commands">commands value from jint parser</param>
/// <param name="scriptCommandFactory">factory to create the command from</param> /// <param name="scriptCommandFactory">factory to create the command from</param>
/// <returns></returns> /// <returns></returns>
private IEnumerable<IManagerCommand> GenerateScriptCommands(JsValue commands, IScriptCommandFactory scriptCommandFactory) public IEnumerable<IManagerCommand> GenerateScriptCommands(JsValue commands, IScriptCommandFactory scriptCommandFactory)
{ {
var commandList = new List<IManagerCommand>(); List<IManagerCommand> commandList = new List<IManagerCommand>();
// go through each defined command // go through each defined command
foreach (var command in commands.AsArray()) foreach (var command in commands.AsArray())
@ -355,10 +307,9 @@ namespace IW4MAdmin.Application.Misc
string alias = dynamicCommand.alias; string alias = dynamicCommand.alias;
string description = dynamicCommand.description; string description = dynamicCommand.description;
string permission = dynamicCommand.permission; string permission = dynamicCommand.permission;
List<Server.Game> supportedGames = null; bool targetRequired = false;
var targetRequired = false;
var args = new List<(string, bool)>(); List<(string, bool)> args = new List<(string, bool)>();
dynamic arguments = null; dynamic arguments = null;
try try
@ -389,164 +340,26 @@ namespace IW4MAdmin.Application.Misc
} }
} }
try void execute(GameEvent e)
{ {
foreach (var game in dynamicCommand.supportedGames) _scriptEngine.SetValue("_event", e);
{ var jsEventObject = _scriptEngine.GetValue("_event");
supportedGames ??= new List<Server.Game>();
supportedGames.Add(Enum.Parse(typeof(Server.Game), game.ToString()));
}
}
catch (RuntimeBinderException)
{
// supported games is optional
}
async Task Execute(GameEvent gameEvent)
{
try try
{ {
await _onProcessing.WaitAsync(); dynamicCommand.execute.Target.Invoke(jsEventObject);
_scriptEngine.SetValue("_event", gameEvent);
var jsEventObject = _scriptEngine.Evaluate("_event");
dynamicCommand.execute.Target.Invoke(_scriptEngine, jsEventObject);
} }
catch (JavaScriptException ex) catch (JavaScriptException ex)
{ {
using (LogContext.PushProperty("Server", gameEvent.Owner?.ToString())) throw new PluginException($"An error occured while executing action for script plugin: {ex.Error} (Line: {ex.Location.Start.Line}, Character: {ex.Location.Start.Column})") { PluginFile = _fileName };
{
_logger.LogError(ex, "Could not execute command action for {Filename} {@Location}",
Path.GetFileName(_fileName), ex.Location);
}
throw new PluginException("A runtime error occured while executing action for script plugin");
}
catch (Exception ex)
{
using (LogContext.PushProperty("Server", gameEvent.Owner?.ToString()))
{
_logger.LogError(ex,
"Could not execute command action for script plugin {FileName}",
Path.GetFileName(_fileName));
}
throw new PluginException("An error occured while executing action for script plugin");
}
finally
{
if (_onProcessing.CurrentCount == 0)
{
_onProcessing.Release(1);
}
} }
} }
commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission, commandList.Add(scriptCommandFactory.CreateScriptCommand(name, alias, description, permission, targetRequired, args, execute));
targetRequired, args, Execute, supportedGames?.ToArray()));
} }
return commandList; return commandList;
} }
private void GetDvarAsync(Server server, string dvarName, Delegate onCompleted)
{
Task.Run(() =>
{
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
string result = null;
var success = true;
try
{
result = server.GetDvarAsync<string>(dvarName, token: tokenSource.Token).GetAwaiter().GetResult().Value;
}
catch
{
success = false;
}
_onProcessing.Wait();
try
{
onCompleted.DynamicInvoke(JsValue.Undefined,
new[]
{
JsValue.FromObject(_scriptEngine, server),
JsValue.FromObject(_scriptEngine, dvarName),
JsValue.FromObject(_scriptEngine, result),
JsValue.FromObject(_scriptEngine, success),
});
}
finally
{
if (_onProcessing.CurrentCount == 0)
{
_onProcessing.Release();
}
}
});
}
private void SetDvarAsync(Server server, string dvarName, string dvarValue, Delegate onCompleted)
{
Task.Run(() =>
{
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(5));
var success = true;
try
{
server.SetDvarAsync(dvarName, dvarValue, tokenSource.Token).GetAwaiter().GetResult();
}
catch
{
success = false;
}
_onProcessing.Wait();
try
{
onCompleted.DynamicInvoke(JsValue.Undefined,
new[]
{
JsValue.FromObject(_scriptEngine, server),
JsValue.FromObject(_scriptEngine, dvarName),
JsValue.FromObject(_scriptEngine, dvarValue),
JsValue.FromObject(_scriptEngine, success)
});
}
finally
{
if (_onProcessing.CurrentCount == 0)
{
_onProcessing.Release();
}
}
});
}
}
public class PermissionLevelToStringConverter : IObjectConverter
{
public bool TryConvert(Engine engine, object value, out JsValue result)
{
if (value is Data.Models.Client.EFClient.Permission)
{
result = value.ToString();
return true;
}
result = JsValue.Null;
return false;
}
} }
} }

View File

@ -1,7 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using IW4MAdmin.Application.Configuration; using IW4MAdmin.Application.Configuration;
using Jint; using Jint;
@ -13,22 +12,17 @@ namespace IW4MAdmin.Application.Misc
public class ScriptPluginConfigurationWrapper public class ScriptPluginConfigurationWrapper
{ {
private readonly BaseConfigurationHandler<ScriptPluginConfiguration> _handler; private readonly BaseConfigurationHandler<ScriptPluginConfiguration> _handler;
private ScriptPluginConfiguration _config; private readonly ScriptPluginConfiguration _config;
private readonly string _pluginName; private readonly string _pluginName;
private readonly Engine _scriptEngine; private readonly Engine _scriptEngine;
public ScriptPluginConfigurationWrapper(string pluginName, Engine scriptEngine) public ScriptPluginConfigurationWrapper(string pluginName, Engine scriptEngine)
{ {
_handler = new BaseConfigurationHandler<ScriptPluginConfiguration>("ScriptPluginSettings"); _handler = new BaseConfigurationHandler<ScriptPluginConfiguration>("ScriptPluginSettings");
_pluginName = pluginName;
_scriptEngine = scriptEngine;
}
public async Task InitializeAsync()
{
await _handler.BuildAsync();
_config = _handler.Configuration() ?? _config = _handler.Configuration() ??
(ScriptPluginConfiguration) new ScriptPluginConfiguration().Generate(); (ScriptPluginConfiguration) new ScriptPluginConfiguration().Generate();
_pluginName = pluginName;
_scriptEngine = scriptEngine;
} }
private static int? AsInteger(double d) private static int? AsInteger(double d)
@ -85,12 +79,12 @@ namespace IW4MAdmin.Application.Misc
var item = _config[_pluginName][key]; var item = _config[_pluginName][key];
if (item is JsonElement { ValueKind: JsonValueKind.Array } jElem) if (item is JArray array)
{ {
item = jElem.Deserialize<List<dynamic>>(); item = array.ToObject<List<dynamic>>();
} }
return JsValue.FromObject(_scriptEngine, item); return JsValue.FromObject(_scriptEngine, item);
} }
} }
} }

View File

@ -1,159 +0,0 @@
using System;
using System.Threading;
using Jint.Native;
using Jint.Runtime;
using Microsoft.Extensions.Logging;
using SharedLibraryCore.Interfaces;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace IW4MAdmin.Application.Misc;
public class ScriptPluginTimerHelper : IScriptPluginTimerHelper
{
private Timer _timer;
private Action _actions;
private Delegate _jsAction;
private string _actionName;
private const int DefaultDelay = 0;
private const int DefaultInterval = 1000;
private readonly ILogger _logger;
private readonly ManualResetEventSlim _onRunningTick = new();
private SemaphoreSlim _onDependentAction;
public ScriptPluginTimerHelper(ILogger<ScriptPluginTimerHelper> logger)
{
_logger = logger;
}
~ScriptPluginTimerHelper()
{
if (_timer != null)
{
Stop();
}
_onRunningTick.Dispose();
}
public void Start(int delay, int interval)
{
if (_actions is null)
{
throw new InvalidOperationException("Timer action must be defined before starting");
}
if (delay < 0)
{
throw new ArgumentException("Timer delay must be >= 0");
}
if (interval < 20)
{
throw new ArgumentException("Timer interval must be at least 20ms");
}
Stop();
_logger.LogDebug("Starting script timer...");
_onRunningTick.Set();
_timer ??= new Timer(callback => _actions(), null, delay, interval);
IsRunning = true;
}
public void Start(int interval)
{
Start(DefaultDelay, interval);
}
public void Start()
{
Start(DefaultDelay, DefaultInterval);
}
public void Stop()
{
if (_timer == null)
{
return;
}
_logger.LogDebug("Stopping script timer...");
_timer.Change(Timeout.Infinite, Timeout.Infinite);
_timer.Dispose();
_timer = null;
IsRunning = false;
}
public void OnTick(Delegate action, string actionName)
{
if (string.IsNullOrEmpty(actionName))
{
throw new ArgumentException("actionName must be provided", nameof(actionName));
}
if (action is null)
{
throw new ArgumentException("action must be provided", nameof(action));
}
_logger.LogDebug("Adding new action with name {ActionName}", actionName);
_jsAction = action;
_actionName = actionName;
_actions = OnTick;
}
private void ReleaseThreads()
{
_onRunningTick.Set();
if (_onDependentAction?.CurrentCount != 0)
{
return;
}
_onDependentAction?.Release(1);
}
private void OnTick()
{
try
{
if (!_onRunningTick.IsSet)
{
_logger.LogDebug("Previous {OnTick} is still running, so we are skipping this one",
nameof(OnTick));
return;
}
_onRunningTick.Reset();
// the js engine is not thread safe so we need to ensure we're not executing OnTick and OnEventAsync simultaneously
_onDependentAction?.WaitAsync().Wait();
var start = DateTime.Now;
_jsAction.DynamicInvoke(JsValue.Undefined, new[] { JsValue.Undefined });
_logger.LogDebug("OnTick took {Time}ms", (DateTime.Now - start).TotalMilliseconds);
ReleaseThreads();
}
catch (Exception ex) when (ex.InnerException is JavaScriptException jsex)
{
_logger.LogError(jsex,
"Could not execute timer tick for script action {ActionName} [@{LocationInfo}]", _actionName,
jsex.Location);
ReleaseThreads();
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not execute timer tick for script action {ActionName}", _actionName);
_onRunningTick.Set();
ReleaseThreads();
}
}
public void SetDependency(SemaphoreSlim dependentSemaphore)
{
_onDependentAction = dependentSemaphore;
}
public bool IsRunning { get; private set; }
}

View File

@ -94,8 +94,7 @@ namespace IW4MAdmin.Application.Misc
PeriodBlock = (int) (DateTimeOffset.UtcNow - DateTimeOffset.UnixEpoch).TotalMinutes, PeriodBlock = (int) (DateTimeOffset.UtcNow - DateTimeOffset.UnixEpoch).TotalMinutes,
ServerId = await server.GetIdForServer(), ServerId = await server.GetIdForServer(),
MapId = await GetOrCreateMap(server.CurrentMap.Name, (Reference.Game) server.GameName, token), MapId = await GetOrCreateMap(server.CurrentMap.Name, (Reference.Game) server.GameName, token),
ClientCount = server.ClientNum, ClientCount = server.ClientNum
ConnectionInterrupted = server.Throttled,
})); }));
return data; return data;
@ -145,4 +144,4 @@ namespace IW4MAdmin.Application.Misc
context.SaveChanges(); context.SaveChanges();
} }
} }
} }

View File

@ -24,7 +24,7 @@ namespace IW4MAdmin.Application.Misc
private readonly IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> _clientHistoryCache; private readonly IDataValueCache<EFServerSnapshot, List<ClientHistoryInfo>> _clientHistoryCache;
private readonly TimeSpan? _cacheTimeSpan = private readonly TimeSpan? _cacheTimeSpan =
Utilities.IsDevelopment ? TimeSpan.FromSeconds(30) : (TimeSpan?) TimeSpan.FromMinutes(10); Utilities.IsDevelopment ? TimeSpan.FromSeconds(1) : (TimeSpan?) TimeSpan.FromMinutes(1);
public ServerDataViewer(ILogger<ServerDataViewer> logger, IDataValueCache<EFServerSnapshot, (int?, DateTime?)> snapshotCache, public ServerDataViewer(ILogger<ServerDataViewer> logger, IDataValueCache<EFServerSnapshot, (int?, DateTime?)> snapshotCache,
IDataValueCache<EFClient, (int, int)> serverStatsCache, IDataValueCache<EFClient, (int, int)> serverStatsCache,
@ -36,8 +36,7 @@ namespace IW4MAdmin.Application.Misc
_clientHistoryCache = clientHistoryCache; _clientHistoryCache = clientHistoryCache;
} }
public async Task<(int?, DateTime?)> public async Task<(int?, DateTime?)> MaxConcurrentClientsAsync(long? serverId = null, TimeSpan? overPeriod = null,
MaxConcurrentClientsAsync(long? serverId = null, TimeSpan? overPeriod = null,
CancellationToken token = default) CancellationToken token = default)
{ {
_snapshotCache.SetCacheItem(async (snapshots, cancellationToken) => _snapshotCache.SetCacheItem(async (snapshots, cancellationToken) =>
@ -84,7 +83,7 @@ namespace IW4MAdmin.Application.Misc
_logger.LogDebug("Max concurrent clients since {Start} is {Clients}", oldestEntry, maxClients); _logger.LogDebug("Max concurrent clients since {Start} is {Clients}", oldestEntry, maxClients);
return (maxClients, maxClientsTime); return (maxClients, maxClientsTime);
}, nameof(MaxConcurrentClientsAsync), _cacheTimeSpan, true); }, nameof(MaxConcurrentClientsAsync), _cacheTimeSpan);
try try
{ {
@ -108,7 +107,7 @@ namespace IW4MAdmin.Application.Misc
cancellationToken); cancellationToken);
return (count, recentCount); return (count, recentCount);
}, nameof(_serverStatsCache), _cacheTimeSpan, true); }, nameof(_serverStatsCache), _cacheTimeSpan);
try try
{ {
@ -135,9 +134,7 @@ namespace IW4MAdmin.Application.Misc
{ {
snapshot.ServerId, snapshot.ServerId,
snapshot.CapturedAt, snapshot.CapturedAt,
snapshot.ClientCount, snapshot.ClientCount
snapshot.ConnectionInterrupted,
MapName = snapshot.Map.Name,
}) })
.OrderBy(snapshot => snapshot.CapturedAt) .OrderBy(snapshot => snapshot.CapturedAt)
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
@ -145,8 +142,8 @@ namespace IW4MAdmin.Application.Misc
return history.GroupBy(snapshot => snapshot.ServerId).Select(byServer => new ClientHistoryInfo return history.GroupBy(snapshot => snapshot.ServerId).Select(byServer => new ClientHistoryInfo
{ {
ServerId = byServer.Key, ServerId = byServer.Key,
ClientCounts = byServer.Select(snapshot => new ClientCountSnapshot ClientCounts = byServer.Select(snapshot => new ClientCountSnapshot()
{ Time = snapshot.CapturedAt, ClientCount = snapshot.ClientCount, ConnectionInterrupted = snapshot.ConnectionInterrupted ?? false, Map = snapshot.MapName}).ToList() {Time = snapshot.CapturedAt, ClientCount = snapshot.ClientCount}).ToList()
}).ToList(); }).ToList();
}, nameof(_clientHistoryCache), TimeSpan.MaxValue); }, nameof(_clientHistoryCache), TimeSpan.MaxValue);
@ -161,4 +158,4 @@ namespace IW4MAdmin.Application.Misc
} }
} }
} }
} }

View File

@ -7,26 +7,26 @@ using System.Text;
namespace IW4MAdmin.Application.Misc namespace IW4MAdmin.Application.Misc
{ {
internal class TokenAuthentication : ITokenAuthentication class TokenAuthentication : ITokenAuthentication
{ {
private readonly ConcurrentDictionary<long, TokenState> _tokens; private readonly ConcurrentDictionary<long, TokenState> _tokens;
private readonly RandomNumberGenerator _random; private readonly RNGCryptoServiceProvider _random;
private static readonly TimeSpan TimeoutPeriod = new TimeSpan(0, 0, 120); private readonly static TimeSpan _timeoutPeriod = new TimeSpan(0, 0, 120);
private const short TokenLength = 4; private const short TOKEN_LENGTH = 4;
public TokenAuthentication() public TokenAuthentication()
{ {
_tokens = new ConcurrentDictionary<long, TokenState>(); _tokens = new ConcurrentDictionary<long, TokenState>();
_random = RandomNumberGenerator.Create(); _random = new RNGCryptoServiceProvider();
} }
public bool AuthorizeToken(long networkId, string token) public bool AuthorizeToken(long networkId, string token)
{ {
var authorizeSuccessful = _tokens.ContainsKey(networkId) && _tokens[networkId].Token == token; bool authorizeSuccessful = _tokens.ContainsKey(networkId) && _tokens[networkId].Token == token;
if (authorizeSuccessful) if (authorizeSuccessful)
{ {
_tokens.TryRemove(networkId, out _); _tokens.TryRemove(networkId, out TokenState _);
} }
return authorizeSuccessful; return authorizeSuccessful;
@ -34,15 +34,15 @@ namespace IW4MAdmin.Application.Misc
public TokenState GenerateNextToken(long networkId) public TokenState GenerateNextToken(long networkId)
{ {
TokenState state; TokenState state = null;
if (_tokens.ContainsKey(networkId)) if (_tokens.ContainsKey(networkId))
{ {
state = _tokens[networkId]; state = _tokens[networkId];
if ((DateTime.Now - state.RequestTime) > TimeoutPeriod) if ((DateTime.Now - state.RequestTime) > _timeoutPeriod)
{ {
_tokens.TryRemove(networkId, out _); _tokens.TryRemove(networkId, out TokenState _);
} }
else else
@ -51,11 +51,11 @@ namespace IW4MAdmin.Application.Misc
} }
} }
state = new TokenState state = new TokenState()
{ {
NetworkId = networkId, NetworkId = networkId,
Token = _generateToken(), Token = _generateToken(),
TokenDuration = TimeoutPeriod TokenDuration = _timeoutPeriod
}; };
_tokens.TryAdd(networkId, state); _tokens.TryAdd(networkId, state);
@ -63,31 +63,31 @@ namespace IW4MAdmin.Application.Misc
// perform some housekeeping so we don't have built up tokens if they're not ever used // perform some housekeeping so we don't have built up tokens if they're not ever used
foreach (var (key, value) in _tokens) foreach (var (key, value) in _tokens)
{ {
if ((DateTime.Now - value.RequestTime) > TimeoutPeriod) if ((DateTime.Now - value.RequestTime) > _timeoutPeriod)
{ {
_tokens.TryRemove(key, out _); _tokens.TryRemove(key, out TokenState _);
} }
} }
return state; return state;
} }
private string _generateToken() public string _generateToken()
{ {
bool ValidCharacter(char c) bool validCharacter(char c)
{ {
// this ensure that the characters are 0-9, A-Z, a-z // this ensure that the characters are 0-9, A-Z, a-z
return (c > 47 && c < 58) || (c > 64 && c < 91) || (c > 96 && c < 123); return (c > 47 && c < 58) || (c > 64 && c < 91) || (c > 96 && c < 123);
} }
var token = new StringBuilder(); StringBuilder token = new StringBuilder();
var charSet = new byte[1]; while (token.Length < TOKEN_LENGTH)
while (token.Length < TokenLength)
{ {
byte[] charSet = new byte[1];
_random.GetBytes(charSet); _random.GetBytes(charSet);
if (ValidCharacter((char)charSet[0])) if (validCharacter((char)charSet[0]))
{ {
token.Append((char)charSet[0]); token.Append((char)charSet[0]);
} }

View File

@ -7,7 +7,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Data.Models; using Data.Models;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -78,20 +77,19 @@ namespace IW4MAdmin.Application.RConParsers
public string RConEngine { get; set; } = "COD"; public string RConEngine { get; set; } = "COD";
public bool IsOneLog { get; set; } public bool IsOneLog { get; set; }
public async Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command, CancellationToken token = default) public async Task<string[]> ExecuteCommandAsync(IRConConnection connection, string command)
{ {
command = command.FormatMessageForEngine(Configuration?.ColorCodeMapping); var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command);
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command, token);
return response.Where(item => item != Configuration.CommandPrefixes.RConResponse).ToArray(); return response.Where(item => item != Configuration.CommandPrefixes.RConResponse).ToArray();
} }
public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default, CancellationToken token = default) public async Task<Dvar<T>> GetDvarAsync<T>(IRConConnection connection, string dvarName, T fallbackValue = default)
{ {
string[] lineSplit; string[] lineSplit;
try try
{ {
lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName, token); lineSplit = await connection.SendQueryAsync(StaticHelpers.QueryType.GET_DVAR, dvarName);
} }
catch catch
{ {
@ -100,10 +98,10 @@ namespace IW4MAdmin.Application.RConParsers
throw; throw;
} }
lineSplit = Array.Empty<string>(); lineSplit = new string[0];
} }
var response = string.Join('\n', lineSplit).TrimEnd('\0'); string response = string.Join('\n', lineSplit).TrimEnd('\0');
var match = Regex.Match(response, Configuration.Dvar.Pattern); var match = Regex.Match(response, Configuration.Dvar.Pattern);
if (response.Contains("Unknown command") || if (response.Contains("Unknown command") ||
@ -111,7 +109,7 @@ namespace IW4MAdmin.Application.RConParsers
{ {
if (fallbackValue != null) if (fallbackValue != null)
{ {
return new Dvar<T> return new Dvar<T>()
{ {
Name = dvarName, Name = dvarName,
Value = fallbackValue Value = fallbackValue
@ -121,17 +119,17 @@ namespace IW4MAdmin.Application.RConParsers
throw new DvarException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"].FormatExt(dvarName)); throw new DvarException(Utilities.CurrentLocalization.LocalizationIndex["SERVER_ERROR_DVAR"].FormatExt(dvarName));
} }
var value = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarValue]].Value; string value = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarValue]].Value;
var defaultValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDefaultValue]].Value; string defaultValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarDefaultValue]].Value;
var latchedValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarLatchedValue]].Value; string latchedValue = match.Groups[Configuration.Dvar.GroupMapping[ParserRegex.GroupType.RConDvarLatchedValue]].Value;
string RemoveTrailingColorCode(string input) => Regex.Replace(input, @"\^7$", ""); string removeTrailingColorCode(string input) => Regex.Replace(input, @"\^7$", "");
value = RemoveTrailingColorCode(value); value = removeTrailingColorCode(value);
defaultValue = RemoveTrailingColorCode(defaultValue); defaultValue = removeTrailingColorCode(defaultValue);
latchedValue = RemoveTrailingColorCode(latchedValue); latchedValue = removeTrailingColorCode(latchedValue);
return new Dvar<T> return new Dvar<T>()
{ {
Name = dvarName, Name = dvarName,
Value = string.IsNullOrEmpty(value) ? default : (T)Convert.ChangeType(value, typeof(T)), Value = string.IsNullOrEmpty(value) ? default : (T)Convert.ChangeType(value, typeof(T)),
@ -141,12 +139,10 @@ namespace IW4MAdmin.Application.RConParsers
}; };
} }
public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection, CancellationToken token = default) public virtual async Task<IStatusResponse> GetStatusAsync(IRConConnection connection)
{ {
var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS, "status", token); var response = await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND_STATUS);
_logger.LogDebug("Status Response {response}", string.Join(Environment.NewLine, response));
_logger.LogDebug("Status Response {Response}", string.Join(Environment.NewLine, response));
return new StatusResponse return new StatusResponse
{ {
Clients = ClientsFromStatus(response).ToArray(), Clients = ClientsFromStatus(response).ToArray(),
@ -187,13 +183,13 @@ namespace IW4MAdmin.Application.RConParsers
return (T)Convert.ChangeType(value, typeof(T)); return (T)Convert.ChangeType(value, typeof(T));
} }
public async Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue, CancellationToken token = default) public async Task<bool> SetDvarAsync(IRConConnection connection, string dvarName, object dvarValue)
{ {
var dvarString = (dvarValue is string str) string dvarString = (dvarValue is string str)
? $"{dvarName} \"{str}\"" ? $"{dvarName} \"{str}\""
: $"{dvarName} {dvarValue}"; : $"{dvarName} {dvarValue}";
return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString, token)).Length > 0; return (await connection.SendQueryAsync(StaticHelpers.QueryType.SET_DVAR, dvarString)).Length > 0;
} }
private List<EFClient> ClientsFromStatus(string[] Status) private List<EFClient> ClientsFromStatus(string[] Status)

View File

@ -3,7 +3,6 @@ using SharedLibraryCore.Interfaces;
using SharedLibraryCore.RCon; using SharedLibraryCore.RCon;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using SharedLibraryCore.Formatting;
namespace IW4MAdmin.Application.RConParsers namespace IW4MAdmin.Application.RConParsers
{ {
@ -29,25 +28,6 @@ namespace IW4MAdmin.Application.RConParsers
public int NoticeMaximumLines { get; set; } = 8; public int NoticeMaximumLines { get; set; } = 8;
public int NoticeMaxCharactersPerLine { get; set; } = 50; public int NoticeMaxCharactersPerLine { get; set; } = 50;
public string NoticeLineSeparator { get; set; } = Environment.NewLine; public string NoticeLineSeparator { get; set; } = Environment.NewLine;
public int? DefaultRConPort { get; set; }
public string DefaultInstallationDirectoryHint { get; set; }
public short FloodProtectInterval { get; set; } = 750;
public ColorCodeMapping ColorCodeMapping { get; set; } = new ColorCodeMapping
{
// this is the default mapping (IW4), but can be overridden as needed in the parsers
{ColorCodes.Black.ToString(), "^0"},
{ColorCodes.Red.ToString(), "^1"},
{ColorCodes.Green.ToString(), "^2"},
{ColorCodes.Yellow.ToString(), "^3"},
{ColorCodes.Blue.ToString(), "^4"},
{ColorCodes.Cyan.ToString(), "^5"},
{ColorCodes.Pink.ToString(), "^6"},
{ColorCodes.White.ToString(), "^7"},
{ColorCodes.Map.ToString(), "^8"},
{ColorCodes.Grey.ToString(), "^9"},
{ColorCodes.Wildcard.ToString(), ":^"},
};
public DynamicRConParserConfiguration(IParserRegexFactory parserRegexFactory) public DynamicRConParserConfiguration(IParserRegexFactory parserRegexFactory)
{ {

View File

@ -5,10 +5,9 @@ using Microsoft.EntityFrameworkCore;
namespace Data.Abstractions namespace Data.Abstractions
{ {
public interface IDataValueCache<TEntityType, TReturnType> where TEntityType : class public interface IDataValueCache<T, V> where T : class
{ {
void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> itemGetter, string keyName, void SetCacheItem(Func<DbSet<T>, CancellationToken, Task<V>> itemGetter, string keyName, TimeSpan? expirationTime = null);
TimeSpan? expirationTime = null, bool autoRefresh = false); Task<V> GetCacheItem(string keyName, CancellationToken token = default);
Task<TReturnType> GetCacheItem(string keyName, CancellationToken token = default);
} }
} }

View File

@ -23,7 +23,7 @@ namespace Data.Context
{ {
var link = new EFAliasLink(); var link = new EFAliasLink();
context.Clients.Add(new EFClient context.Clients.Add(new EFClient()
{ {
Active = false, Active = false,
Connections = 0, Connections = 0,
@ -33,7 +33,7 @@ namespace Data.Context
Masked = true, Masked = true,
NetworkId = 0, NetworkId = 0,
AliasLink = link, AliasLink = link,
CurrentAlias = new EFAlias CurrentAlias = new EFAlias()
{ {
Link = link, Link = link,
Active = true, Active = true,
@ -46,4 +46,4 @@ namespace Data.Context
} }
} }
} }
} }

View File

@ -18,7 +18,6 @@ namespace Data.Context
public DbSet<EFAlias> Aliases { get; set; } public DbSet<EFAlias> Aliases { get; set; }
public DbSet<EFAliasLink> AliasLinks { get; set; } public DbSet<EFAliasLink> AliasLinks { get; set; }
public DbSet<EFPenalty> Penalties { get; set; } public DbSet<EFPenalty> Penalties { get; set; }
public DbSet<EFPenaltyIdentifier> PenaltyIdentifiers { get; set; }
public DbSet<EFMeta> EFMeta { get; set; } public DbSet<EFMeta> EFMeta { get; set; }
public DbSet<EFChangeHistory> EFChangeHistory { get; set; } public DbSet<EFChangeHistory> EFChangeHistory { get; set; }
@ -119,10 +118,7 @@ namespace Data.Context
ent.HasIndex(a => a.Name); ent.HasIndex(a => a.Name);
ent.Property(_alias => _alias.SearchableName).HasMaxLength(24); ent.Property(_alias => _alias.SearchableName).HasMaxLength(24);
ent.HasIndex(_alias => _alias.SearchableName); ent.HasIndex(_alias => _alias.SearchableName);
ent.HasIndex(_alias => new {_alias.Name, _alias.IPAddress}); ent.HasIndex(_alias => new {_alias.Name, _alias.IPAddress}).IsUnique();
ent.Property(alias => alias.SearchableIPAddress)
.HasComputedColumnSql(@"((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", stored: true);
ent.HasIndex(alias => alias.SearchableIPAddress);
}); });
modelBuilder.Entity<EFMeta>(ent => modelBuilder.Entity<EFMeta>(ent =>
@ -134,12 +130,6 @@ namespace Data.Context
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
}); });
modelBuilder.Entity<EFPenaltyIdentifier>(ent =>
{
ent.HasIndex(identifiers => identifiers.NetworkId);
ent.HasIndex(identifiers => identifiers.IPv4Address);
});
modelBuilder.Entity<EFClientConnectionHistory>(ent => ent.HasIndex(history => history.CreatedDateTime)); modelBuilder.Entity<EFClientConnectionHistory>(ent => ent.HasIndex(history => history.CreatedDateTime));
// force full name for database conversion // force full name for database conversion
@ -147,7 +137,6 @@ namespace Data.Context
modelBuilder.Entity<EFAlias>().ToTable("EFAlias"); modelBuilder.Entity<EFAlias>().ToTable("EFAlias");
modelBuilder.Entity<EFAliasLink>().ToTable("EFAliasLinks"); modelBuilder.Entity<EFAliasLink>().ToTable("EFAliasLinks");
modelBuilder.Entity<EFPenalty>().ToTable("EFPenalties"); modelBuilder.Entity<EFPenalty>().ToTable("EFPenalties");
modelBuilder.Entity<EFPenaltyIdentifier>().ToTable("EFPenaltyIdentifiers");
modelBuilder.Entity<EFServerSnapshot>().ToTable(nameof(EFServerSnapshot)); modelBuilder.Entity<EFServerSnapshot>().ToTable(nameof(EFServerSnapshot));
modelBuilder.Entity<EFClientConnectionHistory>().ToTable(nameof(EFClientConnectionHistory)); modelBuilder.Entity<EFClientConnectionHistory>().ToTable(nameof(EFClientConnectionHistory));
@ -156,4 +145,4 @@ namespace Data.Context
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
} }
} }
} }

View File

@ -1,27 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<Configurations>Debug;Release;Prerelease</Configurations> <Configurations>Debug;Release;Prerelease</Configurations>
<Platforms>AnyCPU</Platforms> <Platforms>AnyCPU</Platforms>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>RaidMax.IW4MAdmin.Data</PackageId> <PackageId>RaidMax.IW4MAdmin.Data</PackageId>
<Title>RaidMax.IW4MAdmin.Data</Title> <Title>RaidMax.IW4MAdmin.Data</Title>
<Authors /> <Authors />
<PackageVersion>1.2.0</PackageVersion> <PackageVersion>1.0.7</PackageVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.10">
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Npgsql" Version="6.0.2" /> <PackageReference Include="Npgsql" Version="4.1.7" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.2" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" /> <PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.2.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.1" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.10" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)'=='Debug'">
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.10" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,84 +1,58 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Data.Abstractions; using Data.Abstractions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Timer = System.Timers.Timer;
namespace Data.Helpers namespace Data.Helpers
{ {
public class DataValueCache<TEntityType, TReturnType> : IDataValueCache<TEntityType, TReturnType> public class DataValueCache<T, V> : IDataValueCache<T, V> where T : class
where TEntityType : class
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IDatabaseContextFactory _contextFactory; private readonly IDatabaseContextFactory _contextFactory;
private readonly Dictionary<string, CacheState> _cacheStates = new Dictionary<string, CacheState>();
private readonly ConcurrentDictionary<string, CacheState<TReturnType>> _cacheStates =
new ConcurrentDictionary<string, CacheState<TReturnType>>();
private bool _autoRefresh;
private const int DefaultExpireMinutes = 15; private const int DefaultExpireMinutes = 15;
private Timer _timer;
private class CacheState<TCacheType> private class CacheState
{ {
public string Key { get; set; } public string Key { get; set; }
public DateTime LastRetrieval { get; set; } public DateTime LastRetrieval { get; set; }
public TimeSpan ExpirationTime { get; set; } public TimeSpan ExpirationTime { get; set; }
public Func<DbSet<TEntityType>, CancellationToken, Task<TCacheType>> Getter { get; set; } public Func<DbSet<T>, CancellationToken, Task<V>> Getter { get; set; }
public TCacheType Value { get; set; } public V Value { get; set; }
public bool IsSet { get; set; }
public bool IsExpired => ExpirationTime != TimeSpan.MaxValue && public bool IsExpired => ExpirationTime != TimeSpan.MaxValue &&
(DateTime.Now - LastRetrieval.Add(ExpirationTime)).TotalSeconds > 0; (DateTime.Now - LastRetrieval.Add(ExpirationTime)).TotalSeconds > 0;
} }
public DataValueCache(ILogger<DataValueCache<TEntityType, TReturnType>> logger, public DataValueCache(ILogger<DataValueCache<T, V>> logger, IDatabaseContextFactory contextFactory)
IDatabaseContextFactory contextFactory)
{ {
_logger = logger; _logger = logger;
_contextFactory = contextFactory; _contextFactory = contextFactory;
} }
~DataValueCache() public void SetCacheItem(Func<DbSet<T>, CancellationToken, Task<V>> getter, string key,
{ TimeSpan? expirationTime = null)
_timer?.Stop();
_timer?.Dispose();
}
public void SetCacheItem(Func<DbSet<TEntityType>, CancellationToken, Task<TReturnType>> getter, string key,
TimeSpan? expirationTime = null, bool autoRefresh = false)
{ {
if (_cacheStates.ContainsKey(key)) if (_cacheStates.ContainsKey(key))
{ {
_logger.LogDebug("Cache key {Key} is already added", key); _logger.LogDebug("Cache key {key} is already added", key);
return; return;
} }
var state = new CacheState<TReturnType> var state = new CacheState()
{ {
Key = key, Key = key,
Getter = getter, Getter = getter,
ExpirationTime = expirationTime ?? TimeSpan.FromMinutes(DefaultExpireMinutes) ExpirationTime = expirationTime ?? TimeSpan.FromMinutes(DefaultExpireMinutes)
}; };
_autoRefresh = autoRefresh; _cacheStates.Add(key, state);
_cacheStates.TryAdd(key, state);
if (!_autoRefresh || expirationTime == TimeSpan.MaxValue)
{
return;
}
_timer = new Timer(state.ExpirationTime.TotalMilliseconds);
_timer.Elapsed += async (sender, args) => await RunCacheUpdate(state, CancellationToken.None);
_timer.Start();
} }
public async Task<TReturnType> GetCacheItem(string keyName, CancellationToken cancellationToken = default) public async Task<V> GetCacheItem(string keyName, CancellationToken cancellationToken = default)
{ {
if (!_cacheStates.ContainsKey(keyName)) if (!_cacheStates.ContainsKey(keyName))
{ {
@ -87,9 +61,7 @@ namespace Data.Helpers
var state = _cacheStates[keyName]; var state = _cacheStates[keyName];
// when auto refresh is off we want to check the expiration and value if (state.IsExpired || state.Value == null)
// when auto refresh is on, we want to only check the value, because it'll be refreshed automatically
if ((state.IsExpired || !state.IsSet) && !_autoRefresh || _autoRefresh && !state.IsSet)
{ {
await RunCacheUpdate(state, cancellationToken); await RunCacheUpdate(state, cancellationToken);
} }
@ -97,21 +69,19 @@ namespace Data.Helpers
return state.Value; return state.Value;
} }
private async Task RunCacheUpdate(CacheState<TReturnType> state, CancellationToken token) private async Task RunCacheUpdate(CacheState state, CancellationToken token)
{ {
try try
{ {
_logger.LogDebug("Running update for {ClassName} {@State}", GetType().Name, state);
await using var context = _contextFactory.CreateContext(false); await using var context = _contextFactory.CreateContext(false);
var set = context.Set<TEntityType>(); var set = context.Set<T>();
var value = await state.Getter(set, token); var value = await state.Getter(set, token);
state.Value = value; state.Value = value;
state.IsSet = true;
state.LastRetrieval = DateTime.Now; state.LastRetrieval = DateTime.Now;
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Could not get cached value for {Key}", state.Key); _logger.LogError(ex, "Could not get cached value for {key}", state.Key);
} }
} }
} }

View File

@ -102,7 +102,7 @@ namespace Data.Helpers
{ {
try try
{ {
await using var context = _contextFactory.CreateContext(false); await using var context = _contextFactory.CreateContext();
_cachedItems = await context.Set<T>().ToDictionaryAsync(item => item.Id); _cachedItems = await context.Set<T>().ToDictionaryAsync(item => item.Id);
} }
catch (Exception ex) catch (Exception ex)
@ -111,4 +111,4 @@ namespace Data.Helpers
} }
} }
} }
} }

View File

@ -24,11 +24,10 @@ namespace Data.MigrationContext
{ {
if (MigrationExtensions.IsMigration) if (MigrationExtensions.IsMigration)
{ {
var connectionString = "Server=127.0.0.1;Database=IW4MAdmin_Migration;Uid=root;Pwd=password;"; optionsBuilder.UseMySql("Server=127.0.0.1;Database=IW4MAdmin_Migration;Uid=root;Pwd=password;")
optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)) .EnableDetailedErrors(true)
.EnableDetailedErrors() .EnableSensitiveDataLogging(true);
.EnableSensitiveDataLogging();
} }
} }
} }
} }

View File

@ -23,13 +23,12 @@ namespace Data.MigrationContext
{ {
if (MigrationExtensions.IsMigration) if (MigrationExtensions.IsMigration)
{ {
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
optionsBuilder.UseNpgsql( optionsBuilder.UseNpgsql(
"Host=127.0.0.1;Database=IW4MAdmin_Migration;Username=postgres;Password=password;", "Host=127.0.0.1;Database=IW4MAdmin_Migration;Username=postgres;Password=password;",
options => options.SetPostgresVersion(new Version("12.9"))) options => options.SetPostgresVersion(new Version("9.4")))
.EnableDetailedErrors(true) .EnableDetailedErrors(true)
.EnableSensitiveDataLogging(true); .EnableSensitiveDataLogging(true);
} }
} }
} }
} }

View File

@ -1,32 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Data.Migrations.MySql
{
public partial class RemoveUniqueAliasIndexConstraint : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFAlias_Name_IPAddress",
table: "EFAlias");
migrationBuilder.CreateIndex(
name: "IX_EFAlias_Name_IPAddress",
table: "EFAlias",
columns: new[] { "Name", "IPAddress" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFAlias_Name_IPAddress",
table: "EFAlias");
migrationBuilder.CreateIndex(
name: "IX_EFAlias_Name_IPAddress",
table: "EFAlias",
columns: new[] { "Name", "IPAddress" },
unique: true);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,250 +0,0 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class AddEFPenaltyIdentifier : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EFPenaltyIdentifiers",
columns: table => new
{
PenaltyIdentifierId = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
IPv4Address = table.Column<int>(type: "int", nullable: true),
NetworkId = table.Column<long>(type: "bigint", nullable: false),
PenaltyId = table.Column<int>(type: "int", nullable: false),
Active = table.Column<bool>(type: "tinyint(1)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EFPenaltyIdentifiers", x => x.PenaltyIdentifierId);
table.ForeignKey(
name: "FK_EFPenaltyIdentifiers_EFPenalties_PenaltyId",
column: x => x.PenaltyId,
principalTable: "EFPenalties",
principalColumn: "PenaltyId",
onDelete: ReferentialAction.Cascade);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_IPv4Address",
table: "EFPenaltyIdentifiers",
column: "IPv4Address");
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_NetworkId",
table: "EFPenaltyIdentifiers",
column: "NetworkId");
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_PenaltyId",
table: "EFPenaltyIdentifiers",
column: "PenaltyId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EFPenaltyIdentifiers");
migrationBuilder.AlterColumn<string>(
name: "Message",
table: "InboxMessages",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "EFWeaponAttachments",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "HostName",
table: "EFServers",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "EndPoint",
table: "EFServers",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Offense",
table: "EFPenalties",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "AutomatedOffense",
table: "EFPenalties",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Value",
table: "EFMeta",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Extra",
table: "EFMeta",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "EFMeansOfDeath",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Name",
table: "EFMaps",
type: "longtext CHARACTER SET utf8mb4",
nullable: false,
oldClrType: typeof(string),
oldType: "longtext")
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "PasswordSalt",
table: "EFClients",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Password",
table: "EFClients",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "Message",
table: "EFClientMessages",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "WeaponReference",
table: "EFClientKills",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "PreviousValue",
table: "EFChangeHistory",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "CurrentValue",
table: "EFChangeHistory",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "WeaponReference",
table: "EFACSnapshot",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
migrationBuilder.AlterColumn<string>(
name: "HitLocationReference",
table: "EFACSnapshot",
type: "longtext CHARACTER SET utf8mb4",
nullable: true,
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true)
.Annotation("MySql:CharSet", "utf8mb4")
.OldAnnotation("MySql:CharSet", "utf8mb4");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,56 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class MakeEFPenaltyLinkIdNullable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties");
migrationBuilder.AlterColumn<int>(
name: "LinkId",
table: "EFPenalties",
type: "int",
nullable: true,
oldClrType: typeof(int),
oldType: "int");
migrationBuilder.AddForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties",
column: "LinkId",
principalTable: "EFAliasLinks",
principalColumn: "AliasLinkId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties");
migrationBuilder.AlterColumn<int>(
name: "LinkId",
table: "EFPenalties",
type: "int",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "int",
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties",
column: "LinkId",
principalTable: "EFAliasLinks",
principalColumn: "AliasLinkId",
onDelete: ReferentialAction.Cascade);
}
}
}

View File

@ -1,48 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class AddAuditFieldsToEFPenaltyIdentifier : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Active",
table: "EFPenaltyIdentifiers");
migrationBuilder.AddColumn<DateTime>(
name: "CreatedDateTime",
table: "EFPenaltyIdentifiers",
type: "datetime(6)",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedDateTime",
table: "EFPenaltyIdentifiers",
type: "datetime(6)",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedDateTime",
table: "EFPenaltyIdentifiers");
migrationBuilder.DropColumn(
name: "UpdatedDateTime",
table: "EFPenaltyIdentifiers");
migrationBuilder.AddColumn<bool>(
name: "Active",
table: "EFPenaltyIdentifiers",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
}
}

View File

@ -1,25 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class AddConnectionInterruptedToEFServerSnapshot : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "ConnectionInterrupted",
table: "EFServerSnapshot",
type: "tinyint(1)",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ConnectionInterrupted",
table: "EFServerSnapshot");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class AddSearchableIPToEFAlias : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "SearchableIPAddress",
table: "EFAlias",
type: "longtext",
nullable: true,
computedColumnSql: "CONCAT((IPAddress & 255), \".\", ((IPAddress >> 8) & 255), \".\", ((IPAddress >> 16) & 255), \".\", ((IPAddress >> 24) & 255))",
stored: true)
.Annotation("MySql:CharSet", "utf8mb4");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SearchableIPAddress",
table: "EFAlias");
}
}
}

View File

@ -1,24 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class AddIndexToSearchableIPToEFAlias : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_EFAlias_SearchableIPAddress",
table: "EFAlias",
column: "SearchableIPAddress");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFAlias_SearchableIPAddress",
table: "EFAlias");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.MySql
{
public partial class AddGameToEFClient : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "GameName",
table: "EFClients",
type: "int",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "GameName",
table: "EFClients");
}
}
}

View File

@ -5,8 +5,6 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Data.Migrations.MySql namespace Data.Migrations.MySql
{ {
[DbContext(typeof(MySqlDatabaseContext))] [DbContext(typeof(MySqlDatabaseContext))]
@ -16,7 +14,7 @@ namespace Data.Migrations.MySql
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder
.HasAnnotation("ProductVersion", "6.0.1") .HasAnnotation("ProductVersion", "3.1.10")
.HasAnnotation("Relational:MaxIdentifierLength", 64); .HasAnnotation("Relational:MaxIdentifierLength", 64);
modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b =>
@ -40,7 +38,7 @@ namespace Data.Migrations.MySql
b.HasIndex("Vector3Id"); b.HasIndex("Vector3Id");
b.ToTable("EFACSnapshotVector3", (string)null); b.ToTable("EFACSnapshotVector3");
}); });
modelBuilder.Entity("Data.Models.Client.EFClient", b => modelBuilder.Entity("Data.Models.Client.EFClient", b =>
@ -64,9 +62,6 @@ namespace Data.Migrations.MySql
b.Property<DateTime>("FirstConnection") b.Property<DateTime>("FirstConnection")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.Property<int?>("GameName")
.HasColumnType("int");
b.Property<DateTime>("LastConnection") b.Property<DateTime>("LastConnection")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -80,10 +75,10 @@ namespace Data.Migrations.MySql
.HasColumnType("bigint"); .HasColumnType("bigint");
b.Property<string>("Password") b.Property<string>("Password")
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("PasswordSalt") b.Property<string>("PasswordSalt")
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<int>("TotalConnectionTime") b.Property<int>("TotalConnectionTime")
.HasColumnType("int"); .HasColumnType("int");
@ -97,7 +92,7 @@ namespace Data.Migrations.MySql
b.HasIndex("NetworkId") b.HasIndex("NetworkId")
.IsUnique(); .IsUnique();
b.ToTable("EFClients", (string)null); b.ToTable("EFClients");
}); });
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
@ -129,7 +124,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFClientConnectionHistory", (string)null); b.ToTable("EFClientConnectionHistory");
}); });
modelBuilder.Entity("Data.Models.Client.EFClientKill", b => modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
@ -184,7 +179,7 @@ namespace Data.Migrations.MySql
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("WeaponReference") b.Property<string>("WeaponReference")
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<DateTime>("When") b.Property<DateTime>("When")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -203,7 +198,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ViewAnglesVector3Id"); b.HasIndex("ViewAnglesVector3Id");
b.ToTable("EFClientKills", (string)null); b.ToTable("EFClientKills");
}); });
modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => modelBuilder.Entity("Data.Models.Client.EFClientMessage", b =>
@ -219,7 +214,7 @@ namespace Data.Migrations.MySql
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("Message") b.Property<string>("Message")
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<bool>("SentIngame") b.Property<bool>("SentIngame")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
@ -238,7 +233,7 @@ namespace Data.Migrations.MySql
b.HasIndex("TimeSent"); b.HasIndex("TimeSent");
b.ToTable("EFClientMessages", (string)null); b.ToTable("EFClientMessages");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b =>
@ -278,7 +273,7 @@ namespace Data.Migrations.MySql
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("HitLocationReference") b.Property<string>("HitLocationReference")
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<int>("HitOriginId") b.Property<int>("HitOriginId")
.HasColumnType("int"); .HasColumnType("int");
@ -326,7 +321,7 @@ namespace Data.Migrations.MySql
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("WeaponReference") b.Property<string>("WeaponReference")
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<DateTime>("When") b.Property<DateTime>("When")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -345,7 +340,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFACSnapshot", (string)null); b.ToTable("EFACSnapshot");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b =>
@ -419,7 +414,7 @@ namespace Data.Migrations.MySql
b.HasIndex("WeaponId"); b.HasIndex("WeaponId");
b.ToTable("EFClientHitStatistics", (string)null); b.ToTable("EFClientHitStatistics");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b =>
@ -464,7 +459,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ZScore"); b.HasIndex("ZScore");
b.ToTable("EFClientRankingHistory", (string)null); b.ToTable("EFClientRankingHistory");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b =>
@ -483,7 +478,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ClientId"); b.HasIndex("ClientId");
b.ToTable("EFClientRatingHistory", (string)null); b.ToTable("EFClientRatingHistory");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b =>
@ -541,7 +536,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ClientId", "TimePlayed", "ZScore"); b.HasIndex("ClientId", "TimePlayed", "ZScore");
b.ToTable("EFClientStatistics", (string)null); b.ToTable("EFClientStatistics");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b =>
@ -554,12 +549,12 @@ namespace Data.Migrations.MySql
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<int>("EFClientStatisticsClientId") b.Property<int>("EFClientStatisticsClientId")
.HasColumnType("int") .HasColumnName("EFClientStatisticsClientId")
.HasColumnName("EFClientStatisticsClientId"); .HasColumnType("int");
b.Property<long>("EFClientStatisticsServerId") b.Property<long>("EFClientStatisticsServerId")
.HasColumnType("bigint") .HasColumnName("EFClientStatisticsServerId")
.HasColumnName("EFClientStatisticsServerId"); .HasColumnType("bigint");
b.Property<int>("HitCount") b.Property<int>("HitCount")
.HasColumnType("int"); .HasColumnType("int");
@ -579,7 +574,7 @@ namespace Data.Migrations.MySql
b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId"); b.HasIndex("EFClientStatisticsClientId", "EFClientStatisticsServerId");
b.ToTable("EFHitLocationCounts", (string)null); b.ToTable("EFHitLocationCounts");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b =>
@ -622,7 +617,7 @@ namespace Data.Migrations.MySql
b.HasIndex("When", "ServerId", "Performance", "ActivityAmount"); b.HasIndex("When", "ServerId", "Performance", "ActivityAmount");
b.ToTable("EFRating", (string)null); b.ToTable("EFRating");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFHitLocation", b =>
@ -639,7 +634,7 @@ namespace Data.Migrations.MySql
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasColumnType("varchar(255)"); .HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<DateTime?>("UpdatedDateTime") b.Property<DateTime?>("UpdatedDateTime")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -648,7 +643,7 @@ namespace Data.Migrations.MySql
b.HasIndex("Name"); b.HasIndex("Name");
b.ToTable("EFHitLocations", (string)null); b.ToTable("EFHitLocations");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMap", b =>
@ -665,14 +660,14 @@ namespace Data.Migrations.MySql
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<DateTime?>("UpdatedDateTime") b.Property<DateTime?>("UpdatedDateTime")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.HasKey("MapId"); b.HasKey("MapId");
b.ToTable("EFMaps", (string)null); b.ToTable("EFMaps");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFMeansOfDeath", b =>
@ -689,14 +684,14 @@ namespace Data.Migrations.MySql
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<DateTime?>("UpdatedDateTime") b.Property<DateTime?>("UpdatedDateTime")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.HasKey("MeansOfDeathId"); b.HasKey("MeansOfDeathId");
b.ToTable("EFMeansOfDeath", (string)null); b.ToTable("EFMeansOfDeath");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeapon", b =>
@ -713,7 +708,7 @@ namespace Data.Migrations.MySql
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasColumnType("varchar(255)"); .HasColumnType("varchar(255) CHARACTER SET utf8mb4");
b.Property<DateTime?>("UpdatedDateTime") b.Property<DateTime?>("UpdatedDateTime")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -722,7 +717,7 @@ namespace Data.Migrations.MySql
b.HasIndex("Name"); b.HasIndex("Name");
b.ToTable("EFWeapons", (string)null); b.ToTable("EFWeapons");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachment", b =>
@ -739,14 +734,14 @@ namespace Data.Migrations.MySql
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<DateTime?>("UpdatedDateTime") b.Property<DateTime?>("UpdatedDateTime")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.HasKey("WeaponAttachmentId"); b.HasKey("WeaponAttachmentId");
b.ToTable("EFWeaponAttachments", (string)null); b.ToTable("EFWeaponAttachments");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b =>
@ -781,7 +776,7 @@ namespace Data.Migrations.MySql
b.HasIndex("Attachment3Id"); b.HasIndex("Attachment3Id");
b.ToTable("EFWeaponAttachmentCombos", (string)null); b.ToTable("EFWeaponAttachmentCombos");
}); });
modelBuilder.Entity("Data.Models.EFAlias", b => modelBuilder.Entity("Data.Models.EFAlias", b =>
@ -804,17 +799,12 @@ namespace Data.Migrations.MySql
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(24) .HasColumnType("varchar(24) CHARACTER SET utf8mb4")
.HasColumnType("varchar(24)"); .HasMaxLength(24);
b.Property<string>("SearchableIPAddress")
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("varchar(255)")
.HasComputedColumnSql("((IPAddress & 255) || '.' || ((IPAddress >> 8) & 255)) || '.' || ((IPAddress >> 16) & 255) || '.' || ((IPAddress >> 24) & 255)", true);
b.Property<string>("SearchableName") b.Property<string>("SearchableName")
.HasMaxLength(24) .HasColumnType("varchar(24) CHARACTER SET utf8mb4")
.HasColumnType("varchar(24)"); .HasMaxLength(24);
b.HasKey("AliasId"); b.HasKey("AliasId");
@ -824,13 +814,12 @@ namespace Data.Migrations.MySql
b.HasIndex("Name"); b.HasIndex("Name");
b.HasIndex("SearchableIPAddress");
b.HasIndex("SearchableName"); b.HasIndex("SearchableName");
b.HasIndex("Name", "IPAddress"); b.HasIndex("Name", "IPAddress")
.IsUnique();
b.ToTable("EFAlias", (string)null); b.ToTable("EFAlias");
}); });
modelBuilder.Entity("Data.Models.EFAliasLink", b => modelBuilder.Entity("Data.Models.EFAliasLink", b =>
@ -844,7 +833,7 @@ namespace Data.Migrations.MySql
b.HasKey("AliasLinkId"); b.HasKey("AliasLinkId");
b.ToTable("EFAliasLinks", (string)null); b.ToTable("EFAliasLinks");
}); });
modelBuilder.Entity("Data.Models.EFChangeHistory", b => modelBuilder.Entity("Data.Models.EFChangeHistory", b =>
@ -857,11 +846,11 @@ namespace Data.Migrations.MySql
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<string>("Comment") b.Property<string>("Comment")
.HasMaxLength(128) .HasColumnType("varchar(128) CHARACTER SET utf8mb4")
.HasColumnType("varchar(128)"); .HasMaxLength(128);
b.Property<string>("CurrentValue") b.Property<string>("CurrentValue")
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<int?>("ImpersonationEntityId") b.Property<int?>("ImpersonationEntityId")
.HasColumnType("int"); .HasColumnType("int");
@ -870,7 +859,7 @@ namespace Data.Migrations.MySql
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("PreviousValue") b.Property<string>("PreviousValue")
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<int>("TargetEntityId") b.Property<int>("TargetEntityId")
.HasColumnType("int"); .HasColumnType("int");
@ -902,12 +891,12 @@ namespace Data.Migrations.MySql
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
b.Property<string>("Extra") b.Property<string>("Extra")
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<string>("Key") b.Property<string>("Key")
.IsRequired() .IsRequired()
.HasMaxLength(32) .HasColumnType("varchar(32) CHARACTER SET utf8mb4")
.HasColumnType("varchar(32)"); .HasMaxLength(32);
b.Property<int?>("LinkedMetaId") b.Property<int?>("LinkedMetaId")
.HasColumnType("int"); .HasColumnType("int");
@ -917,7 +906,7 @@ namespace Data.Migrations.MySql
b.Property<string>("Value") b.Property<string>("Value")
.IsRequired() .IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.HasKey("MetaId"); b.HasKey("MetaId");
@ -940,7 +929,7 @@ namespace Data.Migrations.MySql
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<string>("AutomatedOffense") b.Property<string>("AutomatedOffense")
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<DateTime?>("Expires") b.Property<DateTime?>("Expires")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");
@ -948,7 +937,7 @@ namespace Data.Migrations.MySql
b.Property<bool>("IsEvadedOffense") b.Property<bool>("IsEvadedOffense")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<int?>("LinkId") b.Property<int>("LinkId")
.HasColumnType("int"); .HasColumnType("int");
b.Property<int>("OffenderId") b.Property<int>("OffenderId")
@ -956,7 +945,7 @@ namespace Data.Migrations.MySql
b.Property<string>("Offense") b.Property<string>("Offense")
.IsRequired() .IsRequired()
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<int>("PunisherId") b.Property<int>("PunisherId")
.HasColumnType("int"); .HasColumnType("int");
@ -975,39 +964,7 @@ namespace Data.Migrations.MySql
b.HasIndex("PunisherId"); b.HasIndex("PunisherId");
b.ToTable("EFPenalties", (string)null); b.ToTable("EFPenalties");
});
modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b =>
{
b.Property<int>("PenaltyIdentifierId")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<DateTime>("CreatedDateTime")
.HasColumnType("datetime(6)");
b.Property<int?>("IPv4Address")
.HasColumnType("int");
b.Property<long>("NetworkId")
.HasColumnType("bigint");
b.Property<int>("PenaltyId")
.HasColumnType("int");
b.Property<DateTime?>("UpdatedDateTime")
.HasColumnType("datetime(6)");
b.HasKey("PenaltyIdentifierId");
b.HasIndex("IPv4Address");
b.HasIndex("NetworkId");
b.HasIndex("PenaltyId");
b.ToTable("EFPenaltyIdentifiers", (string)null);
}); });
modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b => modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b =>
@ -1026,7 +983,7 @@ namespace Data.Migrations.MySql
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<string>("Message") b.Property<string>("Message")
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<long?>("ServerId") b.Property<long?>("ServerId")
.HasColumnType("bigint"); .HasColumnType("bigint");
@ -1057,13 +1014,13 @@ namespace Data.Migrations.MySql
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
b.Property<string>("EndPoint") b.Property<string>("EndPoint")
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<int?>("GameName") b.Property<int?>("GameName")
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("HostName") b.Property<string>("HostName")
.HasColumnType("longtext"); .HasColumnType("longtext CHARACTER SET utf8mb4");
b.Property<bool>("IsPasswordProtected") b.Property<bool>("IsPasswordProtected")
.HasColumnType("tinyint(1)"); .HasColumnType("tinyint(1)");
@ -1073,7 +1030,7 @@ namespace Data.Migrations.MySql
b.HasKey("ServerId"); b.HasKey("ServerId");
b.ToTable("EFServers", (string)null); b.ToTable("EFServers");
}); });
modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b =>
@ -1091,9 +1048,6 @@ namespace Data.Migrations.MySql
b.Property<int>("ClientCount") b.Property<int>("ClientCount")
.HasColumnType("int"); .HasColumnType("int");
b.Property<bool?>("ConnectionInterrupted")
.HasColumnType("tinyint(1)");
b.Property<int>("MapId") b.Property<int>("MapId")
.HasColumnType("int"); .HasColumnType("int");
@ -1109,7 +1063,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFServerSnapshot", (string)null); b.ToTable("EFServerSnapshot");
}); });
modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b =>
@ -1134,7 +1088,7 @@ namespace Data.Migrations.MySql
b.HasIndex("ServerId"); b.HasIndex("ServerId");
b.ToTable("EFServerStatistics", (string)null); b.ToTable("EFServerStatistics");
}); });
modelBuilder.Entity("Data.Models.Vector3", b => modelBuilder.Entity("Data.Models.Vector3", b =>
@ -1154,7 +1108,7 @@ namespace Data.Migrations.MySql
b.HasKey("Vector3Id"); b.HasKey("Vector3Id");
b.ToTable("Vector3", (string)null); b.ToTable("Vector3");
}); });
modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b => modelBuilder.Entity("Data.Models.Client.EFACSnapshotVector3", b =>
@ -1170,10 +1124,6 @@ namespace Data.Migrations.MySql
.HasForeignKey("Vector3Id") .HasForeignKey("Vector3Id")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Snapshot");
b.Navigation("Vector");
}); });
modelBuilder.Entity("Data.Models.Client.EFClient", b => modelBuilder.Entity("Data.Models.Client.EFClient", b =>
@ -1189,10 +1139,6 @@ namespace Data.Migrations.MySql
.HasForeignKey("CurrentAliasId") .HasForeignKey("CurrentAliasId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("AliasLink");
b.Navigation("CurrentAlias");
}); });
modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b => modelBuilder.Entity("Data.Models.Client.EFClientConnectionHistory", b =>
@ -1208,10 +1154,6 @@ namespace Data.Migrations.MySql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.EFClientKill", b => modelBuilder.Entity("Data.Models.Client.EFClientKill", b =>
@ -1245,18 +1187,6 @@ namespace Data.Migrations.MySql
b.HasOne("Data.Models.Vector3", "ViewAngles") b.HasOne("Data.Models.Vector3", "ViewAngles")
.WithMany() .WithMany()
.HasForeignKey("ViewAnglesVector3Id"); .HasForeignKey("ViewAnglesVector3Id");
b.Navigation("Attacker");
b.Navigation("DeathOrigin");
b.Navigation("KillOrigin");
b.Navigation("Server");
b.Navigation("Victim");
b.Navigation("ViewAngles");
}); });
modelBuilder.Entity("Data.Models.Client.EFClientMessage", b => modelBuilder.Entity("Data.Models.Client.EFClientMessage", b =>
@ -1272,10 +1202,6 @@ namespace Data.Migrations.MySql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b => modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b =>
@ -1313,18 +1239,6 @@ namespace Data.Migrations.MySql
b.HasOne("Data.Models.Server.EFServer", "Server") b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany() .WithMany()
.HasForeignKey("ServerId"); .HasForeignKey("ServerId");
b.Navigation("Client");
b.Navigation("CurrentViewAngle");
b.Navigation("HitDestination");
b.Navigation("HitOrigin");
b.Navigation("LastStrainAngle");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientHitStatistic", b =>
@ -1354,18 +1268,6 @@ namespace Data.Migrations.MySql
b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon") b.HasOne("Data.Models.Client.Stats.Reference.EFWeapon", "Weapon")
.WithMany() .WithMany()
.HasForeignKey("WeaponId"); .HasForeignKey("WeaponId");
b.Navigation("Client");
b.Navigation("HitLocation");
b.Navigation("MeansOfDeath");
b.Navigation("Server");
b.Navigation("Weapon");
b.Navigation("WeaponAttachmentCombo");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRankingHistory", b =>
@ -1379,10 +1281,6 @@ namespace Data.Migrations.MySql
b.HasOne("Data.Models.Server.EFServer", "Server") b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany() .WithMany()
.HasForeignKey("ServerId"); .HasForeignKey("ServerId");
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b =>
@ -1392,8 +1290,6 @@ namespace Data.Migrations.MySql
.HasForeignKey("ClientId") .HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b => modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b =>
@ -1409,10 +1305,6 @@ namespace Data.Migrations.MySql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b => modelBuilder.Entity("Data.Models.Client.Stats.EFHitLocationCount", b =>
@ -1434,10 +1326,6 @@ namespace Data.Migrations.MySql
.HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId") .HasForeignKey("EFClientStatisticsClientId", "EFClientStatisticsServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Client");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b => modelBuilder.Entity("Data.Models.Client.Stats.EFRating", b =>
@ -1451,10 +1339,6 @@ namespace Data.Migrations.MySql
b.HasOne("Data.Models.Server.EFServer", "Server") b.HasOne("Data.Models.Server.EFServer", "Server")
.WithMany() .WithMany()
.HasForeignKey("ServerId"); .HasForeignKey("ServerId");
b.Navigation("RatingHistory");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b => modelBuilder.Entity("Data.Models.Client.Stats.Reference.EFWeaponAttachmentCombo", b =>
@ -1472,12 +1356,6 @@ namespace Data.Migrations.MySql
b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3") b.HasOne("Data.Models.Client.Stats.Reference.EFWeaponAttachment", "Attachment3")
.WithMany() .WithMany()
.HasForeignKey("Attachment3Id"); .HasForeignKey("Attachment3Id");
b.Navigation("Attachment1");
b.Navigation("Attachment2");
b.Navigation("Attachment3");
}); });
modelBuilder.Entity("Data.Models.EFAlias", b => modelBuilder.Entity("Data.Models.EFAlias", b =>
@ -1487,8 +1365,6 @@ namespace Data.Migrations.MySql
.HasForeignKey("LinkId") .HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .IsRequired();
b.Navigation("Link");
}); });
modelBuilder.Entity("Data.Models.EFMeta", b => modelBuilder.Entity("Data.Models.EFMeta", b =>
@ -1501,17 +1377,15 @@ namespace Data.Migrations.MySql
.WithMany() .WithMany()
.HasForeignKey("LinkedMetaId") .HasForeignKey("LinkedMetaId")
.OnDelete(DeleteBehavior.SetNull); .OnDelete(DeleteBehavior.SetNull);
b.Navigation("Client");
b.Navigation("LinkedMeta");
}); });
modelBuilder.Entity("Data.Models.EFPenalty", b => modelBuilder.Entity("Data.Models.EFPenalty", b =>
{ {
b.HasOne("Data.Models.EFAliasLink", "Link") b.HasOne("Data.Models.EFAliasLink", "Link")
.WithMany("ReceivedPenalties") .WithMany("ReceivedPenalties")
.HasForeignKey("LinkId"); .HasForeignKey("LinkId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Data.Models.Client.EFClient", "Offender") b.HasOne("Data.Models.Client.EFClient", "Offender")
.WithMany("ReceivedPenalties") .WithMany("ReceivedPenalties")
@ -1524,23 +1398,6 @@ namespace Data.Migrations.MySql
.HasForeignKey("PunisherId") .HasForeignKey("PunisherId")
.OnDelete(DeleteBehavior.Restrict) .OnDelete(DeleteBehavior.Restrict)
.IsRequired(); .IsRequired();
b.Navigation("Link");
b.Navigation("Offender");
b.Navigation("Punisher");
});
modelBuilder.Entity("Data.Models.EFPenaltyIdentifier", b =>
{
b.HasOne("Data.Models.EFPenalty", "Penalty")
.WithMany()
.HasForeignKey("PenaltyId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Penalty");
}); });
modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b => modelBuilder.Entity("Data.Models.Misc.EFInboxMessage", b =>
@ -1560,12 +1417,6 @@ namespace Data.Migrations.MySql
.HasForeignKey("SourceClientId") .HasForeignKey("SourceClientId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("DestinationClient");
b.Navigation("Server");
b.Navigation("SourceClient");
}); });
modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b => modelBuilder.Entity("Data.Models.Server.EFServerSnapshot", b =>
@ -1581,10 +1432,6 @@ namespace Data.Migrations.MySql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Map");
b.Navigation("Server");
}); });
modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b => modelBuilder.Entity("Data.Models.Server.EFServerStatistics", b =>
@ -1594,39 +1441,6 @@ namespace Data.Migrations.MySql
.HasForeignKey("ServerId") .HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade) .OnDelete(DeleteBehavior.Cascade)
.IsRequired(); .IsRequired();
b.Navigation("Server");
});
modelBuilder.Entity("Data.Models.Client.EFClient", b =>
{
b.Navigation("AdministeredPenalties");
b.Navigation("Meta");
b.Navigation("ReceivedPenalties");
});
modelBuilder.Entity("Data.Models.Client.Stats.EFACSnapshot", b =>
{
b.Navigation("PredictedViewAngles");
});
modelBuilder.Entity("Data.Models.Client.Stats.EFClientRatingHistory", b =>
{
b.Navigation("Ratings");
});
modelBuilder.Entity("Data.Models.Client.Stats.EFClientStatistics", b =>
{
b.Navigation("HitLocations");
});
modelBuilder.Entity("Data.Models.EFAliasLink", b =>
{
b.Navigation("Children");
b.Navigation("ReceivedPenalties");
}); });
#pragma warning restore 612, 618 #pragma warning restore 612, 618
} }

View File

@ -1,32 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Data.Migrations.Postgresql
{
public partial class RemoveUniqueAliasIndexConstraint : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFAlias_Name_IPAddress",
table: "EFAlias");
migrationBuilder.CreateIndex(
name: "IX_EFAlias_Name_IPAddress",
table: "EFAlias",
columns: new[] { "Name", "IPAddress" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFAlias_Name_IPAddress",
table: "EFAlias");
migrationBuilder.CreateIndex(
name: "IX_EFAlias_Name_IPAddress",
table: "EFAlias",
columns: new[] { "Name", "IPAddress" },
unique: true);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,56 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class AddEFPenaltyIdentifier : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "EFPenaltyIdentifiers",
columns: table => new
{
PenaltyIdentifierId = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
IPv4Address = table.Column<int>(type: "integer", nullable: true),
NetworkId = table.Column<long>(type: "bigint", nullable: false),
PenaltyId = table.Column<int>(type: "integer", nullable: false),
Active = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_EFPenaltyIdentifiers", x => x.PenaltyIdentifierId);
table.ForeignKey(
name: "FK_EFPenaltyIdentifiers_EFPenalties_PenaltyId",
column: x => x.PenaltyId,
principalTable: "EFPenalties",
principalColumn: "PenaltyId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_IPv4Address",
table: "EFPenaltyIdentifiers",
column: "IPv4Address");
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_NetworkId",
table: "EFPenaltyIdentifiers",
column: "NetworkId");
migrationBuilder.CreateIndex(
name: "IX_EFPenaltyIdentifiers_PenaltyId",
table: "EFPenaltyIdentifiers",
column: "PenaltyId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "EFPenaltyIdentifiers");
}
}
}

View File

@ -1,56 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class MakeEFPenaltyLinkIdNullable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties");
migrationBuilder.AlterColumn<int>(
name: "LinkId",
table: "EFPenalties",
type: "integer",
nullable: true,
oldClrType: typeof(int),
oldType: "integer");
migrationBuilder.AddForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties",
column: "LinkId",
principalTable: "EFAliasLinks",
principalColumn: "AliasLinkId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties");
migrationBuilder.AlterColumn<int>(
name: "LinkId",
table: "EFPenalties",
type: "integer",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "integer",
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "FK_EFPenalties_EFAliasLinks_LinkId",
table: "EFPenalties",
column: "LinkId",
principalTable: "EFAliasLinks",
principalColumn: "AliasLinkId",
onDelete: ReferentialAction.Cascade);
}
}
}

View File

@ -1,48 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class AddAuditFieldsToEFPenaltyIdentifier : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Active",
table: "EFPenaltyIdentifiers");
migrationBuilder.AddColumn<DateTime>(
name: "CreatedDateTime",
table: "EFPenaltyIdentifiers",
type: "timestamp without time zone",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "UpdatedDateTime",
table: "EFPenaltyIdentifiers",
type: "timestamp without time zone",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "CreatedDateTime",
table: "EFPenaltyIdentifiers");
migrationBuilder.DropColumn(
name: "UpdatedDateTime",
table: "EFPenaltyIdentifiers");
migrationBuilder.AddColumn<bool>(
name: "Active",
table: "EFPenaltyIdentifiers",
type: "boolean",
nullable: false,
defaultValue: false);
}
}
}

View File

@ -1,25 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class AddConnectionInterruptedToEFServerSnapshot : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "ConnectionInterrupted",
table: "EFServerSnapshot",
type: "boolean",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ConnectionInterrupted",
table: "EFServerSnapshot");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class AddSearchableIPToEFAlias : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "SearchableIPAddress",
table: "EFAlias",
type: "text",
nullable: true,
computedColumnSql: "CASE WHEN \"IPAddress\" IS NULL THEN 'NULL'::text ELSE (\"IPAddress\" & 255)::text || '.'::text || ((\"IPAddress\" >> 8) & 255)::text || '.'::text || ((\"IPAddress\" >> 16) & 255)::text || '.'::text || ((\"IPAddress\" >> 24) & 255)::text END",
stored: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SearchableIPAddress",
table: "EFAlias");
}
}
}

View File

@ -1,24 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class AddIndexToSearchableIPToEFAlias : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "IX_EFAlias_SearchableIPAddress",
table: "EFAlias",
column: "SearchableIPAddress");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_EFAlias_SearchableIPAddress",
table: "EFAlias");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Data.Migrations.Postgresql
{
public partial class AddGameToEFClient : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "GameName",
table: "EFClients",
type: "integer",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "GameName",
table: "EFClients");
}
}
}

Some files were not shown because too many files have changed in this diff Show More