1 /*
2 * SPDX-FileCopyrightText: 2013 Daniel Vrátil <dvratil@redhat.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5 *
6 */
7
8 #include "searchtask.h"
9 #include "imapresource_debug.h"
10 #include <Akonadi/KMime/MessageFlags>
11 #include <Akonadi/SearchQuery>
12 #include <KIMAP/SearchJob>
13 #include <KIMAP/SelectJob>
14 #include <KIMAP/Session>
15 #include <KLocalizedString>
Q_DECLARE_METATYPE(KIMAP::Session *)16 Q_DECLARE_METATYPE(KIMAP::Session *)
17
18 SearchTask::SearchTask(const ResourceStateInterface::Ptr &state, const QString &query, QObject *parent)
19 : ResourceTask(ResourceTask::DeferIfNoSession, state, parent)
20 , m_query(query)
21 {
22 }
23
~SearchTask()24 SearchTask::~SearchTask()
25 {
26 }
27
doStart(KIMAP::Session * session)28 void SearchTask::doStart(KIMAP::Session *session)
29 {
30 qCDebug(IMAPRESOURCE_LOG) << collection().remoteId();
31
32 const QString mailbox = mailBoxForCollection(collection());
33 if (session->selectedMailBox() == mailbox) {
34 doSearch(session);
35 return;
36 }
37
38 auto select = new KIMAP::SelectJob(session);
39 select->setMailBox(mailbox);
40 connect(select, &KJob::finished, this, &SearchTask::onSelectDone);
41 select->start();
42 }
43
onSelectDone(KJob * job)44 void SearchTask::onSelectDone(KJob *job)
45 {
46 if (job->error()) {
47 searchFinished(QVector<qint64>());
48 cancelTask(job->errorText());
49 return;
50 }
51
52 doSearch(qobject_cast<KIMAP::SelectJob *>(job)->session());
53 }
54
mapRelation(Akonadi::SearchTerm::Relation relation)55 static KIMAP::Term::Relation mapRelation(Akonadi::SearchTerm::Relation relation)
56 {
57 if (relation == Akonadi::SearchTerm::RelAnd) {
58 return KIMAP::Term::And;
59 }
60 return KIMAP::Term::Or;
61 }
62
recursiveEmailTermMapping(const Akonadi::SearchTerm & term)63 static KIMAP::Term recursiveEmailTermMapping(const Akonadi::SearchTerm &term)
64 {
65 if (!term.subTerms().isEmpty()) {
66 QVector<KIMAP::Term> subterms;
67 const QList<Akonadi::SearchTerm> lstSearchTermsList = term.subTerms();
68 for (const Akonadi::SearchTerm &subterm : lstSearchTermsList) {
69 const KIMAP::Term newTerm = recursiveEmailTermMapping(subterm);
70 if (!newTerm.isNull()) {
71 subterms << newTerm;
72 }
73 }
74 return KIMAP::Term(mapRelation(term.relation()), subterms);
75 } else {
76 const Akonadi::EmailSearchTerm::EmailSearchField field = Akonadi::EmailSearchTerm::fromKey(term.key());
77 switch (field) {
78 case Akonadi::EmailSearchTerm::Message:
79 return KIMAP::Term(KIMAP::Term::Text, term.value().toString()).setNegated(term.isNegated());
80 case Akonadi::EmailSearchTerm::Body:
81 return KIMAP::Term(KIMAP::Term::Body, term.value().toString()).setNegated(term.isNegated());
82 case Akonadi::EmailSearchTerm::Headers:
83 // FIXME
84 // return KIMAP::Term(KIMAP::Term::Header, term.value()).setNegated(term.isNegated());
85 break;
86 case Akonadi::EmailSearchTerm::ByteSize: {
87 int value = term.value().toInt();
88 switch (term.condition()) {
89 case Akonadi::SearchTerm::CondGreaterOrEqual:
90 value--;
91 Q_FALLTHROUGH();
92 case Akonadi::SearchTerm::CondGreaterThan:
93 return KIMAP::Term(KIMAP::Term::Larger, value).setNegated(term.isNegated());
94 case Akonadi::SearchTerm::CondLessOrEqual:
95 value++;
96 Q_FALLTHROUGH();
97 case Akonadi::SearchTerm::CondLessThan:
98 return KIMAP::Term(KIMAP::Term::Smaller, value).setNegated(term.isNegated());
99 case Akonadi::SearchTerm::CondEqual:
100 return KIMAP::Term(KIMAP::Term::And,
101 QVector<KIMAP::Term>() << KIMAP::Term(KIMAP::Term::Smaller, value + 1) << KIMAP::Term(KIMAP::Term::Larger, value + 1))
102 .setNegated(term.isNegated());
103 case Akonadi::SearchTerm::CondContains:
104 qCDebug(IMAPRESOURCE_LOG) << " invalid condition for ByteSize";
105 break;
106 }
107 break;
108 }
109 case Akonadi::EmailSearchTerm::HeaderOnlyDate:
110 case Akonadi::EmailSearchTerm::HeaderDate: {
111 QDate value = term.value().toDateTime().date();
112 switch (term.condition()) {
113 case Akonadi::SearchTerm::CondGreaterOrEqual:
114 value = value.addDays(-1);
115 Q_FALLTHROUGH();
116 case Akonadi::SearchTerm::CondGreaterThan:
117 return KIMAP::Term(KIMAP::Term::SentSince, value).setNegated(term.isNegated());
118 case Akonadi::SearchTerm::CondLessOrEqual:
119 value = value.addDays(1);
120 Q_FALLTHROUGH();
121 case Akonadi::SearchTerm::CondLessThan:
122 return KIMAP::Term(KIMAP::Term::SentBefore, value).setNegated(term.isNegated());
123 case Akonadi::SearchTerm::CondEqual:
124 return KIMAP::Term(KIMAP::Term::SentOn, value).setNegated(term.isNegated());
125 case Akonadi::SearchTerm::CondContains:
126 qCDebug(IMAPRESOURCE_LOG) << " invalid condition for Date";
127 return KIMAP::Term();
128 default:
129 qCWarning(IMAPRESOURCE_LOG) << "unknown term for date" << term.key();
130 return KIMAP::Term();
131 }
132 }
133 case Akonadi::EmailSearchTerm::Subject:
134 return KIMAP::Term(KIMAP::Term::Subject, term.value().toString()).setNegated(term.isNegated());
135 case Akonadi::EmailSearchTerm::HeaderFrom:
136 return KIMAP::Term(KIMAP::Term::From, term.value().toString()).setNegated(term.isNegated());
137 case Akonadi::EmailSearchTerm::HeaderTo:
138 return KIMAP::Term(KIMAP::Term::To, term.value().toString()).setNegated(term.isNegated());
139 case Akonadi::EmailSearchTerm::HeaderCC:
140 return KIMAP::Term(KIMAP::Term::Cc, term.value().toString()).setNegated(term.isNegated());
141 case Akonadi::EmailSearchTerm::HeaderBCC:
142 return KIMAP::Term(KIMAP::Term::Bcc, term.value().toString()).setNegated(term.isNegated());
143 case Akonadi::EmailSearchTerm::MessageStatus: {
144 const QString termStr = term.value().toString();
145 if (termStr == QString::fromLatin1(Akonadi::MessageFlags::Flagged)) {
146 return KIMAP::Term(KIMAP::Term::Flagged).setNegated(term.isNegated());
147 }
148 if (termStr == QString::fromLatin1(Akonadi::MessageFlags::Deleted)) {
149 return KIMAP::Term(KIMAP::Term::Deleted).setNegated(term.isNegated());
150 }
151 if (termStr == QString::fromLatin1(Akonadi::MessageFlags::Replied)) {
152 return KIMAP::Term(KIMAP::Term::Answered).setNegated(term.isNegated());
153 }
154 if (termStr == QString::fromLatin1(Akonadi::MessageFlags::Seen)) {
155 return KIMAP::Term(KIMAP::Term::Seen).setNegated(term.isNegated());
156 }
157 break;
158 }
159 case Akonadi::EmailSearchTerm::MessageTag:
160 break;
161 case Akonadi::EmailSearchTerm::HeaderReplyTo:
162 break;
163 case Akonadi::EmailSearchTerm::HeaderOrganization:
164 break;
165 case Akonadi::EmailSearchTerm::HeaderListId:
166 break;
167 case Akonadi::EmailSearchTerm::HeaderResentFrom:
168 break;
169 case Akonadi::EmailSearchTerm::HeaderXLoop:
170 break;
171 case Akonadi::EmailSearchTerm::HeaderXMailingList:
172 break;
173 case Akonadi::EmailSearchTerm::HeaderXSpamFlag:
174 break;
175 case Akonadi::EmailSearchTerm::Unknown:
176 default:
177 qCWarning(IMAPRESOURCE_LOG) << "unknown term " << term.key();
178 }
179 }
180 return KIMAP::Term();
181 }
182
doSearch(KIMAP::Session * session)183 void SearchTask::doSearch(KIMAP::Session *session)
184 {
185 qCDebug(IMAPRESOURCE_LOG) << m_query;
186
187 Akonadi::SearchQuery query = Akonadi::SearchQuery::fromJSON(m_query.toLatin1());
188 auto searchJob = new KIMAP::SearchJob(session);
189 searchJob->setUidBased(true);
190
191 KIMAP::Term term = recursiveEmailTermMapping(query.term());
192 if (term.isNull()) {
193 qCWarning(IMAPRESOURCE_LOG) << "failed to translate query " << m_query;
194 searchFinished(QVector<qint64>());
195 cancelTask(i18n("Invalid search"));
196 return;
197 }
198 searchJob->setTerm(term);
199
200 connect(searchJob, &KJob::finished, this, &SearchTask::onSearchDone);
201 searchJob->start();
202 }
203
onSearchDone(KJob * job)204 void SearchTask::onSearchDone(KJob *job)
205 {
206 if (job->error()) {
207 qCWarning(IMAPRESOURCE_LOG) << "Failed to execute search " << job->errorString();
208 qCDebug(IMAPRESOURCE_LOG) << m_query;
209 searchFinished(QVector<qint64>());
210 cancelTask(job->errorString());
211 return;
212 }
213
214 auto searchJob = qobject_cast<KIMAP::SearchJob *>(job);
215 const QVector<qint64> result = searchJob->results();
216 qCDebug(IMAPRESOURCE_LOG) << result.count() << "matches";
217
218 searchFinished(result);
219 taskDone();
220 }
221