/// <summary> /// Constructor /// </summary> public TcpAppServer() : base() { MessageDelimiter = Convert.ToByte(Convert.ToChar(TcpAppCommon.Delimiter)); base.ClientConnected += TcpAppServer_ClientConnected; base.ClientDisconnected += TcpAppServer_ClientDisconnected; base.ServerStopped += TcpAppServer_ServerStopped; CommandQueueThread = new Thread(ExecuteQueuedCommandsAsync); AbortCommandQueueThread = false; CommandQueueThread.Start(); //TcpAppServer Format: // TX: TCP: <Command> [-Param0] [-Param1] ... [-ParamN] // RX: TCP: <Status> [Return Message] // Source - [email protected]:23 // Command - Registered Command. //Register System Commands //--- INIT (Commands used by TcpAppClient) --- RegisterSystemCommand("Help", "Show help screen. Include plugin type or object alias name to show commands for selected plugin.", ShowHelp, TcpAppParameter.CreateOptionalParameter("Plugin", "Plugin type or Alias", "-")); RegisterSystemCommand("SignIn", "Sign in to TcpAppServer. Server will verify connection id and return unique ID.", delegate(TcpAppInputCommand sender) { //Assign Name string machineName = sender.Command.Parameter("ConnectionID").Value?.Replace(" ", "_"); if (string.IsNullOrEmpty(machineName)) { machineName = sender.AppClient.Connection.ClientIPAddress.ToString(); } if (sender.AppClient.SignedIn) { //Client already signed in, verify connection ID. if (sender.AppClient.Name.Equals(machineName, StringComparison.InvariantCultureIgnoreCase)) { sender.OutputMessage = sender.AppClient.Name; sender.Status = TcpAppCommandStatus.OK; return; } else { sender.AppClient.SignedIn = false; } } TcpAppServerExEventArgs signInArg = new TcpAppServerExEventArgs(sender.AppClient) { Value = machineName }; ClientSigningIn?.Invoke(this, signInArg); if (signInArg.Cancel == true) { sender.OutputMessage = signInArg.Reason; if (string.IsNullOrEmpty(sender.OutputMessage)) { sender.OutputMessage = "Access Denied!"; } sender.Status = TcpAppCommandStatus.ERR; return; } string uniqueName = machineName; lock (AppClients) { //Cleanup instance with same name but already disconnected without signout if (AppClients.FirstOrDefault(x => x.Name == uniqueName) != null) { for (int x = 0; x < AppClients.Count;) { if (!AppClients[x].Connection.Connected && AppClients[x].Name.StartsWith(uniqueName)) { AppClients[x].Dispose(); AppClients.RemoveAt(x); } else { x++; } } } while (AppClients.FirstOrDefault(x => x.Name == uniqueName) != null) { uniqueName = machineName + "_" + (++Counter).ToString(); } sender.AppClient.Name = uniqueName; sender.OutputMessage = uniqueName; sender.Status = TcpAppCommandStatus.OK; sender.AppClient.SignedIn = true; ClientSignedIn?.Invoke(this, new TcpAppServerEventArgs(sender.AppClient)); //Event } }, TcpAppParameter.CreateParameter("ConnectionID", "Connection ID. If already exist, server will return an updated unique ID.")); RegisterSystemCommand("SignOut", "Signout TcpAppClient.", delegate(TcpAppInputCommand sender) { ClientSigningOut?.Invoke(this, new TcpAppServerEventArgs(sender.AppClient)); sender.AppClient.Dispose(); lock (AppClients) { AppClients.Remove(sender.AppClient); } sender.AppClient.SignedIn = false; sender.Status = TcpAppCommandStatus.OK; }); RegisterSystemCommand("Version?", "Get TcpAppServer Library Version", delegate(TcpAppInputCommand sender) { sender.OutputMessage = Version.ToString(); sender.Status = TcpAppCommandStatus.OK; }); RegisterSystemCommand("Terminate", "Terminate Application. Command only valid after client Signin. Default Exit Code = -99", delegate(TcpAppInputCommand sender) { VerifyUserSignedIn(sender); int exitCode = -99; try { exitCode = Convert.ToInt32(sender.Command.Parameter("ExitCode").Value); } catch { } sender.Status = TcpAppCommandStatus.OK; Environment.ExitCode = exitCode; Thread ptrThread = new Thread(TerminateApplication); ptrThread.Start(); }, TcpAppParameter.CreateOptionalParameter("ExitCode", "Assign Exit Code for application termination.", "-99")); //--- Execution --- RegisterSystemCommand("FunctionList?", "Get list of registered functions.", delegate(TcpAppInputCommand sender) { foreach (TcpAppCommand x in Commands) { sender.OutputMessage += x.Keyword; sender.OutputMessage += " "; } sender.Status = TcpAppCommandStatus.OK; }); RegisterSystemCommand("Execute", "Execute plugin's command. Command only valid after client Signin.", delegate(TcpAppInputCommand sender) { VerifyUserSignedIn(sender); ITcpAppServerPlugin plugin = _Plugins.FirstOrDefault(x => string.Compare(x.Alias, sender.Command.Parameter("Alias").Value, true) == 0); if (plugin == null) { throw new ArgumentException("Plugin not exists!"); } TcpAppInputCommand pluginCommand = plugin.GetPluginCommand(sender.Arguments.Skip(1).ToArray()); TcpAppServerExEventArgs pluginExecuteEventArgs = new TcpAppServerExEventArgs(sender.AppClient) { Plugin = plugin }; BeforeExecutePluginCommand?.Invoke(this, pluginExecuteEventArgs); if (pluginExecuteEventArgs.Cancel) { //Command execution cancelled by server, return error with reason. sender.Status = TcpAppCommandStatus.ERR; sender.OutputMessage = pluginExecuteEventArgs.Reason; } else { //Proceed with execution. pluginCommand.ExecuteCallback(); sender.Status = pluginCommand.Status; sender.OutputMessage = pluginCommand.OutputMessage; } }, TcpAppParameter.CreateParameter("Alias", "Plugin Alias Name.")); RegisterSystemCommand("CheckStatus", "Check execution status for queued command. RETURN: Command status if executed, else BUSY. ERR if no queued message.", delegate(TcpAppInputCommand sender) { int queueID = Convert.ToInt32(sender.Command.Parameter("QueueID").Value); if (queueID == 0) { //ID not specified, get status for next queued command. if (sender.AppClient.NextQueuedCommand == null) { sender.Status = TcpAppCommandStatus.ERR; sender.OutputMessage = "No queued message!"; return; } sender.Status = sender.AppClient.NextQueuedCommand.Status; switch (sender.AppClient.NextQueuedCommand.Status) { case TcpAppCommandStatus.BUSY: case TcpAppCommandStatus.QUEUED: sender.OutputMessage = string.Empty; break; default: lock (CommandQueue) { //Return result for all queued message except executing one. TcpAppInputCommand[] results = ResultQueue.Where(x => x.AppClient == sender.AppClient).ToArray(); TcpAppInputCommand nextQueue = null; foreach (TcpAppInputCommand cmd in results) { if (cmd.Status == TcpAppCommandStatus.BUSY || cmd.Status == TcpAppCommandStatus.QUEUED) { nextQueue = cmd; break; } if (cmd.Status == TcpAppCommandStatus.ERR) { cmd.Status = sender.Status; cmd.OutputMessage += "! "; //Prefix for command with error status. } ResultQueue.Remove(cmd); sender.OutputMessage += cmd.OutputMessage + "\n"; } //Return number of remaining queued commands sender.OutputMessage += CommandQueue.Where(x => x.AppClient == sender.AppClient).Count().ToString(); sender.AppClient.NextQueuedCommand = nextQueue; } break; } } else { //Return status of specific message. TcpAppInputCommand ptrCmd = CommandQueue.FirstOrDefault(x => x.AppClient == sender.AppClient && x.ID == queueID); if (ptrCmd == null) { ptrCmd = ResultQueue.FirstOrDefault(x => x.AppClient == sender.AppClient); if (ptrCmd != null) { ResultQueue.Remove(ptrCmd); } else { sender.Status = TcpAppCommandStatus.ERR; sender.OutputMessage = "Invalid ID: " + queueID.ToString(); return; } } sender.OutputMessage = ptrCmd.OutputMessage; sender.Status = ptrCmd.Status; } }, TcpAppParameter.CreateOptionalParameter("QueueID", "Get status of specific message.", "0")); RegisterSystemCommand("Abort", "Abort last queued command.", delegate(TcpAppInputCommand sender) { VerifyUserSignedIn(sender); if (sender.AppClient.NextQueuedCommand != null) { lock (CommandQueue) { CommandQueue.Remove(sender.AppClient.NextQueuedCommand); ResultQueue.Remove(sender.AppClient.NextQueuedCommand); sender.AppClient.NextQueuedCommand = null; } } sender.Status = TcpAppCommandStatus.OK; }); RegisterSystemCommand("FlushQueue", "Flush message queue for calling client.", delegate(TcpAppInputCommand sender) { VerifyUserSignedIn(sender); lock (CommandQueue) { CommandQueue.RemoveAll(x => x.AppClient == sender.AppClient); ResultQueue.RemoveAll(x => x.AppClient == sender.AppClient); sender.AppClient.NextQueuedCommand = null; } sender.Status = TcpAppCommandStatus.OK; }); RegisterSystemCommand("FlushAllQueue", "Flush message queue for all clients.", delegate(TcpAppInputCommand sender) { VerifyUserSignedIn(sender); lock (CommandQueue) { CommandQueue.Clear(); ResultQueue.Clear(); lock (AppClients) { foreach (TcpAppServerConnection client in AppClients) { client.NextQueuedCommand = null; } } } sender.Status = TcpAppCommandStatus.OK; }); //--- PLUGIN --- RegisterSystemCommand("PluginTypes?", "Get list of plugin class type. Use CreatePlugins command to instantiate type.", delegate(TcpAppInputCommand sender) { if (PluginTypes.Count == 0) { sender.OutputMessage = "-NONE-"; } else { sender.OutputMessage = string.Join(" ", PluginTypes.Select(x => x.Name).ToArray()); } sender.Status = TcpAppCommandStatus.OK; }); RegisterSystemCommand("CreatePlugin", "Create an plugin object from listed plugin types. Command only valid after client Signin.", delegate(TcpAppInputCommand sender) { VerifyUserSignedIn(sender); string typeName = sender.Command.Parameter("TypeName").Value; //Sanity Check - Type Name if (PluginTypes.FirstOrDefault(x => x.Name.Equals(typeName, StringComparison.InvariantCultureIgnoreCase)) == null) { throw new ArgumentException("Unknown plugin type: " + typeName); } Type pluginType = PluginTypes.FirstOrDefault(x => x.Name.Equals(typeName, StringComparison.InvariantCultureIgnoreCase)).Type; string aliasName; sender.OutputMessage = "Plugin created:"; foreach (string value in sender.Command.Parameter("Alias").Values) { aliasName = value; if (Commands.FirstOrDefault(x => string.Compare(x.Keyword, aliasName, true) == 0) != null) { throw new ArgumentException("Unable to create plugin with alias '" + aliasName + "'. Name already registered as command!"); } //Sanity Check - Verify alias name is not plugin type name if (PluginTypes.FirstOrDefault(x => string.Compare(x.Name, aliasName, true) == 0) != null) { throw new ArgumentException("Unable to create plugin with alias '" + aliasName + "'. Name already registered plugin type!"); } //Sanity Check - Alias Name if (_Plugins.FirstOrDefault(x => string.Compare(x.Alias, aliasName, true) == 0) != null) { throw new ArgumentException("Unable to create plugin with alias '" + aliasName + "'. Plugin already exists!"); } ITcpAppServerPlugin pluginInstance = Activator.CreateInstance(pluginType) as ITcpAppServerPlugin; pluginInstance.Alias = aliasName; _Plugins.Add(pluginInstance); PluginCreated?.Invoke(this, new TcpAppServerEventArgs(sender.AppClient) { Plugin = pluginInstance }); sender.OutputMessage += " " + aliasName; } sender.Status = TcpAppCommandStatus.OK; }, TcpAppParameter.CreateParameter("TypeName", "Plugin type name."), TcpAppParameter.CreateParameterArray("Alias", "Plugin object name, case insensitive.", false)); RegisterSystemCommand("Plugins?", "Return plugins list by alias name.", delegate(TcpAppInputCommand sender) { if (_Plugins.Count == 0) { sender.OutputMessage = "-NONE-"; } else { sender.OutputMessage = string.Join(TcpAppCommon.NewLine, _Plugins.Select(x => x.Alias + "(" + PluginTypes.FirstOrDefault(n => n.Type == x.GetType())?.Name + ")").ToArray()); } sender.Status = TcpAppCommandStatus.OK; }); RegisterSystemCommand("DisposePlugin", "Delete plugin by alias name. Command only valid after client Signin.", delegate(TcpAppInputCommand sender) { VerifyUserSignedIn(sender); string alias = sender.Command.Parameter("Alias").Value; ITcpAppServerPlugin item = _Plugins.FirstOrDefault(x => string.Compare(x.Alias, alias, true) == 0); if (item == null) { sender.OutputMessage = "Plugin [" + alias + "] not found / disposed."; sender.Status = TcpAppCommandStatus.OK; return; } else { if (item.DisposeRequest() == true) { _Plugins.Remove(item); PluginDisposed?.Invoke(this, new TcpAppServerEventArgs(sender.AppClient) { Plugin = item }); sender.OutputMessage = alias + " disposed."; sender.Status = TcpAppCommandStatus.OK; } else { sender.Status = TcpAppCommandStatus.ERR; sender.OutputMessage = "Unable to dispose " + alias; } } }, TcpAppParameter.CreateParameter("Alias", "Plugin alias name.")); }