File indexing completed on 2024-05-12 17:08:53

0001 /*
0002     SPDX-FileCopyrightText: 2015 Martin Klapetek <mklapetek@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "eventpluginsmanager.h"
0008 
0009 #include <QAbstractListModel>
0010 #include <QCoreApplication>
0011 #include <QDebug>
0012 #include <QDir>
0013 #include <QJsonObject>
0014 #include <QPluginLoader>
0015 
0016 #include <KPluginMetaData>
0017 
0018 class EventPluginsManagerPrivate
0019 {
0020 public:
0021     explicit EventPluginsManagerPrivate();
0022     ~EventPluginsManagerPrivate();
0023 
0024     friend class EventPluginsModel;
0025     struct PluginData {
0026         QString name;
0027         QString desc;
0028         QString icon;
0029         QString configUi;
0030     };
0031 
0032     std::unique_ptr<EventPluginsModel> model;
0033     QList<CalendarEvents::CalendarEventsPlugin *> plugins;
0034     QMap<QString, PluginData> availablePlugins;
0035     QStringList enabledPlugins;
0036 };
0037 
0038 class EventPluginsModel : public QAbstractListModel
0039 {
0040     Q_OBJECT
0041 public:
0042     EventPluginsModel(EventPluginsManagerPrivate *d)
0043         : d(d)
0044         , m_roles(QAbstractListModel::roleNames())
0045     {
0046         m_roles.insert(Qt::EditRole, QByteArrayLiteral("checked"));
0047         m_roles.insert(Qt::UserRole, QByteArrayLiteral("configUi"));
0048         m_roles.insert(Qt::UserRole + 1, QByteArrayLiteral("pluginPath"));
0049     }
0050 
0051     // make these two available to the manager
0052     void beginResetModel()
0053     {
0054         QAbstractListModel::beginResetModel();
0055     }
0056 
0057     void endResetModel()
0058     {
0059         QAbstractListModel::endResetModel();
0060     }
0061 
0062     QHash<int, QByteArray> roleNames() const override
0063     {
0064         return m_roles;
0065     }
0066 
0067     Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const override
0068     {
0069         Q_UNUSED(parent);
0070         return d->availablePlugins.size();
0071     }
0072 
0073     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
0074     {
0075         if (!index.isValid() || !d) {
0076             return QVariant();
0077         }
0078 
0079         const auto it = std::next(d->availablePlugins.cbegin(), index.row());
0080         const QString currentPlugin = it.key();
0081         const EventPluginsManagerPrivate::PluginData metadata = it.value();
0082 
0083         switch (role) {
0084         case Qt::DisplayRole:
0085             return metadata.name;
0086         case Qt::ToolTipRole:
0087             return metadata.desc;
0088         case Qt::DecorationRole:
0089             return metadata.icon;
0090         case Qt::UserRole: {
0091             // The currentPlugin path contains the full path including
0092             // the plugin filename, so it needs to be cut off from the last '/'
0093             const QStringView prefix = QStringView(currentPlugin).left(currentPlugin.lastIndexOf(QDir::separator()));
0094             const QString qmlFilePath = metadata.configUi;
0095             return QStringLiteral("%1%2%3").arg(prefix, QDir::separator(), qmlFilePath);
0096         }
0097         case Qt::UserRole + 1:
0098             return currentPlugin;
0099         case Qt::EditRole:
0100             return d->enabledPlugins.contains(currentPlugin);
0101         }
0102 
0103         return QVariant();
0104     }
0105 
0106     bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
0107     {
0108         if (role != Qt::EditRole || !index.isValid()) {
0109             return false;
0110         }
0111 
0112         bool enabled = value.toBool();
0113         const QString pluginPath = d->availablePlugins.keys().at(index.row());
0114 
0115         if (enabled) {
0116             if (!d->enabledPlugins.contains(pluginPath)) {
0117                 d->enabledPlugins << pluginPath;
0118             }
0119         } else {
0120             d->enabledPlugins.removeOne(pluginPath);
0121         }
0122 
0123         Q_EMIT dataChanged(index, index);
0124 
0125         return true;
0126     }
0127 
0128     Q_INVOKABLE QVariant get(int row, const QByteArray &role)
0129     {
0130         return data(createIndex(row, 0), roleNames().key(role));
0131     }
0132 
0133 private:
0134     EventPluginsManagerPrivate *d;
0135     QHash<int, QByteArray> m_roles;
0136 };
0137 
0138 EventPluginsManagerPrivate::EventPluginsManagerPrivate()
0139     : model(std::make_unique<EventPluginsModel>(this))
0140 {
0141     auto plugins = KPluginMetaData::findPlugins(QStringLiteral("plasmacalendarplugins"), [](const KPluginMetaData &md) {
0142         return md.rawData().contains(QStringLiteral("KPlugin"));
0143     });
0144     for (const KPluginMetaData &plugin : std::as_const(plugins)) {
0145         availablePlugins.insert(plugin.fileName(),
0146                                 {plugin.name(), plugin.description(), plugin.iconName(), plugin.value(QStringLiteral("X-KDE-PlasmaCalendar-ConfigUi"))});
0147     }
0148 
0149     // Fallback for legacy pre-KPlugin plugins so we can still load them
0150     const QStringList paths = QCoreApplication::libraryPaths();
0151     for (const QString &libraryPath : paths) {
0152         const QString path(libraryPath + QStringLiteral("/plasmacalendarplugins"));
0153         QDir dir(path);
0154 
0155         if (!dir.exists()) {
0156             continue;
0157         }
0158 
0159         const QStringList entryList = dir.entryList(QDir::Files | QDir::NoDotAndDotDot);
0160 
0161         for (const QString &fileName : entryList) {
0162             const QString absolutePath = dir.absoluteFilePath(fileName);
0163             if (availablePlugins.contains(absolutePath)) {
0164                 continue;
0165             }
0166 
0167             QPluginLoader loader(absolutePath);
0168             // Load only our own plugins
0169             if (loader.metaData().value(QStringLiteral("IID")) == QLatin1String("org.kde.CalendarEventsPlugin")) {
0170                 const auto md = loader.metaData().value(QStringLiteral("MetaData")).toObject();
0171                 availablePlugins.insert(absolutePath,
0172                                         {md.value(QStringLiteral("Name")).toString(),
0173                                          md.value(QStringLiteral("Description")).toString(),
0174                                          md.value(QStringLiteral("Icon")).toString(),
0175                                          md.value(QStringLiteral("ConfigUi")).toString()});
0176             }
0177         }
0178     }
0179 }
0180 
0181 EventPluginsManagerPrivate::~EventPluginsManagerPrivate()
0182 {
0183     qDeleteAll(plugins);
0184 }
0185 
0186 EventPluginsManager::EventPluginsManager(QObject *parent)
0187     : QObject(parent)
0188     , d(new EventPluginsManagerPrivate)
0189 {
0190 }
0191 
0192 EventPluginsManager::~EventPluginsManager()
0193 {
0194     delete d;
0195 }
0196 
0197 void EventPluginsManager::populateEnabledPluginsList(const QStringList &pluginsList)
0198 {
0199     d->model->beginResetModel();
0200     d->enabledPlugins = pluginsList;
0201     d->model->endResetModel();
0202 }
0203 
0204 void EventPluginsManager::setEnabledPlugins(QStringList &pluginsList)
0205 {
0206     d->model->beginResetModel();
0207     d->enabledPlugins = pluginsList;
0208 
0209     // Remove all already loaded plugins from the pluginsList
0210     // and unload those plugins that are not in the pluginsList
0211     auto i = d->plugins.begin();
0212     while (i != d->plugins.end()) {
0213         const QString pluginPath = (*i)->property("pluginPath").toString();
0214         if (pluginsList.contains(pluginPath)) {
0215             pluginsList.removeAll(pluginPath);
0216             ++i;
0217         } else {
0218             (*i)->deleteLater();
0219             i = d->plugins.erase(i);
0220         }
0221     }
0222 
0223     // Now load all the plugins left in pluginsList
0224     for (const QString &pluginPath : std::as_const(pluginsList)) {
0225         loadPlugin(pluginPath);
0226     }
0227 
0228     d->model->endResetModel();
0229     Q_EMIT pluginsChanged();
0230 }
0231 
0232 QStringList EventPluginsManager::enabledPlugins() const
0233 {
0234     return d->enabledPlugins;
0235 }
0236 
0237 void EventPluginsManager::loadPlugin(const QString &absolutePath)
0238 {
0239     QPluginLoader loader(absolutePath);
0240 
0241     if (!loader.load()) {
0242         qWarning() << "Could not create Plasma Calendar Plugin: " << absolutePath;
0243         qWarning() << loader.errorString();
0244         return;
0245     }
0246 
0247     QObject *obj = loader.instance();
0248     if (obj) {
0249         CalendarEvents::CalendarEventsPlugin *eventsPlugin = qobject_cast<CalendarEvents::CalendarEventsPlugin *>(obj);
0250         if (eventsPlugin) {
0251             qDebug() << "Loading Calendar plugin" << eventsPlugin;
0252             eventsPlugin->setProperty("pluginPath", absolutePath);
0253             d->plugins << eventsPlugin;
0254 
0255             // Connect the relay signals
0256             connect(eventsPlugin, &CalendarEvents::CalendarEventsPlugin::dataReady, this, &EventPluginsManager::dataReady);
0257             connect(eventsPlugin, &CalendarEvents::CalendarEventsPlugin::eventModified, this, &EventPluginsManager::eventModified);
0258             connect(eventsPlugin, &CalendarEvents::CalendarEventsPlugin::eventRemoved, this, &EventPluginsManager::eventRemoved);
0259             connect(eventsPlugin, &CalendarEvents::CalendarEventsPlugin::alternateDateReady, this, &EventPluginsManager::alternateDateReady);
0260             connect(eventsPlugin, &CalendarEvents::CalendarEventsPlugin::subLabelReady, this, &EventPluginsManager::subLabelReady);
0261         } else {
0262             // not our/valid plugin, so unload it
0263             loader.unload();
0264         }
0265     } else {
0266         loader.unload();
0267     }
0268 }
0269 
0270 QList<CalendarEvents::CalendarEventsPlugin *> EventPluginsManager::plugins() const
0271 {
0272     return d->plugins;
0273 }
0274 
0275 QAbstractListModel *EventPluginsManager::pluginsModel() const
0276 {
0277     return d->model.get();
0278 }
0279 
0280 #include "eventpluginsmanager.moc"