Commit 41de3215 authored by Alexander Lohnau's avatar Alexander Lohnau 💬
Browse files

Port dictionary runner & applet away from Dataengines

Task: https://phabricator.kde.org/T13384
parent 28d8943e
Pipeline #152829 passed with stage
in 3 minutes and 9 seconds
......@@ -74,6 +74,7 @@ add_definitions(
-DQT_NO_URL_CAST_FROM_STRING
)
add_subdirectory(dict)
add_subdirectory(applets)
add_subdirectory(dataengines)
add_subdirectory(runners)
......
......@@ -33,10 +33,10 @@ set(dict_SRCS
add_library(dictplugin SHARED ${dict_SRCS})
target_link_libraries(dictplugin
KF5::Plasma
KF5::I18n
Qt::Quick
Qt::WebEngine
engine_dict_static
)
install(FILES plugin/qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/plasma/private/dict)
......
......@@ -11,9 +11,6 @@
#include <QWebEngineUrlRequestJob>
#include <QWebEngineUrlSchemeHandler>
// The KDE4 applet could use "qstardict" if available, but I have no idea where this came from.
static const char defaultDataEngine[] = "dict";
class DictSchemeHandler : public QWebEngineUrlSchemeHandler
{
Q_OBJECT
......@@ -35,38 +32,26 @@ Q_SIGNALS:
DictObject::DictObject(QObject *parent)
: QObject(parent)
, m_dataEngineName(QString::fromLatin1(defaultDataEngine))
{
m_selectedDict = QStringLiteral("wn");
m_webProfile = new QQuickWebEngineProfile(this);
auto *schemeHandler = new DictSchemeHandler(this);
connect(schemeHandler, &DictSchemeHandler::wordClicked, this, &DictObject::lookup);
connect(&m_engine, &DictEngine::definitionRecieved, this, [this](const QString &html) {
Q_EMIT definitionFound(html);
});
m_webProfile->installUrlSchemeHandler("dict", schemeHandler);
m_dataEngine = dataEngine(m_dataEngineName); // Load it upfront so the config dialog can reuse this one
}
void DictObject::lookup(const QString &word)
{
const QString newSource = m_selectedDict + QLatin1Char(':') + word;
if (!m_source.isEmpty()) {
m_dataEngine->disconnectSource(m_source, this);
}
if (!newSource.isEmpty()) {
// Look up new definition
Q_EMIT searchInProgress();
m_source = newSource;
m_dataEngine->connectSource(m_source, this);
}
}
void DictObject::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data)
{
Q_UNUSED(sourceName); // always == m_source
const QString html = data.value(QStringLiteral("text")).toString();
if (!html.isEmpty()) {
Q_EMIT definitionFound(html);
m_engine.requestDefinition(newSource);
}
}
......
......@@ -6,12 +6,12 @@
#ifndef DICT_OBJECT_H
#define DICT_OBJECT_H
#include <Plasma/DataEngine>
#include <Plasma/DataEngineConsumer>
#include "../../dict/dictengine.h"
#include <QObject>
class QQuickWebEngineProfile;
class DictObject : public QObject, public Plasma::DataEngineConsumer
class DictObject : public QObject
{
Q_OBJECT
Q_PROPERTY(QQuickWebEngineProfile *webProfile READ webProfile CONSTANT)
......@@ -28,9 +28,6 @@ public:
public Q_SLOTS:
void lookup(const QString &word);
private Q_SLOTS:
void dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data);
Q_SIGNALS:
void searchInProgress();
void definitionFound(const QString &html);
......@@ -40,7 +37,7 @@ private:
QString m_dataEngineName;
QString m_selectedDict;
Plasma::DataEngine *m_dataEngine;
DictEngine m_engine;
QQuickWebEngineProfile *m_webProfile;
};
......
......@@ -5,19 +5,23 @@
*/
#include "dictionariesmodel.h"
#include <Plasma/DataContainer>
#include <QDebug>
DictionariesModel::DictionariesModel(QObject *parent)
: QAbstractListModel(parent)
{
Plasma::DataEngine *dataengine = dataEngine(QStringLiteral("dict"));
const QString source = QLatin1String("list-dictionaries");
Plasma::DataContainer *container = dataengine->containerForSource(source);
if (container) { // in practice this never seems to happen, this source is only used here, so never shared
setAvailableDicts(container->data());
}
dataengine->connectSource(source, this);
static DictEngine engine; // Keep this around longer, because then we can use it's cache'
connect(&engine, &DictEngine::dictsRecieved, this, [this](const QMap<QString, QString> &dicts) {
beginResetModel();
m_availableDicts = {};
m_availableDicts.resize(dicts.count());
int i = 0;
for (auto it = dicts.begin(), end = dicts.end(); it != end; ++it, ++i) {
m_availableDicts[i] = AvailableDict{it.key(), it.value()};
}
endResetModel();
});
engine.requestDicts();
}
QVariant DictionariesModel::data(const QModelIndex &index, int role) const
......@@ -46,21 +50,3 @@ QHash<int, QByteArray> DictionariesModel::roleNames() const
{
return {{Qt::DisplayRole, "description"}, {Qt::EditRole, "id"}};
}
void DictionariesModel::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data)
{
Q_ASSERT(sourceName == QLatin1String("list-dictionaries"));
beginResetModel();
setAvailableDicts(data);
endResetModel();
}
void DictionariesModel::setAvailableDicts(const QVariantMap &data)
{
m_availableDicts = {};
m_availableDicts.resize(data.count());
int i = 0;
for (auto it = data.begin(), end = data.end(); it != end; ++it, ++i) {
m_availableDicts[i] = AvailableDict{it.key(), it.value().toString()};
}
}
......@@ -6,12 +6,11 @@
#ifndef DICTIONARIES_MODEL_H
#define DICTIONARIES_MODEL_H
#include <Plasma/DataEngine>
#include <Plasma/DataEngineConsumer>
#include "../../dict/dictengine.h"
#include <QAbstractListModel>
#include <vector>
class DictionariesModel : public QAbstractListModel, public Plasma::DataEngineConsumer
class DictionariesModel : public QAbstractListModel
{
Q_OBJECT
......@@ -22,9 +21,6 @@ public:
int rowCount(const QModelIndex &index = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
private Q_SLOTS:
void dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data);
private:
void setAvailableDicts(const QVariantMap &data);
......
add_definitions(-DTRANSLATION_DOMAIN="plasma_runner_krunner_dictionary")
kcoreaddons_add_plugin(krunner_dictionary SOURCES ${dictionaryrunner_SRCS} INSTALL_NAMESPACE "kf5/krunner")
target_sources(krunner_dictionary PRIVATE dictionaryrunner.cpp dictionarymatchengine.cpp)
target_link_libraries(krunner_dictionary KF5::Runner KF5::I18n)
target_sources(krunner_dictionary PRIVATE dictionaryrunner.cpp)
target_link_libraries(krunner_dictionary KF5::Runner KF5::I18n engine_dict_static)
kcoreaddons_add_plugin(kcm_krunner_dictionary INSTALL_NAMESPACE "kf5/krunner/kcms")
target_sources(kcm_krunner_dictionary PRIVATE dictionaryrunner_config.cpp)
......
/*
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2010, 2012 Jason A. Donenfeld <Jason@zx2c4.com>
*/
#include "dictionarymatchengine.h"
#include <KRunner/AbstractRunner>
#include <QDeadlineTimer>
#include <QDebug>
#include <QMetaMethod>
#include <QThread>
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. */
Q_ASSERT(m_dictionaryEngine);
connect(m_dictionaryEngine, &Plasma::DataEngine::sourceAdded, this, &DictionaryMatchEngine::sourceAdded);
}
/* This function should be called from a different thread. */
QString DictionaryMatchEngine::lookupWord(const QString &word)
{
if (!m_dictionaryEngine) {
qDebug() << "Could not find dictionary data engine.";
return QString();
}
if (thread() == QThread::currentThread()) {
qDebug() << "DictionaryMatchEngine::lookupWord is only meant to be called from non-primary threads.";
return QString();
}
ThreadData data;
m_wordLock.lockForWrite();
m_lockers.insert(word, &data);
m_wordLock.unlock();
QMetaObject::invokeMethod(this, "sourceAdded", Qt::QueuedConnection, Q_ARG(const QString &, word));
QMutexLocker locker(&data.mutex);
if (!data.waitCondition.wait(&data.mutex, QDeadlineTimer(30 * 1000))) { // Timeout after 30 seconds
qDebug() << "The dictionary data engine timed out (word:" << word << ")";
}
locker.unlock();
QMetaObject::invokeMethod(this, "sourceRemoved", Qt::QueuedConnection, Q_ARG(const QString &, word));
// after a timeout, if dataUpdated gets m_wordLock here, it can lock data->mutex successfully.
m_wordLock.lockForWrite();
m_lockers.remove(word, &data);
m_wordLock.unlock();
// after a timeout, if dataUpdated gets m_wordLock here, it won't see this data instance anymore.
locker.relock();
return data.definition;
}
void DictionaryMatchEngine::sourceAdded(const QString &source)
{
m_dictionaryEngine->connectSource(source, this);
}
void DictionaryMatchEngine::sourceRemoved(const QString &source)
{
m_dictionaryEngine->disconnectSource(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());
m_wordLock.lockForRead();
for (ThreadData *data : std::as_const(m_lockers)) {
QMutexLocker locker(&data->mutex);
/* 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->waitCondition.wakeOne();
}
m_wordLock.unlock();
}
/*
* SPDX-License-Identifier: GPL-2.0-or-later
* SPDX-FileCopyrightText: 2010, 2012 Jason A. Donenfeld <Jason@zx2c4.com>
*/
#ifndef DICTIONARYMATCHENGINE_H
#define DICTIONARYMATCHENGINE_H
#include <Plasma/DataEngine>
#include <QHash>
#include <QMultiMap>
#include <QMutex>
#include <QReadWriteLock>
#include <QWaitCondition>
namespace Plasma
{
}
class DictionaryMatchEngine : public QObject
{
Q_OBJECT
public:
explicit DictionaryMatchEngine(Plasma::DataEngine *dictionaryEngine, QObject *parent = nullptr);
QString lookupWord(const QString &word);
private:
struct ThreadData {
QWaitCondition waitCondition;
QMutex mutex;
QString definition;
};
QMultiMap<QString, ThreadData *> m_lockers;
QReadWriteLock m_wordLock;
Plasma::DataEngine *m_dictionaryEngine;
private Q_SLOTS:
void dataUpdated(const QString &name, const Plasma::DataEngine::Data &data);
void sourceAdded(const QString &source);
void sourceRemoved(const QString &source);
};
#endif
......@@ -25,11 +25,6 @@ DictionaryRunner::DictionaryRunner(QObject *parent, const KPluginMetaData &metaD
setObjectName(QLatin1String("Dictionary"));
}
void DictionaryRunner::init()
{
reloadConfiguration();
}
void DictionaryRunner::reloadConfiguration()
{
KConfigGroup c = config();
......@@ -53,34 +48,25 @@ void DictionaryRunner::match(RunnerContext &context)
return;
}
// Initialize engine
{
// It can happen that we are in this function and
// another match starts happening. Hence we lock to
// ensure that we always init the engine once
QMutexLocker lock(&s_initMutex);
if (!m_engine) {
QMetaObject::invokeMethod(
this,
[this] {
m_consumer = std::make_unique<Plasma::DataEngineConsumer>();
m_engine = std::make_unique<DictionaryMatchEngine>(m_consumer->dataEngine(QStringLiteral("dict")));
},
Qt::BlockingQueuedConnection);
}
QEventLoop loop;
QTimer::singleShot(400, &loop, [&loop]() {
loop.quit();
});
loop.exec();
}
if (!context.isValid()) {
return;
}
QString returnedQuery;
QMetaObject::invokeMethod(&m_dictEngine, "requestDefinition", Qt::QueuedConnection, Q_ARG(const QString &, query));
QEventLoop loop;
QTimer::singleShot(400, &loop, [&loop]() {
connect(&m_dictEngine, &DictEngine::definitionRecieved, &loop, [&loop, &query, &returnedQuery, &context](const QString &html) {
returnedQuery = html;
loop.quit();
});
loop.exec();
if (!context.isValid()) {
return;
}
QString returnedQuery = m_engine->lookupWord(query);
if (!context.isValid()) {
if (!context.isValid() || returnedQuery.isEmpty()) {
return;
}
......
......@@ -6,9 +6,8 @@
#ifndef DICTIONARYRUNNER_H
#define DICTIONARYRUNNER_H
#include "dictionarymatchengine.h"
#include "../../dict/dictengine.h"
#include <KRunner/AbstractRunner>
#include <Plasma/DataEngineConsumer>
using namespace Plasma;
......@@ -23,11 +22,7 @@ public:
private:
QString m_triggerWord;
std::unique_ptr<DictionaryMatchEngine> m_engine;
std::unique_ptr<Plasma::DataEngineConsumer> m_consumer;
protected Q_SLOTS:
void init() override;
DictEngine m_dictEngine;
};
#endif
Supports Markdown
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