diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 562781b..06bf6ed 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -1,134 +1,139 @@ ecm_setup_version(PROJECT VARIABLE_PREFIX KIROGI PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KirogiCoreConfigVersion.cmake" SOVERSION ${KIROGI_VERSION_STRING} ) set(kirogicore_SRCS abstractvehicle.cpp + abstractpluginmodel.cpp vehiclesupportplugin.cpp vehiclesupportpluginmodel.cpp ) ecm_qt_declare_logging_category(kirogicore_SRCS HEADER debug.h IDENTIFIER KIROGI_CORE CATEGORY_NAME "kirogi.core" ) ecm_generate_headers(Kirogi_CamelCase_HEADERS HEADER_NAMES AbstractVehicle + AbstractPluginModel VehicleSupportPlugin VehicleSupportPluginModel REQUIRED_HEADERS Kirogi_HEADERS PREFIX kirogi ) add_subdirectory(positionsource) add_subdirectory(settings) add_subdirectory(vehicleparameters) add_library(kirogicore SHARED ${kirogicore_SRCS} ${Kirogi_HEADERS}) add_library(KirogiCore ALIAS kirogicore) generate_export_header(kirogicore BASE_NAME Kirogi EXPORT_FILE_NAME kirogicore_export.h) target_include_directories(kirogicore INTERFACE "$") if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") target_compile_options(kirogicore PRIVATE -pedantic -Woverloaded-virtual -Wunused -Wall -Wextra -Wshadow -Wnon-virtual-dtor -Wold-style-cast ) if(CMAKE_COMPILER_IS_GNUCXX) target_compile_options(kirogicore PRIVATE -Wduplicated-cond -Wduplicated-branches -Wlogical-op -Wnull-dereference -fsanitize=leak ) endif() endif() target_link_libraries(kirogicore PRIVATE Qt5::Core Qt5::Positioning Qt5::Qml Qt5::Quick KF5::CoreAddons + positionsource settings vehicleparameters ) set_target_properties(kirogicore PROPERTIES VERSION ${KIROGI_VERSION_STRING} SOVERSION ${KIROGI_SOVERSION} EXPORT_NAME KirogiCore OUTPUT_NAME kirogicore ) install(TARGETS kirogicore EXPORT kirogicoreLibraryTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${Kirogi_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kirogicore_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR}/Kirogi/kirogi COMPONENT Devel) install(FILES ${Kirogi_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR}/Kirogi/Kirogi COMPONENT Devel) write_basic_config_version_file(${CMAKE_CURRENT_BINARY_DIR}/KirogiCoreConfigVersion.cmake VERSION "${PROJECT_VERSION}" COMPATIBILITY AnyNewerVersion) set(CMAKECONFIG_INSTALL_DIR ${KDE_INSTALL_LIBDIR}/cmake/KirogiCore) configure_package_config_file(KirogiCoreConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/KirogiCoreConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KirogiCoreConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/KirogiCoreConfigVersion.cmake DESTINATION ${CMAKECONFIG_INSTALL_DIR}) install(EXPORT kirogicoreLibraryTargets DESTINATION ${CMAKECONFIG_INSTALL_DIR} FILE KirogiCoreLibraryTargets.cmake) install(FILES kirogivehiclesupportplugin.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR}) install(FILES kirogi.categories DESTINATION ${KDE_INSTALL_CONFDIR}) if(NOT BUILD_QT_QUICK_LIB) return() endif() add_library(kirogiqtquickplugin SHARED qtquickplugin.cpp) target_link_libraries(kirogiqtquickplugin Qt5::Positioning Qt5::Qml Qt5::Quick + KF5::CoreAddons + KirogiCore positionsource settings vehicleparameters ) install(TARGETS kirogiqtquickplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirogi) install(FILES qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/kirogi) diff --git a/src/lib/abstractpluginmodel.cpp b/src/lib/abstractpluginmodel.cpp new file mode 100644 index 0000000..d683be1 --- /dev/null +++ b/src/lib/abstractpluginmodel.cpp @@ -0,0 +1,222 @@ +/* + * Copyright 2020 Tomaz Canabrava + * + * 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 . + */ + +#include "abstractpluginmodel.h" +#include "debug.h" + +#include +#include + +#include +#include + +namespace Kirogi +{ +class Q_DECL_HIDDEN AbstractPluginModel::Private +{ +public: + explicit Private(AbstractPluginModel *q); + ~Private(); + + QVector plugins; + + // This is QMap so `AbstractPluginModel::loadedPlugins` returns a stable sort. + QMap 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 AbstractPluginModel::roleNames() const +{ + QHash 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); +} + + +} diff --git a/src/lib/vehiclesupportpluginmodel.h b/src/lib/abstractpluginmodel.h similarity index 65% copy from src/lib/vehiclesupportpluginmodel.h copy to src/lib/abstractpluginmodel.h index a02207c..e621766 100644 --- a/src/lib/vehiclesupportpluginmodel.h +++ b/src/lib/abstractpluginmodel.h @@ -1,66 +1,61 @@ /* - * Copyright 2019 Eike Hein + * Copyright 2020 Tomaz Canabrava * * 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 . */ #pragma once #include +#include +#include #include "kirogicore_export.h" -namespace Kirogi -{ -class VehicleSupportPlugin; - -class KIROGI_EXPORT VehicleSupportPluginModel : public QAbstractListModel -{ - Q_OBJECT - +namespace Kirogi { +/* This class is the base class of different models for plugins in Kirogi +*/ +class KIROGI_EXPORT AbstractPluginModel : public QAbstractListModel { +Q_OBJECT public: - enum AdditionalRoles { Id = Qt::UserRole + 1, Status, Plugin }; - Q_ENUM(AdditionalRoles) - - enum PluginStatus { PluginNotLoaded = 0, PluginLoaded = 1 }; - Q_ENUM(PluginStatus) - - explicit VehicleSupportPluginModel(QObject *parent = nullptr); - ~VehicleSupportPluginModel() override; + explicit AbstractPluginModel(QObject *parent = nullptr); + ~AbstractPluginModel() override; QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override final; + QObject *pluginForId(const QString& id) const; + KPluginMetaData metadataAt(int row) const; - 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); + 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 unloadPlugin(int row); Q_INVOKABLE bool unloadAllPlugins(); Q_SIGNALS: - // FIXME TODO: QObject -> VehicleSupportPlugin void pluginLoaded(const QString &pluginId, const QString &name, QObject *plugin) const; +protected: + virtual QObject *requestFromFactory(KPluginFactory *factory) = 0; + private: class Private; - QScopedPointer d; + Private *d; }; } diff --git a/src/lib/vehiclesupportpluginmodel.cpp b/src/lib/vehiclesupportpluginmodel.cpp index ad9a0bd..4a61094 100644 --- a/src/lib/vehiclesupportpluginmodel.cpp +++ b/src/lib/vehiclesupportpluginmodel.cpp @@ -1,230 +1,71 @@ /* * Copyright 2019 Eike Hein * * 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 . */ #include "vehiclesupportpluginmodel.h" #include "debug.h" #include "vehiclesupportplugin.h" #include #include #include #include #include -namespace Kirogi -{ -class Q_DECL_HIDDEN VehicleSupportPluginModel::Private -{ -public: - explicit Private(VehicleSupportPluginModel *q); - ~Private(); - - QVector plugins; - - // This is QMap so `VehicleSupportPluginModel::loadedPlugins` returns a stable sort. - QMap 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 VehicleSupportPluginModel::roleNames() const -{ - QHash 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 { if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) { 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(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 {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 {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 {Status, Plugin}); - } - } - - return true; + return factory->create(this); } } diff --git a/src/lib/vehiclesupportpluginmodel.h b/src/lib/vehiclesupportpluginmodel.h index a02207c..e54a03e 100644 --- a/src/lib/vehiclesupportpluginmodel.h +++ b/src/lib/vehiclesupportpluginmodel.h @@ -1,66 +1,51 @@ /* * Copyright 2019 Eike Hein * * 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 . */ #pragma once -#include +#include "abstractpluginmodel.h" #include "kirogicore_export.h" namespace Kirogi { class VehicleSupportPlugin; -class KIROGI_EXPORT VehicleSupportPluginModel : public QAbstractListModel +class KIROGI_EXPORT VehicleSupportPluginModel : public AbstractPluginModel { Q_OBJECT public: enum AdditionalRoles { Id = Qt::UserRole + 1, Status, Plugin }; Q_ENUM(AdditionalRoles) enum PluginStatus { PluginNotLoaded = 0, PluginLoaded = 1 }; Q_ENUM(PluginStatus) explicit VehicleSupportPluginModel(QObject *parent = nullptr); ~VehicleSupportPluginModel() override; - QHash 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 d; +protected: + QObject *requestFromFactory(KPluginFactory *factory) override; }; }