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