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"
12 #include "mailcommon_debug.h"
13 #include <Akonadi/ContactSearchJob>
14 
15 #include <KMime/KMimeMessage>
16 
17 #include <KConfigGroup>
18 
19 #include <QDataStream>
20 #include <QIODevice>
21 
22 #include <algorithm>
23 
24 using 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 
46 bool 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 
98 QString SearchPattern::purify(bool removeAction)
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 == QLatin1String("or")) {
134  mOperator = OpOr;
135  } else if (op == QLatin1String("and")) {
136  mOperator = OpAnd;
137  } else if (op == QLatin1String("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 
151 void 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 == QLatin1String("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 == QLatin1String("or")) {
176  mOperator = OpOr;
177  return;
178  }
179  // This is the interesting case...
180  if (sOperator == QLatin1String("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 
226 int SearchPattern::filterRulesMaximumSize()
227 {
228  return 8;
229 }
230 
231 void 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 += QLatin1String("\n\t") + FilterLog::recode((*it)->asString());
257  }
258 
259  return result;
260 }
261 
262 SearchPattern::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 
325 QDataStream &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 
345 QDataStream &SearchPattern::operator<<(QDataStream &s)
346 {
347  QString op;
348  s >> op;
349  if (op == QLatin1String("and")) {
350  setOp(OpAnd);
351  } else if (op == QLatin1String("or")) {
352  setOp(OpOr);
353  } else if (op == QLatin1String("all")) {
354  setOp(OpAll);
355  }
356 
357  while (!s.atEnd()) {
359  append(rule);
360  }
361  return s;
362 }
363 
364 void SearchPattern::generateSieveScript(QStringList &requiresModules, QString &code)
365 {
366  code += QLatin1String("\n#") + mName + QLatin1Char('\n');
367  switch (mOperator) {
368  case OpOr:
369  code += QLatin1String("if anyof (");
370  break;
371  case OpAnd:
372  code += QLatin1String("if allof (");
373  break;
374  case OpAll:
375  code += QLatin1String("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 += QLatin1String("\n, ");
385  }
386  (*it)->generateSieveScript(requiresModules, code);
387  }
388 }
389 
390 // Needed for MSVC 2010, as it seems to not implicit cast for a pointer anymore
391 #ifdef _MSC_VER
392 namespace MailCommon
393 {
394 uint qHash(SearchRule::Ptr sr)
395 {
396  return ::qHash(sr.get());
397 }
398 }
399 #endif
void append(const T &value)
SparqlQueryError asAkonadiQuery(Akonadi::SearchQuery &) const
Returns the pattern as akonadi query.
QString readEntry(const char *key, const char *aDefault=nullptr) const
void writeEntry(const char *key, const char *value, WriteConfigFlags pFlags=Normal)
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:188
const SearchPattern & operator=(const SearchPattern &aPattern)
Overloaded assignment operator.
QList< SearchTerm > subTerms() const
QByteArray toLatin1() const 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:103
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:75
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.
bool hasKey(const char *key) const
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
const char * constData() const const
@ 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-2023 The KDE developers.
Generated on Tue Jun 6 2023 03:57:38 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.