Mailcommon

searchpattern.cpp
1/*
2
3 SPDX-FileCopyrightText: Marc Mutz <mutz@kde.org>
4 SPDX-FileCopyrightText: 2012 Andras Mantia <amantia@kde.org>
5
6 SPDX-License-Identifier: GPL-2.0-or-later
7*/
8
9#include "searchpattern.h"
10#include "filter/filterlog.h"
12#include "mailcommon_debug.h"
13#include <Akonadi/ContactSearchJob>
14
15#include <KMime/Message>
16
17#include <KConfigGroup>
18
19#include <QDataStream>
20#include <QIODevice>
21
22#include <algorithm>
23
24using namespace MailCommon;
25
26//==================================================
27//
28// class SearchPattern
29//
30//==================================================
31
33 : QList<SearchRule::Ptr>()
34{
35 init();
36}
37
39 : QList<SearchRule::Ptr>()
40{
41 readConfig(config);
42}
43
45
46bool SearchPattern::matches(const Akonadi::Item &item, bool ignoreBody) const
47{
48 if (isEmpty()) {
49 return true;
50 }
51 if (!item.hasPayload<KMime::Message::Ptr>()) {
52 return false;
53 }
54
57 switch (mOperator) {
58 case OpAnd: // all rules must match
59 for (it = constBegin(); it != end; ++it) {
60 if (!((*it)->requiredPart() == SearchRule::CompleteMessage && ignoreBody)) {
61 if (!(*it)->matches(item)) {
62 return false;
63 }
64 }
65 }
66 return true;
67
68 case OpOr: // at least one rule must match
69 for (it = constBegin(); it != end; ++it) {
70 if (!((*it)->requiredPart() == MailCommon::SearchRule::CompleteMessage && ignoreBody)) {
71 if ((*it)->matches(item)) {
72 return true;
73 }
74 }
75 }
76 return false;
77
78 case OpAll:
79 return true;
80
81 default:
82 return false;
83 }
84}
85
87{
89
90 if (!isEmpty()) {
91 reqPart = (*std::max_element(constBegin(), constEnd(), [](const auto &lhs, const auto &rhs) {
92 return lhs->requiredPart() < rhs->requiredPart();
93 }))->requiredPart();
94 }
95 return reqPart;
96}
97
99{
100 QString informationAboutNotValidPattern;
102 while (it != begin()) {
103 --it;
104 if ((*it)->isEmpty()) {
105 if (removeAction) {
106 qCDebug(MAILCOMMON_LOG) << "Removing" << (*it)->asString();
107 if (!informationAboutNotValidPattern.isEmpty()) {
108 informationAboutNotValidPattern += QLatin1Char('\n');
109 }
110 informationAboutNotValidPattern += (*it)->informationAboutNotValidRules();
111
112 erase(it);
113 it = end();
114 }
115 }
116 }
117
118 return informationAboutNotValidPattern;
119}
120
122{
123 init();
124
125 mName = config.readEntry("name");
126 if (!config.hasKey("rules")) {
127 qCDebug(MAILCOMMON_LOG) << "Found legacy config! Converting.";
128 importLegacyConfig(config);
129 return;
130 }
131
132 const QString op = config.readEntry("operator");
133 if (op == QLatin1StringView("or")) {
134 mOperator = OpOr;
135 } else if (op == QLatin1StringView("and")) {
136 mOperator = OpAnd;
137 } else if (op == QLatin1StringView("all")) {
138 mOperator = OpAll;
139 }
140
141 const int nRules = config.readEntry("rules", 0);
142
143 for (int i = 0; i < nRules; ++i) {
145 if (!r->isEmpty()) {
146 append(r);
147 }
148 }
149}
150
151void SearchPattern::importLegacyConfig(const KConfigGroup &config)
152{
153 SearchRule::Ptr rule =
154 SearchRule::createInstance(config.readEntry("fieldA").toLatin1(), config.readEntry("funcA").toLatin1().constData(), config.readEntry("contentsA"));
155
156 if (rule->isEmpty()) {
157 // if the first rule is invalid,
158 // we really can't do much heuristics...
159 return;
160 }
161 append(rule);
162
163 const QString sOperator = config.readEntry("operator");
164 if (sOperator == QLatin1StringView("ignore")) {
165 return;
166 }
167
168 rule = SearchRule::createInstance(config.readEntry("fieldB").toLatin1(), config.readEntry("funcB").toLatin1().constData(), config.readEntry("contentsB"));
169
170 if (rule->isEmpty()) {
171 return;
172 }
173 append(rule);
174
175 if (sOperator == QLatin1StringView("or")) {
176 mOperator = OpOr;
177 return;
178 }
179 // This is the interesting case...
180 if (sOperator == QLatin1StringView("unless")) { // meaning "and not", ie we need to...
181 // ...invert the function (e.g. "equals" <-> "doesn't equal")
182 // We simply toggle the last bit (xor with 0x1)... This assumes that
183 // SearchRule::Function's come in adjacent pairs of pros and cons
184 SearchRule::Function func = last()->function();
185 auto intFunc = (unsigned int)func;
186 func = SearchRule::Function(intFunc ^ 0x1);
187
188 last()->setFunction(func);
189 }
190
191 // treat any other case as "and" (our default).
192}
193
195{
196 config.writeEntry("name", mName);
197 switch (mOperator) {
198 case OpOr:
199 config.writeEntry("operator", "or");
200 break;
201 case OpAnd:
202 config.writeEntry("operator", "and");
203 break;
204 case OpAll:
205 config.writeEntry("operator", "all");
206 break;
207 }
208
209 int i = 0;
212
213 if (count() >= filterRulesMaximumSize()) {
214 qCDebug(MAILCOMMON_LOG) << "Number of patterns > to filter max rules";
215 }
216 for (it = constBegin(); it != endIt && i < filterRulesMaximumSize(); ++i, ++it) {
217 // we could do this ourselves, but we want the rules to be extensible,
218 // so we give the rule it's number and let it do the rest.
219 (*it)->writeConfig(config, i);
220 }
221
222 // save the total number of rules.
223 config.writeEntry("rules", i);
224}
225
226int SearchPattern::filterRulesMaximumSize()
227{
228 return 8;
229}
230
231void SearchPattern::init()
232{
233 clear();
234 mOperator = OpAnd;
235 mName = QLatin1Char('<') + i18nc("name used for a virgin filter", "unknown") + QLatin1Char('>');
236}
237
239{
240 QString result;
241 switch (mOperator) {
242 case OpOr:
243 result = i18n("(match any of the following)");
244 break;
245 case OpAnd:
246 result = i18n("(match all of the following)");
247 break;
248 case OpAll:
249 result = i18n("(match all messages)");
250 break;
251 }
252
255 for (it = constBegin(); it != endIt; ++it) {
256 result += QLatin1StringView("\n\t") + FilterLog::recode((*it)->asString());
257 }
258
259 return result;
260}
261
262SearchPattern::SparqlQueryError SearchPattern::asAkonadiQuery(Akonadi::SearchQuery &query) const
263{
264 query = Akonadi::SearchQuery();
265
266 Akonadi::SearchTerm term(Akonadi::SearchTerm::RelAnd);
267 if (op() == SearchPattern::OpOr) {
268 term = Akonadi::SearchTerm(Akonadi::SearchTerm::RelOr);
269 }
270
271 const_iterator end(constEnd());
272 bool emptyIsNotAnError = false;
273 bool resultAddQuery = false;
274 for (const_iterator it = constBegin(); it != end; ++it) {
275 (*it)->addQueryTerms(term, emptyIsNotAnError);
276 resultAddQuery &= emptyIsNotAnError;
277 }
278
279 if (term.subTerms().isEmpty()) {
280 if (resultAddQuery) {
281 qCDebug(MAILCOMMON_LOG) << " innergroup is Empty. Need to report bug";
282 return MissingCheck;
283 } else {
284 return EmptyResult;
285 }
286 }
287 query.setTerm(term);
288
289 return NoError;
290}
291
293{
294 if (this == &other) {
295 return *this;
296 }
297
298 setOp(other.op());
299 setName(other.name());
300
301 clear(); // ###
304 for (it = other.constBegin(); it != end; ++it) {
305 append(SearchRule::createInstance(**it)); // deep copy
306 }
307
308 return *this;
309}
310
312{
313 QByteArray out;
314 QDataStream stream(&out, QIODevice::WriteOnly);
315 *this >> stream;
316 return out;
317}
318
320{
321 QDataStream stream(str);
322 *this << stream;
323}
324
325QDataStream &SearchPattern::operator>>(QDataStream &s) const
326{
327 switch (op()) {
328 case SearchPattern::OpAnd:
329 s << QStringLiteral("and");
330 break;
331 case SearchPattern::OpOr:
332 s << QStringLiteral("or");
333 break;
334 case SearchPattern::OpAll:
335 s << QStringLiteral("all");
336 break;
337 }
338
339 for (const SearchRule::Ptr &rule : std::as_const(*this)) {
340 *rule >> s;
341 }
342 return s;
343}
344
345QDataStream &SearchPattern::operator<<(QDataStream &s)
346{
347 QString op;
348 s >> op;
349 if (op == QLatin1StringView("and")) {
350 setOp(OpAnd);
351 } else if (op == QLatin1StringView("or")) {
352 setOp(OpOr);
353 } else if (op == QLatin1StringView("all")) {
354 setOp(OpAll);
355 }
356
357 while (!s.atEnd()) {
359 append(rule);
360 }
361 return s;
362}
363
364void SearchPattern::generateSieveScript(QStringList &requiresModules, QString &code)
365{
366 code += QLatin1StringView("\n#") + mName + QLatin1Char('\n');
367 switch (mOperator) {
368 case OpOr:
369 code += QLatin1StringView("if anyof (");
370 break;
371 case OpAnd:
372 code += QLatin1StringView("if allof (");
373 break;
374 case OpAll:
375 code += QLatin1StringView("if (true) {");
376 return;
377 }
378
381 int i = 0;
382 for (it = constBegin(); it != endIt && i < filterRulesMaximumSize(); ++i, ++it) {
383 if (i != 0) {
384 code += QLatin1StringView("\n, ");
385 }
386 (*it)->generateSieveScript(requiresModules, code);
387 }
388}
bool hasPayload() const
QList< SearchTerm > subTerms() const
bool hasKey(const char *key) const
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
QString readEntry(const char *key, const char *aDefault=nullptr) const
KMail Filter Log Collector.
Definition filterlog.h:33
static QString recode(const QString &plain)
Returns an escaped version of the log which can be used in a HTML document.
This class is an abstraction of a search over messages.
void writeConfig(KConfigGroup &config) const
Writes itself into config.
void setOp(SearchPattern::Operator aOp)
Sets the filter operator.
SearchPattern()
Constructor which provides a pattern with minimal, but sufficient initialization.
bool matches(const Akonadi::Item &item, bool ignoreBody=false) const
The central function of this class.
const SearchPattern & operator=(const SearchPattern &aPattern)
Overloaded assignment operator.
void deserialize(const QByteArray &)
Constructs the pattern from a byte array serialization.
SearchRule::RequiredPart requiredPart() const
Returns the required part from the item that is needed for the search to operate.
void setName(const QString &newName)
Sets the name of the search pattern.
QByteArray serialize() const
Writes the pattern into a byte array for persistence purposes.
QString asString() const
Returns the pattern as string.
void readConfig(const KConfigGroup &config)
Reads a search pattern from a KConfigGroup.
QString name() const
Returns the name of the search pattern.
SearchPattern::Operator op() const
Returns the filter operator.
QString purify(bool removeAction=true)
Removes all empty rules from the list.
SparqlQueryError asAkonadiQuery(Akonadi::SearchQuery &) const
Returns the pattern as akonadi query.
This class represents one search pattern rule.
Definition searchrule.h:24
static SearchRule::Ptr createInstanceFromConfig(const KConfigGroup &group, int index)
Creates a new search rule from a given config group.
std::shared_ptr< SearchRule > Ptr
Defines a pointer to a search rule.
Definition searchrule.h:29
Function
Describes operators for comparison of field and contents.
Definition searchrule.h:40
RequiredPart
Possible required parts.
Definition searchrule.h:68
@ CompleteMessage
Whole message.
Definition searchrule.h:71
static SearchRule::Ptr createInstance(const QByteArray &field=QByteArray(), Function function=FuncContains, const QString &contents=QString())
Creates a new search rule of a certain type by instantiating the appropriate subclass depending on th...
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
The filter dialog.
const char * constData() const const
bool atEnd() const const
void append(QList< T > &&value)
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
iterator erase(const_iterator begin, const_iterator end)
bool isEmpty() const const
bool isEmpty() const const
QByteArray toLatin1() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 6 2024 12:02:04 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.