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