OpenSimMirror/OpenSim/Region/OptionalModules/Avatar/Chat/IRCConnector.cs

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);
Watchdog.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
}
}