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 | |
21 | using namespace Baloo; |
22 | |
23 | const int defaultLimit = -1; |
24 | |
25 | class BALOO_CORE_NO_EXPORT Baloo::Query::Private { |
26 | public: |
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 | |
42 | Query::Query() |
43 | : d(new Private) |
44 | { |
45 | } |
46 | |
47 | Query::Query(const Query& rhs) |
48 | : d(new Private(*rhs.d)) |
49 | { |
50 | } |
51 | |
52 | Query::~Query() = default; |
53 | |
54 | void Query::addType(const QString& type) |
55 | { |
56 | d->m_types << type.split(sep: QLatin1Char('/'), behavior: Qt::SkipEmptyParts); |
57 | } |
58 | |
59 | void Query::addTypes(const QStringList& typeList) |
60 | { |
61 | for (const QString& type : typeList) { |
62 | addType(type); |
63 | } |
64 | } |
65 | |
66 | void Query::setType(const QString& type) |
67 | { |
68 | d->m_types.clear(); |
69 | addType(type); |
70 | } |
71 | |
72 | void Query::setTypes(const QStringList& types) |
73 | { |
74 | d->m_types = types; |
75 | } |
76 | |
77 | QStringList Query::types() const |
78 | { |
79 | return d->m_types; |
80 | } |
81 | |
82 | QString Query::searchString() const |
83 | { |
84 | return d->m_searchString; |
85 | } |
86 | |
87 | void Query::setSearchString(const QString& str) |
88 | { |
89 | d->m_searchString = str; |
90 | |
91 | d->m_term = Term(); |
92 | } |
93 | |
94 | uint Query::limit() const |
95 | { |
96 | return d->m_limit; |
97 | } |
98 | |
99 | void Query::setLimit(uint limit) |
100 | { |
101 | d->m_limit = limit; |
102 | } |
103 | |
104 | uint Query::offset() const |
105 | { |
106 | return d->m_offset; |
107 | } |
108 | |
109 | void Query::setOffset(uint offset) |
110 | { |
111 | d->m_offset = offset; |
112 | } |
113 | |
114 | void 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 | |
121 | int Query::yearFilter() const |
122 | { |
123 | return d->m_yearFilter; |
124 | } |
125 | |
126 | int Query::monthFilter() const |
127 | { |
128 | return d->m_monthFilter; |
129 | } |
130 | |
131 | int Query::dayFilter() const |
132 | { |
133 | return d->m_dayFilter; |
134 | } |
135 | |
136 | void Query::setSortingOption(Query::SortingOption option) |
137 | { |
138 | d->m_sortingOption = option; |
139 | } |
140 | |
141 | Query::SortingOption Query::sortingOption() const |
142 | { |
143 | return d->m_sortingOption; |
144 | } |
145 | |
146 | QString Query::includeFolder() const |
147 | { |
148 | return d->m_includeFolder; |
149 | } |
150 | |
151 | void Query::setIncludeFolder(const QString& folder) |
152 | { |
153 | d->m_includeFolder = folder; |
154 | } |
155 | |
156 | ResultIterator 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 | |
196 | QByteArray 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 |
245 | Query 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 | |
288 | QUrl 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 | |
304 | static 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 | |
321 | Query 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 | |
349 | QString Query::titleFromQueryUrl(const QUrl& url) |
350 | { |
351 | QUrlQuery urlQuery(url); |
352 | return urlQuery.queryItemValue(QStringLiteral("title" ), encoding: QUrl::FullyDecoded); |
353 | } |
354 | |
355 | bool 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 | |
379 | bool Query::operator!=(const Query& rhs) const |
380 | { |
381 | return !(*this == rhs); |
382 | } |
383 | |
384 | Query& Query::operator=(const Query& rhs) |
385 | { |
386 | *d = *rhs.d; |
387 | return *this; |
388 | } |
389 | |