2015-08-20 01:06:44 -04:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
2017-05-26 18:49:27 -04:00
using System.Runtime.InteropServices ;
2015-08-20 01:06:44 -04:00
using System.Text ;
2017-05-26 18:49:27 -04:00
using System.Threading ;
using SharedLibrary.Network ;
using SharedLibrary.Commands ;
using System.Threading.Tasks ;
2015-08-20 01:06:44 -04:00
namespace SharedLibrary
{
2017-05-26 18:49:27 -04:00
[Guid("61d3829e-fcbe-44d3-bb7c-51db8c2d7ac5")]
2015-08-20 01:06:44 -04:00
public abstract class Server
{
2017-05-26 18:49:27 -04:00
public Server ( Interfaces . IManager mgr , string address , int port , string password )
2015-08-20 01:06:44 -04:00
{
2017-05-26 18:49:27 -04:00
Password = password ;
2015-08-20 01:06:44 -04:00
IP = address ;
Port = port ;
2017-05-26 18:49:27 -04:00
Manager = mgr ;
2017-05-27 19:29:20 -04:00
Logger = Manager . GetLogger ( ) ;
2017-05-28 21:54:46 -04:00
ClientNum = 0 ;
2015-08-20 01:06:44 -04:00
2017-05-26 18:49:27 -04:00
Players = new List < Player > ( new Player [ 18 ] ) ;
2015-08-20 01:06:44 -04:00
events = new Queue < Event > ( ) ;
Macros = new Dictionary < String , Object > ( ) ;
Reports = new List < Report > ( ) ;
statusPlayers = new Dictionary < string , Player > ( ) ;
2015-08-22 02:04:30 -04:00
playerHistory = new Queue < PlayerHistory > ( ) ;
2015-08-20 01:06:44 -04:00
chatHistory = new List < Chat > ( ) ;
lastWebChat = DateTime . Now ;
nextMessage = 0 ;
initMacros ( ) ;
initMessages ( ) ;
initMaps ( ) ;
initRules ( ) ;
2017-05-26 18:49:27 -04:00
var commands = mgr . GetCommands ( ) ;
2017-05-29 22:25:49 -04:00
owner = Manager . GetClientDatabase ( ) . GetOwner ( ) ;
2017-05-26 18:49:27 -04:00
if ( owner = = null )
commands . Add ( new Owner ( "owner" , "claim ownership of the server" , "owner" , Player . Permission . User , 0 , false ) ) ;
commands . Add ( new Quit ( "quit" , "quit IW4MAdmin" , "q" , Player . Permission . Owner , 0 , false ) ) ;
commands . Add ( new Kick ( "kick" , "kick a player by name. syntax: !kick <player> <reason>." , "k" , Player . Permission . Trusted , 2 , true ) ) ;
commands . Add ( new Say ( "say" , "broadcast message to all players. syntax: !say <message>." , "s" , Player . Permission . Moderator , 1 , false ) ) ;
commands . Add ( new TempBan ( "tempban" , "temporarily ban a player for 1 hour. syntax: !tempban <player> <reason>." , "tb" , Player . Permission . Moderator , 2 , true ) ) ;
2017-05-27 00:22:50 -04:00
commands . Add ( new CBan ( "ban" , "permanently ban a player from the server. syntax: !ban <player> <reason>" , "b" , Player . Permission . SeniorAdmin , 2 , true ) ) ;
commands . Add ( new CWhoAmI ( "whoami" , "give information about yourself. syntax: !whoami." , "who" , Player . Permission . User , 0 , false ) ) ;
commands . Add ( new CList ( "list" , "list active clients syntax: !list." , "l" , Player . Permission . Moderator , 0 , false ) ) ;
commands . Add ( new CHelp ( "help" , "list all available commands. syntax: !help." , "h" , Player . Permission . User , 0 , false ) ) ;
commands . Add ( new CFastRestart ( "fastrestart" , "fast restart current map. syntax: !fastrestart." , "fr" , Player . Permission . Moderator , 0 , false ) ) ;
commands . Add ( new CMapRotate ( "maprotate" , "cycle to the next map in rotation. syntax: !maprotate." , "mr" , Player . Permission . Administrator , 0 , false ) ) ;
commands . Add ( new CSetLevel ( "setlevel" , "set player to specified administration level. syntax: !setlevel <player> <level>." , "sl" , Player . Permission . Owner , 2 , true ) ) ;
commands . Add ( new CUsage ( "usage" , "get current application memory usage. syntax: !usage." , "us" , Player . Permission . Moderator , 0 , false ) ) ;
commands . Add ( new CUptime ( "uptime" , "get current application running time. syntax: !uptime." , "up" , Player . Permission . Moderator , 0 , false ) ) ;
2017-05-26 18:49:27 -04:00
commands . Add ( new Warn ( "warn" , "warn player for infringing rules syntax: !warn <player> <reason>." , "w" , Player . Permission . Trusted , 2 , true ) ) ;
commands . Add ( new WarnClear ( "warnclear" , "remove all warning for a player syntax: !warnclear <player>." , "wc" , Player . Permission . Trusted , 1 , true ) ) ;
2017-05-27 00:22:50 -04:00
commands . Add ( new CUnban ( "unban" , "unban player by database id. syntax: !unban @<id>." , "ub" , Player . Permission . SeniorAdmin , 1 , true ) ) ;
commands . Add ( new CListAdmins ( "admins" , "list currently connected admins. syntax: !admins." , "a" , Player . Permission . User , 0 , false ) ) ;
commands . Add ( new CLoadMap ( "map" , "change to specified map. syntax: !map" , "m" , Player . Permission . Administrator , 1 , false ) ) ;
commands . Add ( new CFindPlayer ( "find" , "find player in database. syntax: !find <player>" , "f" , Player . Permission . SeniorAdmin , 1 , false ) ) ;
commands . Add ( new CListRules ( "rules" , "list server rules. syntax: !rules" , "r" , Player . Permission . User , 0 , false ) ) ;
commands . Add ( new CPrivateMessage ( "privatemessage" , "send message to other player. syntax: !pm <player> <message>" , "pm" , Player . Permission . User , 2 , true ) ) ;
2017-05-26 18:49:27 -04:00
commands . Add ( new Flag ( "flag" , "flag a suspicious player and announce to admins on join . syntax !flag <player>:" , "flag" , Player . Permission . Moderator , 1 , true ) ) ;
2017-05-27 00:22:50 -04:00
commands . Add ( new CReport ( "report" , "report a player for suspicious behaivor. syntax !report <player> <reason>" , "rep" , Player . Permission . User , 2 , true ) ) ;
commands . Add ( new CListReports ( "reports" , "get most recent reports. syntax !reports" , "reports" , Player . Permission . Moderator , 0 , false ) ) ;
commands . Add ( new CMask ( "mask" , "hide your online presence from online admin list. syntax: !mask" , "mask" , Player . Permission . Administrator , 0 , false ) ) ;
commands . Add ( new CListBanInfo ( "baninfo" , "get information about a ban for a player. syntax: !baninfo <player>" , "bi" , Player . Permission . Moderator , 1 , true ) ) ;
commands . Add ( new CListAlias ( "alias" , "get past aliases and ips of a player. syntax: !alias <player>" , "known" , Player . Permission . Moderator , 1 , true ) ) ;
commands . Add ( new CExecuteRCON ( "rcon" , "send rcon command to server. syntax: !rcon <command>" , "rcon" , Player . Permission . Owner , 1 , false ) ) ;
commands . Add ( new CFindAllPlayers ( "findall" , "find a player by their aliase(s). syntax: !findall <player>" , "fa" , Player . Permission . Moderator , 1 , false ) ) ;
2015-08-20 01:06:44 -04:00
}
//Returns the current server name -- *STRING*
public String getName ( )
{
2017-05-26 18:49:27 -04:00
return Hostname ;
2015-08-20 01:06:44 -04:00
}
public String getGametype ( )
{
return Gametype ;
}
//Returns current server IP set by `net_ip` -- *STRING*
public String getIP ( )
{
return IP ;
}
//Returns current server port set by `net_port` -- *INT*
public int getPort ( )
{
return Port ;
}
//Returns number of active clients on server -- *INT*
public int getNumPlayers ( )
{
2017-05-26 18:49:27 -04:00
return ClientNum ;
2015-08-20 01:06:44 -04:00
}
//Returns list of all current players
public List < Player > getPlayers ( )
{
2017-05-26 18:49:27 -04:00
return Players . FindAll ( x = > x ! = null ) ;
2015-08-20 01:06:44 -04:00
}
public int pID ( )
{
return this . PID ;
}
2015-08-20 15:23:13 -04:00
/// <summary>
/// Get any know aliases ( name or ip based ) from the database
/// </summary>
/// <param name="Origin">Player to scan for aliases</param>
2017-05-28 21:07:33 -04:00
abstract public List < Aliases > GetAliases ( Player Origin ) ;
2015-08-21 21:11:35 -04:00
public List < Player > getPlayerAliases ( Player Origin )
{
List < int > databaseIDs = new List < int > ( ) ;
2017-05-28 21:07:33 -04:00
foreach ( Aliases A in GetAliases ( Origin ) )
2015-08-21 21:11:35 -04:00
databaseIDs . Add ( A . Number ) ;
2017-05-29 22:25:49 -04:00
return Manager . GetClientDatabase ( ) . GetPlayers ( databaseIDs ) ;
2015-08-21 21:11:35 -04:00
}
2016-01-21 12:41:00 -05:00
2015-08-20 15:23:13 -04:00
/// <summary>
/// Add a player to the server's player list
/// </summary>
/// <param name="P">Player pulled from memory reading</param>
/// <returns>True if player added sucessfully, false otherwise</returns>
2017-05-26 18:49:27 -04:00
abstract public Task < bool > AddPlayer ( Player P ) ;
2015-08-20 01:06:44 -04:00
2015-08-20 15:23:13 -04:00
/// <summary>
/// Remove player by client number
/// </summary>
/// <param name="cNum">Client ID of player to be removed</param>
/// <returns>true if removal succeded, false otherwise</returns>
2017-05-26 18:49:27 -04:00
abstract public Task RemovePlayer ( int cNum ) ;
2015-08-20 01:06:44 -04:00
2015-08-20 15:23:13 -04:00
/// <summary>
/// Get the player from the server's list by line from game long
/// </summary>
/// <param name="L">Game log line containing event</param>
/// <param name="cIDPos">Position in the line where the cliet ID is written</param>
/// <returns>Matching player if found</returns>
2015-08-20 01:06:44 -04:00
abstract public Player clientFromEventLine ( String [ ] L , int cIDPos ) ;
2015-08-20 15:23:13 -04:00
/// <summary>
/// Get a player by name
/// </summary>
/// <param name="pName">Player name to search for</param>
/// <returns>Matching player if found</returns>
2015-08-20 01:06:44 -04:00
public Player clientFromName ( String pName )
{
2017-05-26 18:49:27 -04:00
lock ( Players )
2015-08-20 01:06:44 -04:00
{
2017-05-26 18:49:27 -04:00
foreach ( var P in Players )
2015-08-20 01:06:44 -04:00
{
if ( P ! = null & & P . Name . ToLower ( ) . Contains ( pName . ToLower ( ) ) )
return P ;
}
}
return null ;
}
2015-08-20 15:23:13 -04:00
/// <summary>
/// Check ban list for every banned player and return ban if match is found
/// </summary>
/// <param name="C">Player to check if banned</param>
/// <returns>Matching ban if found</returns>
2016-01-15 17:15:39 -05:00
abstract public Penalty isBanned ( Player C ) ;
2015-08-20 01:06:44 -04:00
2015-08-20 15:23:13 -04:00
/// <summary>
/// Process requested command correlating to an event
/// </summary>
/// <param name="E">Event parameter</param>
/// <param name="C">Command requested from the event</param>
/// <returns></returns>
2017-05-30 17:23:31 -04:00
abstract public Task < Command > ValidateCommand ( Event E ) ;
2015-08-20 01:06:44 -04:00
2017-05-28 16:47:21 -04:00
virtual public Task ProcessUpdatesAsync ( CancellationToken cts )
2017-05-26 18:49:27 -04:00
{
return null ;
}
2015-08-20 01:06:44 -04:00
2015-08-20 15:23:13 -04:00
/// <summary>
/// Set up the basic variables ( base path / hostname / etc ) that allow the monitor thread to work
/// </summary>
/// <returns>True if no issues initializing, false otherwise</returns>
2017-05-26 18:49:27 -04:00
//abstract public bool intializeBasics();
2015-08-20 01:06:44 -04:00
2015-08-20 15:23:13 -04:00
/// <summary>
/// Process any server event
/// </summary>
/// <param name="E">Event</param>
/// <returns>True on sucess</returns>
2017-05-26 18:49:27 -04:00
abstract protected Task ProcessEvent ( Event E ) ;
abstract public Task ExecuteEvent ( Event E ) ;
2015-08-20 01:06:44 -04:00
2015-08-20 15:23:13 -04:00
/// <summary>
/// Reloads all the server configurations
/// </summary>
/// <returns>True on sucess</returns>
2015-08-20 17:54:38 -04:00
abstract public bool Reload ( ) ;
2017-05-26 18:49:27 -04:00
abstract public bool _Reload ( ) ;
2015-08-20 01:06:44 -04:00
2015-08-20 15:23:13 -04:00
/// <summary>
/// Send a message to all players
/// </summary>
/// <param name="Message">Message to be sent to all players</param>
2017-05-26 18:49:27 -04:00
public async Task Broadcast ( String Message )
2015-08-20 01:06:44 -04:00
{
2017-05-30 17:23:31 -04:00
#if DEBUG
return ;
#endif
2017-05-26 18:49:27 -04:00
await this . ExecuteCommandAsync ( $"sayraw {Message}" ) ;
2015-08-20 01:06:44 -04:00
}
2017-05-26 18:49:27 -04:00
2015-08-20 15:23:13 -04:00
/// <summary>
/// Send a message to a particular players
/// </summary>
/// <param name="Message">Message to send</param>
/// <param name="Target">Player to send message to</param>
2017-05-26 18:49:27 -04:00
public async Task Tell ( String Message , Player Target )
2015-08-20 01:06:44 -04:00
{
2017-05-30 17:23:31 -04:00
#if DEBUG
return ;
#endif
2017-05-26 18:49:27 -04:00
if ( Target . clientID > - 1 & & Message . Length > 0 & & Target . Level ! = Player . Permission . Console & & ! Target . lastEvent . Remote )
await this . ExecuteCommandAsync ( $"tellraw {Target.clientID} {Message}^7" ) ;
2015-08-22 02:04:30 -04:00
if ( Target . Level = = Player . Permission . Console )
{
Console . ForegroundColor = ConsoleColor . Cyan ;
2017-05-26 18:49:27 -04:00
Console . WriteLine ( Utilities . StripColors ( Message ) ) ;
2015-08-22 02:04:30 -04:00
Console . ForegroundColor = ConsoleColor . Gray ;
}
2017-05-26 18:49:27 -04:00
if ( Target . lastEvent . Remote )
commandResult . Enqueue ( Utilities . StripColors ( Message ) ) ;
2015-08-20 01:06:44 -04:00
}
2015-08-20 15:23:13 -04:00
/// <summary>
/// Send a message to all admins on the server
/// </summary>
/// <param name="message">Message to send out</param>
2017-05-26 18:49:27 -04:00
public async Task ToAdmins ( String message )
2015-08-20 01:06:44 -04:00
{
2017-05-26 18:49:27 -04:00
foreach ( Player P in Players )
2015-08-20 15:23:13 -04:00
{
2017-05-26 18:49:27 -04:00
if ( P = = null )
continue ;
2015-08-20 15:23:13 -04:00
2017-05-26 18:49:27 -04:00
if ( P . Level > Player . Permission . Flagged )
await P . Tell ( message ) ;
2015-08-20 15:23:13 -04:00
}
2015-08-20 01:06:44 -04:00
}
2015-08-20 15:23:13 -04:00
/// <summary>
/// Kick a player from the server
/// </summary>
/// <param name="Reason">Reason for kicking</param>
/// <param name="Target">Player to kick</param>
2017-05-26 18:49:27 -04:00
abstract public Task Kick ( String Reason , Player Target , Player Origin ) ;
2015-08-20 01:06:44 -04:00
2015-08-20 15:23:13 -04:00
/// <summary>
/// Temporarily ban a player ( default 1 hour ) from the server
/// </summary>
/// <param name="Reason">Reason for banning the player</param>
/// <param name="Target">The player to ban</param>
2017-05-26 18:49:27 -04:00
abstract public Task TempBan ( String Reason , Player Target , Player Origin ) ;
2015-08-20 01:06:44 -04:00
2015-08-20 15:23:13 -04:00
/// <summary>
/// Perm ban a player from the server
/// </summary>
/// <param name="Reason">The reason for the ban</param>
/// <param name="Target">The person to ban</param>
/// <param name="Origin">The person who banned the target</param>
2017-05-26 18:49:27 -04:00
abstract public Task Ban ( String Reason , Player Target , Player Origin ) ;
2015-08-20 15:23:13 -04:00
2017-05-26 18:49:27 -04:00
abstract public Task Warn ( String Reason , Player Target , Player Origin ) ;
2016-01-16 17:58:24 -05:00
2015-08-20 15:23:13 -04:00
/// <summary>
/// Unban a player by npID / GUID
/// </summary>
/// <param name="npID">npID of the player</param>
/// <param name="Target">I don't remember what this is for</param>
/// <returns></returns>
2017-05-27 18:08:04 -04:00
abstract public Task Unban ( Player Target ) ;
2015-08-20 01:06:44 -04:00
2015-08-20 15:23:13 -04:00
/// <summary>
2017-05-26 18:49:27 -04:00
/// Change the current searver map
2015-08-20 15:23:13 -04:00
/// </summary>
2017-05-26 18:49:27 -04:00
/// <param name="mapName">Non-localized map name</param>
public async Task LoadMap ( string mapName )
2015-08-20 01:06:44 -04:00
{
2017-05-26 18:49:27 -04:00
await this . ExecuteCommandAsync ( $"map {mapName}" ) ;
2015-08-20 01:06:44 -04:00
}
2017-05-26 18:49:27 -04:00
public async Task LoadMap ( Map newMap )
2015-08-20 01:06:44 -04:00
{
2017-05-26 18:49:27 -04:00
await this . ExecuteCommandAsync ( $"map {newMap.Name}" ) ;
2015-08-20 01:06:44 -04:00
}
public void webChat ( Player P , String Message )
{
DateTime requestTime = DateTime . Now ;
if ( ( requestTime - lastWebChat ) . TotalSeconds > 1 )
{
Broadcast ( "^1[WEBCHAT] ^5" + P . Name + "^7 - " + Message ) ;
2017-05-26 18:49:27 -04:00
while ( chatHistory . Count > Math . Ceiling ( ( double ) ClientNum / 2 ) )
2015-08-20 01:06:44 -04:00
chatHistory . RemoveAt ( 0 ) ;
if ( Message . Length > 50 )
Message = Message . Substring ( 0 , 50 ) + "..." ;
2017-05-26 18:49:27 -04:00
chatHistory . Add ( new Chat ( P . Name , Utilities . StripColors ( Message ) , DateTime . Now ) ) ;
2015-08-20 01:06:44 -04:00
lastWebChat = DateTime . Now ;
}
}
2015-08-20 15:23:13 -04:00
/// <summary>
/// Initalize the macro variables
/// </summary>
2015-08-20 01:06:44 -04:00
abstract public void initMacros ( ) ;
2015-08-20 15:23:13 -04:00
/// <summary>
/// Read the map configuration
/// </summary>
2015-08-20 17:54:38 -04:00
protected void initMaps ( )
2015-08-20 01:06:44 -04:00
{
maps = new List < Map > ( ) ;
2017-05-26 18:49:27 -04:00
IFile mapfile = new IFile ( "config/maps.cfg" ) ;
2015-08-20 01:06:44 -04:00
String [ ] _maps = mapfile . readAll ( ) ;
mapfile . Close ( ) ;
if ( _maps . Length > 2 ) // readAll returns minimum one empty string
{
foreach ( String m in _maps )
{
String [ ] m2 = m . Split ( ':' ) ;
if ( m2 . Length > 1 )
{
Map map = new Map ( m2 [ 0 ] . Trim ( ) , m2 [ 1 ] . Trim ( ) ) ;
maps . Add ( map ) ;
}
}
}
else
2017-05-27 19:29:20 -04:00
Logger . WriteInfo ( "Maps configuration appears to be empty - skipping..." ) ;
2015-08-20 01:06:44 -04:00
}
2015-08-20 15:23:13 -04:00
/// <summary>
/// Initialize the messages to be broadcasted
/// </summary>
2015-08-20 17:54:38 -04:00
protected void initMessages ( )
2015-08-20 01:06:44 -04:00
{
messages = new List < String > ( ) ;
2017-05-26 18:49:27 -04:00
IFile messageCFG = new IFile ( "config/messages.cfg" ) ;
2015-08-20 01:06:44 -04:00
String [ ] lines = messageCFG . readAll ( ) ;
messageCFG . Close ( ) ;
if ( lines . Length < 2 ) //readAll returns minimum one empty string
{
2017-05-27 19:29:20 -04:00
Logger . WriteInfo ( "Messages configuration appears empty - skipping..." ) ;
2015-08-20 01:06:44 -04:00
return ;
}
int mTime = - 1 ;
int . TryParse ( lines [ 0 ] , out mTime ) ;
if ( messageTime = = - 1 )
messageTime = 60 ;
else
messageTime = mTime ;
foreach ( String l in lines )
{
if ( lines [ 0 ] ! = l & & l . Length > 1 )
messages . Add ( l ) ;
}
messageCFG . Close ( ) ;
//if (Program.Version != Program.latestVersion && Program.latestVersion != 0)
// messages.Add("^5IW4M Admin ^7is outdated. Please ^5update ^7to version " + Program.latestVersion);
}
2015-08-20 15:23:13 -04:00
/// <summary>
/// Initialize the rules configuration
/// </summary>
2015-08-20 17:54:38 -04:00
protected void initRules ( )
2015-08-20 01:06:44 -04:00
{
rules = new List < String > ( ) ;
2017-05-26 18:49:27 -04:00
IFile ruleFile = new IFile ( "config/rules.cfg" ) ;
2015-08-20 01:06:44 -04:00
String [ ] _rules = ruleFile . readAll ( ) ;
ruleFile . Close ( ) ;
if ( _rules . Length > 2 ) // readAll returns minimum one empty string
{
foreach ( String r in _rules )
{
if ( r . Length > 1 )
rules . Add ( r ) ;
}
}
else
2017-05-27 19:29:20 -04:00
Logger . WriteInfo ( "Rules configuration appears empty - skipping..." ) ;
2015-08-20 01:06:44 -04:00
ruleFile . Close ( ) ;
}
2015-08-20 15:23:13 -04:00
/// <summary>
/// Load up the built in commands
/// </summary>
2015-08-20 01:06:44 -04:00
abstract public void initCommands ( ) ;
//Objects
2017-05-26 18:49:27 -04:00
public Interfaces . IManager Manager { get ; protected set ; }
2017-05-27 19:29:20 -04:00
public Interfaces . ILogger Logger { get ; private set ; }
2015-08-20 01:06:44 -04:00
public Player owner ;
public List < Map > maps ;
public List < String > rules ;
public Queue < Event > events ;
public String Website ;
public String Gametype ;
public int totalKills = 0 ;
public List < Report > Reports ;
public List < Chat > chatHistory ;
2015-08-22 02:04:30 -04:00
public Queue < PlayerHistory > playerHistory { get ; private set ; }
2015-08-20 01:06:44 -04:00
//Info
protected String IP ;
protected int Port ;
2017-05-26 18:49:27 -04:00
public String Hostname { get ; protected set ; }
public Map CurrentMap { get ; protected set ; }
protected string FSGame ;
public int ClientNum { get ; protected set ; }
public int MaxClients { get ; protected set ; }
public List < Player > Players { get ; protected set ; }
2015-08-20 01:06:44 -04:00
protected List < String > messages ;
protected int messageTime ;
protected TimeSpan lastMessage ;
protected DateTime lastPoll ;
protected int nextMessage ;
2017-05-26 18:49:27 -04:00
2015-08-20 01:06:44 -04:00
protected Dictionary < String , Object > Macros ;
protected DateTime lastWebChat ;
2017-05-26 18:49:27 -04:00
public string Password { get ; private set ; }
public int Handle { get ; private set ; }
2015-08-20 01:06:44 -04:00
protected int PID ;
protected IFile logFile ;
// Will probably move this later
public Dictionary < String , Player > statusPlayers ;
public bool isRunning ;
// Log stuff
protected String Mod ;
// Databases
2017-05-27 18:08:04 -04:00
//public ClientsDB clientDB;
2017-05-28 21:54:46 -04:00
//public AliasesDB aliasDB;
2017-05-26 18:49:27 -04:00
//Remote
public Queue < String > commandResult = new Queue < string > ( ) ;
2015-08-20 01:06:44 -04:00
}
}