2015-07-03 00:10:01 -04:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Threading ;
2017-05-26 18:49:27 -04:00
using System.IO ;
using System.Threading.Tasks ;
2015-07-03 00:10:01 -04:00
2018-04-08 02:44:42 -04:00
using SharedLibraryCore ;
using SharedLibraryCore.Interfaces ;
using SharedLibraryCore.Commands ;
using SharedLibraryCore.Helpers ;
using SharedLibraryCore.Exceptions ;
using SharedLibraryCore.Objects ;
using SharedLibraryCore.Services ;
2018-04-08 14:48:40 -04:00
using IW4MAdmin.Application.API ;
2018-03-14 01:36:25 -04:00
using Microsoft.Extensions.Configuration ;
using WebfrontCore ;
2018-04-08 02:44:42 -04:00
using SharedLibraryCore.Configuration ;
2018-03-14 01:36:25 -04:00
using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
2018-04-21 18:18:20 -04:00
using System.Text ;
2017-06-19 13:58:01 -04:00
2018-04-08 14:48:40 -04:00
namespace IW4MAdmin.Application
2015-07-03 00:10:01 -04:00
{
2018-02-21 20:29:23 -05:00
public class ApplicationManager : IManager
2015-07-03 00:10:01 -04:00
{
2017-10-04 19:01:04 -04:00
private List < Server > _servers ;
public List < Server > Servers = > _servers . OrderByDescending ( s = > s . ClientNum ) . ToList ( ) ;
2018-04-02 01:25:06 -04:00
public Dictionary < int , Player > PrivilegedClients { get ; set ; }
2017-06-19 13:58:01 -04:00
public ILogger Logger { get ; private set ; }
public bool Running { get ; private set ; }
2018-04-13 02:32:30 -04:00
public EventHandler < GameEvent > ServerEventOccurred { get ; private set ; }
2018-04-18 16:46:53 -04:00
public DateTime StartTime { get ; private set ; }
2017-06-19 13:58:01 -04:00
static ApplicationManager Instance ;
List < AsyncStatus > TaskStatuses ;
2017-05-26 18:49:27 -04:00
List < Command > Commands ;
2017-06-19 13:58:01 -04:00
List < MessageToken > MessageTokens ;
2017-11-25 20:29:58 -05:00
ClientService ClientSvc ;
AliasService AliasSvc ;
PenaltyService PenaltySvc ;
2018-03-18 22:25:11 -04:00
BaseConfigurationHandler < ApplicationConfiguration > ConfigHandler ;
2018-04-08 14:48:40 -04:00
EventApi Api ;
2018-04-26 02:13:04 -04:00
GameEventHandler Handler ;
ManualResetEventSlim OnEvent ;
Timer StatusUpdateTimer ;
2017-05-26 18:49:27 -04:00
#if FTP_LOG
2018-02-08 02:23:45 -05:00
const int UPDATE_FREQUENCY = 700 ;
2017-05-26 18:49:27 -04:00
#else
2018-04-26 02:13:04 -04:00
const int UPDATE_FREQUENCY = 2000 ;
2017-05-26 18:49:27 -04:00
#endif
2017-06-19 13:58:01 -04:00
private ApplicationManager ( )
2015-07-03 00:10:01 -04:00
{
2018-03-25 00:32:54 -04:00
Logger = new Logger ( $@"{Utilities.OperatingDirectory}IW4MAdmin.log" ) ;
2017-10-04 19:01:04 -04:00
_servers = new List < Server > ( ) ;
2017-05-26 18:49:27 -04:00
Commands = new List < Command > ( ) ;
2017-06-19 13:58:01 -04:00
TaskStatuses = new List < AsyncStatus > ( ) ;
MessageTokens = new List < MessageToken > ( ) ;
2017-11-25 20:29:58 -05:00
ClientSvc = new ClientService ( ) ;
AliasSvc = new AliasService ( ) ;
PenaltySvc = new PenaltyService ( ) ;
2018-04-02 01:25:06 -04:00
PrivilegedClients = new Dictionary < int , Player > ( ) ;
2018-04-08 14:48:40 -04:00
Api = new EventApi ( ) ;
ServerEventOccurred + = Api . OnServerEvent ;
2018-03-18 22:25:11 -04:00
ConfigHandler = new BaseConfigurationHandler < ApplicationConfiguration > ( "IW4MAdminSettings" ) ;
2018-04-05 00:38:45 -04:00
Console . CancelKeyPress + = new ConsoleCancelEventHandler ( OnCancelKey ) ;
2018-04-18 16:46:53 -04:00
StartTime = DateTime . UtcNow ;
2018-04-26 02:13:04 -04:00
OnEvent = new ManualResetEventSlim ( ) ;
2018-03-14 01:36:25 -04:00
}
2018-04-05 00:38:45 -04:00
private void OnCancelKey ( object sender , ConsoleCancelEventArgs args )
{
Stop ( ) ;
}
2017-09-29 22:42:24 -04:00
public IList < Server > GetServers ( )
2015-07-03 00:10:01 -04:00
{
2017-05-26 18:49:27 -04:00
return Servers ;
2015-07-03 00:10:01 -04:00
}
2017-09-29 22:42:24 -04:00
public IList < Command > GetCommands ( )
2015-08-17 16:38:42 -04:00
{
2017-05-26 18:49:27 -04:00
return Commands ;
2015-08-17 16:38:42 -04:00
}
2017-06-19 13:58:01 -04:00
public static ApplicationManager GetInstance ( )
2015-08-22 02:04:30 -04:00
{
2017-06-19 13:58:01 -04:00
return Instance ? ? ( Instance = new ApplicationManager ( ) ) ;
2015-08-22 02:04:30 -04:00
}
2018-04-26 02:13:04 -04:00
public void UpdateStatus ( object state )
{
foreach ( var server in Servers )
{
Task . Run ( ( ) = > server . ProcessUpdatesAsync ( new CancellationToken ( ) ) ) ;
}
}
2018-03-06 02:22:19 -05:00
public async Task Init ( )
2015-07-06 13:13:42 -04:00
{
2018-04-26 02:13:04 -04:00
// setup the event handler after the class is initialized
Handler = new GameEventHandler ( this ) ;
2018-03-06 02:22:19 -05:00
#region DATABASE
2018-03-27 00:54:20 -04:00
var ipList = ( await ClientSvc . Find ( c = > c . Level > Player . Permission . Trusted ) )
2018-04-04 15:38:34 -04:00
. Select ( c = > new
{
c . Password ,
c . PasswordSalt ,
c . ClientId ,
c . Level ,
c . Name
} ) ;
2018-03-27 00:54:20 -04:00
foreach ( var a in ipList )
{
try
{
2018-04-04 15:38:34 -04:00
PrivilegedClients . Add ( a . ClientId , new Player ( )
2018-04-02 01:25:06 -04:00
{
2018-04-04 15:38:34 -04:00
Name = a . Name ,
2018-04-02 01:25:06 -04:00
ClientId = a . ClientId ,
2018-04-04 15:38:34 -04:00
Level = a . Level ,
PasswordSalt = a . PasswordSalt ,
Password = a . Password
2018-04-02 01:25:06 -04:00
} ) ;
2018-03-27 00:54:20 -04:00
}
catch ( ArgumentException )
{
continue ;
}
}
2017-09-29 22:42:24 -04:00
#endregion
2018-03-18 22:25:11 -04:00
#region CONFIG
var config = ConfigHandler . Configuration ( ) ;
2018-04-19 01:48:14 -04:00
// copy over default config if it doesn't exist
if ( config = = null )
2018-03-18 22:25:11 -04:00
{
2018-04-19 01:48:14 -04:00
var defaultConfig = new BaseConfigurationHandler < DefaultConfiguration > ( "DefaultSettings" ) . Configuration ( ) ;
ConfigHandler . Set ( ( ApplicationConfiguration ) new ApplicationConfiguration ( ) . Generate ( ) ) ;
var newConfig = ConfigHandler . Configuration ( ) ;
newConfig . AutoMessagePeriod = defaultConfig . AutoMessagePeriod ;
newConfig . AutoMessages = defaultConfig . AutoMessages ;
newConfig . GlobalRules = defaultConfig . GlobalRules ;
newConfig . Maps = defaultConfig . Maps ;
if ( newConfig . Servers = = null )
{
ConfigHandler . Set ( newConfig ) ;
2018-04-23 17:03:50 -04:00
newConfig . Servers = new List < ServerConfiguration > ( ) ;
do
{
newConfig . Servers . Add ( ( ServerConfiguration ) new ServerConfiguration ( ) . Generate ( ) ) ;
} while ( Utilities . PromptBool ( Utilities . CurrentLocalization . LocalizationSet [ "SETUP_SERVER_SAVE" ] ) ) ;
2018-04-19 01:48:14 -04:00
config = newConfig ;
await ConfigHandler . Save ( ) ;
}
2018-03-18 22:25:11 -04:00
}
2018-04-19 01:48:14 -04:00
else if ( config ! = null )
2018-04-18 16:46:53 -04:00
{
if ( string . IsNullOrEmpty ( config . Id ) )
{
config . Id = Guid . NewGuid ( ) . ToString ( ) ;
await ConfigHandler . Save ( ) ;
}
2018-04-19 01:48:14 -04:00
if ( string . IsNullOrEmpty ( config . WebfrontBindUrl ) )
{
config . WebfrontBindUrl = "http://127.0.0.1:1624" ;
await ConfigHandler . Save ( ) ;
}
2018-04-18 16:46:53 -04:00
}
2018-03-18 22:25:11 -04:00
else if ( config . Servers . Count = = 0 )
throw new ServerException ( "A server configuration in IW4MAdminSettings.json is invalid" ) ;
2018-04-21 18:18:20 -04:00
Encoding . RegisterProvider ( CodePagesEncodingProvider . Instance ) ;
2018-04-22 16:04:18 -04:00
Utilities . EncodingType = Encoding . GetEncoding ( ! string . IsNullOrEmpty ( config . CustomParserEncoding ) ? config . CustomParserEncoding : "windows-1252" ) ;
2018-04-21 18:18:20 -04:00
2018-04-16 16:31:14 -04:00
#endregion
2017-08-09 00:35:23 -04:00
#region PLUGINS
2018-04-08 02:44:42 -04:00
SharedLibraryCore . Plugins . PluginImporter . Load ( this ) ;
2017-08-09 00:35:23 -04:00
2018-04-08 02:44:42 -04:00
foreach ( var Plugin in SharedLibraryCore . Plugins . PluginImporter . ActivePlugins )
2017-08-09 00:35:23 -04:00
{
try
{
2018-03-06 02:22:19 -05:00
await Plugin . OnLoadAsync ( this ) ;
2017-08-09 00:35:23 -04:00
}
catch ( Exception e )
{
2018-04-24 18:01:27 -04:00
Logger . WriteError ( $"{Utilities.CurrentLocalization.LocalizationSet[" SERVER_ERROR_PLUGIN "]} {Plugin.Name}" ) ;
2017-08-09 00:35:23 -04:00
Logger . WriteDebug ( $"Exception: {e.Message}" ) ;
Logger . WriteDebug ( $"Stack Trace: {e.StackTrace}" ) ;
}
}
#endregion
2017-06-19 13:58:01 -04:00
#region COMMANDS
2017-11-25 20:29:58 -05:00
if ( ClientSvc . GetOwners ( ) . Result . Count = = 0 )
2017-11-15 16:04:13 -05:00
Commands . Add ( new COwner ( ) ) ;
Commands . Add ( new CQuit ( ) ) ;
Commands . Add ( new CKick ( ) ) ;
Commands . Add ( new CSay ( ) ) ;
Commands . Add ( new CTempBan ( ) ) ;
Commands . Add ( new CBan ( ) ) ;
Commands . Add ( new CWhoAmI ( ) ) ;
Commands . Add ( new CList ( ) ) ;
Commands . Add ( new CHelp ( ) ) ;
Commands . Add ( new CFastRestart ( ) ) ;
Commands . Add ( new CMapRotate ( ) ) ;
Commands . Add ( new CSetLevel ( ) ) ;
Commands . Add ( new CUsage ( ) ) ;
Commands . Add ( new CUptime ( ) ) ;
Commands . Add ( new CWarn ( ) ) ;
Commands . Add ( new CWarnClear ( ) ) ;
Commands . Add ( new CUnban ( ) ) ;
Commands . Add ( new CListAdmins ( ) ) ;
Commands . Add ( new CLoadMap ( ) ) ;
Commands . Add ( new CFindPlayer ( ) ) ;
Commands . Add ( new CListRules ( ) ) ;
Commands . Add ( new CPrivateMessage ( ) ) ;
Commands . Add ( new CFlag ( ) ) ;
Commands . Add ( new CReport ( ) ) ;
Commands . Add ( new CListReports ( ) ) ;
Commands . Add ( new CListBanInfo ( ) ) ;
Commands . Add ( new CListAlias ( ) ) ;
Commands . Add ( new CExecuteRCON ( ) ) ;
Commands . Add ( new CPlugins ( ) ) ;
Commands . Add ( new CIP ( ) ) ;
2017-11-18 01:59:37 -05:00
Commands . Add ( new CMask ( ) ) ;
2018-02-10 01:26:38 -05:00
Commands . Add ( new CPruneAdmins ( ) ) ;
2018-04-24 18:01:27 -04:00
//Commands.Add(new CKillServer());
2018-04-04 15:38:34 -04:00
Commands . Add ( new CSetPassword ( ) ) ;
2018-04-14 00:51:38 -04:00
Commands . Add ( new CPing ( ) ) ;
2017-06-19 13:58:01 -04:00
2018-04-08 02:44:42 -04:00
foreach ( Command C in SharedLibraryCore . Plugins . PluginImporter . ActiveCommands )
2017-06-19 13:58:01 -04:00
Commands . Add ( C ) ;
#endregion
2018-04-16 16:31:14 -04:00
#region INIT
async Task Init ( ServerConfiguration Conf )
{
try
{
var ServerInstance = new IW4MServer ( this , Conf ) ;
await ServerInstance . Initialize ( ) ;
lock ( _servers )
{
_servers . Add ( ServerInstance ) ;
}
2018-04-24 18:01:27 -04:00
Logger . WriteVerbose ( $"{Utilities.CurrentLocalization.LocalizationSet[" MANAGER_MONITORING_TEXT "]} {ServerInstance.Hostname}" ) ;
2018-04-26 02:13:04 -04:00
// add the start event for this server
Handler . AddEvent ( new GameEvent ( GameEvent . EventType . Start , "Server started" , null , null , ServerInstance ) ) ;
2018-04-16 16:31:14 -04:00
}
catch ( ServerException e )
{
2018-04-24 18:01:27 -04:00
Logger . WriteError ( $"{Utilities.CurrentLocalization.LocalizationSet[" SERVER_ERROR_UNFIXABLE "]} [{Conf.IPAddress}:{Conf.Port}]" ) ;
2018-04-16 16:31:14 -04:00
if ( e . GetType ( ) = = typeof ( DvarException ) )
2018-04-24 18:01:27 -04:00
Logger . WriteDebug ( $"{Utilities.CurrentLocalization.LocalizationSet[" SERVER_ERROR_DVAR "]} {(e as DvarException).Data[" dvar_name "]} ({Utilities.CurrentLocalization.LocalizationSet[" SERVER_ERROR_DVAR_HELP "]})" ) ;
2018-04-16 16:31:14 -04:00
else if ( e . GetType ( ) = = typeof ( NetworkException ) )
{
Logger . WriteDebug ( e . Message ) ;
}
// throw the exception to the main method to stop before instantly exiting
throw e ;
}
}
await Task . WhenAll ( config . Servers . Select ( c = > Init ( c ) ) . ToArray ( ) ) ;
2018-04-26 02:13:04 -04:00
// start polling servers
StatusUpdateTimer = new Timer ( UpdateStatus , null , 0 , 10000 ) ;
2018-04-16 16:31:14 -04:00
#endregion
2017-05-26 18:49:27 -04:00
Running = true ;
2015-07-03 00:10:01 -04:00
}
2017-08-09 00:35:23 -04:00
2018-04-18 16:46:53 -04:00
private void HeartBeatThread ( )
{
bool successfulConnection = false ;
restartConnection :
while ( ! successfulConnection )
{
try
{
API . Master . Heartbeat . Send ( this , true ) . Wait ( ) ;
successfulConnection = true ;
}
catch ( Exception e )
{
successfulConnection = false ;
Logger . WriteWarning ( $"Could not connect to heartbeat server - {e.Message}" ) ;
}
Thread . Sleep ( 30000 ) ;
}
while ( Running )
{
Logger . WriteDebug ( "Sending heartbeat..." ) ;
try
{
API . Master . Heartbeat . Send ( this ) . Wait ( ) ;
}
catch ( System . Net . Http . HttpRequestException e )
{
Logger . WriteWarning ( $"Could not send heartbeat - {e.Message}" ) ;
}
catch ( AggregateException e )
{
Logger . WriteWarning ( $"Could not send heartbeat - {e.Message}" ) ;
var exceptions = e . InnerExceptions . Where ( ex = > ex . GetType ( ) = = typeof ( RestEase . ApiException ) ) ;
foreach ( var ex in exceptions )
{
if ( ( ( RestEase . ApiException ) ex ) . StatusCode = = System . Net . HttpStatusCode . Unauthorized )
{
successfulConnection = false ;
goto restartConnection ;
}
}
}
catch ( RestEase . ApiException e )
{
Logger . WriteWarning ( $"Could not send heartbeat - {e.Message}" ) ;
if ( e . StatusCode = = System . Net . HttpStatusCode . Unauthorized )
{
successfulConnection = false ;
goto restartConnection ;
}
}
Thread . Sleep ( 30000 ) ;
}
}
2017-05-26 18:49:27 -04:00
public void Start ( )
2015-07-03 00:10:01 -04:00
{
2018-04-18 16:46:53 -04:00
Task . Run ( ( ) = > HeartBeatThread ( ) ) ;
2018-04-26 02:13:04 -04:00
GameEvent newEvent ;
while ( Running )
2015-07-03 00:10:01 -04:00
{
2018-04-26 02:13:04 -04:00
// wait for new event to be added
OnEvent . Wait ( ) ;
// todo: sequencially or parallelize?
while ( ( newEvent = Handler . GetNextEvent ( ) ) ! = null )
2015-07-03 00:10:01 -04:00
{
2018-04-26 02:13:04 -04:00
try
2018-02-10 01:26:38 -05:00
{
2018-04-26 16:26:03 -04:00
newEvent . Owner . ExecuteEvent ( newEvent ) . Wait ( ) ;
#if DEBUG
Logger . WriteDebug ( "Processed Event" ) ;
#endif
2017-05-28 16:47:21 -04:00
}
2018-02-10 23:33:42 -05:00
2018-04-26 02:13:04 -04:00
catch ( Exception E )
2018-02-10 23:33:42 -05:00
{
2018-04-26 02:13:04 -04:00
Logger . WriteError ( $"{Utilities.CurrentLocalization.LocalizationSet[" SERVER_ERROR_EXCEPTION "]} {newEvent.Owner}" ) ;
Logger . WriteDebug ( "Error Message: " + E . Message ) ;
Logger . WriteDebug ( "Error Trace: " + E . StackTrace ) ;
2018-04-26 16:26:03 -04:00
newEvent . OnProcessed . Set ( ) ;
continue ;
2018-02-10 23:33:42 -05:00
}
2018-04-26 02:13:04 -04:00
// tell anyone waiting for the output that we're done
newEvent . OnProcessed . Set ( ) ;
2015-07-03 00:10:01 -04:00
}
2017-05-28 16:47:21 -04:00
2018-04-26 02:13:04 -04:00
// signal that all events have been processed
OnEvent . Reset ( ) ;
2015-07-03 00:10:01 -04:00
}
2017-05-26 18:49:27 -04:00
#if ! DEBUG
foreach ( var S in Servers )
2018-04-22 16:04:18 -04:00
S . Broadcast ( Utilities . CurrentLocalization . LocalizationSet [ "BROADCAST_OFFLINE" ] ) . Wait ( ) ;
2017-05-26 18:49:27 -04:00
#endif
2017-10-04 19:01:04 -04:00
_servers . Clear ( ) ;
2015-07-03 00:10:01 -04:00
}
2015-07-06 15:51:08 -04:00
2017-08-09 00:35:23 -04:00
2017-05-26 18:49:27 -04:00
public void Stop ( )
2015-07-06 15:51:08 -04:00
{
2017-05-26 18:49:27 -04:00
Running = false ;
2018-04-26 02:13:04 -04:00
// trigger the event processing loop to end
SetHasEvent ( ) ;
2015-07-06 15:51:08 -04:00
}
2017-05-27 18:08:04 -04:00
2017-06-19 13:58:01 -04:00
public ILogger GetLogger ( )
2017-05-27 19:29:20 -04:00
{
return Logger ;
}
2017-05-31 01:31:56 -04:00
2017-06-19 13:58:01 -04:00
public IList < MessageToken > GetMessageTokens ( )
2017-05-31 01:31:56 -04:00
{
return MessageTokens ;
}
2017-06-12 13:50:00 -04:00
public IList < Player > GetActiveClients ( )
{
var ActiveClients = new List < Player > ( ) ;
2017-10-04 19:01:04 -04:00
foreach ( var server in _servers )
2017-06-12 13:50:00 -04:00
ActiveClients . AddRange ( server . Players . Where ( p = > p ! = null ) ) ;
return ActiveClients ;
}
2017-08-17 19:28:08 -04:00
2017-11-25 20:29:58 -05:00
public ClientService GetClientService ( ) = > ClientSvc ;
public AliasService GetAliasService ( ) = > AliasSvc ;
public PenaltyService GetPenaltyService ( ) = > PenaltySvc ;
2018-03-18 22:25:11 -04:00
public IConfigurationHandler < ApplicationConfiguration > GetApplicationSettings ( ) = > ConfigHandler ;
2018-04-05 00:38:45 -04:00
public IDictionary < int , Player > GetPrivilegedClients ( ) = > PrivilegedClients ;
2018-04-08 14:48:40 -04:00
public IEventApi GetEventApi ( ) = > Api ;
2018-04-08 17:50:58 -04:00
public bool ShutdownRequested ( ) = > ! Running ;
2018-04-26 02:13:04 -04:00
public IEventHandler GetEventHandler ( ) = > Handler ;
public void SetHasEvent ( )
{
OnEvent . Set ( ) ;
}
2015-07-03 00:10:01 -04:00
}
}