Commit 196c4186 authored by Ahmad Samir's avatar Ahmad Samir
Browse files

kio_filenamesearch: code refactoring

- Create a UDSEntry for ioslave's own root '.'
- Previously KCoreDirLister was used, which internally uses KIO::ListJob,
  now we use a ListJob directly, less indirection, less potential overhead.
parent c0fecdd8
Pipeline #79636 passed with stage
in 3 minutes and 3 seconds
......@@ -6,18 +6,18 @@
#include "kio_filenamesearch.h"
#include <KCoreDirLister>
#include <KFileItem>
#include "kio_filenamesearch_debug.h"
#include <KIO/Job>
#include <KLocalizedString>
#include <QTemporaryFile>
#include <QScopedPointer>
#include <QCoreApplication>
#include <QDBusInterface>
#include <QMimeDatabase>
#include <QRegularExpression>
#include <QTemporaryFile>
#include <QUrl>
#include <QUrlQuery>
#include <QDBusInterface>
#include <KLocalizedString>
// Pseudo plugin class to embed meta data
class KIOPluginForMetaData : public QObject
......@@ -26,18 +26,69 @@ class KIOPluginForMetaData : public QObject
Q_PLUGIN_METADATA(IID "org.kde.kio.slave.filenamesearch" FILE "filenamesearch.json")
};
static bool contentContainsPattern(const QUrl &url, const QRegularExpression &regex)
{
auto fileContainsPattern = [&](const QString &path) {
QFile file(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
}
QTextStream in(&file);
while (!in.atEnd()) {
const QString line = in.readLine();
if (regex.match(line).hasMatch()) {
return true;
}
}
return false;
};
if (url.isLocalFile()) {
return fileContainsPattern(url.toLocalFile());
} else {
QTemporaryFile tempFile;
if (tempFile.open()) {
const QString tempName = tempFile.fileName();
KIO::Job *getJob = KIO::file_copy(url, QUrl::fromLocalFile(tempName), -1, KIO::Overwrite | KIO::HideProgressInfo);
if (getJob->exec()) {
// The non-local file was downloaded successfully.
return fileContainsPattern(tempName);
}
}
}
return false;
}
static bool match(const KIO::UDSEntry &entry, const QRegularExpression &regex, bool searchContents)
{
if (!searchContents) {
return regex.match(entry.stringValue(KIO::UDSEntry::UDS_NAME)).hasMatch();
} else {
const QUrl entryUrl(entry.stringValue(KIO::UDSEntry::UDS_URL));
QMimeDatabase mdb;
QMimeType mimetype = mdb.mimeTypeForUrl(entryUrl);
if (mimetype.inherits(QStringLiteral("text/plain"))) {
return contentContainsPattern(entryUrl, regex);
}
}
return false;
}
FileNameSearchProtocol::FileNameSearchProtocol(const QByteArray &pool, const QByteArray &app)
: SlaveBase("search", pool, app)
: QObject()
, SlaveBase("search", pool, app)
{
QDBusInterface kded(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5"));
kded.call(QStringLiteral("loadModule"), QStringLiteral("filenamesearchmodule"));
}
FileNameSearchProtocol::~FileNameSearchProtocol()
{
}
FileNameSearchProtocol::~FileNameSearchProtocol() = default;
void FileNameSearchProtocol::stat(const QUrl& url)
void FileNameSearchProtocol::stat(const QUrl &url)
{
KIO::UDSEntry uds;
uds.reserve(9);
......@@ -59,128 +110,114 @@ void FileNameSearchProtocol::stat(const QUrl& url)
finished();
}
// Create a UDSEntry for "."
void FileNameSearchProtocol::listRootEntry()
{
KIO::UDSEntry entry;
entry.reserve(4);
entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("."));
entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0);
entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH);
listEntry(entry);
}
void FileNameSearchProtocol::listDir(const QUrl &url)
{
listRootEntry();
const QUrlQuery urlQuery(url);
const QString search = urlQuery.queryItemValue("search");
const QString search = urlQuery.queryItemValue(QStringLiteral("search"));
if (search.isEmpty()) {
finished();
return;
}
const QRegularExpression pattern(search, QRegularExpression::CaseInsensitiveOption);
std::function<bool(const KFileItem &)> validator;
if (urlQuery.queryItemValue("checkContent") == QStringLiteral("yes")) {
validator = [pattern](const KFileItem &item) -> bool {
return item.determineMimeType().inherits(QStringLiteral("text/plain")) &&
contentContainsPattern(item.url(), pattern);
};
} else {
validator = [pattern](const KFileItem &item) -> bool {
return item.text().contains(pattern);
};
const QRegularExpression regex(search, QRegularExpression::CaseInsensitiveOption);
if (!regex.isValid()) {
qCWarning(KIO_FILENAMESEARCH) << "Invalid QRegularExpression/PCRE search pattern:" << search;
finished();
return;
}
QSet<QString> iteratedDirs;
const QUrl directory(urlQuery.queryItemValue("url"));
searchDirectory(directory, validator, iteratedDirs);
finished();
}
const QUrl dirUrl = QUrl(urlQuery.queryItemValue(QStringLiteral("url")));
void FileNameSearchProtocol::searchDirectory(const QUrl &directory,
const std::function<bool(const KFileItem &)> &itemValidator,
QSet<QString> &iteratedDirs)
{
if (directory.path() == QStringLiteral("/proc")) {
// Don't try to iterate the /proc directory of Linux
// Don't try to iterate the /proc directory of Linux
if (dirUrl.isLocalFile() && dirUrl.toLocalFile() == QLatin1String("/proc")) {
finished();
return;
}
// Get all items of the directory
QScopedPointer<KCoreDirLister> dirLister(new KCoreDirLister);
dirLister->setDelayedMimeTypes(true);
dirLister->openUrl(directory);
dirLister->setAutoErrorHandlingEnabled(false);
QEventLoop eventLoop;
QObject::connect(dirLister.data(), static_cast<void(KCoreDirLister::*)()>(&KCoreDirLister::canceled),
&eventLoop, &QEventLoop::quit);
QObject::connect(dirLister.data(), static_cast<void(KCoreDirLister::*)()>(&KCoreDirLister::completed),
&eventLoop, &QEventLoop::quit);
eventLoop.exec();
// Visualize all items that match the search pattern
QList<QUrl> pendingDirs;
const KFileItemList items = dirLister->items();
for (const KFileItem &item : items) {
if (itemValidator(item)) {
KIO::UDSEntry entry = item.entry();
entry.replace(KIO::UDSEntry::UDS_URL, item.url().url());
listEntry(entry);
}
const bool isContent = urlQuery.queryItemValue(QStringLiteral("checkContent")) == QLatin1String("yes");
if (item.isDir()) {
if (item.isLink()) {
// Assure that no endless searching is done in directories that
// have already been iterated.
const QUrl linkDest = item.url().resolved(QUrl::fromLocalFile(item.linkDest()));
if (!iteratedDirs.contains(linkDest.path())) {
pendingDirs.append(linkDest);
}
} else {
pendingDirs.append(item.url());
}
}
}
iteratedDirs.insert(directory.path());
std::set<QString> iteratedDirs;
std::vector<QUrl> pendingDirs;
dirLister.reset();
searchDir(dirUrl, regex, isContent, iteratedDirs, pendingDirs);
// Recursively iterate all sub directories
for (const QUrl &pendingDir : qAsConst(pendingDirs)) {
searchDirectory(pendingDir, itemValidator, iteratedDirs);
for (auto it = pendingDirs.begin(); it != pendingDirs.end(); /* */) {
const QUrl pendingUrl = *it;
it = pendingDirs.erase(it);
searchDir(pendingUrl, regex, isContent, iteratedDirs, pendingDirs);
}
finished();
}
bool FileNameSearchProtocol::contentContainsPattern(const QUrl &fileName, const QRegularExpression &pattern)
void FileNameSearchProtocol::searchDir(const QUrl &dirUrl,
const QRegularExpression &regex,
bool searchContents,
std::set<QString> &iteratedDirs,
std::vector<QUrl> &pendingDirs)
{
auto fileContainsPattern = [&pattern](const QString &path) -> bool {
QFile file(path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return false;
KIO::ListJob *listJob = KIO::listRecursive(dirUrl, KIO::HideProgressInfo, false /* hidden */);
connect(this, &QObject::destroyed, listJob, [listJob]() {
listJob->kill();
});
connect(listJob, &KIO::ListJob::entries, this, [&](KJob *, const KIO::UDSEntryList &list) {
if (listJob->error()) {
qCWarning(KIO_FILENAMESEARCH) << "Searching failed:" << listJob->errorText();
return;
}
QTextStream in(&file);
while (!in.atEnd()) {
const QString line = in.readLine();
if (line.contains(pattern)) {
return true;
for (auto entry : list) {
QUrl entryUrl(dirUrl);
QString path = entryUrl.path();
if (!path.endsWith(QLatin1Char('/'))) {
path += QLatin1Char('/');
}
}
// UDS_NAME is e.g. "foo/bar/somefile.txt"
entryUrl.setPath(path + entry.stringValue(KIO::UDSEntry::UDS_NAME));
const QString urlStr = entryUrl.toDisplayString();
entry.replace(KIO::UDSEntry::UDS_URL, urlStr);
const QString fileName = entryUrl.fileName();
entry.replace(KIO::UDSEntry::UDS_NAME, fileName);
if (entry.isDir()) {
// Also search the target of a dir symlink
if (const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); !linkDest.isEmpty()) {
// Remember the dir to prevent endless loops
if (const auto [it, isInserted] = iteratedDirs.insert(linkDest); isInserted) {
pendingDirs.push_back(entryUrl.resolved(QUrl(linkDest)));
}
}
return false;
};
iteratedDirs.insert(urlStr);
}
if (fileName.isLocalFile()) {
return fileContainsPattern(fileName.toLocalFile());
} else {
QTemporaryFile tempFile;
if (tempFile.open()) {
KIO::Job* getJob = KIO::file_copy(fileName,
QUrl::fromLocalFile(tempFile.fileName()),
-1,
KIO::Overwrite | KIO::HideProgressInfo);
if (getJob->exec()) {
// The non-local file was downloaded successfully.
return fileContainsPattern(tempFile.fileName());
if (match(entry, regex, searchContents)) {
// UDS_DISPLAY_NAME is e.g. "foo/bar/somefile.txt"
entry.replace(KIO::UDSEntry::UDS_DISPLAY_NAME, fileName);
listEntry(entry);
}
}
}
});
return false;
listJob->exec();
}
extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv)
......
......@@ -7,42 +7,47 @@
#ifndef KIO_FILENAMESEARCH_H
#define KIO_FILENAMESEARCH_H
#include "kio_filenamesearch_debug.h"
#include <KIO/SlaveBase>
#include <kio/slavebase.h>
#include <QUrl>
#include <functional>
class QUrl;
class QRegularExpression;
class KFileItem;
#include <set>
#include <vector>
/**
* @brief Lists files where the filename matches do a given query.
* @brief Lists files that match a specific search pattern.
*
* For example, an application could create a url:
* filenamesearch:?search=sometext&url=file:///home/foo/bar&title=Query Results from 'sometext'
*
* The query is defined as part of the "search" query item of the URL.
* The directory where the searching is started is defined in the "url" query
* item. If the query item "checkContent" is set to "yes", all files with
* a text MIME type will be checked for the content.
* - The pattern to search for, @c sometext is the value of the "search" query
* item of the URL
* - The directory where the search is performed, @c file:///home/foo/bar, is the value
* of the "url" query item.
*
* By default the files with names matching the search pattern are listed.
* Alternatively if the query item "checkContent" is set to "yes", the contents
* of files with a text MimeType will be searched for the given pattern and
* the matching files will be listed.
*/
class FileNameSearchProtocol : public KIO::SlaveBase
class FileNameSearchProtocol : public QObject, public KIO::SlaveBase
{
Q_OBJECT
public:
FileNameSearchProtocol(const QByteArray &pool, const QByteArray &app);
~FileNameSearchProtocol() override;
void stat(const QUrl& url) override;
void stat(const QUrl &url) override;
void listDir(const QUrl &url) override;
private:
void searchDirectory(const QUrl &directory,
const std::function<bool(const KFileItem &)> &itemValidator,
QSet<QString> &iteratedDirs);
/**
* @return True, if the \a pattern is part of the file \a fileName.
*/
static bool contentContainsPattern(const QUrl &fileName, const QRegularExpression &pattern);
void listRootEntry();
void searchDir(const QUrl &dirUrl,
const QRegularExpression &regex,
bool searchContents,
std::set<QString> &iteratedDirs,
std::vector<QUrl> &pendingDirs);
};
#endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment