clockwerk-opensim-stable – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27  
28 using System;
29 using System.Timers;
30 using System.Collections.Generic;
31 using System.IO;
32 using System.Net.Sockets;
33 using System.Reflection;
34 using System.Text.RegularExpressions;
35 using System.Threading;
36 using OpenMetaverse;
37 using log4net;
38 using Nini.Config;
39 using OpenSim.Framework;
40 using OpenSim.Framework.Monitoring;
41 using OpenSim.Region.Framework.Interfaces;
42 using OpenSim.Region.Framework.Scenes;
43  
44 namespace OpenSim.Region.OptionalModules.Avatar.Chat
45 {
46 public class IRCConnector
47 {
48  
49 #region Global (static) state
50  
51 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
52  
53 // Local constants
54  
55 private static readonly Vector3 CenterOfRegion = new Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 20);
56 private static readonly char[] CS_SPACE = { ' ' };
57  
58 private const int WD_INTERVAL = 1000; // base watchdog interval
59 private static int PING_PERIOD = 15; // WD intervals per PING
60 private static int ICCD_PERIOD = 10; // WD intervals between Connects
61 private static int L_TIMEOUT = 25; // Login time out interval
62  
63 private static int _idk_ = 0; // core connector identifier
64 private static int _pdk_ = 0; // ping interval counter
65 private static int _icc_ = ICCD_PERIOD; // IRC connect counter
66  
67 // List of configured connectors
68  
69 private static List<IRCConnector> m_connectors = new List<IRCConnector>();
70  
71 // Watchdog state
72  
73 private static System.Timers.Timer m_watchdog = null;
74  
75 // The watch-dog gets started as soon as the class is instantiated, and
76 // ticks once every second (WD_INTERVAL)
77  
78 static IRCConnector()
79 {
80 m_log.DebugFormat("[IRC-Connector]: Static initialization started");
81 m_watchdog = new System.Timers.Timer(WD_INTERVAL);
82 m_watchdog.Elapsed += new ElapsedEventHandler(WatchdogHandler);
83 m_watchdog.AutoReset = true;
84 m_watchdog.Start();
85 m_log.DebugFormat("[IRC-Connector]: Static initialization complete");
86 }
87  
88 #endregion
89  
90 #region Instance state
91  
92 // Connector identity
93  
94 internal int idn = _idk_++;
95  
96 // How many regions depend upon this connection
97 // This count is updated by the ChannelState object and reflects the sum
98 // of the region clients associated with the set of associated channel
99 // state instances. That's why it cannot be managed here.
100  
101 internal int depends = 0;
102  
103 // This variable counts the number of resets that have been performed
104 // on the connector. When a listener thread terminates, it checks to
105 // see of the reset count has changed before it schedules another
106 // reset.
107  
108 internal int m_resetk = 0;
109  
110 // Working threads
111  
112 private Thread m_listener = null;
113  
114 private Object msyncConnect = new Object();
115  
116 internal bool m_randomizeNick = true; // add random suffix
117 internal string m_baseNick = null; // base name for randomizing
118 internal string m_nick = null; // effective nickname
119  
120 public string Nick // Public property
121 {
122 get { return m_nick; }
123 set { m_nick = value; }
124 }
125  
126 private bool m_enabled = false; // connector enablement
127 public bool Enabled
128 {
129 get { return m_enabled; }
130 }
131  
132 private bool m_connected = false; // connection status
133 private bool m_pending = false; // login disposition
134 private int m_timeout = L_TIMEOUT; // login timeout counter
135 public bool Connected
136 {
137 get { return m_connected; }
138 }
139  
140 private string m_ircChannel; // associated channel id
141 public string IrcChannel
142 {
143 get { return m_ircChannel; }
144 set { m_ircChannel = value; }
145 }
146  
147 private uint m_port = 6667; // session port
148 public uint Port
149 {
150 get { return m_port; }
151 set { m_port = value; }
152 }
153  
154 private string m_server = null; // IRC server name
155 public string Server
156 {
157 get { return m_server; }
158 set { m_server = value; }
159 }
160 private string m_password = null;
161 public string Password
162 {
163 get { return m_password; }
164 set { m_password = value; }
165 }
166  
167 private string m_user = "USER OpenSimBot 8 * :I'm an OpenSim to IRC bot";
168 public string User
169 {
170 get { return m_user; }
171 }
172  
173 // Network interface
174  
175 private TcpClient m_tcp;
176 private NetworkStream m_stream = null;
177 private StreamReader m_reader;
178 private StreamWriter m_writer;
179  
180 // Channel characteristic info (if available)
181  
182 internal string usermod = String.Empty;
183 internal string chanmod = String.Empty;
184 internal string version = String.Empty;
185 internal bool motd = false;
186  
187 #endregion
188  
189 #region connector instance management
190  
191 internal IRCConnector(ChannelState cs)
192 {
193  
194 // Prepare network interface
195  
196 m_tcp = null;
197 m_writer = null;
198 m_reader = null;
199  
200 // Setup IRC session parameters
201  
202 m_server = cs.Server;
203 m_password = cs.Password;
204 m_baseNick = cs.BaseNickname;
205 m_randomizeNick = cs.RandomizeNickname;
206 m_ircChannel = cs.IrcChannel;
207 m_port = cs.Port;
208 m_user = cs.User;
209  
210 if (m_watchdog == null)
211 {
212 // Non-differentiating
213  
214 ICCD_PERIOD = cs.ConnectDelay;
215 PING_PERIOD = cs.PingDelay;
216  
217 // Smaller values are not reasonable
218  
219 if (ICCD_PERIOD < 5)
220 ICCD_PERIOD = 5;
221  
222 if (PING_PERIOD < 5)
223 PING_PERIOD = 5;
224  
225 _icc_ = ICCD_PERIOD; // get started right away!
226  
227 }
228  
229 // The last line of defense
230  
231 if (m_server == null || m_baseNick == null || m_ircChannel == null || m_user == null)
232 throw new Exception("Invalid connector configuration");
233  
234 // Generate an initial nickname
235  
236 if (m_randomizeNick)
237 m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
238 else
239 m_nick = m_baseNick;
240  
241 m_log.InfoFormat("[IRC-Connector-{0}]: Initialization complete", idn);
242  
243 }
244  
245 ~IRCConnector()
246 {
247 m_watchdog.Stop();
248 Close();
249 }
250  
251 // Mark the connector as connectable. Harmless if already enabled.
252  
253 public void Open()
254 {
255 if (!m_enabled)
256 {
257  
258 if (!Connected)
259 {
260 Connect();
261 }
262  
263 lock (m_connectors)
264 m_connectors.Add(this);
265  
266 m_enabled = true;
267  
268 }
269 }
270  
271 // Only close the connector if the dependency count is zero.
272  
273 public void Close()
274 {
275  
276 m_log.InfoFormat("[IRC-Connector-{0}] Closing", idn);
277  
278 lock (msyncConnect)
279 {
280  
281 if ((depends == 0) && Enabled)
282 {
283  
284 m_enabled = false;
285  
286 if (Connected)
287 {
288 m_log.DebugFormat("[IRC-Connector-{0}] Closing interface", idn);
289  
290 // Cleanup the IRC session
291  
292 try
293 {
294 m_writer.WriteLine(String.Format("QUIT :{0} to {1} wormhole to {2} closing",
295 m_nick, m_ircChannel, m_server));
296 m_writer.Flush();
297 }
298 catch (Exception) { }
299  
300  
301 m_connected = false;
302  
303 try { m_writer.Close(); }
304 catch (Exception) { }
305 try { m_reader.Close(); }
306 catch (Exception) { }
307 try { m_stream.Close(); }
308 catch (Exception) { }
309 try { m_tcp.Close(); }
310 catch (Exception) { }
311  
312 }
313  
314 lock (m_connectors)
315 m_connectors.Remove(this);
316  
317 }
318 }
319  
320 m_log.InfoFormat("[IRC-Connector-{0}] Closed", idn);
321  
322 }
323  
324 #endregion
325  
326 #region session management
327  
328 // Connect to the IRC server. A connector should always be connected, once enabled
329  
330 public void Connect()
331 {
332  
333 if (!m_enabled)
334 return;
335  
336 // Delay until next WD cycle if this is too close to the last start attempt
337  
338 while (_icc_ < ICCD_PERIOD)
339 return;
340  
341 m_log.DebugFormat("[IRC-Connector-{0}]: Connection request for {1} on {2}:{3}", idn, m_nick, m_server, m_ircChannel);
342  
343 lock (msyncConnect)
344 {
345  
346 _icc_ = 0;
347  
348 try
349 {
350  
351 if (m_connected) return;
352  
353 m_connected = true;
354 m_pending = true;
355 m_timeout = L_TIMEOUT;
356  
357 m_tcp = new TcpClient(m_server, (int)m_port);
358 m_stream = m_tcp.GetStream();
359 m_reader = new StreamReader(m_stream);
360 m_writer = new StreamWriter(m_stream);
361  
362 m_log.InfoFormat("[IRC-Connector-{0}]: Connected to {1}:{2}", idn, m_server, m_port);
363  
364 m_listener = new Thread(new ThreadStart(ListenerRun));
365 m_listener.Name = "IRCConnectorListenerThread";
366 m_listener.IsBackground = true;
367 m_listener.Start();
368  
369 // This is the message order recommended by RFC 2812
370 if (m_password != null)
371 m_writer.WriteLine(String.Format("PASS {0}", m_password));
372 m_writer.WriteLine(String.Format("NICK {0}", m_nick));
373 m_writer.Flush();
374 m_writer.WriteLine(m_user);
375 m_writer.Flush();
376 m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
377 m_writer.Flush();
378  
379 m_log.InfoFormat("[IRC-Connector-{0}]: {1} has asked to join {2}", idn, m_nick, m_ircChannel);
380  
381 }
382 catch (Exception e)
383 {
384 m_log.ErrorFormat("[IRC-Connector-{0}] cannot connect {1} to {2}:{3}: {4}",
385 idn, m_nick, m_server, m_port, e.Message);
386 // It might seem reasonable to reset connected and pending status here
387 // Seeing as we know that the login has failed, but if we do that, then
388 // connection will be retried each time the interconnection interval
389 // expires. By leaving them as they are, the connection will be retried
390 // when the login timeout expires. Which is preferred.
391 }
392  
393 }
394  
395 return;
396  
397 }
398  
399 // Reconnect is used to force a re-cycle of the IRC connection. Should generally
400 // be a transparent event
401  
402 public void Reconnect()
403 {
404 m_log.DebugFormat("[IRC-Connector-{0}]: Reconnect request for {1} on {2}:{3}", idn, m_nick, m_server, m_ircChannel);
405  
406 // Don't do this if a Connect is in progress...
407  
408 lock (msyncConnect)
409 {
410  
411 if (m_connected)
412 {
413  
414 m_log.InfoFormat("[IRC-Connector-{0}] Resetting connector", idn);
415  
416 // Mark as disconnected. This will allow the listener thread
417 // to exit if still in-flight.
418  
419  
420 // The listener thread is not aborted - it *might* actually be
421 // the thread that is running the Reconnect! Instead just close
422 // the socket and it will disappear of its own accord, once this
423 // processing is completed.
424  
425 try { m_writer.Close(); }
426 catch (Exception) { }
427 try { m_reader.Close(); }
428 catch (Exception) { }
429 try { m_tcp.Close(); }
430 catch (Exception) { }
431  
432 m_connected = false;
433 m_pending = false;
434 m_resetk++;
435  
436 }
437  
438 }
439  
440 Connect();
441  
442 }
443  
444 #endregion
445  
446 #region Outbound (to-IRC) message handlers
447  
448 public void PrivMsg(string pattern, string from, string region, string msg)
449 {
450  
451 // m_log.DebugFormat("[IRC-Connector-{0}] PrivMsg to IRC from {1}: <{2}>", idn, from,
452 // String.Format(pattern, m_ircChannel, from, region, msg));
453  
454 // One message to the IRC server
455  
456 try
457 {
458 m_writer.WriteLine(pattern, m_ircChannel, from, region, msg);
459 m_writer.Flush();
460 // m_log.DebugFormat("[IRC-Connector-{0}]: PrivMsg from {1} in {2}: {3}", idn, from, region, msg);
461 }
462 catch (IOException)
463 {
464 m_log.ErrorFormat("[IRC-Connector-{0}]: PrivMsg I/O Error: disconnected from IRC server", idn);
465 Reconnect();
466 }
467 catch (Exception ex)
468 {
469 m_log.ErrorFormat("[IRC-Connector-{0}]: PrivMsg exception : {1}", idn, ex.Message);
470 m_log.Debug(ex);
471 }
472  
473 }
474  
475 public void Send(string msg)
476 {
477  
478 // m_log.DebugFormat("[IRC-Connector-{0}] Send to IRC : <{1}>", idn, msg);
479  
480 try
481 {
482 m_writer.WriteLine(msg);
483 m_writer.Flush();
484 // m_log.DebugFormat("[IRC-Connector-{0}] Sent command string: {1}", idn, msg);
485 }
486 catch (IOException)
487 {
488 m_log.ErrorFormat("[IRC-Connector-{0}] Disconnected from IRC server.(Send)", idn);
489 Reconnect();
490 }
491 catch (Exception ex)
492 {
493 m_log.ErrorFormat("[IRC-Connector-{0}] Send exception trap: {0}", idn, ex.Message);
494 m_log.Debug(ex);
495 }
496  
497 }
498  
499 #endregion
500  
501 public void ListenerRun()
502 {
503  
504 string inputLine;
505 int resetk = m_resetk;
506  
507 try
508 {
509 while (m_enabled && m_connected)
510 {
511  
512 if ((inputLine = m_reader.ReadLine()) == null)
513 throw new Exception("Listener input socket closed");
514  
515 // m_log.Info("[IRCConnector]: " + inputLine);
516  
517 if (inputLine.Contains("PRIVMSG"))
518 {
519  
520 Dictionary<string, string> data = ExtractMsg(inputLine);
521  
522 // Any chat ???
523 if (data != null)
524 {
525  
526 OSChatMessage c = new OSChatMessage();
527 c.Message = data["msg"];
528 c.Type = ChatTypeEnum.Region;
529 c.Position = CenterOfRegion;
530 c.From = data["nick"];
531 c.Sender = null;
532 c.SenderUUID = UUID.Zero;
533  
534 // Is message "\001ACTION foo bar\001"?
535 // Then change to: "/me foo bar"
536  
537 if ((1 == c.Message[0]) && c.Message.Substring(1).StartsWith("ACTION"))
538 c.Message = String.Format("/me {0}", c.Message.Substring(8, c.Message.Length - 9));
539  
540 ChannelState.OSChat(this, c, false);
541  
542 }
543  
544 }
545 else
546 {
547 ProcessIRCCommand(inputLine);
548 }
549 }
550 }
551 catch (Exception /*e*/)
552 {
553 // m_log.ErrorFormat("[IRC-Connector-{0}]: ListenerRun exception trap: {1}", idn, e.Message);
554 // m_log.Debug(e);
555 }
556  
557 // This is potentially circular, but harmless if so.
558 // The connection is marked as not connected the first time
559 // through reconnect.
560  
561 if (m_enabled && (m_resetk == resetk))
562 Reconnect();
563 }
564  
565 private Regex RE = new Regex(@":(?<nick>[\w-]*)!(?<user>\S*) PRIVMSG (?<channel>\S+) :(?<msg>.*)",
566 RegexOptions.Multiline);
567  
568 private Dictionary<string, string> ExtractMsg(string input)
569 {
570 //examines IRC commands and extracts any private messages
571 // which will then be reboadcast in the Sim
572  
573 // m_log.InfoFormat("[IRC-Connector-{0}]: ExtractMsg: {1}", idn, input);
574  
575 Dictionary<string, string> result = null;
576 MatchCollection matches = RE.Matches(input);
577  
578 // Get some direct matches $1 $4 is a
579 if ((matches.Count == 0) || (matches.Count != 1) || (matches[0].Groups.Count != 5))
580 {
581 // m_log.Info("[IRCConnector]: Number of matches: " + matches.Count);
582 // if (matches.Count > 0)
583 // {
584 // m_log.Info("[IRCConnector]: Number of groups: " + matches[0].Groups.Count);
585 // }
586 return null;
587 }
588  
589 result = new Dictionary<string, string>();
590 result.Add("nick", matches[0].Groups[1].Value);
591 result.Add("user", matches[0].Groups[2].Value);
592 result.Add("channel", matches[0].Groups[3].Value);
593 result.Add("msg", matches[0].Groups[4].Value);
594  
595 return result;
596 }
597  
598 public void BroadcastSim(string sender, string format, params string[] args)
599 {
600 try
601 {
602 OSChatMessage c = new OSChatMessage();
603 c.From = sender;
604 c.Message = String.Format(format, args);
605 c.Type = ChatTypeEnum.Region; // ChatTypeEnum.Say;
606 c.Position = CenterOfRegion;
607 c.Sender = null;
608 c.SenderUUID = UUID.Zero;
609  
610 ChannelState.OSChat(this, c, true);
611  
612 }
613 catch (Exception ex) // IRC gate should not crash Sim
614 {
615 m_log.ErrorFormat("[IRC-Connector-{0}]: BroadcastSim Exception Trap: {1}\n{2}", idn, ex.Message, ex.StackTrace);
616 }
617 }
618  
619 #region IRC Command Handlers
620  
621 public void ProcessIRCCommand(string command)
622 {
623  
624 string[] commArgs;
625 string c_server = m_server;
626  
627 string pfx = String.Empty;
628 string cmd = String.Empty;
629 string parms = String.Empty;
630  
631 // ":" indicates that a prefix is present
632 // There are NEVER more than 17 real
633 // fields. A parameter that starts with
634 // ":" indicates that the remainder of the
635 // line is a single parameter value.
636  
637 commArgs = command.Split(CS_SPACE, 2);
638  
639 if (commArgs[0].StartsWith(":"))
640 {
641 pfx = commArgs[0].Substring(1);
642 commArgs = commArgs[1].Split(CS_SPACE, 2);
643 }
644  
645 cmd = commArgs[0];
646 parms = commArgs[1];
647  
648 // m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}>", idn, pfx, cmd);
649  
650 switch (cmd)
651 {
652  
653 // Messages 001-004 are always sent
654 // following signon.
655  
656 case "001": // Welcome ...
657 case "002": // Server information
658 case "003": // Welcome ...
659 break;
660 case "004": // Server information
661 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
662 commArgs = parms.Split(CS_SPACE);
663 c_server = commArgs[1];
664 m_server = c_server;
665 version = commArgs[2];
666 usermod = commArgs[3];
667 chanmod = commArgs[4];
668 break;
669 case "005": // Server information
670 break;
671 case "042":
672 case "250":
673 case "251":
674 case "252":
675 case "254":
676 case "255":
677 case "265":
678 case "266":
679 case "332": // Subject
680 case "333": // Subject owner (?)
681 case "353": // Name list
682 case "366": // End-of-Name list marker
683 case "372": // MOTD body
684 case "375": // MOTD start
685 // m_log.InfoFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
686 break;
687 case "376": // MOTD end
688 // m_log.InfoFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
689 motd = true;
690 break;
691 case "451": // Not registered
692 break;
693 case "433": // Nickname in use
694 // Gen a new name
695 m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
696 m_log.ErrorFormat("[IRC-Connector-{0}]: [{1}] IRC SERVER reports NicknameInUse, trying {2}", idn, cmd, m_nick);
697 // Retry
698 m_writer.WriteLine(String.Format("NICK {0}", m_nick));
699 m_writer.Flush();
700 m_writer.WriteLine(m_user);
701 m_writer.Flush();
702 m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
703 m_writer.Flush();
704 break;
705 case "479": // Bad channel name, etc. This will never work, so disable the connection
706 m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE, 2)[1]);
707 m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] Connector disabled", idn, cmd);
708 m_enabled = false;
709 m_connected = false;
710 m_pending = false;
711 break;
712 case "NOTICE":
713 // m_log.WarnFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE,2)[1]);
714 break;
715 case "ERROR":
716 m_log.ErrorFormat("[IRC-Connector-{0}] [{1}] {2}", idn, cmd, parms.Split(CS_SPACE, 2)[1]);
717 if (parms.Contains("reconnect too fast"))
718 ICCD_PERIOD++;
719 m_pending = false;
720 Reconnect();
721 break;
722 case "PING":
723 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
724 m_writer.WriteLine(String.Format("PONG {0}", parms));
725 m_writer.Flush();
726 break;
727 case "PONG":
728 break;
729 case "JOIN":
730 if (m_pending)
731 {
732 m_log.InfoFormat("[IRC-Connector-{0}] [{1}] Connected", idn, cmd);
733 m_pending = false;
734 }
735 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
736 eventIrcJoin(pfx, cmd, parms);
737 break;
738 case "PART":
739 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
740 eventIrcPart(pfx, cmd, parms);
741 break;
742 case "MODE":
743 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
744 eventIrcMode(pfx, cmd, parms);
745 break;
746 case "NICK":
747 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
748 eventIrcNickChange(pfx, cmd, parms);
749 break;
750 case "KICK":
751 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
752 eventIrcKick(pfx, cmd, parms);
753 break;
754 case "QUIT":
755 m_log.DebugFormat("[IRC-Connector-{0}] [{1}] parms = <{2}>", idn, cmd, parms);
756 eventIrcQuit(pfx, cmd, parms);
757 break;
758 default:
759 m_log.DebugFormat("[IRC-Connector-{0}] Command '{1}' ignored, parms = {2}", idn, cmd, parms);
760 break;
761 }
762  
763 // m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}> complete", idn, pfx, cmd);
764  
765 }
766  
767 public void eventIrcJoin(string prefix, string command, string parms)
768 {
769 string[] args = parms.Split(CS_SPACE, 2);
770 string IrcUser = prefix.Split('!')[0];
771 string IrcChannel = args[0];
772  
773 if (IrcChannel.StartsWith(":"))
774 IrcChannel = IrcChannel.Substring(1);
775  
776 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCJoin {1}:{2}", idn, m_server, m_ircChannel);
777 BroadcastSim(IrcUser, "/me joins {0}", IrcChannel);
778 }
779  
780 public void eventIrcPart(string prefix, string command, string parms)
781 {
782 string[] args = parms.Split(CS_SPACE, 2);
783 string IrcUser = prefix.Split('!')[0];
784 string IrcChannel = args[0];
785  
786 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCPart {1}:{2}", idn, m_server, m_ircChannel);
787 BroadcastSim(IrcUser, "/me parts {0}", IrcChannel);
788 }
789  
790 public void eventIrcMode(string prefix, string command, string parms)
791 {
792 string[] args = parms.Split(CS_SPACE, 2);
793 string UserMode = args[1];
794  
795 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCMode {1}:{2}", idn, m_server, m_ircChannel);
796 if (UserMode.Substring(0, 1) == ":")
797 {
798 UserMode = UserMode.Remove(0, 1);
799 }
800 }
801  
802 public void eventIrcNickChange(string prefix, string command, string parms)
803 {
804 string[] args = parms.Split(CS_SPACE, 2);
805 string UserOldNick = prefix.Split('!')[0];
806 string UserNewNick = args[0].Remove(0, 1);
807  
808 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCNickChange {1}:{2}", idn, m_server, m_ircChannel);
809 BroadcastSim(UserOldNick, "/me is now known as {0}", UserNewNick);
810 }
811  
812 public void eventIrcKick(string prefix, string command, string parms)
813 {
814 string[] args = parms.Split(CS_SPACE, 3);
815 string UserKicker = prefix.Split('!')[0];
816 string IrcChannel = args[0];
817 string UserKicked = args[1];
818 string KickMessage = args[2];
819  
820 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCKick {1}:{2}", idn, m_server, m_ircChannel);
821 BroadcastSim(UserKicker, "/me kicks kicks {0} off {1} saying \"{2}\"", UserKicked, IrcChannel, KickMessage);
822  
823 if (UserKicked == m_nick)
824 {
825 BroadcastSim(m_nick, "Hey, that was me!!!");
826 }
827  
828 }
829  
830 public void eventIrcQuit(string prefix, string command, string parms)
831 {
832 string IrcUser = prefix.Split('!')[0];
833 string QuitMessage = parms;
834  
835 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCQuit {1}:{2}", idn, m_server, m_ircChannel);
836 BroadcastSim(IrcUser, "/me quits saying \"{0}\"", QuitMessage);
837 }
838  
839 #endregion
840  
841 #region Connector Watch Dog
842  
843 // A single watch dog monitors extant connectors and makes sure that they
844 // are re-connected as necessary. If a connector IS connected, then it is
845 // pinged, but only if a PING period has elapsed.
846  
847 protected static void WatchdogHandler(Object source, ElapsedEventArgs args)
848 {
849  
850 // m_log.InfoFormat("[IRC-Watchdog] Status scan, pdk = {0}, icc = {1}", _pdk_, _icc_);
851  
852 _pdk_ = (_pdk_ + 1) % PING_PERIOD; // cycle the ping trigger
853 _icc_++; // increment the inter-consecutive-connect-delay counter
854  
855 lock (m_connectors)
856 foreach (IRCConnector connector in m_connectors)
857 {
858  
859 // m_log.InfoFormat("[IRC-Watchdog] Scanning {0}", connector);
860  
861 if (connector.Enabled)
862 {
863 if (!connector.Connected)
864 {
865 try
866 {
867 // m_log.DebugFormat("[IRC-Watchdog] Connecting {1}:{2}", connector.idn, connector.m_server, connector.m_ircChannel);
868 connector.Connect();
869 }
870 catch (Exception e)
871 {
872 m_log.ErrorFormat("[IRC-Watchdog] Exception on connector {0}: {1} ", connector.idn, e.Message);
873 }
874 }
875 else
876 {
877  
878 if (connector.m_pending)
879 {
880 if (connector.m_timeout == 0)
881 {
882 m_log.ErrorFormat("[IRC-Watchdog] Login timed-out for connector {0}, reconnecting", connector.idn);
883 connector.Reconnect();
884 }
885 else
886 connector.m_timeout--;
887 }
888  
889 // Being marked connected is not enough to ping. Socket establishment can sometimes take a long
890 // time, in which case the watch dog might try to ping the server before the socket has been
891 // set up, with nasty side-effects.
892  
893 else if (_pdk_ == 0)
894 {
895 try
896 {
897 connector.m_writer.WriteLine(String.Format("PING :{0}", connector.m_server));
898 connector.m_writer.Flush();
899 }
900 catch (Exception e)
901 {
902 m_log.ErrorFormat("[IRC-PingRun] Exception on connector {0}: {1} ", connector.idn, e.Message);
903 m_log.Debug(e);
904 connector.Reconnect();
905 }
906 }
907  
908 }
909 }
910 }
911  
912 // m_log.InfoFormat("[IRC-Watchdog] Status scan completed");
913  
914 }
915  
916 #endregion
917  
918 }
919 }