File indexing completed on 2024-04-14 03:49:49

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