* Now use config file value
[circ.git] / Circ.Backend.Cil / CilIrcConnection.cs
blob177c4ff6dbae083d00a33c6d678067ffe3401913
1 #region License
2 /* Circ.Cil : Circ's Irc Library, main backend for Circ
3 * Copyright (C) 2007 LAVAL Jérémie
5 * This file is licensed under the terms of the LGPL.
7 * For the complete licence see the file COPYING.
8 */
9 #endregion
10 using System;
11 using System.IO;
12 using System.Net;
13 using System.Net.Sockets;
14 using System.Threading;
15 using System.Collections.Generic;
16 using Circ.Backend;
18 namespace Circ.Cil
20 public sealed class CilIrcConnection: IrcConnection
22 // State info
23 IList<IrcChannel> channelsJoined = new List<IrcChannel>();
25 // Config
26 //const int DefaultTime = 1000;
27 int messageCount = 0;
28 bool identified = false;
30 // Tcpclient
31 TcpClient client = new TcpClient();
33 // Streams access
34 NetworkStream ns;
35 StreamWriter sw;
36 StreamReader sr;
38 // Threads
39 //Timer readTimer = new Timer(DefaultTime);
40 Thread readThread;
41 volatile int nbTimes = 0;
43 // Parser
44 IParser parser = DefaultParser.Instance;
46 public override void Connect()
48 if (client.Connected)
49 return;
51 // Event helpers
52 this.MessageReceived += AutomaticPong;
53 this.MessageReceived += Identification;
55 // Set common option
56 client.LingerState = new LingerOption(true, 2);
58 ConnectionEventArgs e = new ConnectionEventArgs(this.Server, this.Info.Port);
60 try {
61 OnConnecting(e);
62 client.Connect(Info.Server, Info.Port);
63 } catch (Exception ex) {
64 client.Close();
65 throw new ConnectionException("The connection to the server wasn't successful : "+ex.Message, ex);
68 ns = client.GetStream();
69 sw = new StreamWriter(ns, Info.Charset);
70 sr = new StreamReader(ns, Info.Charset);
72 // Thread initialization
73 readThread = new Thread(WorkerRead);
74 readThread.IsBackground = true;
75 readThread.Name = "Read Thread";
76 readThread.Priority = ThreadPriority.BelowNormal;
77 readThread.Start();
79 // First identification pass try
80 if (!string.IsNullOrEmpty(Info.Password))
81 SendRawMessage(Rfc.Pass(Info.Password));
82 SendRawMessage(Rfc.Nick(Info.Pseudo));
83 SendRawMessage(Rfc.User(Info.Pseudo, Info.RealName));
85 // Inform that we are connected. Warning : Connected doesn't mean Identified
86 // (i.e. you can't join channels until you are identified)
87 OnConnected(e);
90 public override void Disconnect()
92 if (client == null || (client != null && !client.Connected))
93 return;
94 // Send quit message
95 SendRawMessage(Rfc.Quit("Powered by Monologue"));
96 sw.Flush();
99 protected override void Dispose (bool managedRes)
101 foreach (IrcChannel chan in JoinedChannels)
102 chan.CloseChannel();
103 // Disconnect the machine
104 this.Disconnect();
106 if (managedRes)
108 try {
109 if (sw != null)
110 sw.Dispose();
111 if (sr != null)
112 sr.Dispose();
113 } catch {}
115 client.Close();
119 public override void SendRawMessage(string message)
121 if (string.IsNullOrEmpty(message))
122 throw new ArgumentNullException("message");
124 try {
125 // Check if the message is correctly finished with a \r\n sequence
126 if (message.EndsWith(Rfc.Crlf))
127 sw.Write(message);
128 else
129 sw.WriteLine(message);
131 // Flush the data
132 sw.Flush();
133 } catch {
134 // TODO: maybe use an event instead of this
135 throw new ConnectionException("Error, the inner socket has been shut down");
137 OnMessageSent(new MessageEventArgs(message));
140 public override IrcChannel JoinChannel(string channelName)
142 IrcChannel temp = new CilIrcChannel(channelName, this);
143 channelsJoined.Add(temp);
144 OnChannelCreated(new ChannelEventArgs(temp));
145 return temp;
148 public override IrcChannel[] JoinedChannels {
149 get {
150 IrcChannel[] temp = new IrcChannel[channelsJoined.Count];
151 channelsJoined.CopyTo(temp, 0);
152 return temp;
156 #region Properties
157 public override string Nick {
158 get { return Info.Pseudo; }
159 set {
160 if (string.IsNullOrEmpty(value))
161 return;
163 Info.Pseudo = value;
164 UpdateNickname();
168 public override string RealName {
169 get { return Info.RealName; }
172 public override string Server {
173 get { return Info.Server; }
176 public bool IsIdentified {
177 get { return identified; }
179 #endregion
181 #region private helpers methods
182 private void UpdateNickname()
184 this.SendRawMessage(Rfc.Nick(Info.Pseudo));
187 private void WorkerRead()
189 int sleepTime = 0;
190 string temp;
192 // Main loop
193 while (true) {
194 if (!client.Connected)
195 return;
197 // Sleep if the channel is idle
198 if (Info.UsePassiveMode && client.Available == 0 && identified) {
199 if (sleepTime < 5000 && ++nbTimes % 1500 == 0)
200 sleepTime += 20;
201 if (nbTimes == int.MaxValue) {
202 nbTimes = 0;
204 if (sleepTime != 0)
205 Thread.Sleep(sleepTime);
206 } else {
207 sleepTime = 0;
210 try {
211 // Process data
212 while ((temp = sr.ReadLine()) != null)
213 OnMessageReceived(new MessageEventArgs(temp));
214 } catch (IOException ex) {
215 Logger.Error(string.Format("Exception while reading data. Inner Exception : {O} ::: Error code : {1}", ex.InnerException.Message,
216 ((ex.InnerException is SocketException) ? ((SocketException)ex.InnerException).ErrorCode.ToString() : "No code")), ex);
218 // Let other things happens
219 Thread.Sleep(500);
223 // Big hack, but don't know why some server are reluctant to accept my Nick, User and all messages
224 // thus if they want the war so be it (I repeatidly send them the connection commands until they accept
225 // me)
226 private void Identification(object sender, MessageEventArgs e)
228 // Some server return an 451 error and other nothing
229 if (((parser.RetrieveMessageType(e.Message) == MessageType.Reply &&
230 parser.RetrieveNumericalReply(e.Message) == 451)) || ++messageCount == 3) {
231 // Resend the connection commands
232 if (!string.IsNullOrEmpty(Info.Password))
233 SendRawMessage(Rfc.Pass(Info.Password));
234 SendRawMessage(Rfc.Nick(Info.Pseudo));
235 SendRawMessage(Rfc.User(Info.Pseudo, Info.RealName));
237 if (parser.RetrieveMessageType(e.Message) == MessageType.Reply
238 && parser.RetrieveNumericalReply(e.Message) == 1) {
239 this.MessageReceived -= Identification;
240 OnIdentified(new ConnectionEventArgs(this.Server, Info.Port));
241 messageCount = 0;
242 identified = true;
246 // An automatic wrapper which call PONG at each PING messages
247 private void AutomaticPong(object sender, MessageEventArgs e)
249 if (string.IsNullOrEmpty(e.Message))
250 return;
252 if (parser.RetrieveMessageType(e.Message) == MessageType.Action
253 && parser.RetrieveAction(e.Message) == ActionType.Ping) {
255 SendRawMessage(Rfc.Pong(parser.RetrieveParameters(e.Message)));
258 #endregion