File indexing completed on 2024-04-28 11:32:53

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