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

KDE's Doxygen guidelines are available online.