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