3a3844fc65ee6f1f422804a741b07e374d212051
[quassel.git] / src / common / ignorelistmanager.h
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 #pragma once
22
23 #include <QDebug>
24 #include <QString>
25 #include <QStringList>
26 #include <QRegExp>
27
28 #include "expressionmatch.h"
29 #include "message.h"
30 #include "syncableobject.h"
31
32 class IgnoreListManager : public SyncableObject
33 {
34     Q_OBJECT
35     SYNCABLE_OBJECT
36
37 public:
38     inline IgnoreListManager(QObject *parent = 0) : SyncableObject(parent) { setAllowClientUpdates(true); }
39     IgnoreListManager &operator=(const IgnoreListManager &other);
40
41     enum IgnoreType {
42         SenderIgnore,
43         MessageIgnore,
44         CtcpIgnore
45     };
46
47     enum StrictnessType {
48         UnmatchedStrictness = 0,
49         SoftStrictness = 1,
50         HardStrictness = 2
51     };
52
53     enum ScopeType {
54         GlobalScope,
55         NetworkScope,
56         ChannelScope,
57     };
58
59     /**
60      * Individual ignore list rule
61      */
62     class IgnoreListItem {
63     public:
64         /**
65          * Construct an empty ignore rule
66          */
67         IgnoreListItem() {}
68
69         /**
70          * Construct an ignore rule with the given parameters
71          *
72          * CAUTION: For legacy reasons, "contents" doubles as the identifier for the ignore rule.
73          * Duplicate entries are not allowed.
74          *
75          * @param type             Type of ignore rule
76          * @param contents         String representing a message contents expression to match
77          * @param isRegEx          True if regular expression, otherwise false
78          * @param strictness       Strictness of ignore rule
79          * @param scope            What to match scope rule against
80          * @param scopeRule        String representing a scope rule expression to match
81          * @param isEnabled        True if enabled, otherwise false
82          */
83         IgnoreListItem(IgnoreType type, const QString &contents, bool isRegEx,
84                        StrictnessType strictness, ScopeType scope, const QString &scopeRule,
85                        bool isEnabled)
86             : _contents(contents), _isRegEx(isRegEx), _strictness(strictness),
87               _scope(scope), _scopeRule(scopeRule), _isEnabled(isEnabled)
88         {
89             // Allow passing empty "contents" as they can happen when editing an ignore rule
90
91             // Handle CTCP ignores
92             setType(type);
93
94             _cacheInvalid = true;
95             // Cache expression matches on construction
96             //
97             // This provides immediate feedback on errors when loading the rule.  If profiling shows
98             // this as a performance bottleneck, this can be removed in deference to caching on
99             // first use.
100             //
101             // Inversely, if needed for validity checks, caching can be done on every update below
102             // instead of on first use.
103             determineExpressions();
104         }
105
106         /**
107          * Gets the type of this ignore rule
108          *
109          * @return IgnoreType of the rule
110          */
111         inline IgnoreType type() const {
112             return _type;
113         }
114         /**
115          * Sets the type of this ignore rule
116          *
117          * @param type IgnoreType of the rule
118          */
119         inline void setType(IgnoreType type) {
120             // Handle CTCP ignores
121             if (type == CtcpIgnore) {
122                 // This is not performance-intensive; sticking with QRegExp for Qt 4 is fine
123                 // Split based on whitespace characters
124                 QStringList split(contents().split(QRegExp("\\s+"), QString::SkipEmptyParts));
125                 // Match on the first item, handling empty rules/matches
126                 if (!split.isEmpty()) {
127                     // Take the first item as the sender
128                     _cacheCtcpSender = split.takeFirst();
129                     // Track the rest as CTCP types to ignore
130                     _cacheCtcpTypes = split;
131                 }
132                 else {
133                     // No match found - this can happen if a pure whitespace CTCP ignore rule is
134                     // created.  Fall back to matching all senders.
135                     if (_isRegEx) {
136                         // RegEx match everything
137                         _cacheCtcpSender = ".*";
138                     }
139                     else {
140                         // Wildcard match everything
141                         _cacheCtcpSender = "*";
142                     }
143                     // Clear the types (split is already empty)
144                     _cacheCtcpTypes = split;
145                 }
146             }
147             _type = type;
148         }
149
150         /**
151          * Gets the message contents this rule matches
152          *
153          * NOTE: Use IgnoreListItem::contentsMatcher() for performing matches
154          *
155          * CAUTION: For legacy reasons, "contents" doubles as the identifier for the ignore rule.
156          * Duplicate entries are not allowed.
157          *
158          * @return String representing a phrase or expression to match
159          */
160         inline QString contents() const {
161             return _contents;
162         }
163         /**
164          * Sets the message contents this rule matches
165          *
166          * @param contents String representing a phrase or expression to match
167          */
168         inline void setContents(const QString &contents) {
169             // Allow passing empty "contents" as they can happen when editing an ignore rule
170             _contents = contents;
171             _cacheInvalid = true;
172         }
173
174         /**
175          * Gets if this is a regular expression rule
176          *
177          * @return True if regular expression, otherwise false
178          */
179         inline bool isRegEx() const {
180             return _isRegEx;
181         }
182         /**
183          * Sets if this rule is a regular expression rule
184          *
185          * @param isRegEx True if regular expression, otherwise false
186          */
187         inline void setIsRegEx(bool isRegEx) {
188             _isRegEx = isRegEx;
189             _cacheInvalid = true;
190         }
191
192         /**
193          * Gets the strictness of this ignore rule
194          *
195          * @return StrictnessType of the rule
196          */
197         inline StrictnessType strictness() const {
198             return _strictness;
199         }
200         /**
201          * Sets the strictness of this ignore rule
202          *
203          * @param strictness StrictnessType of the rule
204          */
205         inline void setStrictness(StrictnessType strictness) {
206             _strictness = strictness;
207         }
208
209         /**
210          * Gets what to match scope rule against
211          *
212          * @return ScopeType of the rule
213          */
214         inline ScopeType scope() const {
215             return _scope;
216         }
217         /**
218          * Sets what to match scope rule against
219          *
220          * @param type ScopeType of the rule
221          */
222         inline void setScope(ScopeType scope) {
223             _scope = scope;
224         }
225
226         /**
227          * Gets the scope rule this rule matches
228          *
229          * NOTE: Use IgnoreListItem::scopeRuleMatcher() for performing matches
230          *
231          * @return String representing a phrase or expression to match
232          */
233         inline QString scopeRule() const {
234             return _scopeRule;
235         }
236         /**
237          * Sets the scope rule this rule matches
238          *
239          * @param scopeRule String representing a phrase or expression to match
240          */
241         inline void setScopeRule(const QString &scopeRule) {
242             _scopeRule = scopeRule;
243             _cacheInvalid = true;
244         }
245
246         /**
247          * Gets if this rule is enabled and active
248          *
249          * @return True if enabled, otherwise false
250          */
251         inline bool isEnabled() const {
252             return _isEnabled;
253         }
254         /**
255          * Sets if this rule is enabled and active
256          *
257          * @param isEnabled True if enabled, otherwise false
258          */
259         inline void setIsEnabled(bool isEnabled) {
260             _isEnabled = isEnabled;
261         }
262
263         /**
264          * Gets the ignored CTCP types for CTCP ignores
265          *
266          * @return List of CTCP types to ignore, or empty for all
267          */
268         inline QStringList ctcpTypes() const {
269             return _cacheCtcpTypes;
270         }
271
272         /**
273          * Gets the expression matcher for the message contents, caching if needed
274          *
275          * @return Expression matcher to compare with message contents
276          */
277         inline ExpressionMatch contentsMatcher() const {
278             if (_cacheInvalid) {
279                 determineExpressions();
280             }
281             return _contentsMatch;
282         }
283
284         /**
285          * Gets the expression matcher for the scope, caching if needed
286          *
287          * @return Expression matcher to compare with scope
288          */
289         inline ExpressionMatch scopeRuleMatcher() const {
290             if (_cacheInvalid) {
291                 determineExpressions();
292             }
293             return _scopeRuleMatch;
294         }
295
296         /**
297          * Gets the expression matcher for the message contents, caching if needed
298          *
299          * @return Expression matcher to compare with message contents
300          */
301         inline ExpressionMatch senderCTCPMatcher() const {
302             if (_cacheInvalid) {
303                 determineExpressions();
304             }
305             return _ctcpSenderMatch;
306         }
307
308         bool operator!=(const IgnoreListItem &other) const;
309
310     private:
311         /**
312          * Update internal cache of expression matching if needed
313          */
314         void determineExpressions() const;
315
316         IgnoreType _type = {};
317         QString _contents = {};
318         bool _isRegEx = false;
319         StrictnessType _strictness = {};
320         ScopeType _scope = {};
321         QString _scopeRule = {};
322         bool _isEnabled = true;
323
324         QString _cacheCtcpSender = {};                    ///< For CTCP rules, precalculate sender
325         QStringList _cacheCtcpTypes = {};                 ///< For CTCP rules, precalculate types
326
327         // These represent internal cache and should be safe to mutate in 'const' functions
328         // See https://stackoverflow.com/questions/3141087/what-is-meant-with-const-at-end-of-function-declaration
329         mutable bool _cacheInvalid = true;                ///< If true, match cache needs redone
330         mutable ExpressionMatch _contentsMatch = {};      ///< Expression match cache for message
331         mutable ExpressionMatch _scopeRuleMatch = {};     ///< Expression match cache for scope rule
332         mutable ExpressionMatch _ctcpSenderMatch = {};    ///< Expression match cache for CTCP nick
333     };
334
335     typedef QList<IgnoreListItem> IgnoreList;
336
337     int indexOf(const QString &ignore) const;
338     inline bool contains(const QString &ignore) const { return indexOf(ignore) != -1; }
339     inline bool isEmpty() const { return _ignoreList.isEmpty(); }
340     inline int count() const { return _ignoreList.count(); }
341     inline void removeAt(int index) { _ignoreList.removeAt(index); }
342     inline IgnoreListItem &operator[](int i) { return _ignoreList[i]; }
343     inline const IgnoreListItem &operator[](int i) const { return _ignoreList.at(i); }
344     inline const IgnoreList &ignoreList() const { return _ignoreList; }
345
346     //! Check if a message matches the IgnoreRule
347     /** This method checks if a message matches the users ignorelist.
348       * \param msg The Message that should be checked
349       * \param network The networkname the message belongs to
350       * \return UnmatchedStrictness, HardStrictness or SoftStrictness representing the match type
351       */
352     inline StrictnessType match(const Message &msg, const QString &network = QString()) { return _match(msg.contents(), msg.sender(), msg.type(), network, msg.bufferInfo().bufferName()); }
353
354     bool ctcpMatch(const QString sender, const QString &network, const QString &type = QString());
355
356 //  virtual void addIgnoreListItem(const IgnoreListItem &item);
357
358 public slots:
359     virtual QVariantMap initIgnoreList() const;
360     virtual void initSetIgnoreList(const QVariantMap &ignoreList);
361
362     //! Request removal of an ignore rule based on the rule itself.
363     /** Use this method if you want to remove a single ignore rule
364       * and get that synced with the core immediately.
365       * \param ignoreRule A valid ignore rule
366       */
367     virtual inline void requestRemoveIgnoreListItem(const QString &ignoreRule) { REQUEST(ARG(ignoreRule)) }
368     virtual void removeIgnoreListItem(const QString &ignoreRule);
369
370     //! Request toggling of "isActive" flag of a given ignore rule.
371     /** Use this method if you want to toggle the "isActive" flag of a single ignore rule
372       * and get that synced with the core immediately.
373       * \param ignoreRule A valid ignore rule
374       */
375     virtual inline void requestToggleIgnoreRule(const QString &ignoreRule) { REQUEST(ARG(ignoreRule)) }
376     virtual void toggleIgnoreRule(const QString &ignoreRule);
377
378     //! Request an IgnoreListItem to be added to the ignore list
379     /** Items added to the list with this method, get immediately synced with the core
380       * \param type The IgnoreType of the new rule
381       * \param ignoreRule The rule itself
382       * \param isRegEx Signals if the rule should be interpreted as a regular expression
383       * \param strictness Th StrictnessType that should be applied
384       * \param scope The ScopeType that should be set
385       * \param scopeRule A string of semi-colon separated network- or channelnames
386       * \param isActive Signals if the rule is enabled or not
387       */
388     virtual inline void requestAddIgnoreListItem(int type, const QString &ignoreRule, bool isRegEx, int strictness,
389         int scope, const QString &scopeRule, bool isActive)
390     {
391         REQUEST(ARG(type), ARG(ignoreRule), ARG(isRegEx), ARG(strictness), ARG(scope), ARG(scopeRule), ARG(isActive))
392     }
393
394
395     virtual void addIgnoreListItem(int type, const QString &ignoreRule, bool isRegEx, int strictness,
396         int scope, const QString &scopeRule, bool isActive);
397
398 protected:
399     void setIgnoreList(const QList<IgnoreListItem> &ignoreList) { _ignoreList = ignoreList; }
400
401     StrictnessType _match(const QString &msgContents, const QString &msgSender, Message::Type msgType, const QString &network, const QString &bufferName);
402
403 signals:
404     void ignoreAdded(IgnoreType type, const QString &ignoreRule, bool isRegex, StrictnessType strictness, ScopeType scope, const QVariant &scopeRule, bool isActive);
405
406 private:
407     IgnoreList _ignoreList;
408 };