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