File indexing completed on 2024-11-10 04:40:44

0001 /*
0002     SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "searchquery.h"
0008 #include "akonadicore_debug.h"
0009 
0010 #include <QJsonDocument>
0011 #include <QJsonObject>
0012 #include <QVariant>
0013 
0014 using namespace Akonadi;
0015 
0016 class Akonadi::SearchTermPrivate : public QSharedData
0017 {
0018 public:
0019     bool operator==(const SearchTermPrivate &other) const
0020     {
0021         return relation == other.relation && isNegated == other.isNegated && terms == other.terms && key == other.key && value == other.value
0022             && condition == other.condition;
0023     }
0024 
0025     QString key;
0026     QVariant value;
0027     SearchTerm::Condition condition = SearchTerm::CondEqual;
0028     SearchTerm::Relation relation = SearchTerm::RelAnd;
0029     QList<SearchTerm> terms;
0030     bool isNegated = false;
0031 };
0032 
0033 class Akonadi::SearchQueryPrivate : public QSharedData
0034 {
0035 public:
0036     bool operator==(const SearchQueryPrivate &other) const
0037     {
0038         return rootTerm == other.rootTerm && limit == other.limit;
0039     }
0040 
0041     static QVariantMap termToJSON(const SearchTerm &term)
0042     {
0043         const QList<SearchTerm> &subTerms = term.subTerms();
0044         QVariantMap termJSON;
0045         termJSON.insert(QStringLiteral("negated"), term.isNegated());
0046         if (subTerms.isEmpty()) {
0047             if (!term.isNull()) {
0048                 termJSON.insert(QStringLiteral("key"), term.key());
0049                 termJSON.insert(QStringLiteral("value"), term.value());
0050                 termJSON.insert(QStringLiteral("cond"), static_cast<int>(term.condition()));
0051             }
0052         } else {
0053             termJSON.insert(QStringLiteral("rel"), static_cast<int>(term.relation()));
0054             QVariantList subTermsJSON;
0055             subTermsJSON.reserve(subTerms.count());
0056             for (const SearchTerm &term : std::as_const(subTerms)) {
0057                 subTermsJSON.append(termToJSON(term));
0058             }
0059             termJSON.insert(QStringLiteral("subTerms"), subTermsJSON);
0060         }
0061 
0062         return termJSON;
0063     }
0064 
0065     static SearchTerm JSONToTerm(const QVariantMap &map)
0066     {
0067         if (map.isEmpty()) {
0068             return SearchTerm();
0069         } else if (map.contains(QStringLiteral("key"))) {
0070             SearchTerm term(map[QStringLiteral("key")].toString(),
0071                             map[QStringLiteral("value")],
0072                             static_cast<SearchTerm::Condition>(map[QStringLiteral("cond")].toInt()));
0073             term.setIsNegated(map[QStringLiteral("negated")].toBool());
0074             return term;
0075         } else if (map.contains(QStringLiteral("rel"))) {
0076             SearchTerm term(static_cast<SearchTerm::Relation>(map[QStringLiteral("rel")].toInt()));
0077             term.setIsNegated(map[QStringLiteral("negated")].toBool());
0078             const QList<QVariant> list = map[QStringLiteral("subTerms")].toList();
0079             for (const QVariant &var : list) {
0080                 term.addSubTerm(JSONToTerm(var.toMap()));
0081             }
0082             return term;
0083         } else {
0084             qCWarning(AKONADICORE_LOG) << "Invalid JSON for term: " << map;
0085             return SearchTerm();
0086         }
0087     }
0088 
0089     SearchTerm rootTerm;
0090     int limit = -1;
0091 };
0092 
0093 SearchTerm::SearchTerm(SearchTerm::Relation relation)
0094     : d(new SearchTermPrivate)
0095 {
0096     d->relation = relation;
0097 }
0098 
0099 SearchTerm::SearchTerm(const QString &key, const QVariant &value, SearchTerm::Condition condition)
0100     : d(new SearchTermPrivate)
0101 {
0102     d->relation = RelAnd;
0103     d->key = key;
0104     d->value = value;
0105     d->condition = condition;
0106 }
0107 
0108 SearchTerm::SearchTerm(const SearchTerm &other)
0109     : d(other.d)
0110 {
0111 }
0112 
0113 SearchTerm::~SearchTerm() = default;
0114 
0115 SearchTerm &SearchTerm::operator=(const SearchTerm &other)
0116 {
0117     d = other.d;
0118     return *this;
0119 }
0120 
0121 bool SearchTerm::operator==(const SearchTerm &other) const
0122 {
0123     return *d == *other.d;
0124 }
0125 
0126 bool SearchTerm::isNull() const
0127 {
0128     return d->key.isEmpty() && d->value.isNull() && d->terms.isEmpty();
0129 }
0130 
0131 QString SearchTerm::key() const
0132 {
0133     return d->key;
0134 }
0135 
0136 QVariant SearchTerm::value() const
0137 {
0138     return d->value;
0139 }
0140 
0141 SearchTerm::Condition SearchTerm::condition() const
0142 {
0143     return d->condition;
0144 }
0145 
0146 void SearchTerm::setIsNegated(bool negated)
0147 {
0148     d->isNegated = negated;
0149 }
0150 
0151 bool SearchTerm::isNegated() const
0152 {
0153     return d->isNegated;
0154 }
0155 
0156 void SearchTerm::addSubTerm(const SearchTerm &term)
0157 {
0158     d->terms << term;
0159 }
0160 
0161 QList<SearchTerm> SearchTerm::subTerms() const
0162 {
0163     return d->terms;
0164 }
0165 
0166 SearchTerm::Relation SearchTerm::relation() const
0167 {
0168     return d->relation;
0169 }
0170 
0171 SearchQuery::SearchQuery(SearchTerm::Relation rel)
0172     : d(new SearchQueryPrivate)
0173 {
0174     d->rootTerm = SearchTerm(rel);
0175 }
0176 
0177 SearchQuery::SearchQuery(const SearchQuery &other)
0178     : d(other.d)
0179 {
0180 }
0181 
0182 SearchQuery::~SearchQuery()
0183 {
0184 }
0185 
0186 SearchQuery &SearchQuery::operator=(const SearchQuery &other)
0187 {
0188     d = other.d;
0189     return *this;
0190 }
0191 
0192 bool SearchQuery::operator==(const SearchQuery &other) const
0193 {
0194     return *d == *other.d;
0195 }
0196 
0197 bool SearchQuery::isNull() const
0198 {
0199     return d->rootTerm.isNull();
0200 }
0201 
0202 SearchTerm SearchQuery::term() const
0203 {
0204     return d->rootTerm;
0205 }
0206 
0207 void SearchQuery::addTerm(const QString &key, const QVariant &value, SearchTerm::Condition condition)
0208 {
0209     addTerm(SearchTerm(key, value, condition));
0210 }
0211 
0212 void SearchQuery::addTerm(const SearchTerm &term)
0213 {
0214     d->rootTerm.addSubTerm(term);
0215 }
0216 
0217 void SearchQuery::setTerm(const SearchTerm &term)
0218 {
0219     d->rootTerm = term;
0220 }
0221 
0222 void SearchQuery::setLimit(int limit)
0223 {
0224     d->limit = limit;
0225 }
0226 
0227 int SearchQuery::limit() const
0228 {
0229     return d->limit;
0230 }
0231 
0232 QByteArray SearchQuery::toJSON() const
0233 {
0234     QVariantMap root;
0235     if (!d->rootTerm.isNull()) {
0236         root = SearchQueryPrivate::termToJSON(d->rootTerm);
0237         root.insert(QStringLiteral("limit"), d->limit);
0238     }
0239 
0240     QJsonObject jo = QJsonObject::fromVariantMap(root);
0241     QJsonDocument jdoc;
0242     jdoc.setObject(jo);
0243     return jdoc.toJson();
0244 }
0245 
0246 SearchQuery SearchQuery::fromJSON(const QByteArray &jsonData)
0247 {
0248     QJsonParseError error;
0249     const QJsonDocument json = QJsonDocument::fromJson(jsonData, &error);
0250     if (error.error != QJsonParseError::NoError || json.isNull()) {
0251         return SearchQuery();
0252     }
0253 
0254     SearchQuery query;
0255     const QJsonObject obj = json.object();
0256     query.d->rootTerm = SearchQueryPrivate::JSONToTerm(obj.toVariantMap());
0257     if (obj.contains(QLatin1StringView("limit"))) {
0258         query.d->limit = obj.value(QStringLiteral("limit")).toInt();
0259     }
0260     return query;
0261 }
0262 
0263 static QMap<EmailSearchTerm::EmailSearchField, QString> emailSearchFieldMapping()
0264 {
0265     static QMap<EmailSearchTerm::EmailSearchField, QString> mapping;
0266     if (mapping.isEmpty()) {
0267         mapping.insert(EmailSearchTerm::Body, QStringLiteral("body"));
0268         mapping.insert(EmailSearchTerm::Headers, QStringLiteral("headers"));
0269         mapping.insert(EmailSearchTerm::Subject, QStringLiteral("subject"));
0270         mapping.insert(EmailSearchTerm::Message, QStringLiteral("message"));
0271         mapping.insert(EmailSearchTerm::HeaderFrom, QStringLiteral("from"));
0272         mapping.insert(EmailSearchTerm::HeaderTo, QStringLiteral("to"));
0273         mapping.insert(EmailSearchTerm::HeaderCC, QStringLiteral("cc"));
0274         mapping.insert(EmailSearchTerm::HeaderBCC, QStringLiteral("bcc"));
0275         mapping.insert(EmailSearchTerm::HeaderReplyTo, QStringLiteral("replyto"));
0276         mapping.insert(EmailSearchTerm::HeaderOrganization, QStringLiteral("organization"));
0277         mapping.insert(EmailSearchTerm::HeaderListId, QStringLiteral("listid"));
0278         mapping.insert(EmailSearchTerm::HeaderResentFrom, QStringLiteral("resentfrom"));
0279         mapping.insert(EmailSearchTerm::HeaderXLoop, QStringLiteral("xloop"));
0280         mapping.insert(EmailSearchTerm::HeaderXMailingList, QStringLiteral("xmailinglist"));
0281         mapping.insert(EmailSearchTerm::HeaderXSpamFlag, QStringLiteral("xspamflag"));
0282         mapping.insert(EmailSearchTerm::HeaderDate, QStringLiteral("date"));
0283         mapping.insert(EmailSearchTerm::HeaderOnlyDate, QStringLiteral("onlydate"));
0284         mapping.insert(EmailSearchTerm::MessageStatus, QStringLiteral("messagestatus"));
0285         mapping.insert(EmailSearchTerm::MessageTag, QStringLiteral("messagetag"));
0286         mapping.insert(EmailSearchTerm::ByteSize, QStringLiteral("size"));
0287         mapping.insert(EmailSearchTerm::Attachment, QStringLiteral("attachment"));
0288     }
0289 
0290     return mapping;
0291 }
0292 
0293 EmailSearchTerm::EmailSearchTerm(EmailSearchTerm::EmailSearchField field, const QVariant &value, SearchTerm::Condition condition)
0294     : SearchTerm(toKey(field), value, condition)
0295 {
0296 }
0297 
0298 QString EmailSearchTerm::toKey(EmailSearchTerm::EmailSearchField field)
0299 {
0300     return emailSearchFieldMapping().value(field);
0301 }
0302 
0303 EmailSearchTerm::EmailSearchField EmailSearchTerm::fromKey(const QString &key)
0304 {
0305     return emailSearchFieldMapping().key(key);
0306 }
0307 
0308 static QMap<ContactSearchTerm::ContactSearchField, QString> contactSearchFieldMapping()
0309 {
0310     static QMap<ContactSearchTerm::ContactSearchField, QString> mapping;
0311     if (mapping.isEmpty()) {
0312         mapping.insert(ContactSearchTerm::Name, QStringLiteral("name"));
0313         mapping.insert(ContactSearchTerm::Nickname, QStringLiteral("nickname"));
0314         mapping.insert(ContactSearchTerm::Email, QStringLiteral("email"));
0315         mapping.insert(ContactSearchTerm::Uid, QStringLiteral("uid"));
0316         mapping.insert(ContactSearchTerm::All, QStringLiteral("all"));
0317     }
0318     return mapping;
0319 }
0320 
0321 ContactSearchTerm::ContactSearchTerm(ContactSearchTerm::ContactSearchField field, const QVariant &value, SearchTerm::Condition condition)
0322     : SearchTerm(toKey(field), value, condition)
0323 {
0324 }
0325 
0326 QString ContactSearchTerm::toKey(ContactSearchTerm::ContactSearchField field)
0327 {
0328     return contactSearchFieldMapping().value(field);
0329 }
0330 
0331 ContactSearchTerm::ContactSearchField ContactSearchTerm::fromKey(const QString &key)
0332 {
0333     return contactSearchFieldMapping().key(key);
0334 }
0335 
0336 QMap<IncidenceSearchTerm::IncidenceSearchField, QString> incidenceSearchFieldMapping()
0337 {
0338     static QMap<IncidenceSearchTerm::IncidenceSearchField, QString> mapping;
0339     if (mapping.isEmpty()) {
0340         mapping.insert(IncidenceSearchTerm::All, QStringLiteral("all"));
0341         mapping.insert(IncidenceSearchTerm::PartStatus, QStringLiteral("partstatus"));
0342         mapping.insert(IncidenceSearchTerm::Organizer, QStringLiteral("organizer"));
0343         mapping.insert(IncidenceSearchTerm::Summary, QStringLiteral("summary"));
0344         mapping.insert(IncidenceSearchTerm::Location, QStringLiteral("location"));
0345     }
0346     return mapping;
0347 }
0348 
0349 IncidenceSearchTerm::IncidenceSearchTerm(IncidenceSearchTerm::IncidenceSearchField field, const QVariant &value, SearchTerm::Condition condition)
0350     : SearchTerm(toKey(field), value, condition)
0351 {
0352 }
0353 
0354 QString IncidenceSearchTerm::toKey(IncidenceSearchTerm::IncidenceSearchField field)
0355 {
0356     return incidenceSearchFieldMapping().value(field);
0357 }
0358 
0359 IncidenceSearchTerm::IncidenceSearchField IncidenceSearchTerm::fromKey(const QString &key)
0360 {
0361     return incidenceSearchFieldMapping().key(key);
0362 }