Mailcommon

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

KDE's Doxygen guidelines are available online.