1/*
2 This file is part of the KDE Baloo 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#include "query.h"
9#include "term.h"
10#include "advancedqueryparser.h"
11#include "searchstore.h"
12#include "baloodebug.h"
13
14#include <QString>
15#include <QStringList>
16#include <QUrlQuery>
17
18#include <QJsonDocument>
19#include <QJsonObject>
20
21using namespace Baloo;
22
23const int defaultLimit = -1;
24
25class BALOO_CORE_NO_EXPORT Baloo::Query::Private {
26public:
27 Term m_term;
28
29 QStringList m_types;
30 QString m_searchString;
31 int m_limit = defaultLimit;
32 uint m_offset = 0;
33
34 int m_yearFilter = 0;
35 int m_monthFilter = 0;
36 int m_dayFilter = 0;
37
38 SortingOption m_sortingOption = SortAuto;
39 QString m_includeFolder;
40};
41
42Query::Query()
43 : d(new Private)
44{
45}
46
47Query::Query(const Query& rhs)
48 : d(new Private(*rhs.d))
49{
50}
51
52Query::~Query() = default;
53
54void Query::addType(const QString& type)
55{
56 d->m_types << type.split(sep: QLatin1Char('/'), behavior: Qt::SkipEmptyParts);
57}
58
59void Query::addTypes(const QStringList& typeList)
60{
61 for (const QString& type : typeList) {
62 addType(type);
63 }
64}
65
66void Query::setType(const QString& type)
67{
68 d->m_types.clear();
69 addType(type);
70}
71
72void Query::setTypes(const QStringList& types)
73{
74 d->m_types = types;
75}
76
77QStringList Query::types() const
78{
79 return d->m_types;
80}
81
82QString Query::searchString() const
83{
84 return d->m_searchString;
85}
86
87void Query::setSearchString(const QString& str)
88{
89 d->m_searchString = str;
90
91 d->m_term = Term();
92}
93
94uint Query::limit() const
95{
96 return d->m_limit;
97}
98
99void Query::setLimit(uint limit)
100{
101 d->m_limit = limit;
102}
103
104uint Query::offset() const
105{
106 return d->m_offset;
107}
108
109void Query::setOffset(uint offset)
110{
111 d->m_offset = offset;
112}
113
114void Query::setDateFilter(int year, int month, int day)
115{
116 d->m_yearFilter = year;
117 d->m_monthFilter = month;
118 d->m_dayFilter = day;
119}
120
121int Query::yearFilter() const
122{
123 return d->m_yearFilter;
124}
125
126int Query::monthFilter() const
127{
128 return d->m_monthFilter;
129}
130
131int Query::dayFilter() const
132{
133 return d->m_dayFilter;
134}
135
136void Query::setSortingOption(Query::SortingOption option)
137{
138 d->m_sortingOption = option;
139}
140
141Query::SortingOption Query::sortingOption() const
142{
143 return d->m_sortingOption;
144}
145
146QString Query::includeFolder() const
147{
148 return d->m_includeFolder;
149}
150
151void Query::setIncludeFolder(const QString& folder)
152{
153 d->m_includeFolder = folder;
154}
155
156ResultIterator Query::exec()
157{
158 if (!d->m_searchString.isEmpty()) {
159 if (d->m_term.isValid()) {
160 qCDebug(BALOO) << "Term already set";
161 }
162 AdvancedQueryParser parser;
163 d->m_term = parser.parse(text: d->m_searchString);
164 }
165
166 Term term(d->m_term);
167 if (!d->m_types.isEmpty()) {
168 for (const QString& type : std::as_const(t&: d->m_types)) {
169 term = term && Term(QStringLiteral("type"), type);
170 }
171 }
172
173 if (!d->m_includeFolder.isEmpty()) {
174 term = term && Term(QStringLiteral("includefolder"), d->m_includeFolder);
175 }
176
177 if (d->m_yearFilter || d->m_monthFilter || d->m_dayFilter) {
178 QByteArray ba = QByteArray::number(d->m_yearFilter);
179 if (d->m_monthFilter < 10) {
180 ba += '0';
181 }
182 ba += QByteArray::number(d->m_monthFilter);
183 if (d->m_dayFilter < 10) {
184 ba += '0';
185 }
186 ba += QByteArray::number(d->m_dayFilter);
187
188 term = term && Term(QStringLiteral("modified"), ba, Term::Equal);
189 }
190
191 SearchStore searchStore;
192 auto results = searchStore.exec(term, offset: d->m_offset, limit: d->m_limit, sortResults: d->m_sortingOption == SortAuto);
193 return ResultIterator(std::move(results));
194}
195
196QByteArray Query::toJSON()
197{
198 QVariantMap map;
199
200 if (!d->m_types.isEmpty()) {
201 map[QStringLiteral("type")] = d->m_types;
202 }
203
204 if (d->m_limit != defaultLimit) {
205 map[QStringLiteral("limit")] = d->m_limit;
206 }
207
208 if (d->m_offset) {
209 map[QStringLiteral("offset")] = d->m_offset;
210 }
211
212 if (!d->m_searchString.isEmpty()) {
213 map[QStringLiteral("searchString")] = d->m_searchString;
214 }
215
216 if (d->m_term.isValid()) {
217 map[QStringLiteral("term")] = QVariant(d->m_term.toVariantMap());
218 }
219
220 if (d->m_yearFilter > 0) {
221 map[QStringLiteral("yearFilter")] = d->m_yearFilter;
222 }
223 if (d->m_monthFilter > 0) {
224 map[QStringLiteral("monthFilter")] = d->m_monthFilter;
225 }
226 if (d->m_dayFilter > 0) {
227 map[QStringLiteral("dayFilter")] = d->m_dayFilter;
228 }
229
230 if (d->m_sortingOption != SortAuto) {
231 map[QStringLiteral("sortingOption")] = static_cast<int>(d->m_sortingOption);
232 }
233
234 if (!d->m_includeFolder.isEmpty()) {
235 map[QStringLiteral("includeFolder")] = d->m_includeFolder;
236 }
237
238 QJsonObject jo = QJsonObject::fromVariantMap(map);
239 QJsonDocument jdoc;
240 jdoc.setObject(jo);
241 return jdoc.toJson(format: QJsonDocument::JsonFormat::Compact);
242}
243
244// static
245Query Query::fromJSON(const QByteArray& arr)
246{
247 QJsonDocument jdoc = QJsonDocument::fromJson(json: arr);
248 const QVariantMap map = jdoc.object().toVariantMap();
249
250 Query query;
251 query.d->m_types = map[QStringLiteral("type")].toStringList();
252
253 if (map.contains(QStringLiteral("limit"))) {
254 query.d->m_limit = map[QStringLiteral("limit")].toUInt();
255 } else {
256 query.d->m_limit = defaultLimit;
257 }
258
259 query.d->m_offset = map[QStringLiteral("offset")].toUInt();
260 query.d->m_searchString = map[QStringLiteral("searchString")].toString();
261 query.d->m_term = Term::fromVariantMap(map: map[QStringLiteral("term")].toMap());
262
263 if (map.contains(QStringLiteral("yearFilter"))) {
264 query.d->m_yearFilter = map[QStringLiteral("yearFilter")].toInt();
265 }
266 if (map.contains(QStringLiteral("monthFilter"))) {
267 query.d->m_monthFilter = map[QStringLiteral("monthFilter")].toInt();
268 }
269 if (map.contains(QStringLiteral("dayFilter"))) {
270 query.d->m_dayFilter = map[QStringLiteral("dayFilter")].toInt();
271 }
272
273 if (map.contains(QStringLiteral("sortingOption"))) {
274 int option = map.value(QStringLiteral("sortingOption")).toInt();
275 query.d->m_sortingOption = static_cast<SortingOption>(option);
276 }
277
278 if (map.contains(QStringLiteral("includeFolder"))) {
279 query.d->m_includeFolder = map.value(QStringLiteral("includeFolder")).toString();
280 }
281
282 if (!query.d->m_searchString.isEmpty() && query.d->m_term.isValid()) {
283 qCWarning(BALOO) << "Only one of 'searchString' and 'term' should be set:" << arr;
284 }
285 return query;
286}
287
288QUrl Query::toSearchUrl(const QString& title)
289{
290 QUrl url;
291 url.setScheme(QStringLiteral("baloosearch"));
292
293 QUrlQuery urlQuery;
294 urlQuery.addQueryItem(QStringLiteral("json"), value: QString::fromUtf8(ba: toJSON()));
295
296 if (!title.isEmpty()) {
297 urlQuery.addQueryItem(QStringLiteral("title"), value: title);
298 }
299
300 url.setQuery(urlQuery);
301 return url;
302}
303
304static QString jsonQueryFromUrl(const QUrl &url)
305{
306 const QString path = url.path();
307
308 if (path == QLatin1String("/documents")) {
309 return QStringLiteral("{\"type\":[\"Document\"]}");
310 } else if (path.endsWith(s: QLatin1String("/images"))) {
311 return QStringLiteral("{\"type\":[\"Image\"]}");
312 } else if (path.endsWith(s: QLatin1String("/audio"))) {
313 return QStringLiteral("{\"type\":[\"Audio\"]}");
314 } else if (path.endsWith(s: QLatin1String("/videos"))) {
315 return QStringLiteral("{\"type\":[\"Video\"]}");
316 }
317
318 return QString();
319}
320
321Query Query::fromSearchUrl(const QUrl& url)
322{
323 if (url.scheme() != QLatin1String("baloosearch")) {
324 return Query();
325 }
326
327 QUrlQuery urlQuery(url);
328
329 if (urlQuery.hasQueryItem(QStringLiteral("json"))) {
330 QString jsonString = urlQuery.queryItemValue(QStringLiteral("json"), encoding: QUrl::FullyDecoded);
331 return Query::fromJSON(arr: jsonString.toUtf8());
332 }
333
334 if (urlQuery.hasQueryItem(QStringLiteral("query"))) {
335 QString queryString = urlQuery.queryItemValue(QStringLiteral("query"), encoding: QUrl::FullyDecoded);
336 Query q;
337 q.setSearchString(queryString);
338 return q;
339 }
340
341 const QString jsonString = jsonQueryFromUrl(url);
342 if (!jsonString.isEmpty()) {
343 return Query::fromJSON(arr: jsonString.toUtf8());
344 }
345
346 return Query();
347}
348
349QString Query::titleFromQueryUrl(const QUrl& url)
350{
351 QUrlQuery urlQuery(url);
352 return urlQuery.queryItemValue(QStringLiteral("title"), encoding: QUrl::FullyDecoded);
353}
354
355bool Query::operator==(const Query& rhs) const
356{
357 if (rhs.d->m_limit != d->m_limit || rhs.d->m_offset != d->m_offset ||
358 rhs.d->m_dayFilter != d->m_dayFilter || rhs.d->m_monthFilter != d->m_monthFilter ||
359 rhs.d->m_yearFilter != d->m_yearFilter || rhs.d->m_includeFolder != d->m_includeFolder ||
360 rhs.d->m_searchString != d->m_searchString ||
361 rhs.d->m_sortingOption != d->m_sortingOption)
362 {
363 return false;
364 }
365
366 if (rhs.d->m_types.size() != d->m_types.size()) {
367 return false;
368 }
369
370 for (const QString& type : std::as_const(t&: rhs.d->m_types)) {
371 if (!d->m_types.contains(str: type)) {
372 return false;
373 }
374 }
375
376 return d->m_term == rhs.d->m_term;
377}
378
379bool Query::operator!=(const Query& rhs) const
380{
381 return !(*this == rhs);
382}
383
384Query& Query::operator=(const Query& rhs)
385{
386 *d = *rhs.d;
387 return *this;
388}
389

source code of baloo/src/lib/query.cpp