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