Kstars

servermanager.cpp
1/*
2 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "servermanager.h"
8
9#include "driverinfo.h"
10#include "clientmanager.h"
11#include "drivermanager.h"
12#include "auxiliary/kspaths.h"
13#include "auxiliary/ksmessagebox.h"
14#include "Options.h"
15#include "ksnotification.h"
16
17#include <indidevapi.h>
18#include <thread>
19
20#include <KMessageBox>
21#include <QUuid>
22
23#include <sys/stat.h>
24
25#include <indi_debug.h>
26
27// Qt version calming
28#include <qtendl.h>
29
30ServerManager::ServerManager(const QString &inHost, int inPort) : host(inHost), port(inPort)
31{
32}
33
34ServerManager::~ServerManager()
35{
36 serverSocket.close();
37 indiFIFO.close();
38
39 QFile::remove(indiFIFO.fileName());
40
41 if (serverProcess.get() != nullptr)
42 serverProcess->close();
43}
44
45bool ServerManager::start()
46{
47#ifdef Q_OS_WIN
48 qWarning() << "INDI server is currently not supported on Windows.";
49 return false;
50#else
51 bool connected = false;
52
53 if (serverProcess.get() == nullptr)
54 {
55 serverBuffer.open();
56
57 serverProcess.reset(new QProcess(this));
58#ifdef Q_OS_OSX
59 QString driversDir = Options::indiDriversDir();
60 if (Options::indiDriversAreInternal())
61 driversDir = QCoreApplication::applicationDirPath() + "/../Resources/DriverSupport";
63 if (Options::indiServerIsInternal())
65 else
66 indiServerDir = QFileInfo(Options::indiServer()).dir().path();
68 env.insert("PATH", driversDir + ':' + indiServerDir + ":/usr/local/bin:/usr/bin:/bin");
69 QString gscDirPath = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("gsc");
70 env.insert("GSCDAT", gscDirPath);
71
72 insertEnvironmentPath(&env, "INDIPREFIX", "/../../");
73 insertEnvironmentPath(&env, "IOLIBS", "/../Resources/DriverSupport/gphoto/IOLIBS");
74 insertEnvironmentPath(&env, "CAMLIBS", "/../Resources/DriverSupport/gphoto/CAMLIBS");
75
76 serverProcess->setProcessEnvironment(env);
77#endif
78 }
79
81
82 args << "-v" << "-p" << QString::number(port);
83
84 QString fifoFile = QString("/tmp/indififo%1").arg(QUuid::createUuid().toString().mid(1, 8));
85
86 if (mkfifo(fifoFile.toLatin1(), S_IRUSR | S_IWUSR) < 0)
87 {
88 emit failed(i18n("Error making FIFO file %1: %2.", fifoFile, strerror(errno)));
89 return false;
90 }
91
92 indiFIFO.setFileName(fifoFile);
93
95 {
96 qCCritical(KSTARS_INDI) << "Unable to create INDI FIFO file: " << fifoFile;
97 emit failed(i18n("Unable to create INDI FIFO file %1", fifoFile));
98 return false;
99 }
100
101 args << "-m" << QString::number(Options::serverTransferBufferSize()) << "-r" << "0" << "-f" << fifoFile;
102
103 qCDebug(KSTARS_INDI) << "Starting INDI Server: " << args << "-f" << fifoFile;
104
105 serverProcess->setProcessChannelMode(QProcess::SeparateChannels);
106 serverProcess->setReadChannel(QProcess::StandardError);
107
108#ifdef Q_OS_OSX
109 if (Options::indiServerIsInternal())
110 serverProcess->start(QCoreApplication::applicationDirPath() + "/indiserver", args);
111 else
112#endif
113 serverProcess->start(Options::indiServer(), args);
114
115 connected = serverProcess->waitForStarted();
116
117 if (connected)
118 {
119 connect(serverProcess.get(), &QProcess::errorOccurred, this, &ServerManager::processServerError);
120 connect(serverProcess.get(), &QProcess::readyReadStandardError, this, &ServerManager::processStandardError);
121 emit started();
122 }
123 else
124 {
125 emit failed(i18n("INDI server failed to start: %1", serverProcess->errorString()));
126 }
127
128 qCDebug(KSTARS_INDI) << "INDI Server Started? " << connected;
129
130 return connected;
131#endif
132}
133
134void ServerManager::insertEnvironmentPath(QProcessEnvironment *env, const QString &variable, const QString &relativePath)
135{
137 if (QFileInfo::exists(environmentPath) && Options::indiDriversAreInternal())
138 env->insert(variable, QDir(environmentPath).absolutePath());
139}
140
141void ServerManager::startDriver(const QSharedPointer<DriverInfo> &driver)
142{
143 QTextStream out(&indiFIFO);
144
145 // Check for duplicates within existing clients
146 if (driver->getUniqueLabel().isEmpty() && driver->getLabel().isEmpty() == false)
147 driver->setUniqueLabel(DriverManager::Instance()->getUniqueDeviceLabel(driver->getLabel()));
148
149 // Check for duplicates within managed drivers
150 if (driver->getUniqueLabel().isEmpty() == false)
151 {
152 QString uniqueLabel;
153 QString label = driver->getUniqueLabel();
154 int nset = std::count_if(m_ManagedDrivers.begin(), m_ManagedDrivers.end(), [label](auto & oneDriver)
155 {
156 return label == oneDriver->getUniqueLabel();
157 });
158 if (nset > 0)
159 {
160 uniqueLabel = QString("%1 %2").arg(label).arg(nset + 1);
161 driver->setUniqueLabel(uniqueLabel);
162 }
163 }
164
165 m_ManagedDrivers.append(driver);
166 driver->setServerManager(this);
167
168 QString driversDir = Options::indiDriversDir();
169 QString indiServerDir = QFileInfo(Options::indiServer()).dir().path();
170
171#ifdef Q_OS_OSX
172 if (Options::indiServerIsInternal())
174 if (Options::indiDriversAreInternal())
175 driversDir = QCoreApplication::applicationDirPath() + "/../Resources/DriverSupport";
176#endif
177
178 QJsonObject startupRule = driver->startupRule();
179 auto PreDelay = startupRule["PreDelay"].toInt(0);
180
181 // Sleep for PreDelay seconds if required.
182 if (PreDelay > 0)
183 {
184 qCDebug(KSTARS_INDI) << driver->getUniqueLabel() << ": Executing pre-driver delay for" << PreDelay << "second(s)";
185 std::this_thread::sleep_for(std::chrono::seconds(PreDelay));
186 }
187
188 // Startup Script?
189 auto PreScript = startupRule["PreScript"].toString();
190 if (!PreScript.isEmpty())
191 {
192 QProcess script;
193 QEventLoop loop;
194 QObject::connect(&script, static_cast<void (QProcess::*)(int exitCode, QProcess::ExitStatus status)>(&QProcess::finished),
195 &loop, &QEventLoop::quit);
197 qCDebug(KSTARS_INDI) << driver->getUniqueLabel() << ": Executing pre-driver script" << PreScript;
198 script.start(PreScript, QStringList());
199 loop.exec();
200
201 if (script.exitCode() != 0)
202 {
203 emit driverFailed(driver, i18n("Pre driver startup script failed with exit code: %1", script.exitCode()));
204 return;
205 }
206 }
207
208 // Remote host?
209 if (driver->getRemoteHost().isEmpty() == false)
210 {
211 QString driverString = driver->getName() + "@" + driver->getRemoteHost() + ":" + driver->getRemotePort();
212 qCDebug(KSTARS_INDI) << "Starting Remote INDI Driver" << driverString;
213 out << "start " << driverString << Qt::endl;
214 out.flush();
215 }
216 // Local?
217 else
218 {
219 QStringList paths;
220 paths << "/usr/bin"
221 << "/usr/local/bin" << driversDir << indiServerDir;
222
223 if (QStandardPaths::findExecutable(driver->getExecutable()).isEmpty())
224 {
225 if (QStandardPaths::findExecutable(driver->getExecutable(), paths).isEmpty())
226 {
227 emit driverFailed(driver, i18n("Driver %1 was not found on the system. Please make sure the package that "
228 "provides the '%1' binary is installed.",
229 driver->getExecutable()));
230 return;
231 }
232 }
233
234 qCDebug(KSTARS_INDI) << "Starting INDI Driver" << driver->getExecutable();
235
236 out << "start " << driver->getExecutable();
237 if (driver->getUniqueLabel().isEmpty() == false)
238 out << " -n \"" << driver->getUniqueLabel() << "\"";
239 if (driver->getSkeletonFile().isEmpty() == false)
240 out << " -s \"" << driversDir << QDir::separator() << driver->getSkeletonFile() << "\"";
241 out << Qt::endl;
242 out.flush();
243
244 driver->setServerState(true);
245
246 driver->setPort(port);
247 }
248
249 auto PostDelay = startupRule["PostDelay"].toInt(0);
250
251 // Sleep for PostDelay seconds if required.
252 if (PostDelay > 0)
253 {
254 qCDebug(KSTARS_INDI) << driver->getUniqueLabel() << ": Executing post-driver delay for" << PreDelay << "second(s)";
255 std::this_thread::sleep_for(std::chrono::seconds(PostDelay));
256 }
257
258 // Startup Script?
259 auto PostScript = startupRule["PostScript"].toString();
260 if (!PostScript.isEmpty())
261 {
262 QProcess script;
263 QEventLoop loop;
264 QObject::connect(&script, static_cast<void (QProcess::*)(int exitCode, QProcess::ExitStatus status)>(&QProcess::finished),
265 &loop, &QEventLoop::quit);
267 qCDebug(KSTARS_INDI) << driver->getUniqueLabel() << ": Executing post-driver script" << PreScript;
268 script.start(PostScript, QStringList());
269 loop.exec();
270
271 if (script.exitCode() != 0)
272 {
273 emit driverFailed(driver, i18n("Post driver startup script failed with exit code: %1", script.exitCode()));
274 return;
275 }
276 }
277
278 // Remove driver from pending list.
279 m_PendingDrivers.erase(std::remove_if(m_PendingDrivers.begin(), m_PendingDrivers.end(), [driver](const auto & oneDriver)
280 {
281 return driver == oneDriver;
282 }), m_PendingDrivers.end());
283 emit driverStarted(driver);
284}
285
286void ServerManager::stopDriver(const QSharedPointer<DriverInfo> &driver)
287{
288 QTextStream out(&indiFIFO);
289 const auto exec = driver->getExecutable();
290
291 qCDebug(KSTARS_INDI) << "Stopping INDI Driver " << exec;
292
293 if (driver->getUniqueLabel().isEmpty() == false)
294 out << "stop " << exec << " -n \"" << driver->getUniqueLabel() << "\"";
295 else
296 out << "stop " << exec;
297 out << Qt::endl;
298 out.flush();
299 driver->setServerState(false);
300 driver->setPort(driver->getUserPort());
301
302 m_ManagedDrivers.erase(std::remove_if(m_ManagedDrivers.begin(), m_ManagedDrivers.end(), [exec](const auto & driver)
303 {
304 return driver->getExecutable() == exec;
305 }));
306 emit driverStopped(driver);
307}
308
309
310bool ServerManager::restartDriver(const QSharedPointer<DriverInfo> &driver)
311{
312 auto cm = driver->getClientManager();
313 const auto label = driver->getLabel();
314
315 if (cm)
316 {
317 qCDebug(KSTARS_INDI) << "Restarting INDI Driver: " << label;
318 // N.B. This MUST be called BEFORE stopping driver below
319 // Since it requires the driver device pointer.
320 cm->removeManagedDriver(driver);
321
322 // Stop driver.
323 stopDriver(driver);
324 }
325 else
326 {
327 qCDebug(KSTARS_INDI) << "restartDriver with no cm, and " << m_ManagedDrivers.size() << " drivers. Trying to remove: " <<
328 label;
329 cm = DriverManager::Instance()->getClientManager(driver);
330 const auto exec = driver->getExecutable();
331 m_ManagedDrivers.erase(std::remove_if(m_ManagedDrivers.begin(), m_ManagedDrivers.end(), [exec](const auto & driver)
332 {
333 return driver->getExecutable() == exec;
334 }));
335 }
336
337 // Wait 1 second before starting the driver again.
338 QTimer::singleShot(1000, this, [this, label, cm]()
339 {
340 auto driver = DriverManager::Instance()->findDriverByLabel(label);
341 if (!driver)
342 {
343 qCDebug(KSTARS_INDI) << "restartDriver timer, did not find driver with label: " << label;
344 return;
345 }
346 cm->appendManagedDriver(driver);
347 if (m_ManagedDrivers.contains(driver) == false)
348 m_ManagedDrivers.append(driver);
349 driver->setServerManager(this);
350
351 QTextStream out(&indiFIFO);
352
353 QString driversDir = Options::indiDriversDir();
354 QString indiServerDir = Options::indiServer();
355
356#ifdef Q_OS_OSX
357 if (Options::indiServerIsInternal())
359 if (Options::indiDriversAreInternal())
360 driversDir = QCoreApplication::applicationDirPath() + "/../Resources/DriverSupport";
361 else
362 indiServerDir = QFileInfo(Options::indiServer()).dir().path();
363#endif
364
365 if (driver->getRemoteHost().isEmpty() == false)
366 {
367 QString driverString = driver->getName() + "@" + driver->getRemoteHost() + ":" + driver->getRemotePort();
368 qCDebug(KSTARS_INDI) << "Restarting Remote INDI Driver" << driverString;
369 out << "start " << driverString;
370#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
371 out << Qt::endl;
372#else
373 out << Qt::endl;
374#endif
375 out.flush();
376 }
377 else
378 {
379 QStringList paths;
380 paths << "/usr/bin"
381 << "/usr/local/bin" << driversDir << indiServerDir;
382
383 qCDebug(KSTARS_INDI) << "Starting INDI Driver " << driver->getExecutable();
384
385 out << "start " << driver->getExecutable();
386 if (driver->getUniqueLabel().isEmpty() == false)
387 out << " -n \"" << driver->getUniqueLabel() << "\"";
388 if (driver->getSkeletonFile().isEmpty() == false)
389 out << " -s \"" << driversDir << QDir::separator() << driver->getSkeletonFile() << "\"";
390 out << Qt::endl;
391 out.flush();
392
393 driver->setServerState(true);
394 driver->setPort(port);
395 }
396 });
397
398 emit driverRestarted(driver);
399 return true;
400}
401
402void ServerManager::stop()
403{
404 if (serverProcess.get() == nullptr)
405 return;
406
407 for (auto &device : m_ManagedDrivers)
408 {
409 device->reset();
410 }
411
412 qCDebug(KSTARS_INDI) << "Stopping INDI Server " << host << "@" << port;
413
414 serverProcess->disconnect(this);
415
416 serverBuffer.close();
417
418 serverProcess->terminate();
419
420 serverProcess->waitForFinished();
421
422 serverProcess.reset();
423
424 indiFIFO.close();
425 QFile::remove(indiFIFO.fileName());
426 emit stopped();
427
428}
429
430void ServerManager::processServerError(QProcess::ProcessError err)
431{
433 emit terminated(i18n("Connection to INDI server %1:%2 terminated: %3.",
434 getHost(), getPort(), serverProcess.get()->errorString()));
435}
436
437void ServerManager::processStandardError()
438{
439#ifdef Q_OS_WIN
440 qWarning() << "INDI server is currently not supported on Windows.";
441 return;
442#else
443 QString stderr = serverProcess->readAllStandardError();
444
445 for (auto &msg : stderr.split('\n'))
446 qCDebug(KSTARS_INDI) << "INDI Server: " << msg;
447
448 serverBuffer.write(stderr.toLatin1());
449 emit newServerLog();
450
451 //if (driverCrashed == false && (stderr.contains("stdin EOF") || stderr.contains("stderr EOF")))
452 QRegularExpression re("Driver (.*): Terminated after #0 restarts");
454 if (match.hasMatch())
455 {
456 QString driverExec = match.captured(1);
457 qCCritical(KSTARS_INDI) << "INDI driver " << driverExec << " crashed!";
458
459 //KSNotification::info(i18n("KStars detected INDI driver %1 crashed. Please check INDI server log in the Device Manager.", driverName));
460
461 auto crashedDriver = std::find_if(m_ManagedDrivers.begin(), m_ManagedDrivers.end(),
463 {
464 return dv->getExecutable() == driverExec;
465 });
466
467 if (crashedDriver != m_ManagedDrivers.end())
468 {
469 connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, crashedDriver]()
470 {
471 //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr);
472 KSMessageBox::Instance()->disconnect(this);
473 restartDriver(*crashedDriver);
474 });
475
476 QString label = (*crashedDriver)->getUniqueLabel();
477 if (label.isEmpty())
478 label = (*crashedDriver)->getExecutable();
479 KSMessageBox::Instance()->warningContinueCancel(i18n("INDI Driver <b>%1</b> crashed. Restart it?",
480 label), i18n("Driver crash"), 10);
481 }
482 }
483#endif
484}
485
486QString ServerManager::errorString()
487{
488 if (serverProcess.get() != nullptr)
489 return serverProcess->errorString();
490
491 return nullptr;
492}
493
494QString ServerManager::getLogBuffer()
495{
496 serverBuffer.flush();
497 serverBuffer.close();
498
499 serverBuffer.open();
500 return serverBuffer.readAll();
501}
QString i18n(const char *text, const TYPE &arg...)
char * toString(const EngineQuery &query)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QString label(StandardShortcut id)
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
virtual void close() override
QString applicationDirPath()
void accepted()
QString filePath(const QString &fileName) const const
QString path() const const
QChar separator()
int exec(ProcessEventsFlags flags)
void quit()
virtual QString fileName() const const override
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
bool remove()
void setFileName(const QString &name)
virtual void close() override
bool flush()
QDir dir() const const
bool exists() const const
QByteArray readAll()
qint64 write(const QByteArray &data)
void append(QList< T > &&value)
iterator begin()
bool contains(const AT &value) const const
iterator end()
iterator erase(const_iterator begin, const_iterator end)
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void errorOccurred(QProcess::ProcessError error)
int exitCode() const const
void finished(int exitCode, QProcess::ExitStatus exitStatus)
void readyReadStandardError()
void start(OpenMode mode)
void insert(const QProcessEnvironment &e)
QProcessEnvironment systemEnvironment()
QString findExecutable(const QString &executableName, const QStringList &paths)
QString arg(Args &&... args) const const
bool isEmpty() const const
QString number(double n, char format, int precision)
QTextStream & endl(QTextStream &stream)
QUuid createUuid()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:03 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.