File indexing completed on 2025-02-16 04:50:20

0001 /*
0002  * SPDX-FileCopyrightText: 2013 Daniel Vrátil <dvratil@redhat.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  *
0006  */
0007 
0008 #include "searchtask.h"
0009 #include "imapresource_debug.h"
0010 #include <Akonadi/MessageFlags>
0011 #include <Akonadi/SearchQuery>
0012 #include <KIMAP/SearchJob>
0013 #include <KIMAP/SelectJob>
0014 #include <KIMAP/Session>
0015 #include <KLocalizedString>
0016 Q_DECLARE_METATYPE(KIMAP::Session *)
0017 
0018 SearchTask::SearchTask(const ResourceStateInterface::Ptr &state, const QString &query, QObject *parent)
0019     : ResourceTask(ResourceTask::DeferIfNoSession, state, parent)
0020     , m_query(query)
0021 {
0022 }
0023 
0024 SearchTask::~SearchTask() = default;
0025 
0026 void SearchTask::doStart(KIMAP::Session *session)
0027 {
0028     qCDebug(IMAPRESOURCE_LOG) << collection().remoteId();
0029 
0030     const QString mailbox = mailBoxForCollection(collection());
0031     if (session->selectedMailBox() == mailbox) {
0032         doSearch(session);
0033         return;
0034     }
0035 
0036     auto select = new KIMAP::SelectJob(session);
0037     select->setMailBox(mailbox);
0038     connect(select, &KJob::finished, this, &SearchTask::onSelectDone);
0039     select->start();
0040 }
0041 
0042 void SearchTask::onSelectDone(KJob *job)
0043 {
0044     if (job->error()) {
0045         searchFinished(QList<qint64>());
0046         cancelTask(job->errorText());
0047         return;
0048     }
0049 
0050     doSearch(qobject_cast<KIMAP::SelectJob *>(job)->session());
0051 }
0052 
0053 static KIMAP::Term::Relation mapRelation(Akonadi::SearchTerm::Relation relation)
0054 {
0055     if (relation == Akonadi::SearchTerm::RelAnd) {
0056         return KIMAP::Term::And;
0057     }
0058     return KIMAP::Term::Or;
0059 }
0060 
0061 static KIMAP::Term recursiveEmailTermMapping(const Akonadi::SearchTerm &term)
0062 {
0063     if (!term.subTerms().isEmpty()) {
0064         QList<KIMAP::Term> subterms;
0065         const QList<Akonadi::SearchTerm> lstSearchTermsList = term.subTerms();
0066         for (const Akonadi::SearchTerm &subterm : lstSearchTermsList) {
0067             const KIMAP::Term newTerm = recursiveEmailTermMapping(subterm);
0068             if (!newTerm.isNull()) {
0069                 subterms << newTerm;
0070             }
0071         }
0072         return KIMAP::Term(mapRelation(term.relation()), subterms);
0073     } else {
0074         const Akonadi::EmailSearchTerm::EmailSearchField field = Akonadi::EmailSearchTerm::fromKey(term.key());
0075         switch (field) {
0076         case Akonadi::EmailSearchTerm::Message:
0077             return KIMAP::Term(KIMAP::Term::Text, term.value().toString()).setNegated(term.isNegated());
0078         case Akonadi::EmailSearchTerm::Body:
0079             return KIMAP::Term(KIMAP::Term::Body, term.value().toString()).setNegated(term.isNegated());
0080         case Akonadi::EmailSearchTerm::Headers:
0081             // FIXME
0082             //                 return KIMAP::Term(KIMAP::Term::Header, term.value()).setNegated(term.isNegated());
0083             break;
0084         case Akonadi::EmailSearchTerm::ByteSize: {
0085             int value = term.value().toInt();
0086             switch (term.condition()) {
0087             case Akonadi::SearchTerm::CondGreaterOrEqual:
0088                 value--;
0089                 [[fallthrough]];
0090             case Akonadi::SearchTerm::CondGreaterThan:
0091                 return KIMAP::Term(KIMAP::Term::Larger, value).setNegated(term.isNegated());
0092             case Akonadi::SearchTerm::CondLessOrEqual:
0093                 value++;
0094                 [[fallthrough]];
0095             case Akonadi::SearchTerm::CondLessThan:
0096                 return KIMAP::Term(KIMAP::Term::Smaller, value).setNegated(term.isNegated());
0097             case Akonadi::SearchTerm::CondEqual:
0098                 return KIMAP::Term(KIMAP::Term::And,
0099                                    QList<KIMAP::Term>() << KIMAP::Term(KIMAP::Term::Smaller, value + 1) << KIMAP::Term(KIMAP::Term::Larger, value + 1))
0100                     .setNegated(term.isNegated());
0101             case Akonadi::SearchTerm::CondContains:
0102                 qCDebug(IMAPRESOURCE_LOG) << " invalid condition for ByteSize";
0103                 break;
0104             }
0105             break;
0106         }
0107         case Akonadi::EmailSearchTerm::HeaderOnlyDate:
0108         case Akonadi::EmailSearchTerm::HeaderDate: {
0109             QDate value = term.value().toDateTime().date();
0110             switch (term.condition()) {
0111             case Akonadi::SearchTerm::CondGreaterOrEqual:
0112                 value = value.addDays(-1);
0113                 [[fallthrough]];
0114             case Akonadi::SearchTerm::CondGreaterThan:
0115                 return KIMAP::Term(KIMAP::Term::SentSince, value).setNegated(term.isNegated());
0116             case Akonadi::SearchTerm::CondLessOrEqual:
0117                 value = value.addDays(1);
0118                 [[fallthrough]];
0119             case Akonadi::SearchTerm::CondLessThan:
0120                 return KIMAP::Term(KIMAP::Term::SentBefore, value).setNegated(term.isNegated());
0121             case Akonadi::SearchTerm::CondEqual:
0122                 return KIMAP::Term(KIMAP::Term::SentOn, value).setNegated(term.isNegated());
0123             case Akonadi::SearchTerm::CondContains:
0124                 qCDebug(IMAPRESOURCE_LOG) << " invalid condition for Date";
0125                 return {};
0126             default:
0127                 qCWarning(IMAPRESOURCE_LOG) << "unknown term for date" << term.key();
0128                 return {};
0129             }
0130         }
0131         case Akonadi::EmailSearchTerm::Subject:
0132             return KIMAP::Term(KIMAP::Term::Subject, term.value().toString()).setNegated(term.isNegated());
0133         case Akonadi::EmailSearchTerm::HeaderFrom:
0134             return KIMAP::Term(KIMAP::Term::From, term.value().toString()).setNegated(term.isNegated());
0135         case Akonadi::EmailSearchTerm::HeaderTo:
0136             return KIMAP::Term(KIMAP::Term::To, term.value().toString()).setNegated(term.isNegated());
0137         case Akonadi::EmailSearchTerm::HeaderCC:
0138             return KIMAP::Term(KIMAP::Term::Cc, term.value().toString()).setNegated(term.isNegated());
0139         case Akonadi::EmailSearchTerm::HeaderBCC:
0140             return KIMAP::Term(KIMAP::Term::Bcc, term.value().toString()).setNegated(term.isNegated());
0141         case Akonadi::EmailSearchTerm::MessageStatus: {
0142             const QString termStr = term.value().toString();
0143             if (termStr == QLatin1StringView(Akonadi::MessageFlags::Flagged)) {
0144                 return KIMAP::Term(KIMAP::Term::Flagged).setNegated(term.isNegated());
0145             }
0146             if (termStr == QLatin1StringView(Akonadi::MessageFlags::Deleted)) {
0147                 return KIMAP::Term(KIMAP::Term::Deleted).setNegated(term.isNegated());
0148             }
0149             if (termStr == QLatin1StringView(Akonadi::MessageFlags::Replied)) {
0150                 return KIMAP::Term(KIMAP::Term::Answered).setNegated(term.isNegated());
0151             }
0152             if (termStr == QLatin1StringView(Akonadi::MessageFlags::Seen)) {
0153                 return KIMAP::Term(KIMAP::Term::Seen).setNegated(term.isNegated());
0154             }
0155             break;
0156         }
0157         case Akonadi::EmailSearchTerm::MessageTag:
0158             break;
0159         case Akonadi::EmailSearchTerm::HeaderReplyTo:
0160             break;
0161         case Akonadi::EmailSearchTerm::HeaderOrganization:
0162             break;
0163         case Akonadi::EmailSearchTerm::HeaderListId:
0164             break;
0165         case Akonadi::EmailSearchTerm::HeaderResentFrom:
0166             break;
0167         case Akonadi::EmailSearchTerm::HeaderXLoop:
0168             break;
0169         case Akonadi::EmailSearchTerm::HeaderXMailingList:
0170             break;
0171         case Akonadi::EmailSearchTerm::HeaderXSpamFlag:
0172             break;
0173         case Akonadi::EmailSearchTerm::Unknown:
0174         default:
0175             qCWarning(IMAPRESOURCE_LOG) << "unknown term " << term.key();
0176         }
0177     }
0178     return {};
0179 }
0180 
0181 void SearchTask::doSearch(KIMAP::Session *session)
0182 {
0183     qCDebug(IMAPRESOURCE_LOG) << m_query;
0184 
0185     Akonadi::SearchQuery query = Akonadi::SearchQuery::fromJSON(m_query.toLatin1());
0186     auto searchJob = new KIMAP::SearchJob(session);
0187     searchJob->setUidBased(true);
0188 
0189     KIMAP::Term term = recursiveEmailTermMapping(query.term());
0190     if (term.isNull()) {
0191         qCWarning(IMAPRESOURCE_LOG) << "failed to translate query " << m_query;
0192         searchFinished(QList<qint64>());
0193         cancelTask(i18n("Invalid search"));
0194         return;
0195     }
0196     searchJob->setTerm(term);
0197 
0198     connect(searchJob, &KJob::finished, this, &SearchTask::onSearchDone);
0199     searchJob->start();
0200 }
0201 
0202 void SearchTask::onSearchDone(KJob *job)
0203 {
0204     if (job->error()) {
0205         qCWarning(IMAPRESOURCE_LOG) << "Failed to execute search " << job->errorString();
0206         qCDebug(IMAPRESOURCE_LOG) << m_query;
0207         searchFinished(QList<qint64>());
0208         cancelTask(job->errorString());
0209         return;
0210     }
0211 
0212     auto searchJob = qobject_cast<KIMAP::SearchJob *>(job);
0213     const QList<qint64> result = searchJob->results();
0214     qCDebug(IMAPRESOURCE_LOG) << result.count() << "matches";
0215 
0216     searchFinished(result);
0217     taskDone();
0218 }
0219 
0220 #include "moc_searchtask.cpp"