#region Arthea License
/***********************************************************************
* Arthea MUD by R. Jennings (2007) http://arthea.googlecode.com/ *
* By using this code you comply with the Artistic and GPLv2 Licenses. *
***********************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Remoting.Channels;
#if !MONO
using System.Runtime.Remoting.Channels.Tcp;
#endif
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml.Serialization;
using Arthea.Commands.Admin;
using Arthea.Connections;
using Arthea.Environment;
using Arthea.Updates;
namespace Arthea
{
/// <summary>
/// The starting point
/// </summary>
public class Server
{
#region [rgn] Fields (10)
private const int BacklogSize = 20;
private static readonly Server instance =
(!Persistance.XmlFileExists(Paths.ServerConfigFile))
? new Server()
: Persistance.Load<Server>(Paths.ServerConfigFile);
private readonly string about;
private readonly string version;
private bool databaseLog;
private string databaseName;
private string databasePassword;
private string databaseServer;
private string databaseType;
private string databaseUser;
private bool databaseEnabled = true;
private ServerState serverState;
private Socket socket;
#endregion [rgn]
#region [rgn] Constructors (1)
private Server()
{
version = string.Format("Arthea v{0}.{1}", System.Environment.Version.Major,
System.Environment.Version.Minor);
StringBuilder buf = new StringBuilder();
buf.AppendLine("by R. Jennings 2007");
buf.AppendLine("See http://arthea.googlecode.com/ for more information.");
about = buf.ToString();
}
#endregion [rgn]
#region [rgn] Properties (8)
/// <summary>
/// Gets information about the server.
/// </summary>
public string About
{
get { return about; }
}
/// <summary>
/// Gets or sets a value indicating whether [database enabled].
/// </summary>
/// <value><c>true</c> if [database enabled]; otherwise, <c>false</c>.</value>
[XmlIgnore]
public bool DatabaseEnabled
{
get { return databaseEnabled; }
set { databaseEnabled = value; }
}
/// <summary>
/// Gets or sets the name of the database.
/// </summary>
/// <value>The name of the database.</value>
public string DatabaseName
{
get { return databaseName; }
set { databaseName = value; }
}
/// <summary>
/// Gets or sets the database password.
/// </summary>
/// <value>The database password.</value>
public string DatabasePassword
{
get { return databasePassword; }
set { databasePassword = value; }
}
/// <summary>
/// Gets or sets the database server.
/// </summary>
/// <value>The database server.</value>
public string DatabaseServer
{
get { return databaseServer; }
set { databaseServer = value; }
}
/// <summary>
/// Gets or sets the type of the database.
/// </summary>
/// <value>The type of the database.</value>
public string DatabaseType
{
get { return databaseType; }
set { databaseType = value; }
}
/// <summary>
/// Gets or sets the database user.
/// </summary>
/// <value>The database user.</value>
public string DatabaseUser
{
get { return databaseUser; }
set { databaseUser = value; }
}
/// <summary>
/// Gets or sets a value indicating whether [database log].
/// </summary>
/// <value><c>true</c> if [database log]; otherwise, <c>false</c>.</value>
public bool DatabaseLog
{
get { return databaseLog; }
set { databaseLog = value; }
}
/// <summary>
/// Gets or sets the run level.
/// </summary>
/// <value>The run level.</value>
[XmlIgnore]
public ServerState State
{
get { return serverState; }
set
{
serverState = value;
switch (value)
{
case ServerState.Running:
Log.Info("Arthea running on port {0}.", Globals.PortNumber);
break;
case ServerState.Stopping:
Log.Info("Arthea is shutting down...");
break;
default:
Log.Info("Arthea is {0}...", value.ToString().ToLower());
break;
}
}
}
/// <summary>
/// Gets the version of the server.
/// </summary>
public string Version
{
get { return version; }
}
/// <summary>
/// Gets the instance.
/// </summary>
/// <value>The instance.</value>
public static Server Instance
{
get { return instance; }
}
#endregion [rgn]
#region [rgn] Methods (2)
// [rgn] Public Methods (2)
/// <summary>
/// Shutdowns this instance.
/// </summary>
public void Shutdown()
{
if (socket != null)
{
socket.Close();
socket = null;
}
}
/// <summary>
/// Starts the server.
/// </summary>
/// <param name="rebooting">if set to <c>true</c> [rebooting].</param>
public void Start(bool rebooting)
{
#if !MONO
Dictionary<string, SocketInformation> recoveredConnections = null;
if (rebooting)
{
TcpChannel channel = new TcpChannel();
ChannelServices.RegisterChannel(channel, false);
RebootRecovery recovery = (RebootRecovery)
Activator.GetObject(typeof (RebootRecovery),
string.Format("tcp://localhost:{0}/RebootRecovery",
RebootRecovery.Port));
if (recovery == null)
{
Log.Fatal("Reboot recovery failed.");
return;
}
serverState = ServerState.Rebooting;
try
{
recoveredConnections =
recovery.TransferConnections(Process.GetCurrentProcess().Id);
ChannelServices.UnregisterChannel(channel);
// signal we are done to close the previous process
recovery.Finished();
// wait a bit for sanity
Thread.Sleep(1000);
}
catch (Exception ex)
{
Log.Bug(ex.Message);
Log.Error(ex.StackTrace);
Thread.Sleep(5000);
return;
}
}
#endif
socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
try
{
socket.Bind(new IPEndPoint(IPAddress.Any, Globals.PortNumber));
}
catch (Exception)
{
Log.Fatal("Port {0} is in use.", Globals.PortNumber);
Thread.Sleep(5000);
System.Environment.Exit(1);
}
socket.Listen(BacklogSize);
State = ServerState.Booting;
Lists.Continents.Load();
#if !MONO
if (recoveredConnections != null)
{
Log.Info("Recovering {0} player{1}...", recoveredConnections.Count,
recoveredConnections.Count == 1 ? "" : "s");
foreach (KeyValuePair<string, SocketInformation> entry in recoveredConnections)
{
try
{
Connection connection = new Connection(entry.Key, entry.Value);
connection.Attach();
}
catch
{
Log.Error("Failed to load {0}.", entry.Key);
}
}
}
#endif
Lists.Areas.Update();
State = ServerState.Running;
try
{
while (State == ServerState.Running)
{
if (socket.Poll(1, SelectMode.SelectRead))
{
new Connection(socket.Accept());
}
Lists.Connections.ReadInput();
//Update world
UpdateManager.Update();
Lists.Connections.Update();
Lists.Connections.ProcessOutput();
Thread.Sleep(1);
}
}
catch (ThreadAbortException ex)
{
Log.Info(ex.ExceptionState);
}
finally
{
Cleanup();
Shutdown();
}
}
// [rgn] Private Methods (1)
/// <summary>
/// Cleanups this instance.
/// </summary>
private void Cleanup()
{
Globals.Save();
Persistance.Save(Paths.ServerConfigFile, this);
}
#endregion [rgn]
}
/// <summary>
/// Arguments class
/// </summary>
public class Arguments
{
#region [rgn] Fields (1)
// Variables
private readonly StringDictionary Parameters;
#endregion [rgn]
#region [rgn] Constructors (1)
/// <summary>
/// Initializes a new instance of the <see cref="Arguments"/> class.
/// </summary>
/// <param name="Args">The args.</param>
public Arguments(IEnumerable<string> Args)
{
Parameters = new StringDictionary();
Regex Spliter = new Regex(@"^-{1,2}|^/|=|:",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
Regex Remover = new Regex(@"^['""]?(.*?)['""]?$",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
string Parameter = null;
string[] Parts;
// Valid parameters forms:
// {-,/,--}param{ ,=,:}((",')value(",'))
// Examples:
// -param1 value1 --param2 /param3:"Test-:-work"
// /param4=happy -param5 '--=nice=--'
foreach (string Txt in Args)
{
// Look for new parameters (-,/ or --) and a
// possible enclosed value (=,:)
Parts = Spliter.Split(Txt, 3);
switch (Parts.Length)
{
// Found a value (for the last parameter
// found (space separator))
case 1:
if (Parameter != null)
{
if (!Parameters.ContainsKey(Parameter))
{
Parts[0] =
Remover.Replace(Parts[0], "$1");
Parameters.Add(Parameter, Parts[0]);
}
Parameter = null;
}
// else Error: no parameter waiting for a value (skipped)
break;
// Found just a parameter
case 2:
// The last parameter is still waiting.
// With no value, set it to true.
if (Parameter != null)
{
if (!Parameters.ContainsKey(Parameter))
Parameters.Add(Parameter, "true");
}
Parameter = Parts[1];
break;
// Parameter with enclosed value
case 3:
// The last parameter is still waiting.
// With no value, set it to true.
if (Parameter != null)
{
if (!Parameters.ContainsKey(Parameter))
Parameters.Add(Parameter, "true");
}
Parameter = Parts[1];
// Remove possible enclosing characters (",')
if (!Parameters.ContainsKey(Parameter))
{
Parts[2] = Remover.Replace(Parts[2], "$1");
Parameters.Add(Parameter, Parts[2]);
}
Parameter = null;
break;
}
}
// In case a parameter is still waiting
if (Parameter != null)
{
if (!Parameters.ContainsKey(Parameter))
Parameters.Add(Parameter, "true");
}
}
#endregion [rgn]
#region [rgn] Properties (1)
/// <summary>
/// Gets the <see cref="System.String"/> with the specified param.
/// </summary>
/// <value>the parameter value</value>
public string this[string Param]
{
get { return (Parameters[Param]); }
}
#endregion [rgn]
}
}