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

KDE's Doxygen guidelines are available online.