Query buffers are now automatically renamed on nickchanges.
[quassel.git] / src / core / networkconnection.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-08 by the Quassel Project                          *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 #include "networkconnection.h"
21
22 #include <QMetaObject>
23 #include <QMetaMethod>
24 #include <QDateTime>
25
26 #include "util.h"
27 #include "core.h"
28 #include "coresession.h"
29
30 #include "ircchannel.h"
31 #include "ircuser.h"
32 #include "network.h"
33 #include "identity.h"
34
35 #include "ircserverhandler.h"
36 #include "userinputhandler.h"
37 #include "ctcphandler.h"
38
39 NetworkConnection::NetworkConnection(Network *network, CoreSession *session, const QVariant &state) : QObject(network),
40     _connectionState(Network::Disconnected),
41     _network(network),
42     _coreSession(session),
43     _ircServerHandler(new IrcServerHandler(this)),
44     _userInputHandler(new UserInputHandler(this)),
45     _ctcpHandler(new CtcpHandler(this)),
46     _previousState(state),
47     _autoReconnectCount(0)
48 {
49   _autoReconnectTimer.setSingleShot(true);
50   connect(&_autoReconnectTimer, SIGNAL(timeout()), this, SLOT(doAutoReconnect()));
51
52   connect(network, SIGNAL(currentServerSet(const QString &)), this, SLOT(networkInitialized(const QString &)));
53   connect(network, SIGNAL(useAutoReconnectSet(bool)), this, SLOT(autoReconnectSettingsChanged()));
54   connect(network, SIGNAL(autoReconnectIntervalSet(quint32)), this, SLOT(autoReconnectSettingsChanged()));
55   connect(network, SIGNAL(autoReconnectRetriesSet(quint16)), this, SLOT(autoReconnectSettingsChanged()));
56
57   connect(&socket, SIGNAL(connected()), this, SLOT(socketConnected()));
58   connect(&socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
59   connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
60   connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
61   connect(&socket, SIGNAL(readyRead()), this, SLOT(socketHasData()));
62
63   connect(_ircServerHandler, SIGNAL(nickChanged(const QString &, const QString &)),
64           this, SLOT(nickChanged(const QString &, const QString &)));
65 }
66
67 NetworkConnection::~NetworkConnection() {
68   if(connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting)
69     disconnectFromIrc();
70   delete _ircServerHandler;
71   delete _userInputHandler;
72   delete _ctcpHandler;
73 }
74
75 bool NetworkConnection::isConnected() const {
76   // return socket.state() == QAbstractSocket::ConnectedState;
77   return connectionState() == Network::Initialized;
78 }
79
80 Network::ConnectionState NetworkConnection::connectionState() const {
81   return _connectionState;
82 }
83
84 void NetworkConnection::setConnectionState(Network::ConnectionState state) {
85   _connectionState = state;
86   network()->setConnectionState(state);
87   emit connectionStateChanged(state);
88 }
89
90 NetworkId NetworkConnection::networkId() const {
91   return network()->networkId();
92 }
93
94 QString NetworkConnection::networkName() const {
95   return network()->networkName();
96 }
97
98 Identity *NetworkConnection::identity() const {
99   return coreSession()->identity(network()->identity());
100 }
101
102 Network *NetworkConnection::network() const {
103   return _network;
104 }
105
106 CoreSession *NetworkConnection::coreSession() const {
107   return _coreSession;
108 }
109
110 IrcServerHandler *NetworkConnection::ircServerHandler() const {
111   return _ircServerHandler;
112 }
113
114 UserInputHandler *NetworkConnection::userInputHandler() const {
115   return _userInputHandler;
116 }
117
118 CtcpHandler *NetworkConnection::ctcpHandler() const {
119   return _ctcpHandler;
120 }
121
122 QString NetworkConnection::serverDecode(const QByteArray &string) const {
123   return network()->decodeServerString(string);
124 }
125
126 QString NetworkConnection::channelDecode(const QString &bufferName, const QByteArray &string) const {
127   if(!bufferName.isEmpty()) {
128     IrcChannel *channel = network()->ircChannel(bufferName);
129     if(channel) return channel->decodeString(string);
130   }
131   return network()->decodeString(string);
132 }
133
134 QString NetworkConnection::userDecode(const QString &userNick, const QByteArray &string) const {
135   IrcUser *user = network()->ircUser(userNick);
136   if(user) return user->decodeString(string);
137   return network()->decodeString(string);
138 }
139
140 QByteArray NetworkConnection::serverEncode(const QString &string) const {
141   return network()->encodeServerString(string);
142 }
143
144 QByteArray NetworkConnection::channelEncode(const QString &bufferName, const QString &string) const {
145   if(!bufferName.isEmpty()) {
146     IrcChannel *channel = network()->ircChannel(bufferName);
147     if(channel) return channel->encodeString(string);
148   }
149   return network()->encodeString(string);
150 }
151
152 QByteArray NetworkConnection::userEncode(const QString &userNick, const QString &string) const {
153   IrcUser *user = network()->ircUser(userNick);
154   if(user) return user->encodeString(string);
155   return network()->encodeString(string);
156 }
157
158 void NetworkConnection::autoReconnectSettingsChanged() {
159   if(!network()->useAutoReconnect()) {
160     _autoReconnectTimer.stop();
161     _autoReconnectCount = 0;
162   } else {
163     _autoReconnectTimer.setInterval(network()->autoReconnectInterval() * 1000);
164     if(_autoReconnectCount != 0) {
165       if(network()->unlimitedReconnectRetries()) _autoReconnectCount = -1;
166       else _autoReconnectCount = network()->autoReconnectRetries();
167     }
168   }
169 }
170
171 void NetworkConnection::connectToIrc(bool reconnecting) {
172   if(!reconnecting && network()->useAutoReconnect() && _autoReconnectCount == 0) {
173     _autoReconnectTimer.setInterval(network()->autoReconnectInterval() * 1000);
174     if(network()->unlimitedReconnectRetries()) _autoReconnectCount = -1;
175     else _autoReconnectCount = network()->autoReconnectRetries();
176   }
177   QVariantList serverList = network()->serverList();
178   Identity *identity = coreSession()->identity(network()->identity());
179   if(!serverList.count()) {
180     qWarning() << "Server list empty, ignoring connect request!";
181     return;
182   }
183   if(!identity) {
184     qWarning() << "Invalid identity configures, ignoring connect request!";
185     return;
186   }
187   // TODO implement cycling / random servers
188   QString host = serverList[0].toMap()["Host"].toString();
189   quint16 port = serverList[0].toMap()["Port"].toUInt();
190   displayStatusMsg(tr("Connecting to %1:%2...").arg(host).arg(port));
191   displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Connecting to %1:%2...").arg(host).arg(port));
192   socket.connectToHost(host, port);
193 }
194
195 void NetworkConnection::networkInitialized(const QString &currentServer) {
196   if(currentServer.isEmpty()) return;
197
198   if(network()->useAutoReconnect() && !network()->unlimitedReconnectRetries()) {
199     _autoReconnectCount = network()->autoReconnectRetries(); // reset counter
200   }
201
202   sendPerform();
203
204     // rejoin channels we've been in
205   QStringList chans = _previousState.toStringList();
206   if(chans.count() > 0) {
207     qDebug() << "autojoining" << chans;
208     QVariantList list;
209     list << serverEncode(chans.join(",")); // TODO add channel passwords
210     putCmd("JOIN", list);  // FIXME check for 512 byte limit!
211   }
212   // delete _previousState, we won't need it again
213   _previousState = QVariant();
214   // now we are initialized
215   setConnectionState(Network::Initialized);
216   network()->setConnected(true);
217   emit connected(networkId());
218 }
219
220 void NetworkConnection::sendPerform() {
221   BufferInfo statusBuf = Core::bufferInfo(coreSession()->user(), network()->networkId(), BufferInfo::StatusBuffer);
222   // do auto identify
223   if(network()->useAutoIdentify() && !network()->autoIdentifyService().isEmpty() && !network()->autoIdentifyPassword().isEmpty()) {
224     userInputHandler()->handleMsg(statusBuf, QString("%1 IDENTIFY %2").arg(network()->autoIdentifyService(), network()->autoIdentifyPassword()));
225   }
226   // send perform list
227   foreach(QString line, network()->perform()) {
228     if(!line.isEmpty()) userInput(statusBuf, line);
229   }
230 }
231
232 QVariant NetworkConnection::state() const {
233   IrcUser *me = network()->ircUser(network()->myNick());
234   if(!me) return QVariant();  // this shouldn't really happen, I guess
235   return me->channels();
236 }
237
238 void NetworkConnection::disconnectFromIrc() {
239   _autoReconnectTimer.stop();
240   _autoReconnectCount = 0;
241   displayMsg(Message::Server, BufferInfo::StatusBuffer, "", tr("Disconnecting."));
242   if(socket.state() < QAbstractSocket::ConnectedState) {
243     setConnectionState(Network::Disconnected);
244     socketDisconnected();
245   } else socket.disconnectFromHost();
246 }
247
248 void NetworkConnection::socketHasData() {
249   while(socket.canReadLine()) {
250     QByteArray s = socket.readLine().trimmed();
251     ircServerHandler()->handleServerMsg(s);
252   }
253 }
254
255 void NetworkConnection::socketError(QAbstractSocket::SocketError) {
256   qDebug() << qPrintable(tr("Could not connect to %1 (%2)").arg(network()->networkName(), socket.errorString()));
257   emit connectionError(socket.errorString());
258   emit displayMsg(Message::Error, BufferInfo::StatusBuffer, "", tr("Connection failure: %1").arg(socket.errorString()));
259   network()->emitConnectionError(socket.errorString());
260   if(socket.state() < QAbstractSocket::ConnectedState) {
261     setConnectionState(Network::Disconnected);
262     socketDisconnected();
263   }
264 }
265
266 void NetworkConnection::socketConnected() {
267   //emit connected(networkId());  initialize first!
268   Identity *identity = coreSession()->identity(network()->identity());
269   if(!identity) {
270     qWarning() << "Identity invalid!";
271     disconnectFromIrc();
272     return;
273   }
274   putRawLine(serverEncode(QString("NICK :%1").arg(identity->nicks()[0])));  // FIXME: try more nicks if error occurs
275   putRawLine(serverEncode(QString("USER %1 8 * :%2").arg(identity->ident(), identity->realName())));
276 }
277
278 void NetworkConnection::socketStateChanged(QAbstractSocket::SocketState socketState) {
279   Network::ConnectionState state;
280   switch(socketState) {
281     case QAbstractSocket::UnconnectedState:
282       state = Network::Disconnected;
283       break;
284     case QAbstractSocket::HostLookupState:
285     case QAbstractSocket::ConnectingState:
286       state = Network::Connecting;
287       break;
288     case QAbstractSocket::ConnectedState:
289       state = Network::Initializing;
290       break;
291     case QAbstractSocket::ClosingState:
292       state = Network::Disconnecting;
293       break;
294     default:
295       state = Network::Disconnected;
296   }
297   setConnectionState(state);
298 }
299
300 void NetworkConnection::socketDisconnected() {
301   network()->setConnected(false);
302   emit disconnected(networkId());
303   if(_autoReconnectCount == 0) emit quitRequested(networkId());
304   else {
305     setConnectionState(Network::Reconnecting);
306     if(_autoReconnectCount == network()->autoReconnectRetries()) doAutoReconnect(); // first try is immediate
307     else _autoReconnectTimer.start();
308   }
309 }
310
311 void NetworkConnection::doAutoReconnect() {
312   if(connectionState() != Network::Disconnected && connectionState() != Network::Reconnecting) {
313     qWarning() << "NetworkConnection::doAutoReconnect(): Cannot reconnect while not being disconnected!";
314     return;
315   }
316   if(_autoReconnectCount > 0) _autoReconnectCount--;
317   connectToIrc(true);
318 }
319
320 // FIXME switch to BufferId
321 void NetworkConnection::userInput(BufferInfo buf, QString msg) {
322   userInputHandler()->handleUserInput(buf, msg);
323 }
324
325 void NetworkConnection::putRawLine(QByteArray s) {
326   s += "\r\n";
327   socket.write(s);
328 }
329
330 void NetworkConnection::putCmd(const QString &cmd, const QVariantList &params, const QByteArray &prefix) {
331   QByteArray msg;
332   if(!prefix.isEmpty())
333     msg += ":" + prefix + " ";
334   msg += cmd.toUpper().toAscii();
335
336   for(int i = 0; i < params.size() - 1; i++) {
337     msg += " " + params[i].toByteArray();
338   }
339   if(!params.isEmpty())
340     msg += " :" + params.last().toByteArray();
341
342   putRawLine(msg);
343 }
344
345 void NetworkConnection::nickChanged(const QString &newNick, const QString &oldNick) {
346   emit nickChanged(_network->networkId(), newNick, oldNick);
347 }
348
349 /* Exception classes for message handling */
350 NetworkConnection::ParseError::ParseError(QString cmd, QString prefix, QStringList params) {
351   Q_UNUSED(prefix);
352   _msg = QString("Command Parse Error: ") + cmd + params.join(" ");
353 }
354
355 NetworkConnection::UnknownCmdError::UnknownCmdError(QString cmd, QString prefix, QStringList params) {
356   Q_UNUSED(prefix);
357   _msg = QString("Unknown Command: ") + cmd + params.join(" ");
358 }
359