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 }