KIMAP2

searchjob.cpp
1/*
2 Copyright (c) 2009 Andras Mantia <amantia@kde.org>
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
31namespace KIMAP2
32{
33
34class Term::Private
35{
36public:
37 Private(): isFuzzy(false), isNegated(false), isNull(false) {}
38 QByteArray command;
39 bool isFuzzy;
40 bool isNegated;
41 bool isNull;
42};
43
44Term::Term()
45 : d(new Term::Private)
46{
47 d->isNull = true;
48}
49
50Term::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
79Term::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
116Term::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
124Term::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
155static 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
161Term::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
191Term::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
205Term::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
220Term::Term(const Term &other)
221 : d(new Term::Private)
222{
223 *d = *other.d;
224}
225
226Term &Term::operator=(const Term &other)
227{
228 *d = *other.d;
229 return *this;
230}
231
232bool 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
239QByteArray 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
251Term &Term::setFuzzy(bool fuzzy)
252{
253 d->isFuzzy = fuzzy;
254 return *this;
255}
256
257Term &Term::setNegated(bool negated)
258{
259 d->isNegated = negated;
260 return *this;
261}
262
263bool Term::isNull() const
264{
265 return d->isNull;
266}
267
268//TODO: when custom error codes are introduced, handle the NO [TRYCREATE] response
269
270class SearchJobPrivate : public JobPrivate
271{
272public:
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;
332 SearchJob::SearchLogic logic;
333 QList<QByteArray> contents;
334 QVector<qint64> results;
335 uint nextContent;
336 bool uidBased;
337 Term term;
338};
339}
340
341using namespace KIMAP2;
342
343SearchJob::SearchJob(Session *session)
344 : Job(*new SearchJobPrivate(session, "Search"))
345{
346}
347
348SearchJob::~SearchJob()
349{
350}
351
352void SearchJob::setTerm(const Term &term)
353{
354 Q_D(SearchJob);
355 d->term = term;
356}
357
358void 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
410void 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
430void SearchJob::setCharset(const QByteArray &charset)
431{
432 Q_D(SearchJob);
433 d->charset = charset;
434}
435
436QByteArray SearchJob::charset() const
437{
438 Q_D(const SearchJob);
439 return d->charset;
440}
441
442void SearchJob::setUidBased(bool uidBased)
443{
444 Q_D(SearchJob);
445 d->uidBased = uidBased;
446}
447
448bool SearchJob::isUidBased() const
449{
450 Q_D(const SearchJob);
451 return d->uidBased;
452}
453
454QVector<qint64> SearchJob::results() const
455{
456 Q_D(const SearchJob);
457 return d->results;
458}
Represents a set of natural numbers (1->∞) in a as compact as possible form.
Definition imapset.h:142
void optimize()
Optimizes the ImapSet by sorting and merging overlapping intervals.
Definition imapset.cpp:319
A query term.
Definition searchjob.h:46
AKONADI_MIME_EXPORT const char Answered[]
AKONADI_MIME_EXPORT const char Seen[]
AKONADI_MIME_EXPORT const char Deleted[]
AKONADI_MIME_EXPORT const char Flagged[]
AKONADI_MIME_EXPORT const char Body[]
QString name(StandardAction id)
char at(qsizetype i) const const
const char * constData() const const
QByteArray mid(qsizetype pos, qsizetype len) const const
QByteArray number(double n, char format, int precision)
qsizetype size() const const
bool startsWith(QByteArrayView bv) const const
int day() const const
int month() const const
int year() const const
bool isEmpty() const const
qsizetype size() const const
QByteArray toUtf8() const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:56:30 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.