opensim – Blame information for rev 1

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