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