Rework sync protocol for highlight rules
[quassel.git] / src / common / highlightrulemanager.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 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 "highlightrulemanager.h"
22
23 #include "util.h"
24
25 #include <QDebug>
26
27 INIT_SYNCABLE_OBJECT(HighlightRuleManager)
28 HighlightRuleManager &HighlightRuleManager::operator=(const HighlightRuleManager &other) {
29     if (this == &other)
30         return *this;
31
32     SyncableObject::operator=(other);
33     _highlightRuleList = other._highlightRuleList;
34     _nicksCaseSensitive = other._nicksCaseSensitive;
35     _highlightNick = other._highlightNick;
36     return *this;
37 }
38
39
40 int HighlightRuleManager::indexOf(int id) const {
41     for (int i = 0; i < _highlightRuleList.count(); i++) {
42         if (_highlightRuleList[i].id == id)
43             return i;
44     }
45     return -1;
46 }
47
48 int HighlightRuleManager::nextId() {
49     int max = 0;
50     for (int i = 0; i < _highlightRuleList.count(); i++) {
51         int id = _highlightRuleList[i].id;
52         if (id > max) {
53             max = id;
54         }
55     }
56     return max+1;
57 }
58
59
60 QVariantMap HighlightRuleManager::initHighlightRuleList() const {
61     QVariantList id;
62     QVariantMap highlightRuleListMap;
63     QStringList name;
64     QVariantList isRegEx;
65     QVariantList isCaseSensitive;
66     QVariantList isActive;
67     QVariantList isInverse;
68     QStringList sender;
69     QStringList channel;
70
71     for (int i = 0; i < _highlightRuleList.count(); i++) {
72         id << _highlightRuleList[i].id;
73         name << _highlightRuleList[i].name;
74         isRegEx << _highlightRuleList[i].isRegEx;
75         isCaseSensitive << _highlightRuleList[i].isCaseSensitive;
76         isActive << _highlightRuleList[i].isEnabled;
77         isInverse << _highlightRuleList[i].isInverse;
78         sender << _highlightRuleList[i].sender;
79         channel << _highlightRuleList[i].chanName;
80     }
81
82     highlightRuleListMap["id"] = id;
83     highlightRuleListMap["name"] = name;
84     highlightRuleListMap["isRegEx"] = isRegEx;
85     highlightRuleListMap["isCaseSensitive"] = isCaseSensitive;
86     highlightRuleListMap["isEnabled"] = isActive;
87     highlightRuleListMap["isInverse"] = isInverse;
88     highlightRuleListMap["sender"] = sender;
89     highlightRuleListMap["channel"] = channel;
90     return highlightRuleListMap;
91 }
92
93
94 void HighlightRuleManager::initSetHighlightRuleList(const QVariantMap &highlightRuleList) {
95     QVariantList id = highlightRuleList["id"].toList();
96     QStringList name = highlightRuleList["name"].toStringList();
97     QVariantList isRegEx = highlightRuleList["isRegEx"].toList();
98     QVariantList isCaseSensitive = highlightRuleList["isCaseSensitive"].toList();
99     QVariantList isActive = highlightRuleList["isEnabled"].toList();
100     QVariantList isInverse = highlightRuleList["isInverse"].toList();
101     QStringList sender = highlightRuleList["sender"].toStringList();
102     QStringList channel = highlightRuleList["channel"].toStringList();
103
104     int count = id.count();
105     if (count != name.count() || count != isRegEx.count() || count != isCaseSensitive.count() ||
106         count != isActive.count() || count != isInverse.count() || count != sender.count() ||
107         count != channel.count()) {
108         qWarning() << "Corrupted HighlightRuleList settings! (Count mismatch)";
109         return;
110     }
111
112     _highlightRuleList.clear();
113     for (int i = 0; i < name.count(); i++) {
114         _highlightRuleList << HighlightRule(id[i].toInt(), name[i], isRegEx[i].toBool(), isCaseSensitive[i].toBool(),
115                                             isActive[i].toBool(), isInverse[i].toBool(), sender[i], channel[i]);
116     }
117 }
118
119 void HighlightRuleManager::addHighlightRule(int id, const QString &name, bool isRegEx, bool isCaseSensitive,
120                                             bool isActive, bool isInverse, const QString &sender,
121                                             const QString &channel) {
122     if (contains(id)) {
123         return;
124     }
125
126     HighlightRule newItem = HighlightRule(id, name, isRegEx, isCaseSensitive, isActive, isInverse, sender, channel);
127     _highlightRuleList << newItem;
128
129     SYNC(ARG(id), ARG(name), ARG(isRegEx), ARG(isCaseSensitive), ARG(isActive), ARG(isInverse), ARG(sender),
130          ARG(channel))
131 }
132
133
134 bool HighlightRuleManager::match(const QString &msgContents,
135                                  const QString &msgSender,
136                                  Message::Type msgType,
137                                  Message::Flags msgFlags,
138                                  const QString &bufferName,
139                                  const QString &currentNick,
140                                  const QStringList identityNicks) {
141     if (!((msgType & (Message::Plain | Message::Notice | Message::Action)) && !(msgFlags & Message::Self))) {
142        return false;
143     }
144
145     bool matches = false;
146
147     for (int i = 0; i < _highlightRuleList.count(); i++) {
148         const HighlightRule &rule = _highlightRuleList.at(i);
149         if (!rule.isEnabled)
150             continue;
151
152         if (!rule.chanName.isEmpty()
153                 && !scopeMatch(bufferName, rule.chanName, rule.isRegEx, rule.isCaseSensitive)) {
154             // A channel name rule is specified and does NOT match the current buffer name, skip
155             // this rule
156             continue;
157         }
158
159         QRegExp rx;
160         if (rule.isRegEx) {
161             rx = QRegExp(rule.name, rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
162         } else {
163             rx = QRegExp("(^|\\W)" + QRegExp::escape(rule.name) + "(\\W|$)", rule.isCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
164         }
165         bool nameMatch = (rx.indexIn(stripFormatCodes(msgContents)) >= 0);
166
167         bool senderMatch;
168         if (rule.sender.isEmpty()) {
169             senderMatch = true;
170         } else {
171             // A sender name rule is specified, match according to scope rules.
172             senderMatch = scopeMatch(msgSender, rule.sender, rule.isRegEx, rule.isCaseSensitive);
173         }
174
175         if (nameMatch && senderMatch) {
176             // If an inverse rule matches, then we know that we never want to return a highlight.
177             if (rule.isInverse) {
178                 return false;
179             } else {
180                 matches = true;
181             }
182         }
183     }
184
185     if (matches)
186         return true;
187
188     if (!currentNick.isEmpty()) {
189         QStringList nickList;
190         if (_highlightNick == CurrentNick) {
191             nickList << currentNick;
192         }
193         else if (_highlightNick == AllNicks) {
194             nickList = identityNicks;
195             if (!nickList.contains(currentNick))
196                 nickList.prepend(currentNick);
197         }
198
199         for(const QString &nickname : nickList) {
200             QRegExp nickRegExp("(^|\\W)" + QRegExp::escape(nickname) + "(\\W|$)", _nicksCaseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
201             if (nickRegExp.indexIn(stripFormatCodes(msgContents)) >= 0) {
202                 return true;
203             }
204         }
205     }
206
207     return false;
208 }
209
210 void HighlightRuleManager::removeHighlightRule(int highlightRule) {
211     removeAt(indexOf(highlightRule));
212     SYNC(ARG(highlightRule))
213 }
214
215
216 void HighlightRuleManager::toggleHighlightRule(int highlightRule) {
217     int idx = indexOf(highlightRule);
218     if (idx == -1)
219         return;
220     _highlightRuleList[idx].isEnabled = !_highlightRuleList[idx].isEnabled;
221     SYNC(ARG(highlightRule))
222 }
223
224 bool HighlightRuleManager::match(const Message &msg, const QString &currentNick, const QStringList &identityNicks) {
225     return match(msg.contents(), msg.sender(), msg.type(), msg.flags(), msg.bufferInfo().bufferName(), currentNick, identityNicks);
226 }