1 /*
2 * This file is part of the KDE Akonadi Search Project
3 * SPDX-FileCopyrightText: 2013 Vishesh Handa <me@vhanda.in>
4 *
5 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 *
7 */
8
9 #include "query.h"
10 #include "searchstore.h"
11 #include "term.h"
12
13 #include <QSharedPointer>
14 #include <QString>
15 #include <QStringList>
16 #include <QUrlQuery>
17 #include <QVariant>
18
19 #include <QJsonDocument>
20 #include <QJsonObject>
21
22 using namespace Akonadi::Search;
23
24 const int defaultLimit = 100000;
25
26 class Akonadi::Search::QueryPrivate
27 {
28 public:
29 Term m_term;
30
31 QStringList m_types;
32 QString m_searchString;
33 uint m_limit = defaultLimit;
34 uint m_offset = 0;
35
36 int m_yearFilter = -1;
37 int m_monthFilter = -1;
38 int m_dayFilter = -1;
39
40 Query::SortingOption m_sortingOption = Query::SortAuto;
41 QString m_sortingProperty;
42 QVariantMap m_customOptions;
43 };
44
Query()45 Query::Query()
46 : d(new QueryPrivate)
47 {
48 }
49
Query(const Term & t)50 Query::Query(const Term &t)
51 : d(new QueryPrivate)
52 {
53 d->m_term = t;
54 }
55
Query(const Query & rhs)56 Query::Query(const Query &rhs)
57 : d(new QueryPrivate(*rhs.d))
58 {
59 }
60
61 Query::~Query() = default;
62
setTerm(const Term & t)63 void Query::setTerm(const Term &t)
64 {
65 d->m_term = t;
66 }
67
term() const68 Term Query::term() const
69 {
70 return d->m_term;
71 }
72
addType(const QString & type)73 void Query::addType(const QString &type)
74 {
75 d->m_types << type.split(QLatin1Char('/'), Qt::SkipEmptyParts);
76 }
77
addTypes(const QStringList & typeList)78 void Query::addTypes(const QStringList &typeList)
79 {
80 for (const QString &type : typeList) {
81 addType(type);
82 }
83 }
84
setType(const QString & type)85 void Query::setType(const QString &type)
86 {
87 d->m_types.clear();
88 addType(type);
89 }
90
setTypes(const QStringList & types)91 void Query::setTypes(const QStringList &types)
92 {
93 d->m_types = types;
94 }
95
types() const96 QStringList Query::types() const
97 {
98 return d->m_types;
99 }
100
searchString() const101 QString Query::searchString() const
102 {
103 return d->m_searchString;
104 }
105
setSearchString(const QString & str)106 void Query::setSearchString(const QString &str)
107 {
108 d->m_searchString = str;
109 }
110
limit() const111 uint Query::limit() const
112 {
113 return d->m_limit;
114 }
115
setLimit(uint limit)116 void Query::setLimit(uint limit)
117 {
118 d->m_limit = limit;
119 }
120
offset() const121 uint Query::offset() const
122 {
123 return d->m_offset;
124 }
125
setOffset(uint offset)126 void Query::setOffset(uint offset)
127 {
128 d->m_offset = offset;
129 }
130
setDateFilter(int year,int month,int day)131 void Query::setDateFilter(int year, int month, int day)
132 {
133 d->m_yearFilter = year;
134 d->m_monthFilter = month;
135 d->m_dayFilter = day;
136 }
137
yearFilter() const138 int Query::yearFilter() const
139 {
140 return d->m_yearFilter;
141 }
142
monthFilter() const143 int Query::monthFilter() const
144 {
145 return d->m_monthFilter;
146 }
147
dayFilter() const148 int Query::dayFilter() const
149 {
150 return d->m_dayFilter;
151 }
152
setSortingOption(Query::SortingOption option)153 void Query::setSortingOption(Query::SortingOption option)
154 {
155 d->m_sortingOption = option;
156 }
157
sortingOption() const158 Query::SortingOption Query::sortingOption() const
159 {
160 return d->m_sortingOption;
161 }
162
setSortingProperty(const QString & property)163 void Query::setSortingProperty(const QString &property)
164 {
165 d->m_sortingOption = SortProperty;
166 d->m_sortingProperty = property;
167 }
168
sortingProperty() const169 QString Query::sortingProperty() const
170 {
171 return d->m_sortingProperty;
172 }
173
addCustomOption(const QString & option,const QVariant & value)174 void Query::addCustomOption(const QString &option, const QVariant &value)
175 {
176 d->m_customOptions.insert(option, value);
177 }
178
customOption(const QString & option) const179 QVariant Query::customOption(const QString &option) const
180 {
181 return d->m_customOptions.value(option);
182 }
183
customOptions() const184 QVariantMap Query::customOptions() const
185 {
186 return d->m_customOptions;
187 }
188
removeCustomOption(const QString & option)189 void Query::removeCustomOption(const QString &option)
190 {
191 d->m_customOptions.remove(option);
192 }
193
194 Q_GLOBAL_STATIC_WITH_ARGS(SearchStore::List, s_searchStores, (SearchStore::searchStores()))
195
exec()196 ResultIterator Query::exec()
197 {
198 // vHanda: Maybe this should default to allow searches on all search stores?
199 Q_ASSERT_X(!types().isEmpty(), "Akonadi::Search::Query::exec", "A query is being initialized without a type");
200 if (types().isEmpty()) {
201 return ResultIterator();
202 }
203
204 SearchStore *storeMatch = nullptr;
205 for (const QSharedPointer<SearchStore> &store : std::as_const(*s_searchStores)) {
206 bool matches = true;
207 for (const QString &type : types()) {
208 if (!store->types().contains(type)) {
209 matches = false;
210 break;
211 }
212 }
213
214 if (matches) {
215 storeMatch = store.data();
216 break;
217 }
218 }
219
220 if (!storeMatch) {
221 return ResultIterator();
222 }
223
224 int id = storeMatch->exec(*this);
225 return ResultIterator(id, storeMatch);
226 }
227
toJSON() const228 QByteArray Query::toJSON() const
229 {
230 QVariantMap map;
231
232 if (!d->m_types.isEmpty()) {
233 map[QStringLiteral("type")] = d->m_types;
234 }
235
236 if (d->m_limit != defaultLimit) {
237 map[QStringLiteral("limit")] = d->m_limit;
238 }
239
240 if (d->m_offset) {
241 map[QStringLiteral("offset")] = d->m_offset;
242 }
243
244 if (!d->m_searchString.isEmpty()) {
245 map[QStringLiteral("searchString")] = d->m_searchString;
246 }
247
248 if (d->m_term.isValid()) {
249 map[QStringLiteral("term")] = QVariant(d->m_term.toVariantMap());
250 }
251
252 if (d->m_yearFilter >= 0) {
253 map[QStringLiteral("yearFilter")] = d->m_yearFilter;
254 }
255 if (d->m_monthFilter >= 0) {
256 map[QStringLiteral("monthFilter")] = d->m_monthFilter;
257 }
258 if (d->m_dayFilter >= 0) {
259 map[QStringLiteral("dayFilter")] = d->m_dayFilter;
260 }
261
262 if (d->m_sortingOption != SortAuto) {
263 map[QStringLiteral("sortingOption")] = static_cast<int>(d->m_sortingOption);
264 }
265 if (!d->m_sortingProperty.isEmpty()) {
266 map[QStringLiteral("sortingProperty")] = d->m_sortingProperty;
267 }
268
269 if (!d->m_customOptions.isEmpty()) {
270 map[QStringLiteral("customOptions")] = d->m_customOptions;
271 }
272
273 QJsonObject jo = QJsonObject::fromVariantMap(map);
274 QJsonDocument jdoc;
275 jdoc.setObject(jo);
276 return jdoc.toJson();
277 }
278
279 // static
fromJSON(const QByteArray & arr)280 Query Query::fromJSON(const QByteArray &arr)
281 {
282 QJsonDocument jdoc = QJsonDocument::fromJson(arr);
283 const QVariantMap map = jdoc.object().toVariantMap();
284
285 Query query;
286 query.d->m_types = map[QStringLiteral("type")].toStringList();
287
288 if (map.contains(QStringLiteral("limit"))) {
289 query.d->m_limit = map[QStringLiteral("limit")].toUInt();
290 } else {
291 query.d->m_limit = defaultLimit;
292 }
293
294 query.d->m_offset = map[QStringLiteral("offset")].toUInt();
295 query.d->m_searchString = map[QStringLiteral("searchString")].toString();
296 query.d->m_term = Term::fromVariantMap(map[QStringLiteral("term")].toMap());
297
298 if (map.contains(QStringLiteral("yearFilter"))) {
299 query.d->m_yearFilter = map[QStringLiteral("yearFilter")].toInt();
300 }
301 if (map.contains(QStringLiteral("monthFilter"))) {
302 query.d->m_monthFilter = map[QStringLiteral("monthFilter")].toInt();
303 }
304 if (map.contains(QStringLiteral("dayFilter"))) {
305 query.d->m_dayFilter = map[QStringLiteral("dayFilter")].toInt();
306 }
307
308 if (map.contains(QStringLiteral("sortingOption"))) {
309 int option = map.value(QStringLiteral("sortingOption")).toInt();
310 query.d->m_sortingOption = static_cast<SortingOption>(option);
311 }
312
313 if (map.contains(QStringLiteral("sortingProperty"))) {
314 query.d->m_sortingProperty = map.value(QStringLiteral("sortingProperty")).toString();
315 }
316
317 if (map.contains(QStringLiteral("customOptions"))) {
318 QVariant var = map[QStringLiteral("customOptions")];
319 if (var.type() == QVariant::Map) {
320 query.d->m_customOptions = map[QStringLiteral("customOptions")].toMap();
321 } else if (var.type() == QVariant::Hash) {
322 QVariantHash hash = var.toHash();
323
324 QHash<QString, QVariant>::const_iterator it = hash.constBegin();
325 const QHash<QString, QVariant>::const_iterator end = hash.constEnd();
326 for (; it != end; ++it) {
327 query.d->m_customOptions.insert(it.key(), it.value());
328 }
329 }
330 }
331
332 return query;
333 }
334
toSearchUrl(const QString & title)335 QUrl Query::toSearchUrl(const QString &title)
336 {
337 QUrl url;
338 url.setScheme(QStringLiteral("akonadisearch"));
339
340 QUrlQuery urlQuery;
341 urlQuery.addQueryItem(QStringLiteral("json"), QString::fromUtf8(toJSON()));
342
343 if (!title.isEmpty()) {
344 urlQuery.addQueryItem(QStringLiteral("title"), title);
345 }
346
347 url.setQuery(urlQuery);
348 return url;
349 }
350
fromSearchUrl(const QUrl & url)351 Query Query::fromSearchUrl(const QUrl &url)
352 {
353 if (url.scheme() != QLatin1String("akonadisearch")) {
354 return Query();
355 }
356
357 QUrlQuery urlQuery(url);
358 const QString jsonString = urlQuery.queryItemValue(QStringLiteral("json"), QUrl::FullyDecoded);
359 return Query::fromJSON(jsonString.toUtf8());
360 }
361
titleFromQueryUrl(const QUrl & url)362 QString Query::titleFromQueryUrl(const QUrl &url)
363 {
364 QUrlQuery urlQuery(url);
365 return urlQuery.queryItemValue(QStringLiteral("title"), QUrl::FullyDecoded);
366 }
367
operator ==(const Query & rhs) const368 bool Query::operator==(const Query &rhs) const
369 {
370 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
371 || rhs.d->m_yearFilter != d->m_yearFilter || rhs.d->m_customOptions != d->m_customOptions || rhs.d->m_searchString != d->m_searchString
372 || rhs.d->m_sortingProperty != d->m_sortingProperty || rhs.d->m_sortingOption != d->m_sortingOption) {
373 return false;
374 }
375
376 if (rhs.d->m_types.size() != d->m_types.size()) {
377 return false;
378 }
379
380 for (const QString &type : std::as_const(rhs.d->m_types)) {
381 if (!d->m_types.contains(type)) {
382 return false;
383 }
384 }
385
386 return d->m_term == rhs.d->m_term;
387 }
388
operator =(const Query & rhs)389 Query &Query::operator=(const Query &rhs)
390 {
391 *d = *rhs.d;
392 return *this;
393 }
394