1 /*
2 SPDX-FileCopyrightText: 2019 Michail Vourlakos <mvourlakos@gmail.com>
3 SPDX-License-Identifier: GPL-2.0-or-later
4 */
5
6 #include "factory.h"
7
8 // local
9 #include "../layouts/importer.h"
10
11 // Qt
12 #include <QDebug>
13 #include <QDialogButtonBox>
14 #include <QDir>
15 #include <QDirIterator>
16 #include <QMessageBox>
17 #include <QProcess>
18 #include <QTemporaryDir>
19 #include <QTimer>
20 #include <QLatin1String>
21
22 // KDE
23 #include <KDirWatch>
24 #include <KLocalizedString>
25 #include <KMessageBox>
26 #include <KNotification>
27 #include <KPluginMetaData>
28 #include <KArchive/KTar>
29 #include <KArchive/KZip>
30 #include <KArchive/KArchiveEntry>
31 #include <KArchive/KArchiveDirectory>
32 #include <KNewStuff3/KNS3/DownloadDialog>
33
34 namespace Latte {
35 namespace Indicator {
36
Factory(QObject * parent)37 Factory::Factory(QObject *parent)
38 : QObject(parent)
39 {
40 m_parentWidget = new QWidget();
41
42 m_mainPaths = Latte::Layouts::Importer::standardPaths();
43
44 for(int i=0; i<m_mainPaths.count(); ++i) {
45 m_mainPaths[i] = m_mainPaths[i] + "/latte/indicators";
46 discoverNewIndicators(m_mainPaths[i]);
47 }
48
49 //! track paths for changes
50 for(const auto &dir : m_mainPaths) {
51 KDirWatch::self()->addDir(dir);
52 }
53
54 connect(KDirWatch::self(), &KDirWatch::dirty, this, [ & ](const QString & path) {
55 if (m_indicatorsPaths.contains(path)) {
56 //! indicator updated
57 reload(path);
58 } else if (m_mainPaths.contains(path)){
59 //! consider indicator addition
60 discoverNewIndicators(path);
61 }
62 });
63
64 connect(KDirWatch::self(), &KDirWatch::deleted, this, [ & ](const QString & path) {
65 if (m_indicatorsPaths.contains(path)) {
66 //! indicator removed
67 removeIndicatorRecords(path);
68 }
69 });
70
71 qDebug() << m_plugins["org.kde.latte.default"].name();
72 }
73
~Factory()74 Factory::~Factory()
75 {
76 m_parentWidget->deleteLater();
77 }
78
pluginExists(QString id) const79 bool Factory::pluginExists(QString id) const
80 {
81 return m_plugins.contains(id);
82 }
83
customPluginsCount()84 int Factory::customPluginsCount()
85 {
86 return m_customPluginIds.count();
87 }
88
customPluginIds()89 QStringList Factory::customPluginIds()
90 {
91 return m_customPluginIds;
92 }
93
customPluginNames()94 QStringList Factory::customPluginNames()
95 {
96 return m_customPluginNames;
97 }
98
customLocalPluginIds()99 QStringList Factory::customLocalPluginIds()
100 {
101 return m_customLocalPluginIds;
102 }
103
metadata(QString pluginId)104 KPluginMetaData Factory::metadata(QString pluginId)
105 {
106 if (m_plugins.contains(pluginId)) {
107 return m_plugins[pluginId];
108 }
109
110 return KPluginMetaData();
111 }
112
reload(const QString & indicatorPath)113 void Factory::reload(const QString &indicatorPath)
114 {
115 QString pluginChangedId;
116
117 if (!indicatorPath.isEmpty() && indicatorPath != "." && indicatorPath != "..") {
118 QString metadataFile = indicatorPath + "/metadata.desktop";
119
120 if(QFileInfo(metadataFile).exists()) {
121 KPluginMetaData metadata = KPluginMetaData::fromDesktopFile(metadataFile);
122
123 if (metadataAreValid(metadata)) {
124 pluginChangedId = metadata.pluginId();
125 QString uiFile = indicatorPath + "/package/" + metadata.value("X-Latte-MainScript");
126
127 if (!m_plugins.contains(metadata.pluginId())) {
128 m_plugins[metadata.pluginId()] = metadata;
129 }
130
131 if (QFileInfo(uiFile).exists()) {
132 m_pluginUiPaths[metadata.pluginId()] = QFileInfo(uiFile).absolutePath();
133 }
134
135 if ((metadata.pluginId() != "org.kde.latte.default")
136 && (metadata.pluginId() != "org.kde.latte.plasma")
137 && (metadata.pluginId() != "org.kde.latte.plasmatabstyle")) {
138
139 //! find correct alphabetical position
140 int newPos = -1;
141
142 if (!m_customPluginIds.contains(metadata.pluginId())) {
143 for (int i=0; i<m_customPluginNames.count(); ++i) {
144 if (QString::compare(metadata.name(), m_customPluginNames[i], Qt::CaseInsensitive)<=0) {
145 newPos = i;
146 break;
147 }
148 }
149 }
150
151 if (!m_customPluginIds.contains(metadata.pluginId())) {
152 if (newPos == -1) {
153 m_customPluginIds << metadata.pluginId();
154 } else {
155 m_customPluginIds.insert(newPos, metadata.pluginId());
156 }
157 }
158
159 if (!m_customPluginNames.contains(metadata.name())) {
160 if (newPos == -1) {
161 m_customPluginNames << metadata.name();
162 } else {
163 m_customPluginNames.insert(newPos, metadata.name());
164 }
165 }
166 }
167
168 if (indicatorPath.startsWith(QDir::homePath())) {
169 m_customLocalPluginIds << metadata.pluginId();
170 }
171 }
172
173 qDebug() << " Indicator Package Loaded ::: " << metadata.name() << " [" << metadata.pluginId() << "]" << " - [" << indicatorPath <<"]";
174
175 /*qDebug() << " Indicator value ::: " << metadata.pluginId();
176 qDebug() << " Indicator value ::: " << metadata.fileName();
177 qDebug() << " Indicator value ::: " << metadata.value("X-Latte-MainScript");
178 qDebug() << " Indicator value ::: " << metadata.value("X-Latte-ConfigUi");
179 qDebug() << " Indicator value ::: " << metadata.value("X-Latte-ConfigXml");*/
180 }
181 }
182
183 if (!pluginChangedId.isEmpty()) {
184 emit indicatorChanged(pluginChangedId);
185 }
186 }
187
discoverNewIndicators(const QString & main)188 void Factory::discoverNewIndicators(const QString &main)
189 {
190 if (!m_mainPaths.contains(main)) {
191 return;
192 }
193
194 QDirIterator indicatorsDirs(main, QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot, QDirIterator::NoIteratorFlags);
195
196 while(indicatorsDirs.hasNext()){
197 indicatorsDirs.next();
198 QString iPath = indicatorsDirs.filePath();
199
200 if (!m_indicatorsPaths.contains(iPath)) {
201 m_indicatorsPaths << iPath;
202 KDirWatch::self()->addDir(iPath);
203 reload(iPath);
204 }
205 }
206 }
207
removeIndicatorRecords(const QString & path)208 void Factory::removeIndicatorRecords(const QString &path)
209 {
210 if (m_indicatorsPaths.contains(path)) {
211 QString pluginId = path.section('/',-1);
212 m_plugins.remove(pluginId);
213 m_pluginUiPaths.remove(pluginId);
214
215 int pos = m_customPluginIds.indexOf(pluginId);
216
217 m_customPluginIds.removeAt(pos);
218 m_customPluginNames.removeAt(pos);
219 m_customLocalPluginIds.removeAll(pluginId);
220
221 m_indicatorsPaths.removeAll(path);
222
223 KDirWatch::self()->removeDir(path);
224
225 //! delay informing the removal in case it is just an update
226 QTimer::singleShot(1000, [this, pluginId]() {
227 emit indicatorRemoved(pluginId);
228 });
229 }
230 }
231
isCustomType(const QString & id) const232 bool Factory::isCustomType(const QString &id) const
233 {
234 return ((id != "org.kde.latte.default") && (id != "org.kde.latte.plasma") && (id != "org.kde.latte.plasmatabstyle"));
235 }
236
metadataAreValid(KPluginMetaData & metadata)237 bool Factory::metadataAreValid(KPluginMetaData &metadata)
238 {
239 return metadata.isValid()
240 && metadata.category() == QLatin1String("Latte Indicator")
241 && !metadata.value("X-Latte-MainScript").isEmpty();
242 }
243
metadataAreValid(QString & file)244 bool Factory::metadataAreValid(QString &file)
245 {
246 if (QFileInfo(file).exists()) {
247 KPluginMetaData metadata = KPluginMetaData::fromDesktopFile(file);
248 return metadata.isValid();
249 }
250
251 return false;
252 }
253
uiPath(QString pluginName) const254 QString Factory::uiPath(QString pluginName) const
255 {
256 if (!m_pluginUiPaths.contains(pluginName)) {
257 return "";
258 }
259
260 return m_pluginUiPaths[pluginName];
261 }
262
importIndicatorFile(QString compressedFile)263 Latte::ImportExport::State Factory::importIndicatorFile(QString compressedFile)
264 {
265 auto showNotificationError = []() {
266 auto notification = new KNotification("import-fail", KNotification::CloseOnTimeout);
267 notification->setText(i18n("Failed to import indicator"));
268 notification->sendEvent();
269 };
270
271 auto showNotificationSucceed = [](QString name, bool updated) {
272 auto notification = new KNotification("import-done", KNotification::CloseOnTimeout);
273 notification->setText(updated ? i18nc("indicator_name, imported updated","%1 indicator updated successfully", name) :
274 i18nc("indicator_name, imported success","%1 indicator installed successfully", name));
275 notification->sendEvent();
276 };
277
278 KArchive *archive;
279
280 KZip *zipArchive = new KZip(compressedFile);
281 zipArchive->open(QIODevice::ReadOnly);
282
283 //! if the file isnt a zip archive
284 if (!zipArchive->isOpen()) {
285 delete zipArchive;
286
287 KTar *tarArchive = new KTar(compressedFile, QStringLiteral("application/x-tar"));
288 tarArchive->open(QIODevice::ReadOnly);
289
290 if (!tarArchive->isOpen()) {
291 delete tarArchive;
292 showNotificationError();
293 return Latte::ImportExport::FailedState;
294 } else {
295 archive = tarArchive;
296 }
297 } else {
298 archive = zipArchive;
299 }
300
301 QTemporaryDir archiveTempDir;
302 archive->directory()->copyTo(archiveTempDir.path());
303
304 //metadata file
305 QString packagePath = archiveTempDir.path();
306 QString metadataFile = archiveTempDir.path() + "/metadata.desktop";
307
308 if (!QFileInfo(metadataFile).exists()){
309 QDirIterator iter(archiveTempDir.path(), QDir::Dirs | QDir::NoDotAndDotDot);
310
311 while(iter.hasNext() ) {
312 QString currentPath = iter.next();
313
314 QString tempMetadata = currentPath + "/metadata.desktop";
315
316 if (QFileInfo(tempMetadata).exists()) {
317 metadataFile = tempMetadata;
318 packagePath = currentPath;
319 }
320 }
321 }
322
323 KPluginMetaData metadata = KPluginMetaData::fromDesktopFile(metadataFile);
324
325 if (metadataAreValid(metadata)) {
326 QStringList standardPaths = Latte::Layouts::Importer::standardPaths();
327 QString installPath = standardPaths[0] + "/latte/indicators/" + metadata.pluginId();
328
329 bool updated{QDir(installPath).exists()};
330
331 if (QDir(installPath).exists()) {
332 QDir(installPath).removeRecursively();
333 }
334
335 QProcess process;
336 process.start(QString("mv " +packagePath + " " + installPath));
337 process.waitForFinished();
338 QString output(process.readAllStandardOutput());
339
340 showNotificationSucceed(metadata.name(), updated);
341 return Latte::ImportExport::InstalledState;
342 }
343
344 showNotificationError();
345 return Latte::ImportExport::FailedState;
346 }
347
removeIndicator(QString id)348 void Factory::removeIndicator(QString id)
349 {
350 if (m_plugins.contains(id)) {
351 QString pluginName = m_plugins[id].name();
352
353 QDialog* dialog = new QDialog(nullptr);
354 dialog->setWindowTitle(i18n("Remove Indicator Confirmation"));
355 dialog->setObjectName("warning");
356 dialog->setAttribute(Qt::WA_DeleteOnClose);
357
358 auto buttonbox = new QDialogButtonBox(QDialogButtonBox::Yes | QDialogButtonBox::No);
359
360 KMessageBox::createKMessageBox(dialog,
361 buttonbox,
362 QMessageBox::Question,
363 i18n("Do you want to remove completely <b>%1</b> indicator from your system?", pluginName),
364 QStringList(),
365 QString(),
366 0,
367 KMessageBox::NoExec,
368 QString());
369
370 connect(buttonbox, &QDialogButtonBox::accepted, [&, id, pluginName]() {
371 auto showRemovedSucceed = [](QString name) {
372 auto notification = new KNotification("remove-done", KNotification::CloseOnTimeout);
373 notification->setText(i18nc("indicator_name, removed success","<b>%1</b> indicator removed successfully", name));
374 notification->sendEvent();
375 };
376
377 qDebug() << "Trying to remove indicator :: " << id;
378 QProcess process;
379 process.start(QString("kpackagetool5 -r " +id + " -t Latte/Indicator"));
380 process.waitForFinished();
381 showRemovedSucceed(pluginName);
382 });
383
384 dialog->show();
385 }
386 }
387
downloadIndicator()388 void Factory::downloadIndicator()
389 {
390 KNS3::DownloadDialog dialog(QStringLiteral("latte-indicators.knsrc"), m_parentWidget);
391
392 dialog.exec();
393 }
394
395 }
396 }
397