Kstars

servermanager.cpp
1 /*
2  SPDX-FileCopyrightText: 2012 Jasem Mutlaq <[email protected]>
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 
30 ServerManager::ServerManager(const QString &inHost, int inPort) : host(inHost), port(inPort)
31 {
32 }
33 
34 ServerManager::~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 
45 bool 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";
62  QString indiServerDir;
63  if (Options::indiServerIsInternal())
64  indiServerDir = QCoreApplication::applicationDirPath();
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 
80  QStringList args;
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 
94  if (!indiFIFO.open(QIODevice::ReadWrite | QIODevice::Text))
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 
134 void ServerManager::insertEnvironmentPath(QProcessEnvironment *env, const QString &variable, const QString &relativePath)
135 {
136  QString environmentPath = QCoreApplication::applicationDirPath() + relativePath;
137  if (QFileInfo::exists(environmentPath) && Options::indiDriversAreInternal())
138  env->insert(variable, QDir(environmentPath).absolutePath());
139 }
140 
141 void ServerManager::startDriver(DriverInfo *dv)
142 {
143  QTextStream out(&indiFIFO);
144 
145  // Check for duplicates within existing clients
146  if (dv->getUniqueLabel().isEmpty() && dv->getLabel().isEmpty() == false)
147  dv->setUniqueLabel(DriverManager::Instance()->getUniqueDeviceLabel(dv->getLabel()));
148 
149  // Check for duplicates within managed drivers
150  if (dv->getUniqueLabel().isEmpty() == false)
151  {
152  QString uniqueLabel;
153  QString label = dv->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  dv->setUniqueLabel(uniqueLabel);
162  }
163  }
164 
165  m_ManagedDrivers.append(dv);
166  dv->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())
173  indiServerDir = QCoreApplication::applicationDirPath();
174  if (Options::indiDriversAreInternal())
175  driversDir = QCoreApplication::applicationDirPath() + "/../Resources/DriverSupport";
176 #endif
177 
178  QJsonObject startupRule = dv->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) << dv->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), &loop, &QEventLoop::quit);
196 
197  qCDebug(KSTARS_INDI) << dv->getUniqueLabel() << ": Executing pre-driver script" << PreScript;
198 
199  script.start(PreScript, QStringList());
200  loop.exec();
201 
202  if (script.exitCode() != 0)
203  {
204  emit driverFailed(dv, i18n("Pre driver startup script failed with exit code: %1", script.exitCode()));
205  return;
206  }
207  }
208 
209  // Remote host?
210  if (dv->getRemoteHost().isEmpty() == false)
211  {
212  QString driverString = dv->getName() + "@" + dv->getRemoteHost() + ":" + dv->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(dv->getExecutable()).isEmpty())
225  {
226  if (QStandardPaths::findExecutable(dv->getExecutable(), paths).isEmpty())
227  {
228  emit driverFailed(dv, i18n("Driver %1 was not found on the system. Please make sure the package that "
229  "provides the '%1' binary is installed.",
230  dv->getExecutable()));
231  return;
232  }
233  }
234 
235  qCDebug(KSTARS_INDI) << "Starting INDI Driver" << dv->getExecutable();
236 
237  out << "start " << dv->getExecutable();
238  if (dv->getUniqueLabel().isEmpty() == false)
239  out << " -n \"" << dv->getUniqueLabel() << "\"";
240  if (dv->getSkeletonFile().isEmpty() == false)
241  out << " -s \"" << driversDir << QDir::separator() << dv->getSkeletonFile() << "\"";
242  out << Qt::endl;
243  out.flush();
244 
245  dv->setServerState(true);
246 
247  dv->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  qCDebug(KSTARS_INDI) << dv->getUniqueLabel() << ": Executing post-driver delay for" << PreDelay << "second(s)";
256  std::this_thread::sleep_for(std::chrono::seconds(PostDelay));
257  }
258 
259  // Startup Script?
260  auto PostScript = startupRule["PostScript"].toString();
261  if (!PostScript.isEmpty())
262  {
263  QProcess script;
264  QEventLoop loop;
265  QObject::connect(&script, static_cast<void (QProcess::*)(int exitCode, QProcess::ExitStatus status)>(&QProcess::finished), &loop, &QEventLoop::quit);
267 
268  qCDebug(KSTARS_INDI) << dv->getUniqueLabel() << ": Executing post-driver script" << PreScript;
269 
270  script.start(PostScript, QStringList());
271  loop.exec();
272 
273  if (script.exitCode() != 0)
274  {
275  emit driverFailed(dv, 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(), [dv](const auto & oneDriver)
282  {
283  return dv == oneDriver;
284  }), m_PendingDrivers.end());
285  emit driverStarted(dv);
286 }
287 
288 void ServerManager::stopDriver(DriverInfo *dv)
289 {
290  QTextStream out(&indiFIFO);
291 
292  m_ManagedDrivers.removeOne(dv);
293 
294  qCDebug(KSTARS_INDI) << "Stopping INDI Driver " << dv->getExecutable();
295 
296  if (dv->getUniqueLabel().isEmpty() == false)
297  out << "stop " << dv->getExecutable() << " -n \"" << dv->getUniqueLabel() << "\"";
298  else
299  out << "stop " << dv->getExecutable();
300  out << Qt::endl;
301  out.flush();
302  dv->setServerState(false);
303  dv->setPort(dv->getUserPort());
304 
305  emit driverStopped(dv);
306 }
307 
308 
309 bool ServerManager::restartDriver(DriverInfo *dv)
310 {
311  ClientManager *cm = dv->getClientManager();
312 
313  // N.B. This MUST be called BEFORE stopping driver below
314  // Since it requires the driver device pointer.
315  cm->removeManagedDriver(dv);
316 
317  // Stop driver.
318  stopDriver(dv);
319 
320  // Wait 1 second before starting the driver again.
321  QTimer::singleShot(1000, this, [this, dv, cm]()
322  {
323  cm->appendManagedDriver(dv);
324  m_ManagedDrivers.append(dv);
325  dv->setServerManager(this);
326 
327  QTextStream out(&indiFIFO);
328 
329  QString driversDir = Options::indiDriversDir();
330  QString indiServerDir = Options::indiServer();
331 
332 #ifdef Q_OS_OSX
333  if (Options::indiServerIsInternal())
334  indiServerDir = QCoreApplication::applicationDirPath();
335  if (Options::indiDriversAreInternal())
336  driversDir = QCoreApplication::applicationDirPath() + "/../Resources/DriverSupport";
337  else
338  indiServerDir = QFileInfo(Options::indiServer()).dir().path();
339 #endif
340 
341  if (dv->getRemoteHost().isEmpty() == false)
342  {
343  QString driverString = dv->getName() + "@" + dv->getRemoteHost() + ":" + dv->getRemotePort();
344  qCDebug(KSTARS_INDI) << "Restarting Remote INDI Driver" << driverString;
345  out << "start " << driverString;
346 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
347  out << Qt::endl;
348 #else
349  out << Qt::endl;
350 #endif
351  out.flush();
352  }
353  else
354  {
355  QStringList paths;
356  paths << "/usr/bin"
357  << "/usr/local/bin" << driversDir << indiServerDir;
358 
359  qCDebug(KSTARS_INDI) << "Starting INDI Driver " << dv->getExecutable();
360 
361  out << "start " << dv->getExecutable();
362  if (dv->getUniqueLabel().isEmpty() == false)
363  out << " -n \"" << dv->getUniqueLabel() << "\"";
364  if (dv->getSkeletonFile().isEmpty() == false)
365  out << " -s \"" << driversDir << QDir::separator() << dv->getSkeletonFile() << "\"";
366  out << Qt::endl;
367  out.flush();
368 
369  dv->setServerState(true);
370  dv->setPort(port);
371  }
372  });
373 
374  emit driverRestarted(dv);
375  return true;
376 }
377 
378 void ServerManager::stop()
379 {
380  if (serverProcess.get() == nullptr)
381  return;
382 
383  for (auto &device : m_ManagedDrivers)
384  {
385  device->reset();
386  }
387 
388  qCDebug(KSTARS_INDI) << "Stopping INDI Server " << host << "@" << port;
389 
390  serverProcess->disconnect(this);
391 
392  serverBuffer.close();
393 
394  serverProcess->terminate();
395 
396  serverProcess->waitForFinished();
397 
398  serverProcess.reset();
399 
400  indiFIFO.close();
401  QFile::remove(indiFIFO.fileName());
402  emit stopped();
403 
404 }
405 
406 void ServerManager::processServerError(QProcess::ProcessError err)
407 {
408  Q_UNUSED(err)
409  emit terminated(i18n("Connection to INDI server %1:%2 terminated: %3.",
410  getHost(), getPort(), serverProcess.get()->errorString()));
411 }
412 
413 void ServerManager::processStandardError()
414 {
415 #ifdef Q_OS_WIN
416  qWarning() << "INDI server is currently not supported on Windows.";
417  return;
418 #else
419  QString stderr = serverProcess->readAllStandardError();
420 
421  for (auto &msg : stderr.split('\n'))
422  qCDebug(KSTARS_INDI) << "INDI Server: " << msg;
423 
424  serverBuffer.write(stderr.toLatin1());
425  emit newServerLog();
426 
427  //if (driverCrashed == false && (stderr.contains("stdin EOF") || stderr.contains("stderr EOF")))
428  QRegularExpression re("Driver (.*): Terminated after #0 restarts");
429  QRegularExpressionMatch match = re.match(stderr);
430  if (match.hasMatch())
431  {
432  QString driverExec = match.captured(1);
433  qCCritical(KSTARS_INDI) << "INDI driver " << driverExec << " crashed!";
434 
435  //KSNotification::info(i18n("KStars detected INDI driver %1 crashed. Please check INDI server log in the Device Manager.", driverName));
436 
437  auto crashedDriver = std::find_if(m_ManagedDrivers.begin(), m_ManagedDrivers.end(),
438  [driverExec](DriverInfo * dv)
439  {
440  return dv->getExecutable() == driverExec;
441  });
442 
443  if (crashedDriver != m_ManagedDrivers.end())
444  {
445  connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, crashedDriver]()
446  {
447  //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr);
448  KSMessageBox::Instance()->disconnect(this);
449  restartDriver(*crashedDriver);
450  });
451 
452  QString label = (*crashedDriver)->getUniqueLabel();
453  if (label.isEmpty())
454  label = (*crashedDriver)->getExecutable();
455  KSMessageBox::Instance()->warningContinueCancel(i18n("INDI Driver <b>%1</b> crashed. Restart it?",
456  label), i18n("Driver crash"), 10);
457  }
458  }
459 #endif
460 }
461 
462 QString ServerManager::errorString()
463 {
464  if (serverProcess.get() != nullptr)
465  return serverProcess->errorString();
466 
467  return nullptr;
468 }
469 
470 QString ServerManager::getLogBuffer()
471 {
472  serverBuffer.flush();
473  serverBuffer.close();
474 
475  serverBuffer.open();
476  return serverBuffer.readAll();
477 }
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
void append(const T &value)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QTextStream & endl(QTextStream &stream)
QString number(int n, int base)
bool remove()
virtual bool open(QIODevice::OpenMode mode) override
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QChar separator()
virtual void close() override
QProcessEnvironment systemEnvironment()
void removeManagedDriver(DriverInfo *dv)
removeManagedDriver Remove managed driver from pool of drivers managed by this client manager.
QString applicationDirPath()
QByteArray toLatin1() const const
int exec(QEventLoop::ProcessEventsFlags flags)
void finished(int exitCode)
bool exists() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
virtual QString fileName() const const override
QString findExecutable(const QString &executableName, const QStringList &paths)
void flush()
QString i18n(const char *text, const TYPE &arg...)
void quit()
char * toString(const T &value)
bool removeOne(const T &value)
bool isEmpty() const const
NETWORKMANAGERQT_EXPORT NetworkManager::Status status()
QUuid createUuid()
void setFileName(const QString &name)
void insert(const QString &name, const QString &value)
void readyReadStandardError()
void appendManagedDriver(DriverInfo *dv)
appendManagedDriver Add driver to pool of managed drivers by this client manager.
virtual void close() override
QString path() const const
QString label(StandardShortcut id)
ScriptableExtension * host() const
bool flush()
QList::iterator erase(QList::iterator pos)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
QString filePath(const QString &fileName) const const
QList::iterator begin()
QByteArray readAll()
void errorOccurred(QProcess::ProcessError error)
QList::iterator end()
int exitCode() const const
qint64 write(const char *data, qint64 maxSize)
QDir dir() const const
void accepted()
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Aug 12 2022 04:00:58 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.