Files
opensim/OpenSim/Region/OptionalModules/Avatar/Chat/IRCConnector.cs
Justin Clark-Casey (justincc) 86367d7219 refactor: Move methods to start a monitored thread, start work in its own thread and run work in the jobengine from Watchdog to a WorkManager class.
This is to achieve a clean separation of concerns - the watchdog is an inappropriate place for work management.
Also adds a WorkManager.RunInThreadPool() class which feeds through to Util.FireAndForget.
Also switches around the name and obj arguments to the new RunInThread() and RunJob() methods so that the callback obj comes after the callback as seen in the SDK and elsewhere
2014-11-25 23:56:32 +00:00

914 lines
33 KiB
C#

/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Timers;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using OpenMetaverse;
using log4net;
using Nini.Config;
using OpenSim.Framework;
using OpenSim.Framework.Monitoring;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
namespace OpenSim.Region.OptionalModules.Avatar.Chat
{
public class IRCConnector
{
#region Global (static) state
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
// Local constants
// This computation is not the real region center if the region is larger than 256.
// This computation isn't fixed because there is not a handle back to the region.
private static readonly Vector3 CenterOfRegion = new Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 20);
private static readonly char[] CS_SPACE = { ' ' };
private const int WD_INTERVAL = 1000; // base watchdog interval
private static int PING_PERIOD = 15; // WD intervals per PING
private static int ICCD_PERIOD = 10; // WD intervals between Connects
private static int L_TIMEOUT = 25; // Login time out interval
private static int _idk_ = 0; // core connector identifier
private static int _pdk_ = 0; // ping interval counter
private static int _icc_ = ICCD_PERIOD; // IRC connect counter
// List of configured connectors
private static List<IRCConnector> m_connectors = new List<IRCConnector>();
// Watchdog state
private static System.Timers.Timer m_watchdog = null;
// The watch-dog gets started as soon as the class is instantiated, and
// ticks once every second (WD_INTERVAL)
static IRCConnector()
{
m_log.DebugFormat("[IRC-Connector]: Static initialization started");
m_watchdog = new System.Timers.Timer(WD_INTERVAL);
m_watchdog.Elapsed += new ElapsedEventHandler(WatchdogHandler);
m_watchdog.AutoReset = true;
m_watchdog.Start();
m_log.DebugFormat("[IRC-Connector]: Static initialization complete");
}
#endregion
#region Instance state
// Connector identity
internal int idn = _idk_++;
// How many regions depend upon this connection
// This count is updated by the ChannelState object and reflects the sum
// of the region clients associated with the set of associated channel
// state instances. That's why it cannot be managed here.
internal int depends = 0;
// This variable counts the number of resets that have been performed
// on the connector. When a listener thread terminates, it checks to
// see of the reset count has changed before it schedules another
// reset.
internal int m_resetk = 0;
private Object msyncConnect = new Object();
internal bool m_randomizeNick = true; // add random suffix
internal string m_baseNick = null; // base name for randomizing
internal string m_nick = null; // effective nickname
public string Nick // Public property
{
get { return m_nick; }
set { m_nick = value; }
}
private bool m_enabled = false; // connector enablement
public bool Enabled
{
get { return m_enabled; }
}
private bool m_connected = false; // connection status
private bool m_pending = false; // login disposition
private int m_timeout = L_TIMEOUT; // login timeout counter
public bool Connected
{
get { return m_connected; }
}
private string m_ircChannel; // associated channel id
public string IrcChannel
{
get { return m_ircChannel; }
set { m_ircChannel = value; }
}
private uint m_port = 6667; // session port
public uint Port
{
get { return m_port; }
set { m_port = value; }
}
private string m_server = null; // IRC server name
public string Server
{
get { return m_server; }
set { m_server = value; }
}
private string m_password = null;
public string Password
{
get { return m_password; }
set { m_password = value; }
}
private string m_user = "USER OpenSimBot 8 * :I'm an OpenSim to IRC bot";
public string User
{
get { return m_user; }
}
// Network interface
private TcpClient m_tcp;
private NetworkStream m_stream = null;
private StreamReader m_reader;
private StreamWriter m_writer;
// Channel characteristic info (if available)
internal string usermod = String.Empty;
internal string chanmod = String.Empty;
internal string version = String.Empty;
internal bool motd = false;
#endregion
#region connector instance management
internal IRCConnector(ChannelState cs)
{
// Prepare network interface
m_tcp = null;
m_writer = null;
m_reader = null;
// Setup IRC session parameters
m_server = cs.Server;
m_password = cs.Password;
m_baseNick = cs.BaseNickname;
m_randomizeNick = cs.RandomizeNickname;
m_ircChannel = cs.IrcChannel;
m_port = cs.Port;
m_user = cs.User;
if (m_watchdog == null)
{
// Non-differentiating
ICCD_PERIOD = cs.ConnectDelay;
PING_PERIOD = cs.PingDelay;
// Smaller values are not reasonable
if (ICCD_PERIOD < 5)
ICCD_PERIOD = 5;
if (PING_PERIOD < 5)
PING_PERIOD = 5;
_icc_ = ICCD_PERIOD; // get started right away!
}
// The last line of defense
if (m_server == null || m_baseNick == null || m_ircChannel == null || m_user == null)
throw new Exception("Invalid connector configuration");
// Generate an initial nickname
if (m_randomizeNick)
m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
else
m_nick = m_baseNick;
m_log.InfoFormat("[IRC-Connector-{0}]: Initialization complete", idn);
}
~IRCConnector()
{
m_watchdog.Stop();
Close();
}
// Mark the connector as connectable. Harmless if already enabled.
public void Open()
{
if (!m_enabled)
{
if (!Connected)
{
Connect();
}
lock (m_connectors)
m_connectors.Add(this);
m_enabled = true;
}
}
// Only close the connector if the dependency count is zero.
public void Close()
{
m_log.InfoFormat("[IRC-Connector-{0}] Closing", idn);
lock (msyncConnect)
{
if ((depends == 0) && Enabled)
{
m_enabled = false;
if (Connected)
{
m_log.DebugFormat("[IRC-Connector-{0}] Closing interface", idn);
// Cleanup the IRC session
try
{
m_writer.WriteLine(String.Format("QUIT :{0} to {1} wormhole to {2} closing",
m_nick, m_ircChannel, m_server));
m_writer.Flush();
}
catch (Exception) { }
m_connected = false;
try { m_writer.Close(); }
catch (Exception) { }
try { m_reader.Close(); }
catch (Exception) { }
try { m_stream.Close(); }
catch (Exception) { }
try { m_tcp.Close(); }
catch (Exception) { }
}
lock (m_connectors)
m_connectors.Remove(this);
}
}
m_log.InfoFormat("[IRC-Connector-{0}] Closed", idn);
}
#endregion
#region session management
// Connect to the IRC server. A connector should always be connected, once enabled
public void Connect()
{
if (!m_enabled)
return;
// Delay until next WD cycle if this is too close to the last start attempt
while (_icc_ < ICCD_PERIOD)
return;
m_log.DebugFormat("[IRC-Connector-{0}]: Connection request for {1} on {2}:{3}", idn, m_nick, m_server, m_ircChannel);
lock (msyncConnect)
{
_icc_ = 0;
try
{
if (m_connected) return;
m_connected = true;
m_pending = true;
m_timeout = L_TIMEOUT;
m_tcp = new TcpClient(m_server, (int)m_port);
m_stream = m_tcp.GetStream();
m_reader = new StreamReader(m_stream);
m_writer = new StreamWriter(m_stream);
m_log.InfoFormat("[IRC-Connector-{0}]: Connected to {1}:{2}", idn, m_server, m_port);
WorkManager.StartThread(ListenerRun, "IRCConnectionListenerThread", ThreadPriority.Normal, true, false);
// This is the message order recommended by RFC 2812
if (m_password != null)
m_writer.WriteLine(String.Format("PASS {0}", m_password));
m_writer.WriteLine(String.Format("NICK {0}", m_nick));
m_writer.Flush();
m_writer.WriteLine(m_user);
m_writer.Flush();
m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
m_writer.Flush();
m_log.InfoFormat("[IRC-Connector-{0}]: {1} has asked to join {2}", idn, m_nick, m_ircChannel);
}
catch (Exception e)
{
m_log.ErrorFormat("[IRC-Connector-{0}] cannot connect {1} to {2}:{3}: {4}",
idn, m_nick, m_server, m_port, e.Message);
// It might seem reasonable to reset connected and pending status here
// Seeing as we know that the login has failed, but if we do that, then
// connection will be retried each time the interconnection interval
// expires. By leaving them as they are, the connection will be retried
// when the login timeout expires. Which is preferred.
}
}
return;
}
// Reconnect is used to force a re-cycle of the IRC connection. Should generally
// be a transparent event
public void Reconnect()
{
m_log.DebugFormat("[IRC-Connector-{0}]: Reconnect request for {1} on {2}:{3}", idn, m_nick, m_server, m_ircChannel);
// Don't do this if a Connect is in progress...
lock (msyncConnect)
{
if (m_connected)
{
m_log.InfoFormat("[IRC-Connector-{0}] Resetting connector", idn);
// Mark as disconnected. This will allow the listener thread
// to exit if still in-flight.
// The listener thread is not aborted - it *might* actually be
// the thread that is running the Reconnect! Instead just close
// the socket and it will disappear of its own accord, once this
// processing is completed.
try { m_writer.Close(); }
catch (Exception) { }
try { m_reader.Close(); }
catch (Exception) { }
try { m_tcp.Close(); }
catch (Exception) { }
m_connected = false;
m_pending = false;
m_resetk++;
}
}
Connect();
}
#endregion
#region Outbound (to-IRC) message handlers
public void PrivMsg(string pattern, string from, string region, string msg)
{
// m_log.DebugFormat("[IRC-Connector-{0}] PrivMsg to IRC from {1}: <{2}>", idn, from,
// String.Format(pattern, m_ircChannel, from, region, msg));
// One message to the IRC server
try
{
m_writer.WriteLine(pattern, m_ircChannel, from, region, msg);
m_writer.Flush();
// m_log.DebugFormat("[IRC-Connector-{0}]: PrivMsg from {1} in {2}: {3}", idn, from, region, msg);
}
catch (IOException)
{
m_log.ErrorFormat("[IRC-Connector-{0}]: PrivMsg I/O Error: disconnected from IRC server", idn);
Reconnect();
}
catch (Exception ex)
{
m_log.ErrorFormat("[IRC-Connector-{0}]: PrivMsg exception : {1}", idn, ex.Message);
m_log.Debug(ex);
}
}
public void Send(string msg)
{
// m_log.DebugFormat("[IRC-Connector-{0}] Send to IRC : <{1}>", idn, msg);
try
{
m_writer.WriteLine(msg);
m_writer.Flush();
// m_log.DebugFormat("[IRC-Connector-{0}] Sent command string: {1}", idn, msg);
}
catch (IOException)
{
m_log.ErrorFormat("[IRC-Connector-{0}] Disconnected from IRC server.(Send)", idn);
Reconnect();
}
catch (Exception ex)
{
m_log.ErrorFormat("[IRC-Connector-{0}] Send exception trap: {0}", idn, ex.Message);
m_log.Debug(ex);
}
}
#endregion
public void ListenerRun()
{
string inputLine;
int resetk = m_resetk;
try
{
while (m_enabled && m_connected)
{
if ((inputLine = m_reader.ReadLine()) == null)
throw new Exception("Listener input socket closed");
Watchdog.UpdateThread();
// m_log.Info("[IRCConnector]: " + inputLine);
if (inputLine.Contains("PRIVMSG"))
{
Dictionary<string, string> data = ExtractMsg(inputLine);
// Any chat ???
if (data != null)
{
OSChatMessage c = new OSChatMessage();
c.Message = data["msg"];
c.Type = ChatTypeEnum.Region;
c.Position = CenterOfRegion;
c.From = data["nick"];
c.Sender = null;
c.SenderUUID = UUID.Zero;
// Is message "\001ACTION foo bar\001"?
// Then change to: "/me foo bar"
if ((1 == c.Message[0]) && c.Message.Substring(1).StartsWith("ACTION"))
c.Message = String.Format("/me {0}", c.Message.Substring(8, c.Message.Length - 9));
ChannelState.OSChat(this, c, false);
}
}
else
{
ProcessIRCCommand(inputLine);
}
}
}
catch (Exception /*e*/)
{
// m_log.ErrorFormat("[IRC-Connector-{0}]: ListenerRun exception trap: {1}", idn, e.Message);
// m_log.Debug(e);
}
// This is potentially circular, but harmless if so.
// The connection is marked as not connected the first time
// through reconnect.
if (m_enabled && (m_resetk == resetk))
Reconnect();
Watchdog.RemoveThread();
}
private Regex RE = new Regex(@":(?<nick>[\w-]*)!(?<user>\S*) PRIVMSG (?<channel>\S+) :(?<msg>.*)",
RegexOptions.Multiline);
private Dictionary<string, string> ExtractMsg(string input)
{
//examines IRC commands and extracts any private messages
// which will then be reboadcast in the Sim
// m_log.InfoFormat("[IRC-Connector-{0}]: ExtractMsg: {1}", idn, input);
Dictionary<string, string> result = null;
MatchCollection matches = RE.Matches(input);
// Get some direct matches $1 $4 is a
if ((matches.Count == 0) || (matches.Count != 1) || (matches[0].Groups.Count != 5))
{
// m_log.Info("[IRCConnector]: Number of matches: " + matches.Count);
// if (matches.Count > 0)
// {
// m_log.Info("[IRCConnector]: Number of groups: " + matches[0].Groups.Count);
// }
return null;
}
result = new Dictionary<string, string>();
result.Add("nick", matches[0].Groups[1].Value);
result.Add("user", matches[0].Groups[2].Value);
result.Add("channel", matches[0].Groups[3].Value);
result.Add("msg", matches[0].Groups[4].Value);
return result;
}
public void BroadcastSim(string sender, string format, params string[] args)
{
try
{
OSChatMessage c = new OSChatMessage();
c.From = sender;
c.Message = String.Format(format, args);
c.Type = ChatTypeEnum.Region; // ChatTypeEnum.Say;
c.Position = CenterOfRegion;
c.Sender = null;
c.SenderUUID = UUID.Zero;
ChannelState.OSChat(this, c, true);
}
catch (Exception ex) // IRC gate should not crash Sim
{
m_log.ErrorFormat("[IRC-Connector-{0}]: BroadcastSim Exception Trap: {1}\n{2}", idn, ex.Message, ex.StackTrace);
}
}
#region IRC Command Handlers
public void ProcessIRCCommand(string command)
{
string[] commArgs;
string c_server = m_server;
string pfx = String.Empty;
string cmd = String.Empty;
string parms = String.Empty;
// ":" indicates that a prefix is present
// There are NEVER more than 17 real
// fields. A parameter that starts with
// ":" indicates that the remainder of the
// line is a single parameter value.
commArgs = command.Split(CS_SPACE, 2);
if (commArgs[0].StartsWith(":"))
{
pfx = commArgs[0].Substring(1);
commArgs = commArgs[1].Split(CS_SPACE, 2);
}
cmd = commArgs[0];
parms = commArgs[1];
// m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}>", idn, pfx, cmd);
switch (cmd)
{
// Messages 001-004 are always sent
// following signon.
case "001": // Welcome ...
case "002": // Server information
case "003": // Welcome ...
break;
case "004": // Server information
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
commArgs = parms.Split(CS_SPACE);
c_server = commArgs[1];
m_server = c_server;
version = commArgs[2];
usermod = commArgs[3];
chanmod = commArgs[4];
break;
case "005": // Server information
break;
case "042":
case "250":
case "251":
case "252":
case "254":
case "255":
case "265":
case "266":
case "332": // Subject
case "333": // Subject owner (?)
case "353": // Name list
case "366": // End-of-Name list marker
case "372": // MOTD body
case "375": // MOTD start
// m_log.InfoFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
break;
case "376": // MOTD end
// m_log.InfoFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
motd = true;
break;
case "451": // Not registered
break;
case "433": // Nickname in use
// Gen a new name
m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
m_log.ErrorFormat("[IRC-Connector-{0}]: [{1}] IRC SERVER reports NicknameInUse, trying {2}", idn, cmd, m_nick);
// Retry
m_writer.WriteLine(String.Format("NICK {0}", m_nick));
m_writer.Flush();
m_writer.WriteLine(m_user);
m_writer.Flush();
m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
m_writer.Flush();
break;
case "479": // Bad channel name, etc. This will never work, so disable the connection
m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE, 2)[1]);
m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] Connector disabled", idn, cmd);
m_enabled = false;
m_connected = false;
m_pending = false;
break;
case "NOTICE":
// m_log.WarnFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
break;
case "ERROR":
m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE, 2)[1]);
if (parms.Contains("reconnect too fast"))
ICCD_PERIOD++;
m_pending = false;
Reconnect();
break;
case "PING":
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
m_writer.WriteLine(String.Format("PONG {0}", parms));
m_writer.Flush();
break;
case "PONG":
break;
case "JOIN":
if (m_pending)
{
m_log.InfoFormat("[IRC-Connector-{0}] [{1}] Connected", idn, cmd);
m_pending = false;
}
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
eventIrcJoin(pfx, cmd, parms);
break;
case "PART":
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
eventIrcPart(pfx, cmd, parms);
break;
case "MODE":
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
eventIrcMode(pfx, cmd, parms);
break;
case "NICK":
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
eventIrcNickChange(pfx, cmd, parms);
break;
case "KICK":
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
eventIrcKick(pfx, cmd, parms);
break;
case "QUIT":
m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
eventIrcQuit(pfx, cmd, parms);
break;
default:
m_log.DebugFormat("[IRC-Connector-{0}] Command '{1}' ignored, parms = {2}", idn, cmd, parms);
break;
}
// m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}> complete", idn, pfx, cmd);
}
public void eventIrcJoin(string prefix, string command, string parms)
{
string[] args = parms.Split(CS_SPACE, 2);
string IrcUser = prefix.Split('!')[0];
string IrcChannel = args[0];
if (IrcChannel.StartsWith(":"))
IrcChannel = IrcChannel.Substring(1);
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCJoin {1}:{2}", idn, m_server, m_ircChannel);
BroadcastSim(IrcUser, "/me joins {0}", IrcChannel);
}
public void eventIrcPart(string prefix, string command, string parms)
{
string[] args = parms.Split(CS_SPACE, 2);
string IrcUser = prefix.Split('!')[0];
string IrcChannel = args[0];
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCPart {1}:{2}", idn, m_server, m_ircChannel);
BroadcastSim(IrcUser, "/me parts {0}", IrcChannel);
}
public void eventIrcMode(string prefix, string command, string parms)
{
string[] args = parms.Split(CS_SPACE, 2);
string UserMode = args[1];
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCMode {1}:{2}", idn, m_server, m_ircChannel);
if (UserMode.Substring(0, 1) == ":")
{
UserMode = UserMode.Remove(0, 1);
}
}
public void eventIrcNickChange(string prefix, string command, string parms)
{
string[] args = parms.Split(CS_SPACE, 2);
string UserOldNick = prefix.Split('!')[0];
string UserNewNick = args[0].Remove(0, 1);
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCNickChange {1}:{2}", idn, m_server, m_ircChannel);
BroadcastSim(UserOldNick, "/me is now known as {0}", UserNewNick);
}
public void eventIrcKick(string prefix, string command, string parms)
{
string[] args = parms.Split(CS_SPACE, 3);
string UserKicker = prefix.Split('!')[0];
string IrcChannel = args[0];
string UserKicked = args[1];
string KickMessage = args[2];
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCKick {1}:{2}", idn, m_server, m_ircChannel);
BroadcastSim(UserKicker, "/me kicks kicks {0} off {1} saying \"{2}\"", UserKicked, IrcChannel, KickMessage);
if (UserKicked == m_nick)
{
BroadcastSim(m_nick, "Hey, that was me!!!");
}
}
public void eventIrcQuit(string prefix, string command, string parms)
{
string IrcUser = prefix.Split('!')[0];
string QuitMessage = parms;
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCQuit {1}:{2}", idn, m_server, m_ircChannel);
BroadcastSim(IrcUser, "/me quits saying \"{0}\"", QuitMessage);
}
#endregion
#region Connector Watch Dog
// A single watch dog monitors extant connectors and makes sure that they
// are re-connected as necessary. If a connector IS connected, then it is
// pinged, but only if a PING period has elapsed.
protected static void WatchdogHandler(Object source, ElapsedEventArgs args)
{
// m_log.InfoFormat("[IRC-Watchdog] Status scan, pdk = {0}, icc = {1}", _pdk_, _icc_);
_pdk_ = (_pdk_ + 1) % PING_PERIOD; // cycle the ping trigger
_icc_++; // increment the inter-consecutive-connect-delay counter
lock (m_connectors)
foreach (IRCConnector connector in m_connectors)
{
// m_log.InfoFormat("[IRC-Watchdog] Scanning {0}", connector);
if (connector.Enabled)
{
if (!connector.Connected)
{
try
{
// m_log.DebugFormat("[IRC-Watchdog] Connecting {1}:{2}", connector.idn, connector.m_server, connector.m_ircChannel);
connector.Connect();
}
catch (Exception e)
{
m_log.ErrorFormat("[IRC-Watchdog] Exception on connector {0}: {1} ", connector.idn, e.Message);
}
}
else
{
if (connector.m_pending)
{
if (connector.m_timeout == 0)
{
m_log.ErrorFormat("[IRC-Watchdog] Login timed-out for connector {0}, reconnecting", connector.idn);
connector.Reconnect();
}
else
connector.m_timeout--;
}
// Being marked connected is not enough to ping. Socket establishment can sometimes take a long
// time, in which case the watch dog might try to ping the server before the socket has been
// set up, with nasty side-effects.
else if (_pdk_ == 0)
{
try
{
connector.m_writer.WriteLine(String.Format("PING :{0}", connector.m_server));
connector.m_writer.Flush();
}
catch (Exception e)
{
m_log.ErrorFormat("[IRC-PingRun] Exception on connector {0}: {1} ", connector.idn, e.Message);
m_log.Debug(e);
connector.Reconnect();
}
}
}
}
}
// m_log.InfoFormat("[IRC-Watchdog] Status scan completed");
}
#endregion
}
}