1 /*
2     SPDX-FileCopyrightText: 2015 Martin Klapetek <mklapetek@kde.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "eventpluginsmanager.h"
8 
9 #include <CalendarEvents/CalendarEventsPlugin>
10 
11 #include <QAbstractListModel>
12 #include <QCoreApplication>
13 #include <QDebug>
14 #include <QDir>
15 #include <QJsonObject>
16 #include <QPluginLoader>
17 
18 #include <KPluginMetaData>
19 
20 class EventPluginsModel : public QAbstractListModel
21 {
22     Q_OBJECT
23 public:
EventPluginsModel(EventPluginsManager * manager)24     EventPluginsModel(EventPluginsManager *manager)
25         : QAbstractListModel(manager)
26     {
27         m_manager = manager;
28         m_roles = QAbstractListModel::roleNames();
29         m_roles.insert(Qt::EditRole, QByteArrayLiteral("checked"));
30         m_roles.insert(Qt::UserRole, QByteArrayLiteral("configUi"));
31         m_roles.insert(Qt::UserRole + 1, QByteArrayLiteral("pluginPath"));
32     }
33 
34     // make these two available to the manager
beginResetModel()35     void beginResetModel()
36     {
37         QAbstractListModel::beginResetModel();
38     }
39 
endResetModel()40     void endResetModel()
41     {
42         QAbstractListModel::endResetModel();
43     }
44 
roleNames() const45     QHash<int, QByteArray> roleNames() const override
46     {
47         return m_roles;
48     }
49 
rowCount(const QModelIndex & parent=QModelIndex ()) const50     Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const override
51     {
52         Q_UNUSED(parent);
53         return m_manager->m_availablePlugins.size();
54     }
55 
data(const QModelIndex & index,int role=Qt::DisplayRole) const56     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
57     {
58         if (!index.isValid() && !m_manager) {
59             return QVariant();
60         }
61 
62         const auto it = m_manager->m_availablePlugins.cbegin() + index.row();
63         const QString currentPlugin = it.key();
64         const EventPluginsManager::PluginData metadata = it.value();
65 
66         switch (role) {
67         case Qt::DisplayRole:
68             return metadata.name;
69         case Qt::ToolTipRole:
70             return metadata.desc;
71         case Qt::DecorationRole:
72             return metadata.icon;
73         case Qt::UserRole: {
74             // The currentPlugin path contains the full path including
75             // the plugin filename, so it needs to be cut off from the last '/'
76             const QStringView prefix = QStringView(currentPlugin).left(currentPlugin.lastIndexOf(QLatin1Char('/')));
77             const QString qmlFilePath = metadata.configUi;
78             return QString(prefix % QLatin1Char('/') % qmlFilePath);
79         }
80         case Qt::UserRole + 1:
81             return currentPlugin;
82         case Qt::EditRole:
83             return m_manager->m_enabledPlugins.contains(currentPlugin);
84         }
85 
86         return QVariant();
87     }
88 
setData(const QModelIndex & index,const QVariant & value,int role=Qt::EditRole)89     bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
90     {
91         if (role != Qt::EditRole || !index.isValid()) {
92             return false;
93         }
94 
95         bool enabled = value.toBool();
96         const QString pluginPath = m_manager->m_availablePlugins.keys().at(index.row());
97 
98         if (enabled) {
99             if (!m_manager->m_enabledPlugins.contains(pluginPath)) {
100                 m_manager->m_enabledPlugins << pluginPath;
101             }
102         } else {
103             m_manager->m_enabledPlugins.removeOne(pluginPath);
104         }
105 
106         Q_EMIT dataChanged(index, index);
107 
108         return true;
109     }
110 
get(int row,const QByteArray & role)111     Q_INVOKABLE QVariant get(int row, const QByteArray &role)
112     {
113         return data(createIndex(row, 0), roleNames().key(role));
114     }
115 
116 private:
117     EventPluginsManager *m_manager;
118     QHash<int, QByteArray> m_roles;
119 };
120 
EventPluginsManager(QObject * parent)121 EventPluginsManager::EventPluginsManager(QObject *parent)
122     : QObject(parent)
123 {
124     auto plugins = KPluginMetaData::findPlugins(QStringLiteral("plasmacalendarplugins"), [](const KPluginMetaData &md) {
125         return md.rawData().contains(QStringLiteral("KPlugin"));
126     });
127     for (const KPluginMetaData &plugin : std::as_const(plugins)) {
128         m_availablePlugins.insert(plugin.fileName(),
129                                   {plugin.name(), plugin.description(), plugin.iconName(), plugin.value(QStringLiteral("X-KDE-PlasmaCalendar-ConfigUi"))});
130     }
131 
132     // Fallback for legacy pre-KPlugin plugins so we can still load them
133     const QStringList paths = QCoreApplication::libraryPaths();
134     for (const QString &libraryPath : paths) {
135         const QString path(libraryPath + QStringLiteral("/plasmacalendarplugins"));
136         QDir dir(path);
137 
138         if (!dir.exists()) {
139             continue;
140         }
141 
142         const QStringList entryList = dir.entryList(QDir::Files | QDir::NoDotAndDotDot);
143 
144         for (const QString &fileName : entryList) {
145             const QString absolutePath = dir.absoluteFilePath(fileName);
146             if (m_availablePlugins.contains(absolutePath)) {
147                 continue;
148             }
149 
150             QPluginLoader loader(absolutePath);
151             // Load only our own plugins
152             if (loader.metaData().value(QStringLiteral("IID")) == QLatin1String("org.kde.CalendarEventsPlugin")) {
153                 const auto md = loader.metaData().value(QStringLiteral("MetaData")).toObject();
154                 m_availablePlugins.insert(absolutePath,
155                                           {md.value(QStringLiteral("Name")).toString(),
156                                            md.value(QStringLiteral("Description")).toString(),
157                                            md.value(QStringLiteral("Icon")).toString(),
158                                            md.value(QStringLiteral("ConfigUi")).toString()});
159             }
160         }
161     }
162 
163     m_model = new EventPluginsModel(this);
164 }
165 
~EventPluginsManager()166 EventPluginsManager::~EventPluginsManager()
167 {
168     qDeleteAll(m_plugins);
169 }
170 
populateEnabledPluginsList(const QStringList & pluginsList)171 void EventPluginsManager::populateEnabledPluginsList(const QStringList &pluginsList)
172 {
173     m_model->beginResetModel();
174     m_enabledPlugins = pluginsList;
175     m_model->endResetModel();
176 }
177 
setEnabledPlugins(QStringList & pluginsList)178 void EventPluginsManager::setEnabledPlugins(QStringList &pluginsList)
179 {
180     m_model->beginResetModel();
181     m_enabledPlugins = pluginsList;
182 
183     // Remove all already loaded plugins from the pluginsList
184     // and unload those plugins that are not in the pluginsList
185     auto i = m_plugins.begin();
186     while (i != m_plugins.end()) {
187         const QString pluginPath = (*i)->property("pluginPath").toString();
188         if (pluginsList.contains(pluginPath)) {
189             pluginsList.removeAll(pluginPath);
190             ++i;
191         } else {
192             (*i)->deleteLater();
193             i = m_plugins.erase(i);
194         }
195     }
196 
197     // Now load all the plugins left in pluginsList
198     for (const QString &pluginPath : std::as_const(pluginsList)) {
199         loadPlugin(pluginPath);
200     }
201 
202     m_model->endResetModel();
203     Q_EMIT pluginsChanged();
204 }
205 
enabledPlugins() const206 QStringList EventPluginsManager::enabledPlugins() const
207 {
208     return m_enabledPlugins;
209 }
210 
loadPlugin(const QString & absolutePath)211 void EventPluginsManager::loadPlugin(const QString &absolutePath)
212 {
213     QPluginLoader loader(absolutePath);
214 
215     if (!loader.load()) {
216         qWarning() << "Could not create Plasma Calendar Plugin: " << absolutePath;
217         qWarning() << loader.errorString();
218         return;
219     }
220 
221     QObject *obj = loader.instance();
222     if (obj) {
223         CalendarEvents::CalendarEventsPlugin *eventsPlugin = qobject_cast<CalendarEvents::CalendarEventsPlugin *>(obj);
224         if (eventsPlugin) {
225             qDebug() << "Loading Calendar plugin" << eventsPlugin;
226             eventsPlugin->setProperty("pluginPath", absolutePath);
227             m_plugins << eventsPlugin;
228 
229             // Connect the relay signals
230             connect(eventsPlugin, &CalendarEvents::CalendarEventsPlugin::dataReady, this, &EventPluginsManager::dataReady);
231             connect(eventsPlugin, &CalendarEvents::CalendarEventsPlugin::eventModified, this, &EventPluginsManager::eventModified);
232             connect(eventsPlugin, &CalendarEvents::CalendarEventsPlugin::eventRemoved, this, &EventPluginsManager::eventRemoved);
233         } else {
234             // not our/valid plugin, so unload it
235             loader.unload();
236         }
237     } else {
238         loader.unload();
239     }
240 }
241 
plugins() const242 QList<CalendarEvents::CalendarEventsPlugin *> EventPluginsManager::plugins() const
243 {
244     return m_plugins;
245 }
246 
pluginsModel() const247 QAbstractListModel *EventPluginsManager::pluginsModel() const
248 {
249     return m_model;
250 }
251 
252 #include "eventpluginsmanager.moc"
253