KIMAP2

searchjob.cpp
1 /*
2  Copyright (c) 2009 Andras Mantia <[email protected]>
3 
4  This library is free software; you can redistribute it and/or modify it
5  under the terms of the GNU Library General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or (at your
7  option) any later version.
8 
9  This library is distributed in the hope that it will be useful, but WITHOUT
10  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12  License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to the
16  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  02110-1301, USA.
18 */
19 
20 #include "searchjob.h"
21 
22 #include "kimap_debug.h"
23 
24 #include <QtCore/QDate>
25 
26 #include "job_p.h"
27 #include "message_p.h"
28 #include "session_p.h"
29 #include "imapset.h"
30 
31 namespace KIMAP2
32 {
33 
34 class Term::Private
35 {
36 public:
37  Private(): isFuzzy(false), isNegated(false), isNull(false) {}
38  QByteArray command;
39  bool isFuzzy;
40  bool isNegated;
41  bool isNull;
42 };
43 
44 Term::Term()
45  : d(new Term::Private)
46 {
47  d->isNull = true;
48 }
49 
50 Term::Term(Term::Relation relation, const QVector<Term> &subterms)
51  : d(new Term::Private)
52 {
53  if (subterms.size() >= 2) {
54  if (relation == KIMAP2::Term::Or) {
55  for (int i = 0; i < subterms.size() - 1; ++i) {
56  d->command += "(OR " + subterms[i].serialize() + " ";
57  }
58  d->command += subterms.back().serialize();
59  for (int i = 0; i < subterms.size() - 1; ++i) {
60  d->command += ")";
61  }
62  } else {
63  d->command += "(";
64  for (const Term &t : subterms) {
65  d->command += t.serialize() + ' ';
66  }
67  if (!subterms.isEmpty()) {
68  d->command.chop(1);
69  }
70  d->command += ")";
71  }
72  } else if (subterms.size() == 1) {
73  d->command += subterms.first().serialize();
74  } else {
75  d->isNull = true;
76  }
77 }
78 
79 Term::Term(Term::SearchKey key, const QString &value)
80  : d(new Term::Private)
81 {
82  switch (key) {
83  case All:
84  d->command += "ALL";
85  break;
86  case Bcc:
87  d->command += "BCC";
88  break;
89  case Cc:
90  d->command += "CC";
91  break;
92  case Body:
93  d->command += "BODY";
94  break;
95  case From:
96  d->command += "FROM";
97  break;
98  case Keyword:
99  d->command += "KEYWORD";
100  break;
101  case Subject:
102  d->command += "SUBJECT";
103  break;
104  case Text:
105  d->command += "TEXT";
106  break;
107  case To:
108  d->command += "TO";
109  break;
110  }
111  if (key != All) {
112  d->command += " \"" + QByteArray(value.toUtf8().constData()) + "\"";
113  }
114 }
115 
116 Term::Term(const QString &header, const QString &value)
117  : d(new Term::Private)
118 {
119  d->command += "HEADER";
120  d->command += ' ' + QByteArray(header.toUtf8().constData());
121  d->command += " \"" + QByteArray(value.toUtf8().constData()) + "\"";
122 }
123 
124 Term::Term(Term::BooleanSearchKey key)
125  : d(new Term::Private)
126 {
127  switch (key) {
128  case Answered:
129  d->command = "ANSWERED";
130  break;
131  case Deleted:
132  d->command = "DELETED";
133  break;
134  case Draft:
135  d->command = "DRAFT";
136  break;
137  case Flagged:
138  d->command = "FLAGGED";
139  break;
140  case New:
141  d->command = "NEW";
142  break;
143  case Old:
144  d->command = "OLD";
145  break;
146  case Recent:
147  d->command = "RECENT";
148  break;
149  case Seen:
150  d->command = "SEEN";
151  break;
152  }
153 }
154 
155 static QByteArray monthName(int month)
156 {
157  static const char* names[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
158  return (month >= 1 && month <= 12) ? QByteArray(names[month - 1]) : QByteArray();
159 }
160 
161 Term::Term(Term::DateSearchKey key, const QDate &date)
162  : d(new Term::Private)
163 {
164  switch (key) {
165  case Before:
166  d->command = "BEFORE";
167  break;
168  case On:
169  d->command = "ON";
170  break;
171  case SentBefore:
172  d->command = "SENTBEFORE";
173  break;
174  case SentOn:
175  d->command = "SENTON";
176  break;
177  case SentSince:
178  d->command = "SENTSINCE";
179  break;
180  case Since:
181  d->command = "SINCE";
182  break;
183  }
184  d->command += " \"";
185  d->command += QByteArray::number(date.day()) + '-';
186  d->command += monthName(date.month()) + '-';
187  d->command += QByteArray::number(date.year());
188  d->command += '\"';
189 }
190 
191 Term::Term(Term::NumberSearchKey key, int value)
192  : d(new Term::Private)
193 {
194  switch (key) {
195  case Larger:
196  d->command = "LARGER";
197  break;
198  case Smaller:
199  d->command = "SMALLER";
200  break;
201  }
202  d->command += " " + QByteArray::number(value);
203 }
204 
205 Term::Term(Term::SequenceSearchKey key, const ImapSet &set)
206  : d(new Term::Private)
207 {
208  switch (key) {
209  case Uid:
210  d->command = "UID";
211  break;
212  case SequenceNumber:
213  break;
214  }
215  auto optimizedSet = set;
216  optimizedSet.optimize();
217  d->command += " " + optimizedSet.toImapSequenceSet();
218 }
219 
220 Term::Term(const Term &other)
221  : d(new Term::Private)
222 {
223  *d = *other.d;
224 }
225 
226 Term &Term::operator=(const Term &other)
227 {
228  *d = *other.d;
229  return *this;
230 }
231 
232 bool Term::operator==(const Term &other) const
233 {
234  return d->command == other.d->command &&
235  d->isNegated == other.d->isNegated &&
236  d->isFuzzy == other.d->isFuzzy;
237 }
238 
239 QByteArray Term::serialize() const
240 {
241  QByteArray command;
242  if (d->isFuzzy) {
243  command = "FUZZY ";
244  }
245  if (d->isNegated) {
246  command = "NOT ";
247  }
248  return command + d->command;
249 }
250 
251 Term &Term::setFuzzy(bool fuzzy)
252 {
253  d->isFuzzy = fuzzy;
254  return *this;
255 }
256 
257 Term &Term::setNegated(bool negated)
258 {
259  d->isNegated = negated;
260  return *this;
261 }
262 
263 bool Term::isNull() const
264 {
265  return d->isNull;
266 }
267 
268 //TODO: when custom error codes are introduced, handle the NO [TRYCREATE] response
269 
270 class SearchJobPrivate : public JobPrivate
271 {
272 public:
273  SearchJobPrivate(Session *session, const QString &name) : JobPrivate(session, name), logic(SearchJob::And)
274  {
275  criteriaMap[SearchJob::All] = "ALL";
276  criteriaMap[SearchJob::Answered] = "ANSWERED";
277  criteriaMap[SearchJob::BCC] = "BCC";
278  criteriaMap[SearchJob::Before] = "BEFORE";
279  criteriaMap[SearchJob::Body] = "BODY";
280  criteriaMap[SearchJob::CC] = "CC";
281  criteriaMap[SearchJob::Deleted] = "DELETED";
282  criteriaMap[SearchJob::Draft] = "DRAFT";
283  criteriaMap[SearchJob::Flagged] = "FLAGGED";
284  criteriaMap[SearchJob::From] = "FROM";
285  criteriaMap[SearchJob::Header] = "HEADER";
286  criteriaMap[SearchJob::Keyword] = "KEYWORD";
287  criteriaMap[SearchJob::Larger] = "LARGER";
288  criteriaMap[SearchJob::New] = "NEW";
289  criteriaMap[SearchJob::Old] = "OLD";
290  criteriaMap[SearchJob::On] = "ON";
291  criteriaMap[SearchJob::Recent] = "RECENT";
292  criteriaMap[SearchJob::Seen] = "SEEN";
293  criteriaMap[SearchJob::SentBefore] = "SENTBEFORE";
294  criteriaMap[SearchJob::SentOn] = "SENTON";
295  criteriaMap[SearchJob::SentSince] = "SENTSINCE";
296  criteriaMap[SearchJob::Since] = "SINCE";
297  criteriaMap[SearchJob::Smaller] = "SMALLER";
298  criteriaMap[SearchJob::Subject] = "SUBJECT";
299  criteriaMap[SearchJob::Text] = "TEXT";
300  criteriaMap[SearchJob::To] = "TO";
301  criteriaMap[SearchJob::Uid] = "UID";
302  criteriaMap[SearchJob::Unanswered] = "UNANSWERED";
303  criteriaMap[SearchJob::Undeleted] = "UNDELETED";
304  criteriaMap[SearchJob::Undraft] = "UNDRAFT";
305  criteriaMap[SearchJob::Unflagged] = "UNFLAGGED";
306  criteriaMap[SearchJob::Unkeyword] = "UNKEYWORD";
307  criteriaMap[SearchJob::Unseen] = "UNSEEN";
308 
309  //don't use QDate::shortMonthName(), it returns a localized month name
310  months[1] = "Jan";
311  months[2] = "Feb";
312  months[3] = "Mar";
313  months[4] = "Apr";
314  months[5] = "May";
315  months[6] = "Jun";
316  months[7] = "Jul";
317  months[8] = "Aug";
318  months[9] = "Sep";
319  months[10] = "Oct";
320  months[11] = "Nov";
321  months[12] = "Dec";
322 
323  nextContent = 0;
324  uidBased = false;
325  }
326  ~SearchJobPrivate() { }
327 
328  QByteArray charset;
329  QList<QByteArray> criterias;
331  QMap<int, QByteArray> months;
332  SearchJob::SearchLogic logic;
333  QList<QByteArray> contents;
334  QVector<qint64> results;
335  uint nextContent;
336  bool uidBased;
337  Term term;
338 };
339 }
340 
341 using namespace KIMAP2;
342 
343 SearchJob::SearchJob(Session *session)
344  : Job(*new SearchJobPrivate(session, "Search"))
345 {
346 }
347 
348 SearchJob::~SearchJob()
349 {
350 }
351 
352 void SearchJob::setTerm(const Term &term)
353 {
354  Q_D(SearchJob);
355  d->term = term;
356 }
357 
358 void SearchJob::doStart()
359 {
360  Q_D(SearchJob);
361 
362  QByteArray searchKey;
363 
364  if (!d->charset.isEmpty()) {
365  searchKey = "CHARSET " + d->charset;
366  }
367 
368  if (!d->term.isNull()) {
369  const QByteArray term = d->term.serialize();
370  if (term.startsWith('(')) {
371  searchKey += term.mid(1, term.size() - 2);
372  } else {
373  searchKey += term;
374  }
375  } else {
376 
377  if (d->logic == SearchJob::Not) {
378  searchKey += "NOT ";
379  } else if (d->logic == SearchJob::Or && d->criterias.size() > 1) {
380  searchKey += "OR ";
381  }
382 
383  if (d->logic == SearchJob::And) {
384  for (int i = 0; i < d->criterias.size(); i++) {
385  const QByteArray key = d->criterias.at(i);
386  if (i > 0) {
387  searchKey += ' ';
388  }
389  searchKey += key;
390  }
391  } else {
392  for (int i = 0; i < d->criterias.size(); i++) {
393  const QByteArray key = d->criterias.at(i);
394  if (i > 0) {
395  searchKey += ' ';
396  }
397  searchKey += '(' + key + ')';
398  }
399  }
400  }
401 
402  QByteArray command = "SEARCH";
403  if (d->uidBased) {
404  command = "UID " + command;
405  }
406 
407  d->sendCommand(command, searchKey);
408 }
409 
410 void SearchJob::handleResponse(const Message &response)
411 {
412  Q_D(SearchJob);
413 
414  if (handleErrorReplies(response) == NotHandled) {
415  if (response.content.size() >= 1 && response.content[0].toString() == "+") {
416  if (d->term.isNull()) {
417  d->sessionInternal()->sendData(d->contents[d->nextContent]);
418  } else {
419  qCWarning(KIMAP2_LOG) << "The term API only supports inline strings.";
420  }
421  d->nextContent++;
422  } else if (response.content.size() >= 2 && response.content[1].toString() == "SEARCH") {
423  for (int i = 2; i < response.content.size(); i++) {
424  d->results.append(response.content[i].toString().toInt());
425  }
426  }
427  }
428 }
429 
430 void SearchJob::setCharset(const QByteArray &charset)
431 {
432  Q_D(SearchJob);
433  d->charset = charset;
434 }
435 
436 QByteArray SearchJob::charset() const
437 {
438  Q_D(const SearchJob);
439  return d->charset;
440 }
441 
442 void SearchJob::setUidBased(bool uidBased)
443 {
444  Q_D(SearchJob);
445  d->uidBased = uidBased;
446 }
447 
448 bool SearchJob::isUidBased() const
449 {
450  Q_D(const SearchJob);
451  return d->uidBased;
452 }
453 
454 QVector<qint64> SearchJob::results() const
455 {
456  Q_D(const SearchJob);
457  return d->results;
458 }
Subject
char at(int i) const const
AKONADI_MIME_EXPORT const char Deleted[]
AKONADI_MIME_EXPORT const char Answered[]
bool startsWith(const QByteArray &ba) const const
int day() const const
QVector::reference back()
Represents a set of natural numbers (1->∞) in a as compact as possible form.
Definition: imapset.h:141
QByteArray number(int n, int base)
const char * constData() const const
A query term.
Definition: searchjob.h:45
Definition: acl.cpp:25
QByteArray mid(int pos, int len) const const
AKONADI_MIME_EXPORT const char Body[]
bool isNull() const const
int size() const const
int year() const const
int size() const const
int month() const const
AKONADI_MIME_EXPORT const char Seen[]
AKONADI_MIME_EXPORT const char Flagged[]
QByteArray toUtf8() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Thu Dec 2 2021 23:01:35 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.