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