Event backend porting
[quassel.git] / src / core / netsplit.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2010 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
21 #include "netsplit.h"
22 #include "network.h"
23 #include "util.h"
24
25 #include <QRegExp>
26
27 Netsplit::Netsplit(Network *network, QObject *parent)
28   : QObject(parent),
29     _network(network), _quitMsg(""), _sentQuit(false), _joinCounter(0), _quitCounter(0)
30 {
31   _discardTimer.setSingleShot(true);
32   _joinTimer.setSingleShot(true);
33   _quitTimer.setSingleShot(true);
34
35   connect(&_discardTimer, SIGNAL(timeout()), this, SIGNAL(finished()));
36
37   connect(&_joinTimer, SIGNAL(timeout()), this, SLOT(joinTimeout()));
38   connect(&_quitTimer, SIGNAL(timeout()), this, SLOT(quitTimeout()));
39
40   // wait for a maximum of 1 hour until we discard the netsplit
41   _discardTimer.start(3600000);
42 }
43
44 void Netsplit::userQuit(const QString &sender, const QStringList &channels, const QString &msg)
45 {
46   if(_quitMsg.isEmpty())
47     _quitMsg = msg;
48   foreach(QString channel, channels) {
49     _quits[channel].append(sender);
50   }
51   _quitCounter++;
52   // now let's wait 10s to finish the netsplit-quit
53   _quitTimer.start(10000);
54 }
55
56 bool Netsplit::userJoined(const QString &sender, const QString &channel) {
57   if(!_quits.contains(channel))
58     return false;
59
60   QStringList &users = _quits[channel];
61
62   QStringList::iterator userIter;
63   const QString senderNick = nickFromMask(sender);
64   for(userIter = users.begin(); userIter != users.end(); ++userIter) {
65     if(nickFromMask(*userIter) == senderNick)
66       break;
67   }
68   if(userIter == users.end())
69     return false;
70
71   _joins[channel].first.append(*userIter);
72   _joins[channel].second.append(QString());
73
74   users.erase(userIter);
75
76   if(users.empty())
77     _quits.remove(channel);
78
79   _joinCounter++;
80
81   if(_quits.empty()) // all users joined already - no need to wait
82     _joinTimer.start(0);
83   else // wait 30s to finish the netsplit-join
84     _joinTimer.start(30000);
85
86   return true;
87 }
88
89 bool Netsplit::userAlreadyJoined(const QString &sender, const QString &channel) {
90   if(_joins.value(channel).first.contains(sender))
91     return true;
92   return false;
93 }
94
95 void Netsplit::addMode(const QString &sender, const QString &channel, const QString &mode) {
96   if(!_joins.contains(channel))
97     return;
98   int idx = _joins.value(channel).first.indexOf(sender);
99   if(idx == -1)
100     return;
101   _joins[channel].second[idx].append(mode);
102 }
103
104 bool Netsplit::isNetsplit(const QString &quitMessage)
105 {
106   // check if we find some common chars that disqualify the netsplit as such
107   if(quitMessage.contains(':') || quitMessage.contains('/'))
108     return false;
109
110   // now test if message consists only of two dns names as the RFC requests
111   // but also allow the commonly used "*.net *.split"
112   QRegExp hostRx("^(?:[\\w\\d-.]+|\\*)\\.[\\w\\d-]+\\s(?:[\\w\\d-.]+|\\*)\\.[\\w\\d-]+$");
113   if(hostRx.exactMatch(quitMessage))
114     return true;
115
116   return false;
117 }
118
119 void Netsplit::joinTimeout()
120 {
121   if(!_sentQuit) {
122     _quitTimer.stop();
123     quitTimeout();
124   }
125
126   QHash<QString, QPair<QStringList, QStringList> >::iterator it;
127
128   /*
129     Try to catch server jumpers.
130     If we have too few joins for a netsplit-quit,
131     we assume that the users manually changed servers and join them
132     without ending the netsplit.
133     A netsplit is assumed over only if at least 1/3 of all quits had their corresponding
134     join again.
135   */
136   if(_joinCounter < _quitCounter/3) {
137     for(it = _joins.begin(); it != _joins.end(); ++it)
138       emit earlyJoin(network(), it.key(), it.value().first, it.value().second);
139
140     // we don't care about those anymore
141     _joins.clear();
142
143     // restart the timer with 5min timeout
144     // This might happen a few times if netsplit lasts longer.
145     // As soon as another user joins, the timer is set to a shorter timeout again.
146     _joinTimer.start(300000);
147     return;
148   }
149
150   // send netsplitJoin for every recorded channel
151   for(it = _joins.begin(); it != _joins.end(); ++it)
152     emit netsplitJoin(network(), it.key(), it.value().first, it.value().second ,_quitMsg);
153   _joins.clear();
154   _discardTimer.stop();
155   emit finished();
156 }
157
158 void Netsplit::quitTimeout()
159 {
160   // send netsplitQuit for every recorded channel
161   QHash<QString, QStringList>::iterator channelIter;
162   for(channelIter = _quits.begin(); channelIter != _quits.end(); ++channelIter) {
163
164     QStringList usersToSend;
165
166     foreach(QString user, channelIter.value()) {
167       if(!_quitsWithMessageSent.value(channelIter.key()).contains(user)) {
168         usersToSend << user;
169         _quitsWithMessageSent[channelIter.key()].append(user);
170       }
171     }
172     // not yet sure how that could happen, but never send empty netsplit-quits
173     // anyway.
174     if(!usersToSend.isEmpty())
175       emit netsplitQuit(network(), channelIter.key(), usersToSend, _quitMsg);
176   }
177   _sentQuit = true;
178 }