Commit 902f556e authored by Tomaz  Canabrava's avatar Tomaz Canabrava Committed by Tomaz Canabrava

Add a AbstractPluginModel

This allow us to extend the plugin model with different
plugins in the future - I plan to start a Plugin for
interface elements.

This code decouples anything that's plugin-irrelevant
from the VehicleSupportPluginModel, loading, unloading, etc.

The VehicleSupportPluginModel now only deals with data()
and with creating the plugin *if* the plugin is VehicleSupportPlugin
parent 39c3b94f
......@@ -6,6 +6,7 @@ ecm_setup_version(PROJECT
set(kirogicore_SRCS
abstractvehicle.cpp
abstractpluginmodel.cpp
vehiclesupportplugin.cpp
vehiclesupportpluginmodel.cpp
)
......@@ -19,6 +20,7 @@ ecm_qt_declare_logging_category(kirogicore_SRCS
ecm_generate_headers(Kirogi_CamelCase_HEADERS
HEADER_NAMES
AbstractVehicle
AbstractPluginModel
VehicleSupportPlugin
VehicleSupportPluginModel
REQUIRED_HEADERS Kirogi_HEADERS
......@@ -68,6 +70,7 @@ target_link_libraries(kirogicore
Qt5::Qml
Qt5::Quick
KF5::CoreAddons
positionsource
settings
vehicleparameters
......@@ -124,6 +127,8 @@ target_link_libraries(kirogiqtquickplugin
Qt5::Positioning
Qt5::Qml
Qt5::Quick
KF5::CoreAddons
KirogiCore
positionsource
settings
......
/*
* Copyright 2020 Tomaz Canabrava <tcanabrava@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3, or any
* later version accepted by the membership of KDE e.V. (or its
* successor approved by the membership of KDE e.V.), which shall
* act as a proxy defined in Section 6 of version 3 of the license.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "abstractpluginmodel.h"
#include "debug.h"
#include <KPluginLoader>
#include <KPluginFactory>
#include <QCoreApplication>
#include <QMetaEnum>
namespace Kirogi
{
class Q_DECL_HIDDEN AbstractPluginModel::Private
{
public:
explicit Private(AbstractPluginModel *q);
~Private();
QVector<KPluginMetaData> plugins;
// This is QMap so `AbstractPluginModel::loadedPlugins` returns a stable sort.
QMap<QString, QObject*> loadedPlugins;
void loadPluginByService(const QString& serviceType);
private:
AbstractPluginModel *m_q;
};
AbstractPluginModel::Private::Private(AbstractPluginModel *q)
: m_q(q)
{
}
AbstractPluginModel::Private::~Private() = default;
AbstractPluginModel::AbstractPluginModel(QObject *parent)
: QAbstractListModel(parent)
, d(new AbstractPluginModel::Private(this))
{
}
AbstractPluginModel::~AbstractPluginModel()
{
unloadAllPlugins();
delete d;
}
void AbstractPluginModel::Private::loadPluginByService(const QString& serviceType)
{
auto filter = [serviceType](const KPluginMetaData &metaData) { return metaData.serviceTypes().contains(serviceType); };
const QString lowercaseMetadata = serviceType.toLower();
// Look for plugins in a relative path, covers the case when the application is
// not installed in the system.
const QString possiblePluginPath = QCoreApplication::applicationDirPath()
+ QStringLiteral("/../lib/plugins/%1").arg(lowercaseMetadata);
plugins = KPluginLoader::findPlugins(possiblePluginPath, filter);
plugins += KPluginLoader::findPlugins(lowercaseMetadata, filter);
// Unload plugins that apparently got uninstalled at runtime.
for (const QString &id : loadedPlugins.keys()) {
const bool found = std::any_of(plugins.constBegin(), plugins.constEnd(),
[id](const auto &md) { return md.pluginId() == id; });
if (!found) {
loadedPlugins.take(id)->deleteLater();
}
}
}
void AbstractPluginModel::loadPluginByService(const QString& serviceType)
{
d->loadPluginByService(serviceType);
}
bool AbstractPluginModel::loadPluginByIndex(int row)
{
const KPluginMetaData &md = d->plugins.at(row);
if (d->loadedPlugins.contains(md.pluginId())) {
return false;
}
KPluginLoader loader(md.fileName(), this);
KPluginFactory *factory = loader.factory();
if (!factory) {
qCWarning(KIROGI_CORE) << "Error loading plugin:" << md.pluginId() << "-" << loader.errorString();
return false;
}
QObject *plugin = requestFromFactory(factory);
if (!plugin) {
qCWarning(KIROGI_CORE) << "Scheduling invalid plugin to be deleted:" << md.pluginId() << "/" << factory;
factory->deleteLater();
return false;
}
qCWarning(KIROGI_CORE) << "Loaded plugin with id:" << md.pluginId();
d->loadedPlugins[md.pluginId()] = plugin;
emit pluginLoaded(md.pluginId(), md.name(), plugin);
const QModelIndex &idx = index(row, 0);
emit dataChanged(idx, idx);
return true;
}
bool AbstractPluginModel::loadPluginById(const QString &id)
{
for (int i = 0; i < d->plugins.count(); ++i) {
const KPluginMetaData &md = d->plugins.at(i);
if (md.pluginId() == id) {
return loadPluginByIndex(i);
}
}
return false;
}
bool AbstractPluginModel::unloadPlugin(int row)
{
const QString &id = d->plugins.at(row).pluginId();
if (!d->loadedPlugins.contains(id)) {
return false;
}
d->loadedPlugins.take(id)->deleteLater();
const QModelIndex &idx = index(row, 0);
emit dataChanged(idx, idx);
return true;
}
bool AbstractPluginModel::unloadAllPlugins()
{
if (!d->loadedPlugins.count()) {
return false;
}
for (int i = 0; i < d->plugins.count(); ++i) {
const KPluginMetaData &md = d->plugins.at(i);
QObject *plugin = d->loadedPlugins.take(md.pluginId());
if (plugin) {
plugin->deleteLater();
const QModelIndex &idx = index(i, 0);
emit dataChanged(idx, idx);
}
}
return true;
}
QHash<int, QByteArray> AbstractPluginModel::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles"));
auto desCapitalize = [](const char *input) -> QByteArray {
QByteArray array(input);
return array.left(1).toLower() + array.mid(1);
};
for (int i = 0; i < e.keyCount(); ++i) {
roles.insert(e.value(i), desCapitalize(e.key(i)));
}
return roles;
}
int AbstractPluginModel::rowCount(const QModelIndex &parent) const
{
if (!checkIndex(parent, CheckIndexOption::ParentIsInvalid)) {
return 0;
}
return d->plugins.count();
}
KPluginMetaData AbstractPluginModel::metadataAt(int row) const
{
Q_ASSERT(rowCount() > row);
return d->plugins.at(row);
}
QObject * AbstractPluginModel::pluginForId(const QString& id) const
{
return d->loadedPlugins.value(id, nullptr);
}
}
/*
* Copyright 2020 Tomaz Canabrava <tcanabrava@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3, or any
* later version accepted by the membership of KDE e.V. (or its
* successor approved by the membership of KDE e.V.), which shall
* act as a proxy defined in Section 6 of version 3 of the license.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QAbstractListModel>
#include <KPluginMetaData>
#include <KPluginFactory>
#include "kirogicore_export.h"
namespace Kirogi {
/* This class is the base class of different models for plugins in Kirogi
*/
class KIROGI_EXPORT AbstractPluginModel : public QAbstractListModel {
Q_OBJECT
public:
explicit AbstractPluginModel(QObject *parent = nullptr);
~AbstractPluginModel() override;
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override final;
QObject *pluginForId(const QString& id) const;
KPluginMetaData metadataAt(int row) const;
void loadPluginByService(const QString& serviceType);
Q_INVOKABLE bool loadPluginByIndex(int idx);
Q_INVOKABLE bool loadPluginById(const QString &id);
Q_INVOKABLE bool unloadPlugin(int row);
Q_INVOKABLE bool unloadAllPlugins();
Q_SIGNALS:
void pluginLoaded(const QString &pluginId, const QString &name, QObject *plugin) const;
protected:
virtual QObject *requestFromFactory(KPluginFactory *factory) = 0;
private:
class Private;
Private *d;
};
}
......@@ -29,86 +29,16 @@
#include <QCoreApplication>
#include <QMetaEnum>
namespace Kirogi
{
class Q_DECL_HIDDEN VehicleSupportPluginModel::Private
{
public:
explicit Private(VehicleSupportPluginModel *q);
~Private();
QVector<KPluginMetaData> plugins;
// This is QMap so `VehicleSupportPluginModel::loadedPlugins` returns a stable sort.
QMap<QString, VehicleSupportPlugin *> loadedPlugins;
void findPlugins();
private:
VehicleSupportPluginModel *m_q;
};
VehicleSupportPluginModel::Private::Private(VehicleSupportPluginModel *q)
: m_q(q)
{
}
VehicleSupportPluginModel::Private::~Private() = default;
void VehicleSupportPluginModel::Private::findPlugins()
{
auto filter = [](const KPluginMetaData &metaData) { return metaData.serviceTypes().contains(QStringLiteral("Kirogi/VehicleSupport")); };
// Look for plugins in a relative path, covers the case when the application is
// not installed in the system.
plugins = KPluginLoader::findPlugins(QCoreApplication::applicationDirPath() + QStringLiteral("/../lib/plugins/kirogi/vehiclesupport"), filter);
plugins += KPluginLoader::findPlugins(QStringLiteral("kirogi/vehiclesupport"), filter);
// Unload plugins that apparently got uninstalled at runtime.
for (const QString &id : loadedPlugins.keys()) {
const bool found = std::any_of(plugins.constBegin(), plugins.constEnd(), [id](const auto &md) { return md.pluginId() == id; });
if (!found) {
delete loadedPlugins.take(id);
}
}
}
namespace Kirogi {
VehicleSupportPluginModel::VehicleSupportPluginModel(QObject *parent)
: QAbstractListModel(parent)
, d(new Private(this))
: AbstractPluginModel(parent)
{
// FIXME TODO: Watch KSycoca and reload when new plugins are installed at runtime.
d->findPlugins();
loadPluginByService(QStringLiteral("Kirogi/VehicleSupport"));
}
VehicleSupportPluginModel::~VehicleSupportPluginModel() = default;
QHash<int, QByteArray> VehicleSupportPluginModel::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles"));
auto desCapitalize = [](const char *input) -> QByteArray {
QByteArray array(input);
return array.left(1).toLower() + array.mid(1);
};
for (int i = 0; i < e.keyCount(); ++i) {
roles.insert(e.value(i), desCapitalize(e.key(i)));
}
return roles;
}
int VehicleSupportPluginModel::rowCount(const QModelIndex &parent) const
{
if (!checkIndex(parent, CheckIndexOption::ParentIsInvalid)) {
return 0;
}
return d->plugins.count();
}
QVariant VehicleSupportPluginModel::data(const QModelIndex &index, int role) const
{
......@@ -116,115 +46,26 @@ QVariant VehicleSupportPluginModel::data(const QModelIndex &index, int role) con
return QVariant();
}
switch (role) {
case Qt::DisplayRole: {
return d->plugins.at(index.row()).name();
}
case Id: {
return d->plugins.at(index.row()).pluginId();
}
case Status: {
const QString &id = d->plugins.at(index.row()).pluginId();
if (d->loadedPlugins.contains(id)) {
return PluginLoaded;
}
return PluginNotLoaded;
}
case Plugin: {
const QString &id = d->plugins.at(index.row()).pluginId();
auto metadata = metadataAt(index.row());
const QString &id = metadata.pluginId();
return QVariant::fromValue(d->loadedPlugins.value(id));
}
switch (role) {
case Qt::DisplayRole:
return metadata.name();
case Id:
return metadata.pluginId();
case Status:
return pluginForId(id) ? PluginLoaded : PluginNotLoaded;
case Plugin:
return QVariant::fromValue(pluginForId(id));
}
return QVariant();
}
bool VehicleSupportPluginModel::loadPlugin(int row)
{
const KPluginMetaData &md = d->plugins.at(row);
if (d->loadedPlugins.contains(md.pluginId())) {
return false;
}
KPluginLoader loader(md.fileName(), this);
KPluginFactory *factory = loader.factory();
if (!factory) {
qCWarning(KIROGI_CORE) << "Error loading plugin:" << md.pluginId() << "-" << loader.errorString();
} else {
VehicleSupportPlugin *vehicleSupportPlugin = factory->create<VehicleSupportPlugin>(this);
if (!vehicleSupportPlugin) {
qCWarning(KIROGI_CORE) << "Scheduling invalid plugin to be deleted:" << md.pluginId() << "/" << factory;
factory->deleteLater();
} else {
qCWarning(KIROGI_CORE) << "Loaded plugin with id:" << md.pluginId();
d->loadedPlugins[md.pluginId()] = vehicleSupportPlugin;
emit pluginLoaded(md.pluginId(), md.name(), vehicleSupportPlugin);
const QModelIndex &idx = index(row, 0);
emit dataChanged(idx, idx, QVector<int> {Status, Plugin});
}
}
return false;
}
bool VehicleSupportPluginModel::loadPluginById(const QString &id)
QObject *VehicleSupportPluginModel::requestFromFactory(KPluginFactory *factory)
{
for (int i = 0; i < d->plugins.count(); ++i) {
const KPluginMetaData &md = d->plugins.at(i);
if (md.pluginId() == id) {
return loadPlugin(i);
}
}
return false;
}
bool VehicleSupportPluginModel::unloadPlugin(int row)
{
const QString &id = d->plugins.at(row).pluginId();
if (!d->loadedPlugins.contains(id)) {
return false;
}
delete d->loadedPlugins.take(id);
const QModelIndex &idx = index(row, 0);
emit dataChanged(idx, idx, QVector<int> {Status, Plugin});
return true;
}
bool VehicleSupportPluginModel::unloadAllPlugins()
{
if (!d->loadedPlugins.count()) {
return false;
}
for (int i = 0; i < d->plugins.count(); ++i) {
const KPluginMetaData &md = d->plugins.at(i);
VehicleSupportPlugin *plugin = d->loadedPlugins.take(md.pluginId());
if (plugin) {
delete plugin;
const QModelIndex &idx = index(i, 0);
emit dataChanged(idx, idx, QVector<int> {Status, Plugin});
}
}
return true;
return factory->create<VehicleSupportPlugin>(this);
}
}
......@@ -20,7 +20,7 @@
#pragma once
#include <QAbstractListModel>
#include "abstractpluginmodel.h"
#include "kirogicore_export.h"
......@@ -28,7 +28,7 @@ namespace Kirogi
{
class VehicleSupportPlugin;
class KIROGI_EXPORT VehicleSupportPluginModel : public QAbstractListModel
class KIROGI_EXPORT VehicleSupportPluginModel : public AbstractPluginModel
{
Q_OBJECT
......@@ -42,25 +42,10 @@ public:
explicit VehicleSupportPluginModel(QObject *parent = nullptr);
~VehicleSupportPluginModel() override;
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
Q_INVOKABLE bool loadPlugin(int row);
Q_INVOKABLE bool loadPluginById(const QString &id);
Q_INVOKABLE bool unloadPlugin(int row);
Q_INVOKABLE bool unloadAllPlugins();
Q_SIGNALS:
// FIXME TODO: QObject -> VehicleSupportPlugin
void pluginLoaded(const QString &pluginId, const QString &name, QObject *plugin) const;
private:
class Private;
QScopedPointer<Private> d;
protected:
QObject *requestFromFactory(KPluginFactory *factory) override;
};
}
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