File indexing completed on 2024-05-19 05:11:52
0001 /* 0002 * This file is part of the KDE Akonadi Search Project 0003 * SPDX-FileCopyrightText: 2013 Vishesh Handa <me@vhanda.in> 0004 * 0005 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 * 0007 */ 0008 0009 #include <xapian.h> 0010 0011 #include "../search/email/agepostingsource.h" 0012 #include "akonadi_search_pim_debug.h" 0013 #include "emailquery.h" 0014 #include "resultiterator_p.h" 0015 0016 #include <QFile> 0017 #include <QRegularExpression> 0018 #include <QStandardPaths> 0019 0020 using namespace Akonadi::Search::PIM; 0021 0022 class Akonadi::Search::PIM::EmailQueryPrivate 0023 { 0024 public: 0025 EmailQueryPrivate(); 0026 0027 QStringList involves; 0028 QStringList to; 0029 QStringList cc; 0030 QStringList bcc; 0031 QString from; 0032 0033 QList<Akonadi::Collection::Id> collections; 0034 0035 char important; 0036 char read; 0037 char attachment; 0038 0039 QString matchString; 0040 QString subjectMatchString; 0041 QString bodyMatchString; 0042 0043 EmailQuery::OpType opType = EmailQuery::OpAnd; 0044 int limit = 0; 0045 bool splitSearchMatchString = true; 0046 }; 0047 0048 EmailQueryPrivate::EmailQueryPrivate() 0049 : important('0') 0050 , read('0') 0051 , attachment('0') 0052 { 0053 } 0054 0055 EmailQuery::EmailQuery() 0056 : Query() 0057 , d(new EmailQueryPrivate) 0058 { 0059 } 0060 0061 EmailQuery::~EmailQuery() = default; 0062 0063 void EmailQuery::setSplitSearchMatchString(bool split) 0064 { 0065 d->splitSearchMatchString = split; 0066 } 0067 0068 void EmailQuery::setSearchType(EmailQuery::OpType op) 0069 { 0070 d->opType = op; 0071 } 0072 0073 void EmailQuery::addInvolves(const QString &email) 0074 { 0075 d->involves << email; 0076 } 0077 0078 void EmailQuery::setInvolves(const QStringList &involves) 0079 { 0080 d->involves = involves; 0081 } 0082 0083 void EmailQuery::addBcc(const QString &bcc) 0084 { 0085 d->bcc << bcc; 0086 } 0087 0088 void EmailQuery::setBcc(const QStringList &bcc) 0089 { 0090 d->bcc = bcc; 0091 } 0092 0093 void EmailQuery::setCc(const QStringList &cc) 0094 { 0095 d->cc = cc; 0096 } 0097 0098 void EmailQuery::setFrom(const QString &from) 0099 { 0100 d->from = from; 0101 } 0102 0103 void EmailQuery::addTo(const QString &to) 0104 { 0105 d->to << to; 0106 } 0107 0108 void EmailQuery::setTo(const QStringList &to) 0109 { 0110 d->to = to; 0111 } 0112 0113 void EmailQuery::addCc(const QString &cc) 0114 { 0115 d->cc << cc; 0116 } 0117 0118 void EmailQuery::addFrom(const QString &from) 0119 { 0120 d->from = from; 0121 } 0122 0123 void EmailQuery::addCollection(Akonadi::Collection::Id id) 0124 { 0125 d->collections << id; 0126 } 0127 0128 void EmailQuery::setCollection(const QList<Akonadi::Collection::Id> &collections) 0129 { 0130 d->collections = collections; 0131 } 0132 0133 int EmailQuery::limit() const 0134 { 0135 return d->limit; 0136 } 0137 0138 void EmailQuery::setLimit(int limit) 0139 { 0140 d->limit = limit; 0141 } 0142 0143 void EmailQuery::matches(const QString &match) 0144 { 0145 d->matchString = match; 0146 } 0147 0148 void EmailQuery::subjectMatches(const QString &subjectMatch) 0149 { 0150 d->subjectMatchString = subjectMatch; 0151 } 0152 0153 void EmailQuery::bodyMatches(const QString &bodyMatch) 0154 { 0155 d->bodyMatchString = bodyMatch; 0156 } 0157 0158 void EmailQuery::setAttachment(bool hasAttachment) 0159 { 0160 d->attachment = hasAttachment ? 'T' : 'F'; 0161 } 0162 0163 void EmailQuery::setImportant(bool important) 0164 { 0165 d->important = important ? 'T' : 'F'; 0166 } 0167 0168 void EmailQuery::setRead(bool read) 0169 { 0170 d->read = read ? 'T' : 'F'; 0171 } 0172 0173 ResultIterator EmailQuery::exec() 0174 { 0175 const QString dir = defaultLocation(QStringLiteral("email")); 0176 Xapian::Database db; 0177 try { 0178 db = Xapian::Database(QFile::encodeName(dir).toStdString()); 0179 } catch (const Xapian::DatabaseOpeningError &) { 0180 qCWarning(AKONADI_SEARCH_PIM_LOG) << "Xapian Database does not exist at " << dir; 0181 return {}; 0182 } catch (const Xapian::DatabaseCorruptError &) { 0183 qCWarning(AKONADI_SEARCH_PIM_LOG) << "Xapian Database corrupted"; 0184 return {}; 0185 } catch (const Xapian::DatabaseError &e) { 0186 qCWarning(AKONADI_SEARCH_PIM_LOG) << "Failed to open Xapian database:" << QString::fromStdString(e.get_description()); 0187 return {}; 0188 } catch (...) { 0189 qCWarning(AKONADI_SEARCH_PIM_LOG) << "Random exception, but we do not want to crash"; 0190 return {}; 0191 } 0192 0193 QList<Xapian::Query> m_queries; 0194 0195 if (!d->involves.isEmpty()) { 0196 Xapian::QueryParser parser; 0197 parser.set_database(db); 0198 parser.add_prefix("", "F"); 0199 parser.add_prefix("", "T"); 0200 parser.add_prefix("", "CC"); 0201 parser.add_prefix("", "BCC"); 0202 0203 // vHanda: Do we really need the query parser over here? 0204 for (const QString &str : std::as_const(d->involves)) { 0205 const QByteArray ba = str.toUtf8(); 0206 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); 0207 } 0208 } 0209 0210 if (!d->from.isEmpty()) { 0211 Xapian::QueryParser parser; 0212 parser.set_database(db); 0213 parser.add_prefix("", "F"); 0214 const QByteArray ba = d->from.toUtf8(); 0215 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); 0216 } 0217 0218 if (!d->to.isEmpty()) { 0219 Xapian::QueryParser parser; 0220 parser.set_database(db); 0221 parser.add_prefix("", "T"); 0222 0223 for (const QString &str : std::as_const(d->to)) { 0224 const QByteArray ba = str.toUtf8(); 0225 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); 0226 } 0227 } 0228 0229 if (!d->cc.isEmpty()) { 0230 Xapian::QueryParser parser; 0231 parser.set_database(db); 0232 parser.add_prefix("", "CC"); 0233 0234 for (const QString &str : std::as_const(d->cc)) { 0235 const QByteArray ba = str.toUtf8(); 0236 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); 0237 } 0238 } 0239 0240 if (!d->bcc.isEmpty()) { 0241 Xapian::QueryParser parser; 0242 parser.set_database(db); 0243 parser.add_prefix("", "BC"); 0244 0245 for (const QString &str : std::as_const(d->bcc)) { 0246 const QByteArray ba = str.toUtf8(); 0247 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); 0248 } 0249 } 0250 0251 if (!d->subjectMatchString.isEmpty()) { 0252 Xapian::QueryParser parser; 0253 parser.set_database(db); 0254 parser.add_prefix("", "SU"); 0255 parser.set_default_op(Xapian::Query::OP_AND); 0256 const QByteArray ba = d->subjectMatchString.toUtf8(); 0257 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); 0258 } 0259 0260 if (!d->collections.isEmpty()) { 0261 Xapian::Query query; 0262 for (Akonadi::Collection::Id id : std::as_const(d->collections)) { 0263 const QString c = QString::number(id); 0264 const Xapian::Query q = Xapian::Query('C' + c.toStdString()); 0265 0266 query = Xapian::Query(Xapian::Query::OP_OR, query, q); 0267 } 0268 0269 m_queries << query; 0270 } 0271 0272 if (!d->bodyMatchString.isEmpty()) { 0273 Xapian::QueryParser parser; 0274 parser.set_database(db); 0275 parser.add_prefix("", "BO"); 0276 parser.set_default_op(Xapian::Query::OP_AND); 0277 const QByteArray ba = d->bodyMatchString.toUtf8(); 0278 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); 0279 } 0280 0281 if (d->important == 'T') { 0282 m_queries << Xapian::Query("BI"); 0283 } else if (d->important == 'F') { 0284 m_queries << Xapian::Query("BNI"); 0285 } 0286 0287 if (d->read == 'T') { 0288 m_queries << Xapian::Query("BR"); 0289 } else if (d->read == 'F') { 0290 m_queries << Xapian::Query("BNR"); 0291 } 0292 0293 if (d->attachment == 'T') { 0294 m_queries << Xapian::Query("BA"); 0295 } else if (d->attachment == 'F') { 0296 m_queries << Xapian::Query("BNA"); 0297 } 0298 0299 if (!d->matchString.isEmpty()) { 0300 Xapian::QueryParser parser; 0301 parser.set_database(db); 0302 parser.set_default_op(Xapian::Query::OP_AND); 0303 if (d->splitSearchMatchString) { 0304 const QStringList list = d->matchString.split(QRegularExpression(QStringLiteral("\\s")), Qt::SkipEmptyParts); 0305 for (const QString &s : list) { 0306 const QByteArray ba = s.toUtf8(); 0307 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); 0308 } 0309 } else { 0310 const QByteArray ba = d->matchString.toUtf8(); 0311 m_queries << parser.parse_query(ba.constData(), Xapian::QueryParser::FLAG_PARTIAL); 0312 } 0313 } 0314 Xapian::Query query; 0315 switch (d->opType) { 0316 case OpAnd: 0317 query = Xapian::Query(Xapian::Query::OP_AND, m_queries.begin(), m_queries.end()); 0318 break; 0319 case OpOr: 0320 query = Xapian::Query(Xapian::Query::OP_OR, m_queries.begin(), m_queries.end()); 0321 break; 0322 } 0323 0324 AgePostingSource ps(0); 0325 query = Xapian::Query(Xapian::Query::OP_AND_MAYBE, query, Xapian::Query(&ps)); 0326 0327 try { 0328 Xapian::Enquire enquire(db); 0329 enquire.set_query(query); 0330 0331 if (d->limit == 0) { 0332 // d->limit = 1000000; 0333 d->limit = 100000; 0334 } 0335 0336 Xapian::MSet mset = enquire.get_mset(0, d->limit); 0337 0338 ResultIterator iter; 0339 iter.d->init(mset); 0340 return iter; 0341 } catch (const Xapian::Error &e) { 0342 qCWarning(AKONADI_SEARCH_PIM_LOG) << QString::fromStdString(e.get_type()) << QString::fromStdString(e.get_description()); 0343 return {}; 0344 } 0345 }