Akonadi Search

emailquery.cpp
1/*
2 * This file is part of the KDE Akonadi Search Project
3 * SPDX-FileCopyrightText: 2013 Vishesh Handa <me@vhanda.in>
4 *
5 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 *
7 */
8
9#include <xapian.h>
10
11#include "akonadi_search_pim_debug.h"
12#include "emailquery.h"
13#include "resultiterator_p.h"
14#include "search/email/agepostingsource.h"
15
16#include <QFile>
17#include <QRegularExpression>
18#include <QStandardPaths>
19
20using namespace Akonadi::Search::PIM;
21
22class Akonadi::Search::PIM::EmailQueryPrivate
23{
24public:
25 EmailQueryPrivate();
26
27 QStringList involves;
28 QStringList to;
29 QStringList cc;
30 QStringList bcc;
31 QString from;
32
33 QList<Akonadi::Collection::Id> collections;
34
35 char important{'0'};
36 char read{'0'};
37 char attachment{'0'};
38
39 QString matchString;
40 QString subjectMatchString;
41 QString bodyMatchString;
42
43 EmailQuery::OpType opType = EmailQuery::OpAnd;
44 int limit = 0;
45 bool splitSearchMatchString = true;
46};
47
48EmailQueryPrivate::EmailQueryPrivate()
49
50{
51}
52
53EmailQuery::EmailQuery()
54 : d(new EmailQueryPrivate)
55{
56}
57
58EmailQuery::~EmailQuery() = default;
59
60void EmailQuery::setSplitSearchMatchString(bool split)
61{
62 d->splitSearchMatchString = split;
63}
64
65void EmailQuery::setSearchType(EmailQuery::OpType op)
66{
67 d->opType = op;
68}
69
70void EmailQuery::addInvolves(const QString &email)
71{
72 d->involves << email;
73}
74
75void EmailQuery::setInvolves(const QStringList &involves)
76{
77 d->involves = involves;
78}
79
80void EmailQuery::addBcc(const QString &bcc)
81{
82 d->bcc << bcc;
83}
84
85void EmailQuery::setBcc(const QStringList &bcc)
86{
87 d->bcc = bcc;
88}
89
90void EmailQuery::setCc(const QStringList &cc)
91{
92 d->cc = cc;
93}
94
95void EmailQuery::setFrom(const QString &from)
96{
97 d->from = from;
98}
99
100void EmailQuery::addTo(const QString &to)
101{
102 d->to << to;
103}
104
105void EmailQuery::setTo(const QStringList &to)
106{
107 d->to = to;
108}
109
110void EmailQuery::addCc(const QString &cc)
111{
112 d->cc << cc;
113}
114
115void EmailQuery::addFrom(const QString &from)
116{
117 d->from = from;
118}
119
120void EmailQuery::addCollection(Akonadi::Collection::Id id)
121{
122 d->collections << id;
123}
124
125void EmailQuery::setCollection(const QList<Akonadi::Collection::Id> &collections)
126{
127 d->collections = collections;
128}
129
130int EmailQuery::limit() const
131{
132 return d->limit;
133}
134
135void EmailQuery::setLimit(int limit)
136{
137 d->limit = limit;
138}
139
140void EmailQuery::matches(const QString &match)
141{
142 d->matchString = match;
143}
144
145void EmailQuery::subjectMatches(const QString &subjectMatch)
146{
147 d->subjectMatchString = subjectMatch;
148}
149
150void EmailQuery::bodyMatches(const QString &bodyMatch)
151{
152 d->bodyMatchString = bodyMatch;
153}
154
155void EmailQuery::setAttachment(bool hasAttachment)
156{
157 d->attachment = hasAttachment ? 'T' : 'F';
158}
159
160void EmailQuery::setImportant(bool important)
161{
162 d->important = important ? 'T' : 'F';
163}
164
165void EmailQuery::setRead(bool read)
166{
167 d->read = read ? 'T' : 'F';
168}
169
171{
172 const QString dir = defaultLocation(QStringLiteral("email"));
173 Xapian::Database db;
174 try {
175 db = Xapian::Database(QFile::encodeName(dir).toStdString());
176 } catch (const Xapian::DatabaseOpeningError &) {
177 qCWarning(AKONADI_SEARCH_PIM_LOG) << "Xapian Database does not exist at " << dir;
178 return {};
179 } catch (const Xapian::DatabaseCorruptError &) {
180 qCWarning(AKONADI_SEARCH_PIM_LOG) << "Xapian Database corrupted";
181 return {};
182 } catch (const Xapian::DatabaseError &e) {
183 qCWarning(AKONADI_SEARCH_PIM_LOG) << "Failed to open Xapian database:" << QString::fromStdString(e.get_description());
184 return {};
185 } catch (...) {
186 qCWarning(AKONADI_SEARCH_PIM_LOG) << "Random exception, but we do not want to crash";
187 return {};
188 }
189
190 QList<Xapian::Query> m_queries;
191
192 if (!d->involves.isEmpty()) {
193 Xapian::QueryParser parser;
194 parser.set_database(db);
195 parser.add_prefix("", "F");
196 parser.add_prefix("", "T");
197 parser.add_prefix("", "CC");
198 parser.add_prefix("", "BCC");
199
200 // vHanda: Do we really need the query parser over here?
201 for (const QString &str : std::as_const(d->involves)) {
202 const QByteArray ba = str.toUtf8();
203 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL);
204 }
205 }
206
207 if (!d->from.isEmpty()) {
208 Xapian::QueryParser parser;
209 parser.set_database(db);
210 parser.add_prefix("", "F");
211 const QByteArray ba = d->from.toUtf8();
212 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL);
213 }
214
215 if (!d->to.isEmpty()) {
216 Xapian::QueryParser parser;
217 parser.set_database(db);
218 parser.add_prefix("", "T");
219
220 for (const QString &str : std::as_const(d->to)) {
221 const QByteArray ba = str.toUtf8();
222 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL);
223 }
224 }
225
226 if (!d->cc.isEmpty()) {
227 Xapian::QueryParser parser;
228 parser.set_database(db);
229 parser.add_prefix("", "CC");
230
231 for (const QString &str : std::as_const(d->cc)) {
232 const QByteArray ba = str.toUtf8();
233 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL);
234 }
235 }
236
237 if (!d->bcc.isEmpty()) {
238 Xapian::QueryParser parser;
239 parser.set_database(db);
240 parser.add_prefix("", "BC");
241
242 for (const QString &str : std::as_const(d->bcc)) {
243 const QByteArray ba = str.toUtf8();
244 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL);
245 }
246 }
247
248 if (!d->subjectMatchString.isEmpty()) {
249 Xapian::QueryParser parser;
250 parser.set_database(db);
251 parser.add_prefix("", "SU");
252 parser.set_default_op(Xapian::Query::OP_AND);
253 const QByteArray ba = d->subjectMatchString.toUtf8();
254 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL);
255 }
256
257 if (!d->collections.isEmpty()) {
258 Xapian::Query query;
259 for (Akonadi::Collection::Id id : std::as_const(d->collections)) {
260 const QString c = QString::number(id);
261 const Xapian::Query q = Xapian::Query('C' + c.toStdString());
262
263 query = Xapian::Query(Xapian::Query::OP_OR, query, q);
264 }
265
266 m_queries << query;
267 }
268
269 if (!d->bodyMatchString.isEmpty()) {
270 Xapian::QueryParser parser;
271 parser.set_database(db);
272 parser.add_prefix("", "BO");
273 parser.set_default_op(Xapian::Query::OP_AND);
274 const QByteArray ba = d->bodyMatchString.toUtf8();
275 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL);
276 }
277
278 if (d->important == 'T') {
279 m_queries << Xapian::Query("BI");
280 } else if (d->important == 'F') {
281 m_queries << Xapian::Query("BNI");
282 }
283
284 if (d->read == 'T') {
285 m_queries << Xapian::Query("BR");
286 } else if (d->read == 'F') {
287 m_queries << Xapian::Query("BNR");
288 }
289
290 if (d->attachment == 'T') {
291 m_queries << Xapian::Query("BA");
292 } else if (d->attachment == 'F') {
293 m_queries << Xapian::Query("BNA");
294 }
295
296 if (!d->matchString.isEmpty()) {
297 Xapian::QueryParser parser;
298 parser.set_database(db);
299 parser.set_default_op(Xapian::Query::OP_AND);
300 if (d->splitSearchMatchString) {
301 const QStringList list = d->matchString.split(QRegularExpression(QStringLiteral("\\s")), Qt::SkipEmptyParts);
302 for (const QString &s : list) {
303 const QByteArray ba = s.toUtf8();
304 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL);
305 }
306 } else {
307 const QByteArray ba = d->matchString.toUtf8();
308 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL);
309 }
310 }
311 Xapian::Query query;
312 switch (d->opType) {
313 case OpType::OpAnd:
314 query = Xapian::Query(Xapian::Query::OP_AND, m_queries.begin(), m_queries.end());
315 break;
316 case OpType::OpOr:
317 query = Xapian::Query(Xapian::Query::OP_OR, m_queries.begin(), m_queries.end());
318 break;
319 }
320
321 AgePostingSource ps(0);
322 query = Xapian::Query(Xapian::Query::OP_AND_MAYBE, query, Xapian::Query(&ps));
323
324 try {
325 Xapian::Enquire enquire(db);
326 enquire.set_query(query);
327
328 if (d->limit == 0) {
329 // d->limit = 1000000;
330 d->limit = 100000;
331 }
332
333 Xapian::MSet mset = enquire.get_mset(0, d->limit);
334
335 ResultIterator iter;
336 iter.d->init(mset);
337 return iter;
338 } catch (const Xapian::Error &e) {
339 qCWarning(AKONADI_SEARCH_PIM_LOG) << QString::fromStdString(e.get_type()) << QString::fromStdString(e.get_description());
340 return {};
341 }
342}
void matches(const QString &match)
Matches the string match anywhere in the entire email body.
void setAttachment(bool hasAttachment=true)
By default the attachment status is ignored.
ResultIterator exec() override
Execute the query and return an iterator to fetch the results.
void setImportant(bool important=true)
By default the importance is ignored.
void setRead(bool read=true)
By default the read status is ignored.
void bodyMatches(const QString &bodyMatch)
Matches the string bodyMatch specifically in the body email.
void subjectMatches(const QString &subjectMatch)
Matches the string subjectMatch specifically in the email subject.
PIM specific search API.
const char * constData() const const
QByteArray encodeName(const QString &fileName)
iterator begin()
iterator end()
QString fromStdString(const std::string &str)
QString number(double n, char format, int precision)
std::string toStdString() const const
SkipEmptyParts
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:51:42 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.