kdeplasma-addons: rework the dictionary runner

requires the following commit from kde-workspace:
6cfe505af4ede2843d7faa3d302e09cb171d823d

and because the runner is running in its own thread it is still not
thread-safe, that's something to fix

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
This commit is contained in:
Ivailo Monev 2023-07-11 04:51:27 +03:00
parent 698d7f49c7
commit 9b90a8609f
5 changed files with 78 additions and 181 deletions

View file

@ -1,4 +1,4 @@
set(dictionaryrunner_SRCS dictionaryrunner.cpp dictionarymatchengine.cpp)
set(dictionaryrunner_SRCS dictionaryrunner.cpp)
set(kcm_dictionaryrunner_SRCS dictionaryrunner_config.cpp)
kde4_add_plugin(krunner_dictionary ${dictionaryrunner_SRCS})
@ -7,5 +7,13 @@ kde4_add_plugin(kcm_krunner_dictionary ${kcm_dictionaryrunner_SRCS})
target_link_libraries(krunner_dictionary KDE4::plasma)
target_link_libraries(kcm_krunner_dictionary KDE4::kcmutils)
install(TARGETS krunner_dictionary kcm_krunner_dictionary DESTINATION ${KDE4_PLUGIN_INSTALL_DIR})
install(FILES plasma-runner-dictionary.desktop plasma-runner-dictionary_config.desktop DESTINATION ${KDE4_SERVICES_INSTALL_DIR})
install(
TARGETS krunner_dictionary kcm_krunner_dictionary
DESTINATION ${KDE4_PLUGIN_INSTALL_DIR}
)
install(
FILES
plasma-runner-dictionary.desktop
plasma-runner-dictionary_config.desktop
DESTINATION ${KDE4_SERVICES_INSTALL_DIR}
)

View file

@ -1,73 +0,0 @@
/*
* Copyright (C) 2010, 2012 Jason A. Donenfeld <Jason@zx2c4.com>
*/
#include "dictionarymatchengine.h"
#include <Plasma/AbstractRunner>
#include <QThread>
#include <QtCore/qmetaobject.h>
#include <KDebug>
DictionaryMatchEngine::DictionaryMatchEngine(Plasma::DataEngine *dictionaryEngine, QObject *parent)
: QObject(parent),
m_dictionaryEngine(dictionaryEngine)
{
/* We have to connect source in two different places, due to the difference in
* how the connection is made based on data availability. There are two cases,
* and this extra connection handles the second case. */
connect(m_dictionaryEngine, SIGNAL(sourceAdded(QString)), this, SLOT(sourceAdded(QString)));
}
/* This function should be called from a different thread. */
QString DictionaryMatchEngine::lookupWord(const QString &word)
{
if (!m_dictionaryEngine) {
kDebug() << "Could not find dictionary data engine.";
return QString();
}
if (thread() == QThread::currentThread()) {
kDebug() << "DictionaryMatchEngine::lookupWord is only meant to be called from non-primary threads.";
return QString();
}
ThreadData data;
QMutexLocker locker(&m_wordLock);
m_lockers.insert(word, &data);
locker.unlock();
/* We lock it in this thread. Then we try to lock it again, which we cannot do, until the other thread
* unlocks it for us first. We time-out after 30 seconds. */
data.mutex.lock();
QMetaObject::invokeMethod(this, "sourceAdded", Qt::QueuedConnection, Q_ARG(const QString&, QLatin1Char(':') + word));
if (!data.mutex.tryLock(30 * 1000))
kDebug() << "The dictionary data engine timed out.";
locker.relock();
m_lockers.remove(word, &data);
return data.definition;
}
void DictionaryMatchEngine::sourceAdded(const QString &source)
{
m_dictionaryEngine->connectSource(source, this);
}
void DictionaryMatchEngine::dataUpdated(const QString &source, const Plasma::DataEngine::Data &result)
{
if (!result.contains(QLatin1String("text")))
return;
QString definition(result[QLatin1String("text")].toString());
QMutexLocker locker(&m_wordLock);
foreach (ThreadData *data, m_lockers.values(source)) {
/* Because of QString's CoW semantics, we don't have to worry about
* the overhead of assigning this to every item. */
data->definition = definition;
data->mutex.unlock();
}
}
#include "moc_dictionarymatchengine.cpp"

View file

@ -1,40 +0,0 @@
/*
* Copyright (C) 2010, 2012 Jason A. Donenfeld <Jason@zx2c4.com>
*/
#ifndef DICTIONARYMATCHENGINE_H
#define DICTIONARYMATCHENGINE_H
#include <Plasma/DataEngine>
#include <QHash>
#include <QMutex>
#include <QtCore/qmap.h>
namespace Plasma
{
class RunnerContext;
}
class DictionaryMatchEngine : public QObject
{
Q_OBJECT
public:
DictionaryMatchEngine(Plasma::DataEngine *dictionaryEngine, QObject *parent = 0);
QString lookupWord(const QString &word);
private:
struct ThreadData {
QMutex mutex;
QString definition;
};
QMultiMap <QString, ThreadData*> m_lockers;
QMutex m_wordLock;
Plasma::DataEngine *m_dictionaryEngine;
private slots:
void dataUpdated(const QString &name, const Plasma::DataEngine::Data &data);
void sourceAdded(const QString &source);
};
#endif

View file

@ -4,78 +4,80 @@
#include "dictionaryrunner.h"
#include "dictionaryrunner_config.h"
#include <QStringList>
#include <QApplication>
#include <QClipboard>
#include <KIcon>
#include <KDebug>
DictionaryRunner::DictionaryRunner(QObject *parent, const QVariantList &args)
: AbstractRunner(parent, args)
: AbstractRunner(parent, args),
m_engine(nullptr)
{
m_engine = new DictionaryMatchEngine(dataEngine("dict"), this);
setSpeed(SlowSpeed);
setPriority(LowPriority);
setObjectName(QLatin1String("Dictionary"));
setIgnoredTypes(Plasma::RunnerContext::Directory | Plasma::RunnerContext::File |
Plasma::RunnerContext::NetworkLocation | Plasma::RunnerContext::Executable |
Plasma::RunnerContext::ShellCommand);
setSpeed(SlowSpeed);
setPriority(LowPriority);
setObjectName(QLatin1String("Dictionary"));
setIgnoredTypes(
Plasma::RunnerContext::Directory | Plasma::RunnerContext::File |
Plasma::RunnerContext::NetworkLocation | Plasma::RunnerContext::Executable |
Plasma::RunnerContext::ShellCommand
);
}
void DictionaryRunner::init()
{
reloadConfiguration();
reloadConfiguration();
m_engine = dataEngine("dict");
}
void DictionaryRunner::reloadConfiguration()
{
KConfigGroup c = config();
m_triggerWord = c.readEntry(CONFIG_TRIGGERWORD, i18nc("Trigger word before word to define", "define"));
if (!m_triggerWord.isEmpty())
m_triggerWord.append(QLatin1Char(' '));
setSyntaxes(QList<Plasma::RunnerSyntax>() << Plasma::RunnerSyntax(Plasma::RunnerSyntax(i18nc("Dictionary keyword", "%1:q:", m_triggerWord), i18n("Finds the definition of :q:."))));
KConfigGroup c = config();
m_triggerWord = c.readEntry(CONFIG_TRIGGERWORD, i18nc("Trigger word before word to define", "define"));
if (!m_triggerWord.isEmpty()) {
m_triggerWord.append(QLatin1Char(' '));
}
setSyntaxes(
QList<Plasma::RunnerSyntax>()
<< Plasma::RunnerSyntax(Plasma::RunnerSyntax(i18nc("Dictionary keyword", "%1:q:", m_triggerWord), i18n("Finds the definition of :q:."))
)
);
}
void DictionaryRunner::match(Plasma::RunnerContext &context)
{
QString query = context.query();
if (!query.startsWith(m_triggerWord, Qt::CaseInsensitive))
return;
query.remove(0, m_triggerWord.length());
if (query.isEmpty())
return;
QString returnedQuery = m_engine->lookupWord(query);
if (!context.isValid()) {
return;
}
QString query = context.query();
if (!query.startsWith(m_triggerWord, Qt::CaseInsensitive)) {
return;
}
query.remove(0, m_triggerWord.length());
if (query.isEmpty()) {
return;
}
if (!context.isValid())
return;
Plasma::DataEngine::Data enginedata = m_engine->query(query);
// qDebug() << Q_FUNC_INFO << query << enginedata;
const QString enginedefinition = enginedata.value("definition").toString();
if (!enginedefinition.isEmpty()) {
const QString engineexample = enginedata.value("example").toString();
Plasma::QueryMatch match(this);
match.setText(enginedefinition);
match.setSubtext(engineexample);
match.setData(enginedefinition);
match.setType(Plasma::QueryMatch::InformationalMatch);
match.setIcon(KIcon(QLatin1String("accessories-dictionary")));
context.addMatch(context.query(), match);
}
}
static const QRegExp removeHtml(QLatin1String("<[^>]*>"));
QString definitions(returnedQuery);
definitions.remove(QLatin1Char('\r')).remove(removeHtml);
while (definitions.contains(QLatin1String(" ")))
definitions.replace(QLatin1String(" "), QLatin1String(" "));
QStringList lines = definitions.split(QLatin1Char('\n'), QString::SkipEmptyParts);
if (lines.length() < 2)
return;
lines.removeFirst();
QList<Plasma::QueryMatch> matches;
int item = 0;
static const QRegExp partOfSpeech(QLatin1String("(?: ([a-z]{1,5})){0,1} [0-9]{1,2}: (.*)"));
QString lastPartOfSpeech;
static const KIcon icon(QLatin1String("accessories-dictionary"));
foreach (const QString &line, lines) {
if (partOfSpeech.indexIn(line) == -1)
continue;
if (!partOfSpeech.cap(1).isEmpty())
lastPartOfSpeech = partOfSpeech.cap(1);
Plasma::QueryMatch match(this);
match.setText(QString(QLatin1String("%1: %2")).arg(query, lastPartOfSpeech));
match.setRelevance(1 - (static_cast<double>(++item) / static_cast<double>(lines.length())));
match.setType(Plasma::QueryMatch::InformationalMatch);
match.setIcon(icon);
match.setSubtext(partOfSpeech.cap(2));
matches.append(match);
}
context.addMatches(context.query(), matches);
void DictionaryRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match)
{
Q_UNUSED(context)
const QString data = match.data().toString();
QApplication::clipboard()->setText(data);
}
K_EXPORT_PLASMA_RUNNER(krunner_dictionary, DictionaryRunner)

View file

@ -6,24 +6,24 @@
#define DICTIONARYRUNNER_H
#include <Plasma/AbstractRunner>
#include "dictionarymatchengine.h"
#include <Plasma/DataEngine>
class DictionaryRunner : public Plasma::AbstractRunner
{
Q_OBJECT
Q_OBJECT
public:
DictionaryRunner(QObject *parent, const QVariantList &args);
void match(Plasma::RunnerContext &context);
void reloadConfiguration();
DictionaryRunner(QObject *parent, const QVariantList &args);
void match(Plasma::RunnerContext &context);
void run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &match);
void reloadConfiguration();
private:
QString m_triggerWord;
DictionaryMatchEngine *m_engine;
QString m_triggerWord;
Plasma::DataEngine *m_engine;
protected slots:
void init();
void init();
};
#endif
#endif // DICTIONARYRUNNER_H