Kstars

extensions.cpp
1#include "extensions.h"
2#include "auxiliary/kspaths.h"
3#include "kstars_debug.h"
4#include "version.h"
5
6#include <QDir>
7#include <QIcon>
8#include <QDebug>
9#include <QProcess>
10
11extensions::extensions(QObject *parent) : QObject{ parent }
12{
13 found = new QMap<QString, extDetails>;
14 extensionProcess = new QProcess(this);
15
16 connect(extensionProcess, &QProcess::started, this, [ = ]()
17 {
18 emit extensionStateChanged(Ekos::EXTENSION_STARTED);
19 });
20 connect(extensionProcess, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, [ = ]()
21 {
22 emit extensionStateChanged(Ekos::EXTENSION_STOPPED);
23 });
24 connect(extensionProcess, &QProcess::readyRead, this, [this]
25 {
26 emit extensionOutput(extensionProcess->readAll());
27 });
28}
29
30bool extensions::discover()
31{
32 bool sucess = false;
33
34 QDir dir = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/extensions");
35 // Makes directory if not existing, doesn't change anything if already exists
36 if (dir.mkpath("."))
37 {
38 m_Directory = dir.absolutePath();
39 QStringList filesExe = dir.entryList(QStringList(), QDir::Files | QDir::Executable);
40 // Remove any conf or icon files that have been made executable from the exe list
41 QStringList imageExtensions = {".conf", ".jpg", ".bmp", ".gif", ".png", ".svg"};
42 for (int i = filesExe.count() - 1; i >= 0; i--)
43 {
44 const QString &filename = filesExe.at(i);
45 for (const QString &ext : imageExtensions)
46 {
47 if (filename.endsWith(ext))
48 {
49 filesExe.removeAt(i);
50 break;
51 }
52 }
53 }
54 QStringList filesConf = dir.entryList(QStringList() << "*.conf", QDir::Files | QDir::Readable);
55 QStringList filesIcons = dir.entryList(QStringList() << "*.jpg" << "*.bmp" << "*.gif" << "*.png" << "*.svg",
57 if (! filesExe.isEmpty())
58 {
59 qCDebug(KSTARS) << "Found extension(s): " << filesExe;
60
61 // Remove any executable without a .conf fileROCm
62 foreach (QString exe, filesExe)
63 {
64 if (!filesConf.contains(QString(exe).append(".conf")))
65 {
66 qCDebug(KSTARS) << QString(".conf file not found for extension %1").arg(exe);
67 filesExe.removeOne(exe);
68 }
69 }
70
71 if (! filesExe.isEmpty())
72 {
73
74 // Remove any .conf files that don't share filename with an executable
75 foreach (QString conf, filesConf)
76 {
77 if (!filesExe.contains(QString(conf).remove(".conf")))
78 {
79 qCDebug(KSTARS) << QString("Extraneous extension %1 file found without executable").arg(conf);
80 filesConf.removeOne(conf);
81 }
82 // Remove any .conf file that is not valid
83 if (!confValid(conf))
84 {
85 qCDebug(KSTARS) << QString(".conf file %1 is not valid").arg(conf);
86 filesConf.removeOne(conf);
87 }
88 }
89
90 //Check if any executable doesn't have a valid .conf
91 foreach (QString exe, filesExe)
92 {
93 if (!filesConf.contains(QString(exe).append(".conf")))
94 {
95 filesExe.removeOne(exe);
96 }
97 }
98
99 // Check if we have any executables with valid .conf files and build map
100 if (! filesExe.isEmpty() && (filesExe.count() == filesConf.count()))
101 {
102 foreach (QString exe, filesExe)
103 {
104 extDetails m_ext;
105
106 QString iconName = "";
107 foreach (QString name, filesIcons)
108 {
109 if (name.contains(exe))
110 {
111 iconName = name;
112 break;
113 }
114 }
115 QIcon icon;
116 if (iconName != "")
117 {
118 QString temp;
119 temp.append(m_Directory).append("/").append(iconName);
120 icon.addFile(temp);
121 }
122 else
123 {
124 icon = QIcon::fromTheme("plugins");
125 }
126
127 QString confFileName;
128 QString tooltip = "";
129 bool runDetached = false;
130 confFileName.append(m_Directory).append("/").append(exe).append(".conf");
131 QFile confFile(confFileName);
132 if (confFile.exists())
133 {
134 if (confFile.open(QIODevice::ReadOnly) && confFile.isReadable())
135 {
136 QTextStream confTS = QTextStream(&confFile);
137 while (!confTS.atEnd())
138 {
139 QString confLine = confTS.readLine();
140 if (confLine.contains("tooltip="))
141 {
142 tooltip = confLine.right(confLine.length() - (confLine.indexOf("=")) - 1);
143 }
144 else if (confLine.contains("runDetached=true"))
145 {
146 runDetached = true;
147 }
148 }
149 }
150 else qCDebug(KSTARS) << QString("Can't access .conf file %1").arg((exe).append(".conf"));
151 }
152 else qCDebug(KSTARS) << QString(".conf file %1 disappeared").arg((exe).append(".conf"));
153
154 m_ext.tooltip = tooltip;
155 m_ext.icon = icon;
156 m_ext.detached = runDetached;
157 found->insert(exe, m_ext);
158
159 sucess = true;
160 }
161 }
162 else qCDebug(KSTARS) << "No extensions found with valid .conf files";
163 }
164 else qCDebug(KSTARS) << "No extensions found with .conf files";
165 }
166 else qCDebug(KSTARS) << "No extensions found";
167 }
168 else qCDebug(KSTARS) << "Could not access extensions directory";
169
170 return sucess;
171}
172
173bool extensions::confValid(const QString &filePath)
174{
175 // Check that the passed extension .conf file contains a line starting
176 // minimum_kstars_version=xx.yy.zz
177 // and that the xx.yy.zz is no higher that the KSTARS_VERSION
178
179 bool valid = false;
180
181 QString confFileName;
182 confFileName.append(m_Directory).append("/").append(filePath);
183 QFile confFile(confFileName);
184 if (confFile.exists())
185 {
186 if (confFile.open(QIODevice::ReadOnly) && confFile.isReadable())
187 {
188 QTextStream confTS = QTextStream(&confFile);
189 while (!confTS.atEnd())
190 {
191 QString confLine = confTS.readLine();
192 if (confLine.contains("minimum_kstars_version="))
193 {
194 QString minVersion = confLine.right(confLine.length() - (confLine.indexOf("=")) - 1);
195 QStringList minVersionElements = minVersion.split(".");
196 if (minVersionElements.count() == 3)
197 {
198 QList <int> minVersionElementInts;
199 foreach (QString element, minVersionElements)
200 {
201 if (element.toInt() || element == "0")
202 {
203 minVersionElementInts.append(element.toInt());
204 }
205 else break;
206 }
207 if (minVersionElementInts.count() == minVersionElements.count())
208 {
209 QStringList KStarsVersionElements = QString(KSTARS_VERSION).split(".");
210 QList <int> KStarsVersionElementInts;
211 foreach (QString element, KStarsVersionElements)
212 {
213 if (element.toInt() || element == "0")
214 {
215 KStarsVersionElementInts.append(element.toInt());
216 }
217 }
218 uint minVerTot = ((minVersionElementInts[0] * 10000) +
219 (minVersionElementInts[1] * 1000) +
220 (minVersionElementInts[2]));
221 uint KStarsVerTot = ((KStarsVersionElementInts[0] * 10000) +
222 (KStarsVersionElementInts[1] * 1000) +
223 (KStarsVersionElementInts[2]));
224 if (KStarsVerTot >= minVerTot)
225 {
226 valid = true;
227 } else qCDebug(KSTARS) << QString(".conf file %1 requires a minimum KStars version of %2").arg(filePath, minVersion);
228 }
229 }
230 }
231 }
232 if (!valid) qCDebug(KSTARS) << QString(".conf file %1 does not contain a valid minimum_kstars_version string").arg(filePath);
233 }
234 else qCDebug(KSTARS) << QString("Can't access .conf file %1").arg(filePath);
235 }
236 else qCDebug(KSTARS) << QString(".conf file %1 disappeared").arg(filePath);
237
238 return valid;
239}
240
241QIcon extensions::getIcon(const QString &name)
242{
243 if (found->contains(name))
244 {
245 extDetails m_ext = found->value("name");
246 return m_ext.icon;
247 }
248 else return QIcon();
249}
250
251QString extensions::getTooltip(const QString &name)
252{
253 if (found->contains(name))
254 {
255 extDetails m_ext = found->value(name);
256 return m_ext.tooltip;
257 }
258 else return "";
259}
260
261void extensions::run(const QString &extension)
262{
263 QString processPath;
264 processPath.append(QString("%1%2%3").arg(m_Directory, "/", extension));
265 QStringList arguments;
266 extensionProcess->setWorkingDirectory(m_Directory);
267 extDetails m_ext = found->value(extension);
268 if (m_ext.detached)
269 {
270 extensionProcess->startDetached(processPath, arguments);
271 }
272 else
273 {
274 extensionProcess->setProcessChannelMode(QProcess::MergedChannels);
275 extensionProcess->start(processPath, arguments);
276 }
277 emit extensionStateChanged(Ekos::EXTENSION_START_REQUESTED);
278}
279
280void extensions::stop()
281{
282 emit extensionStateChanged(Ekos::EXTENSION_STOP_REQUESTED);
283}
284
285void extensions::kill()
286{
287 extensionProcess->kill();
288}
KIOCORE_EXPORT QString dir(const QString &fileClass)
QString name(StandardAction id)
KGuiItem remove()
void addFile(const QString &fileName, const QSize &size, Mode mode, State state)
QIcon fromTheme(const QString &name)
void readyRead()
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
qsizetype count() const const
bool isEmpty() const const
void removeAt(qsizetype i)
bool removeOne(const AT &t)
void finished(int exitCode, QProcess::ExitStatus exitStatus)
void started()
QString & append(QChar ch)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
qsizetype length() const const
QString right(qsizetype n) const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
bool atEnd() const const
QString readLine(qint64 maxlen)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:58:35 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.