File indexing completed on 2024-05-19 05:11:50

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 "query.h"
0010 #include "searchstore.h"
0011 #include "term.h"
0012 
0013 #include <QSharedPointer>
0014 #include <QString>
0015 #include <QStringList>
0016 #include <QUrlQuery>
0017 #include <QVariant>
0018 
0019 #include <QJsonDocument>
0020 #include <QJsonObject>
0021 
0022 using namespace Akonadi::Search;
0023 
0024 const int defaultLimit = 100000;
0025 
0026 class Akonadi::Search::QueryPrivate
0027 {
0028 public:
0029     Term m_term;
0030 
0031     QStringList m_types;
0032     QString m_searchString;
0033     uint m_limit = defaultLimit;
0034     uint m_offset = 0;
0035 
0036     int m_yearFilter = -1;
0037     int m_monthFilter = -1;
0038     int m_dayFilter = -1;
0039 
0040     Query::SortingOption m_sortingOption = Query::SortAuto;
0041     QString m_sortingProperty;
0042     QVariantMap m_customOptions;
0043 };
0044 
0045 Query::Query()
0046     : d(new QueryPrivate)
0047 {
0048 }
0049 
0050 Query::Query(const Term &t)
0051     : d(new QueryPrivate)
0052 {
0053     d->m_term = t;
0054 }
0055 
0056 Query::Query(const Query &rhs)
0057     : d(new QueryPrivate(*rhs.d))
0058 {
0059 }
0060 
0061 Query::~Query() = default;
0062 
0063 void Query::setTerm(const Term &t)
0064 {
0065     d->m_term = t;
0066 }
0067 
0068 Term Query::term() const
0069 {
0070     return d->m_term;
0071 }
0072 
0073 void Query::addType(const QString &type)
0074 {
0075     d->m_types << type.split(QLatin1Char('/'), Qt::SkipEmptyParts);
0076 }
0077 
0078 void Query::addTypes(const QStringList &typeList)
0079 {
0080     for (const QString &type : typeList) {
0081         addType(type);
0082     }
0083 }
0084 
0085 void Query::setType(const QString &type)
0086 {
0087     d->m_types.clear();
0088     addType(type);
0089 }
0090 
0091 void Query::setTypes(const QStringList &types)
0092 {
0093     d->m_types = types;
0094 }
0095 
0096 QStringList Query::types() const
0097 {
0098     return d->m_types;
0099 }
0100 
0101 QString Query::searchString() const
0102 {
0103     return d->m_searchString;
0104 }
0105 
0106 void Query::setSearchString(const QString &str)
0107 {
0108     d->m_searchString = str;
0109 }
0110 
0111 uint Query::limit() const
0112 {
0113     return d->m_limit;
0114 }
0115 
0116 void Query::setLimit(uint limit)
0117 {
0118     d->m_limit = limit;
0119 }
0120 
0121 uint Query::offset() const
0122 {
0123     return d->m_offset;
0124 }
0125 
0126 void Query::setOffset(uint offset)
0127 {
0128     d->m_offset = offset;
0129 }
0130 
0131 void Query::setDateFilter(int year, int month, int day)
0132 {
0133     d->m_yearFilter = year;
0134     d->m_monthFilter = month;
0135     d->m_dayFilter = day;
0136 }
0137 
0138 int Query::yearFilter() const
0139 {
0140     return d->m_yearFilter;
0141 }
0142 
0143 int Query::monthFilter() const
0144 {
0145     return d->m_monthFilter;
0146 }
0147 
0148 int Query::dayFilter() const
0149 {
0150     return d->m_dayFilter;
0151 }
0152 
0153 void Query::setSortingOption(Query::SortingOption option)
0154 {
0155     d->m_sortingOption = option;
0156 }
0157 
0158 Query::SortingOption Query::sortingOption() const
0159 {
0160     return d->m_sortingOption;
0161 }
0162 
0163 void Query::setSortingProperty(const QString &property)
0164 {
0165     d->m_sortingOption = SortProperty;
0166     d->m_sortingProperty = property;
0167 }
0168 
0169 QString Query::sortingProperty() const
0170 {
0171     return d->m_sortingProperty;
0172 }
0173 
0174 void Query::addCustomOption(const QString &option, const QVariant &value)
0175 {
0176     d->m_customOptions.insert(option, value);
0177 }
0178 
0179 QVariant Query::customOption(const QString &option) const
0180 {
0181     return d->m_customOptions.value(option);
0182 }
0183 
0184 QVariantMap Query::customOptions() const
0185 {
0186     return d->m_customOptions;
0187 }
0188 
0189 void Query::removeCustomOption(const QString &option)
0190 {
0191     d->m_customOptions.remove(option);
0192 }
0193 
0194 Q_GLOBAL_STATIC_WITH_ARGS(SearchStore::List, s_searchStores, (SearchStore::searchStores()))
0195 
0196 ResultIterator Query::exec()
0197 {
0198     // vHanda: Maybe this should default to allow searches on all search stores?
0199     Q_ASSERT_X(!types().isEmpty(), "Akonadi::Search::Query::exec", "A query is being initialized without a type");
0200     if (types().isEmpty()) {
0201         return {};
0202     }
0203 
0204     SearchStore *storeMatch = nullptr;
0205     for (const QSharedPointer<SearchStore> &store : std::as_const(*s_searchStores)) {
0206         bool matches = true;
0207         const auto typeList{types()};
0208         for (const QString &type : typeList) {
0209             if (!store->types().contains(type)) {
0210                 matches = false;
0211                 break;
0212             }
0213         }
0214 
0215         if (matches) {
0216             storeMatch = store.data();
0217             break;
0218         }
0219     }
0220 
0221     if (!storeMatch) {
0222         return {};
0223     }
0224 
0225     int id = storeMatch->exec(*this);
0226     return {id, storeMatch};
0227 }
0228 
0229 QByteArray Query::toJSON() const
0230 {
0231     QVariantMap map;
0232 
0233     if (!d->m_types.isEmpty()) {
0234         map[QStringLiteral("type")] = d->m_types;
0235     }
0236 
0237     if (d->m_limit != defaultLimit) {
0238         map[QStringLiteral("limit")] = d->m_limit;
0239     }
0240 
0241     if (d->m_offset) {
0242         map[QStringLiteral("offset")] = d->m_offset;
0243     }
0244 
0245     if (!d->m_searchString.isEmpty()) {
0246         map[QStringLiteral("searchString")] = d->m_searchString;
0247     }
0248 
0249     if (d->m_term.isValid()) {
0250         map[QStringLiteral("term")] = QVariant(d->m_term.toVariantMap());
0251     }
0252 
0253     if (d->m_yearFilter >= 0) {
0254         map[QStringLiteral("yearFilter")] = d->m_yearFilter;
0255     }
0256     if (d->m_monthFilter >= 0) {
0257         map[QStringLiteral("monthFilter")] = d->m_monthFilter;
0258     }
0259     if (d->m_dayFilter >= 0) {
0260         map[QStringLiteral("dayFilter")] = d->m_dayFilter;
0261     }
0262 
0263     if (d->m_sortingOption != SortAuto) {
0264         map[QStringLiteral("sortingOption")] = static_cast<int>(d->m_sortingOption);
0265     }
0266     if (!d->m_sortingProperty.isEmpty()) {
0267         map[QStringLiteral("sortingProperty")] = d->m_sortingProperty;
0268     }
0269 
0270     if (!d->m_customOptions.isEmpty()) {
0271         map[QStringLiteral("customOptions")] = d->m_customOptions;
0272     }
0273 
0274     QJsonObject jo = QJsonObject::fromVariantMap(map);
0275     QJsonDocument jdoc;
0276     jdoc.setObject(jo);
0277     return jdoc.toJson();
0278 }
0279 
0280 // static
0281 Query Query::fromJSON(const QByteArray &arr)
0282 {
0283     QJsonDocument jdoc = QJsonDocument::fromJson(arr);
0284     const QVariantMap map = jdoc.object().toVariantMap();
0285 
0286     Query query;
0287     query.d->m_types = map[QStringLiteral("type")].toStringList();
0288 
0289     if (map.contains(QStringLiteral("limit"))) {
0290         query.d->m_limit = map[QStringLiteral("limit")].toUInt();
0291     } else {
0292         query.d->m_limit = defaultLimit;
0293     }
0294 
0295     query.d->m_offset = map[QStringLiteral("offset")].toUInt();
0296     query.d->m_searchString = map[QStringLiteral("searchString")].toString();
0297     query.d->m_term = Term::fromVariantMap(map[QStringLiteral("term")].toMap());
0298 
0299     if (map.contains(QStringLiteral("yearFilter"))) {
0300         query.d->m_yearFilter = map[QStringLiteral("yearFilter")].toInt();
0301     }
0302     if (map.contains(QStringLiteral("monthFilter"))) {
0303         query.d->m_monthFilter = map[QStringLiteral("monthFilter")].toInt();
0304     }
0305     if (map.contains(QStringLiteral("dayFilter"))) {
0306         query.d->m_dayFilter = map[QStringLiteral("dayFilter")].toInt();
0307     }
0308 
0309     if (map.contains(QStringLiteral("sortingOption"))) {
0310         int option = map.value(QStringLiteral("sortingOption")).toInt();
0311         query.d->m_sortingOption = static_cast<SortingOption>(option);
0312     }
0313 
0314     if (map.contains(QStringLiteral("sortingProperty"))) {
0315         query.d->m_sortingProperty = map.value(QStringLiteral("sortingProperty")).toString();
0316     }
0317 
0318     if (map.contains(QStringLiteral("customOptions"))) {
0319         QVariant var = map[QStringLiteral("customOptions")];
0320         if (var.userType() == QMetaType::QVariantMap) {
0321             query.d->m_customOptions = map[QStringLiteral("customOptions")].toMap();
0322         } else if (var.userType() == QMetaType::QVariantHash) {
0323             QVariantHash hash = var.toHash();
0324 
0325             QHash<QString, QVariant>::const_iterator it = hash.constBegin();
0326             const QHash<QString, QVariant>::const_iterator end = hash.constEnd();
0327             for (; it != end; ++it) {
0328                 query.d->m_customOptions.insert(it.key(), it.value());
0329             }
0330         }
0331     }
0332 
0333     return query;
0334 }
0335 
0336 QUrl Query::toSearchUrl(const QString &title)
0337 {
0338     QUrl url;
0339     url.setScheme(QStringLiteral("akonadisearch"));
0340 
0341     QUrlQuery urlQuery;
0342     urlQuery.addQueryItem(QStringLiteral("json"), QString::fromUtf8(toJSON()));
0343 
0344     if (!title.isEmpty()) {
0345         urlQuery.addQueryItem(QStringLiteral("title"), title);
0346     }
0347 
0348     url.setQuery(urlQuery);
0349     return url;
0350 }
0351 
0352 Query Query::fromSearchUrl(const QUrl &url)
0353 {
0354     if (url.scheme() != QLatin1StringView("akonadisearch")) {
0355         return {};
0356     }
0357 
0358     QUrlQuery urlQuery(url);
0359     const QString jsonString = urlQuery.queryItemValue(QStringLiteral("json"), QUrl::FullyDecoded);
0360     return Query::fromJSON(jsonString.toUtf8());
0361 }
0362 
0363 QString Query::titleFromQueryUrl(const QUrl &url)
0364 {
0365     QUrlQuery urlQuery(url);
0366     return urlQuery.queryItemValue(QStringLiteral("title"), QUrl::FullyDecoded);
0367 }
0368 
0369 bool Query::operator==(const Query &rhs) const
0370 {
0371     if (rhs.d->m_limit != d->m_limit || rhs.d->m_offset != d->m_offset || rhs.d->m_dayFilter != d->m_dayFilter || rhs.d->m_monthFilter != d->m_monthFilter
0372         || rhs.d->m_yearFilter != d->m_yearFilter || rhs.d->m_customOptions != d->m_customOptions || rhs.d->m_searchString != d->m_searchString
0373         || rhs.d->m_sortingProperty != d->m_sortingProperty || rhs.d->m_sortingOption != d->m_sortingOption) {
0374         return false;
0375     }
0376 
0377     if (rhs.d->m_types.size() != d->m_types.size()) {
0378         return false;
0379     }
0380 
0381     for (const QString &type : std::as_const(rhs.d->m_types)) {
0382         if (!d->m_types.contains(type)) {
0383             return false;
0384         }
0385     }
0386 
0387     return d->m_term == rhs.d->m_term;
0388 }
0389 
0390 Query &Query::operator=(const Query &rhs)
0391 {
0392     *d = *rhs.d;
0393     return *this;
0394 }