File indexing completed on 2024-12-15 04:14:22

0001 /*
0002  * Copyright (C) 2015 Dan Leinir Turthra Jensen <admin@leinir.dk>
0003  *
0004  * This library is free software; you can redistribute it and/or
0005  * modify it under the terms of the GNU Lesser General Public
0006  * License as published by the Free Software Foundation; either
0007  * version 2.1 of the License, or (at your option) version 3, or any
0008  * later version accepted by the membership of KDE e.V. (or its
0009  * successor approved by the membership of KDE e.V.), which shall
0010  * act as a proxy defined in Section 6 of version 3 of the license.
0011  *
0012  * This library is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  * Lesser General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU Lesser General Public
0018  * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
0019  *
0020  */
0021 
0022 #include "BalooContentLister.h"
0023 
0024 #include <Baloo/IndexerConfig>
0025 #include <Baloo/File>
0026 #include <KFileMetaData/PropertyInfo>
0027 #include <KFileMetaData/UserMetaData>
0028 
0029 #include <QDateTime>
0030 #include <QList>
0031 #include <QFileInfo>
0032 #include <QProcess>
0033 #include <QThreadPool>
0034 #include <QMimeDatabase>
0035 
0036 #include "ContentQuery.h"
0037 
0038 class BalooContentLister::Private
0039 {
0040 public:
0041     Private(BalooContentLister* qq) : q(qq) {}
0042 
0043     BalooContentLister* q = nullptr;
0044 
0045     Baloo::QueryRunnable* createQuery(ContentQuery* contentQuery, const QString& location = QString{});
0046 
0047     QStringList locations;
0048     QString searchString;
0049     QList<Baloo::QueryRunnable*> queries;
0050     QList<QString> queryLocations;
0051 
0052     QMimeDatabase mimeDatabase;
0053 };
0054 
0055 BalooContentLister::BalooContentLister(QObject* parent)
0056     : ContentListerBase(parent)
0057     , d(new Private(this))
0058 {
0059 }
0060 
0061 BalooContentLister::~BalooContentLister()
0062 {
0063     QThreadPool::globalInstance()->waitForDone();
0064     delete d;
0065 }
0066 
0067 bool BalooContentLister::balooEnabled() const
0068 {
0069     // Baloo is not intended to be used outside of Plasma sessions
0070     // and so we can bypass all the testing if we are not actually
0071     // in a full KDE session.
0072     bool result{qEnvironmentVariableIsSet("KDE_FULL_SESSION")};
0073 
0074     if (result) {
0075         Baloo::IndexerConfig config;
0076         result = config.fileIndexingEnabled();
0077 
0078         if(result)
0079         {
0080             // It would be terribly nice with a bit of baloo engine exporting, so
0081             // we can ask the database about whether or not it is accessible...
0082             // But, this is a catch-all check anyway, so we get a complete "everything's broken"
0083             // result if anything is broken... guess it will do :)
0084             QProcess statuscheck;
0085             statuscheck.start("balooctl", QStringList() << "status");
0086             statuscheck.waitForFinished();
0087             QString output = statuscheck.readAll();
0088             if(statuscheck.exitStatus() == QProcess::CrashExit || statuscheck.exitCode() != 0)
0089             {
0090                 result = false;
0091             }
0092         }
0093     }
0094 
0095     return result;
0096 }
0097 
0098 void BalooContentLister::startSearch(const QList<ContentQuery*>& queries)
0099 {
0100     for(const auto& query : queries)
0101     {
0102         for(const auto& location : query->locations())
0103         {
0104             d->queries.append(d->createQuery(query, location));
0105         }
0106 
0107         if(query->locations().isEmpty())
0108             d->queries.append(d->createQuery(query));
0109     }
0110 
0111     if(!d->queries.empty())
0112     {
0113         QThreadPool::globalInstance()->start(d->queries.first());
0114     }
0115 }
0116 
0117 void BalooContentLister::queryCompleted(Baloo::QueryRunnable* query)
0118 {
0119     d->queries.removeAll(query);
0120     if(d->queries.empty())
0121     {
0122         emit searchCompleted();
0123     }
0124     else
0125     {
0126         QThreadPool::globalInstance()->start(d->queries.first());
0127     }
0128 }
0129 
0130 void BalooContentLister::queryResult(const ContentQuery* query, const QString& location, const QString& file)
0131 {
0132     if(knownFiles.contains(file)) {
0133         return;
0134     }
0135 
0136     // wow, this isn't nice... why is baloo not limiting searches like it's supposed to?
0137     if(!file.startsWith(location)) {
0138         return;
0139     }
0140 
0141     // Like the one above, this is also not nice: apparently Baloo can return results to
0142     // files that no longer exist on the file system. So we have to check manually whether
0143     // the results provided are actually sensible results...
0144     if(!QFile::exists(file)) {
0145         return;
0146     }
0147 
0148     // It would be nice if Baloo could do mime type filtering on its own...
0149     if(!query->mimeTypes().isEmpty()) {
0150         const auto &mimeType = d->mimeDatabase.mimeTypeForFile(file).name();
0151         if(!query->mimeTypes().contains(mimeType))
0152             return;
0153     }
0154 
0155     auto metadata = metaDataForFile(file);
0156 
0157     Baloo::File balooFile(file);
0158     balooFile.load();
0159     KFileMetaData::PropertyMap properties = balooFile.properties();
0160     KFileMetaData::PropertyMap::const_iterator it = properties.constBegin();
0161     for (; it != properties.constEnd(); it++)
0162     {
0163         KFileMetaData::PropertyInfo propInfo(it.key());
0164         metadata[propInfo.name()] = it.value();
0165     }
0166 
0167     emit fileFound(file, metadata);
0168 }
0169 
0170 Baloo::QueryRunnable* BalooContentLister::Private::createQuery(ContentQuery* contentQuery, const QString& location)
0171 {
0172     auto balooQuery = Baloo::Query{};
0173     if(!location.isEmpty())
0174         balooQuery.setIncludeFolder(location);
0175 
0176     switch(contentQuery->type())
0177     {
0178         case ContentQuery::Audio:
0179             balooQuery.setType("Audio");
0180             break;
0181         case ContentQuery::Documents:
0182             balooQuery.setType("Document");
0183             break;
0184         case ContentQuery::Images:
0185             balooQuery.setType("Image");
0186             break;
0187         case ContentQuery::Video:
0188             balooQuery.setType("Video");
0189             break;
0190         default:
0191             break;
0192     }
0193 
0194     if(!contentQuery->searchString().isEmpty())
0195         balooQuery.setSearchString(contentQuery->searchString());
0196 
0197     auto runnable = new Baloo::QueryRunnable{balooQuery};
0198     connect(runnable, &Baloo::QueryRunnable::queryResult, q, [this, contentQuery, location](QRunnable*, const QString& file) {
0199         q->queryResult(contentQuery, location, file);
0200     });
0201     connect(runnable, &Baloo::QueryRunnable::finished, q, &BalooContentLister::queryCompleted);
0202 
0203     return runnable;
0204 }