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