File indexing completed on 2024-04-21 14:46:12

0001 /*
0002     SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "servermanager.h"
0008 
0009 #include "driverinfo.h"
0010 #include "clientmanager.h"
0011 #include "drivermanager.h"
0012 #include "auxiliary/kspaths.h"
0013 #include "auxiliary/ksmessagebox.h"
0014 #include "Options.h"
0015 #include "ksnotification.h"
0016 
0017 #include <indidevapi.h>
0018 #include <thread>
0019 
0020 #include <KMessageBox>
0021 #include <QUuid>
0022 
0023 #include <sys/stat.h>
0024 
0025 #include <indi_debug.h>
0026 
0027 // Qt version calming
0028 #include <qtendl.h>
0029 
0030 ServerManager::ServerManager(const QString &inHost, int inPort) : host(inHost), port(inPort)
0031 {
0032 }
0033 
0034 ServerManager::~ServerManager()
0035 {
0036     serverSocket.close();
0037     indiFIFO.close();
0038 
0039     QFile::remove(indiFIFO.fileName());
0040 
0041     if (serverProcess.get() != nullptr)
0042         serverProcess->close();
0043 }
0044 
0045 bool ServerManager::start()
0046 {
0047 #ifdef Q_OS_WIN
0048     qWarning() << "INDI server is currently not supported on Windows.";
0049     return false;
0050 #else
0051     bool connected = false;
0052 
0053     if (serverProcess.get() == nullptr)
0054     {
0055         serverBuffer.open();
0056 
0057         serverProcess.reset(new QProcess(this));
0058 #ifdef Q_OS_OSX
0059         QString driversDir = Options::indiDriversDir();
0060         if (Options::indiDriversAreInternal())
0061             driversDir = QCoreApplication::applicationDirPath() + "/../Resources/DriverSupport";
0062         QString indiServerDir;
0063         if (Options::indiServerIsInternal())
0064             indiServerDir = QCoreApplication::applicationDirPath();
0065         else
0066             indiServerDir = QFileInfo(Options::indiServer()).dir().path();
0067         QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
0068         env.insert("PATH", driversDir + ':' + indiServerDir + ":/usr/local/bin:/usr/bin:/bin");
0069         QString gscDirPath = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("gsc");
0070         env.insert("GSCDAT", gscDirPath);
0071 
0072         insertEnvironmentPath(&env, "INDIPREFIX", "/../../");
0073         insertEnvironmentPath(&env, "IOLIBS", "/../Resources/DriverSupport/gphoto/IOLIBS");
0074         insertEnvironmentPath(&env, "CAMLIBS", "/../Resources/DriverSupport/gphoto/CAMLIBS");
0075 
0076         serverProcess->setProcessEnvironment(env);
0077 #endif
0078     }
0079 
0080     QStringList args;
0081 
0082     args << "-v" << "-p" << QString::number(port);
0083 
0084     QString fifoFile = QString("/tmp/indififo%1").arg(QUuid::createUuid().toString().mid(1, 8));
0085 
0086     if (mkfifo(fifoFile.toLatin1(), S_IRUSR | S_IWUSR) < 0)
0087     {
0088         emit failed(i18n("Error making FIFO file %1: %2.", fifoFile, strerror(errno)));
0089         return false;
0090     }
0091 
0092     indiFIFO.setFileName(fifoFile);
0093 
0094     if (!indiFIFO.open(QIODevice::ReadWrite | QIODevice::Text))
0095     {
0096         qCCritical(KSTARS_INDI) << "Unable to create INDI FIFO file: " << fifoFile;
0097         emit failed(i18n("Unable to create INDI FIFO file %1", fifoFile));
0098         return false;
0099     }
0100 
0101     args << "-m" << QString::number(Options::serverTransferBufferSize()) << "-r" << "0" << "-f" << fifoFile;
0102 
0103     qCDebug(KSTARS_INDI) << "Starting INDI Server: " << args << "-f" << fifoFile;
0104 
0105     serverProcess->setProcessChannelMode(QProcess::SeparateChannels);
0106     serverProcess->setReadChannel(QProcess::StandardError);
0107 
0108 #ifdef Q_OS_OSX
0109     if (Options::indiServerIsInternal())
0110         serverProcess->start(QCoreApplication::applicationDirPath() + "/indiserver", args);
0111     else
0112 #endif
0113         serverProcess->start(Options::indiServer(), args);
0114 
0115     connected = serverProcess->waitForStarted();
0116 
0117     if (connected)
0118     {
0119         connect(serverProcess.get(), &QProcess::errorOccurred, this, &ServerManager::processServerError);
0120         connect(serverProcess.get(), &QProcess::readyReadStandardError, this, &ServerManager::processStandardError);
0121         emit started();
0122     }
0123     else
0124     {
0125         emit failed(i18n("INDI server failed to start: %1", serverProcess->errorString()));
0126     }
0127 
0128     qCDebug(KSTARS_INDI) << "INDI Server Started? " << connected;
0129 
0130     return connected;
0131 #endif
0132 }
0133 
0134 void ServerManager::insertEnvironmentPath(QProcessEnvironment *env, const QString &variable, const QString &relativePath)
0135 {
0136     QString environmentPath = QCoreApplication::applicationDirPath() + relativePath;
0137     if (QFileInfo::exists(environmentPath) && Options::indiDriversAreInternal())
0138         env->insert(variable, QDir(environmentPath).absolutePath());
0139 }
0140 
0141 void ServerManager::startDriver(const QSharedPointer<DriverInfo> &driver)
0142 {
0143     QTextStream out(&indiFIFO);
0144 
0145     // Check for duplicates within existing clients
0146     if (driver->getUniqueLabel().isEmpty() && driver->getLabel().isEmpty() == false)
0147         driver->setUniqueLabel(DriverManager::Instance()->getUniqueDeviceLabel(driver->getLabel()));
0148 
0149     // Check for duplicates within managed drivers
0150     if (driver->getUniqueLabel().isEmpty() == false)
0151     {
0152         QString uniqueLabel;
0153         QString label = driver->getUniqueLabel();
0154         int nset = std::count_if(m_ManagedDrivers.begin(), m_ManagedDrivers.end(), [label](auto & oneDriver)
0155         {
0156             return label == oneDriver->getUniqueLabel();
0157         });
0158         if (nset > 0)
0159         {
0160             uniqueLabel = QString("%1 %2").arg(label).arg(nset + 1);
0161             driver->setUniqueLabel(uniqueLabel);
0162         }
0163     }
0164 
0165     m_ManagedDrivers.append(driver);
0166     driver->setServerManager(this);
0167 
0168     QString driversDir    = Options::indiDriversDir();
0169     QString indiServerDir = QFileInfo(Options::indiServer()).dir().path();
0170 
0171 #ifdef Q_OS_OSX
0172     if (Options::indiServerIsInternal())
0173         indiServerDir = QCoreApplication::applicationDirPath();
0174     if (Options::indiDriversAreInternal())
0175         driversDir = QCoreApplication::applicationDirPath() + "/../Resources/DriverSupport";
0176 #endif
0177 
0178     QJsonObject startupRule = driver->startupRule();
0179     auto PreDelay = startupRule["PreDelay"].toInt(0);
0180 
0181     // Sleep for PreDelay seconds if required.
0182     if (PreDelay > 0)
0183     {
0184         qCDebug(KSTARS_INDI) << driver->getUniqueLabel() << ": Executing pre-driver delay for" << PreDelay << "second(s)";
0185         std::this_thread::sleep_for(std::chrono::seconds(PreDelay));
0186     }
0187 
0188     // Startup Script?
0189     auto PreScript = startupRule["PreScript"].toString();
0190     if (!PreScript.isEmpty())
0191     {
0192         QProcess script;
0193         QEventLoop loop;
0194         QObject::connect(&script, static_cast<void (QProcess::*)(int exitCode, QProcess::ExitStatus status)>(&QProcess::finished),
0195                          &loop, &QEventLoop::quit);
0196         QObject::connect(&script, &QProcess::errorOccurred, &loop, &QEventLoop::quit);
0197         qCDebug(KSTARS_INDI) << driver->getUniqueLabel() << ": Executing pre-driver script" << PreScript;
0198         script.start(PreScript, QStringList());
0199         loop.exec();
0200 
0201         if (script.exitCode() != 0)
0202         {
0203             emit driverFailed(driver, i18n("Pre driver startup script failed with exit code: %1", script.exitCode()));
0204             return;
0205         }
0206     }
0207 
0208     // Remote host?
0209     if (driver->getRemoteHost().isEmpty() == false)
0210     {
0211         QString driverString = driver->getName() + "@" + driver->getRemoteHost() + ":" + driver->getRemotePort();
0212         qCDebug(KSTARS_INDI) << "Starting Remote INDI Driver" << driverString;
0213         out << "start " << driverString << Qt::endl;
0214         out.flush();
0215     }
0216     // Local?
0217     else
0218     {
0219         QStringList paths;
0220         paths << "/usr/bin"
0221               << "/usr/local/bin" << driversDir << indiServerDir;
0222 
0223         if (QStandardPaths::findExecutable(driver->getExecutable()).isEmpty())
0224         {
0225             if (QStandardPaths::findExecutable(driver->getExecutable(), paths).isEmpty())
0226             {
0227                 emit driverFailed(driver, i18n("Driver %1 was not found on the system. Please make sure the package that "
0228                                                "provides the '%1' binary is installed.",
0229                                                driver->getExecutable()));
0230                 return;
0231             }
0232         }
0233 
0234         qCDebug(KSTARS_INDI) << "Starting INDI Driver" << driver->getExecutable();
0235 
0236         out << "start " << driver->getExecutable();
0237         if (driver->getUniqueLabel().isEmpty() == false)
0238             out << " -n \"" << driver->getUniqueLabel() << "\"";
0239         if (driver->getSkeletonFile().isEmpty() == false)
0240             out << " -s \"" << driversDir << QDir::separator() << driver->getSkeletonFile() << "\"";
0241         out << Qt::endl;
0242         out.flush();
0243 
0244         driver->setServerState(true);
0245 
0246         driver->setPort(port);
0247     }
0248 
0249     auto PostDelay = startupRule["PostDelay"].toInt(0);
0250 
0251     // Sleep for PostDelay seconds if required.
0252     if (PostDelay > 0)
0253     {
0254         qCDebug(KSTARS_INDI) << driver->getUniqueLabel() << ": Executing post-driver delay for" << PreDelay << "second(s)";
0255         std::this_thread::sleep_for(std::chrono::seconds(PostDelay));
0256     }
0257 
0258     // Startup Script?
0259     auto PostScript = startupRule["PostScript"].toString();
0260     if (!PostScript.isEmpty())
0261     {
0262         QProcess script;
0263         QEventLoop loop;
0264         QObject::connect(&script, static_cast<void (QProcess::*)(int exitCode, QProcess::ExitStatus status)>(&QProcess::finished),
0265                          &loop, &QEventLoop::quit);
0266         QObject::connect(&script, &QProcess::errorOccurred, &loop, &QEventLoop::quit);
0267         qCDebug(KSTARS_INDI) << driver->getUniqueLabel() << ": Executing post-driver script" << PreScript;
0268         script.start(PostScript, QStringList());
0269         loop.exec();
0270 
0271         if (script.exitCode() != 0)
0272         {
0273             emit driverFailed(driver, i18n("Post driver startup script failed with exit code: %1", script.exitCode()));
0274             return;
0275         }
0276     }
0277 
0278     // Remove driver from pending list.
0279     m_PendingDrivers.erase(std::remove_if(m_PendingDrivers.begin(), m_PendingDrivers.end(), [driver](const auto & oneDriver)
0280     {
0281         return driver == oneDriver;
0282     }), m_PendingDrivers.end());
0283     emit driverStarted(driver);
0284 }
0285 
0286 void ServerManager::stopDriver(const QSharedPointer<DriverInfo> &driver)
0287 {
0288     QTextStream out(&indiFIFO);
0289     const auto exec = driver->getExecutable();
0290 
0291     qCDebug(KSTARS_INDI) << "Stopping INDI Driver " << exec;
0292 
0293     if (driver->getUniqueLabel().isEmpty() == false)
0294         out << "stop " << exec << " -n \"" << driver->getUniqueLabel() << "\"";
0295     else
0296         out << "stop " << exec;
0297     out << Qt::endl;
0298     out.flush();
0299     driver->setServerState(false);
0300     driver->setPort(driver->getUserPort());
0301 
0302     m_ManagedDrivers.erase(std::remove_if(m_ManagedDrivers.begin(), m_ManagedDrivers.end(), [exec](const auto & driver)
0303     {
0304         return driver->getExecutable() == exec;
0305     }));
0306     emit driverStopped(driver);
0307 }
0308 
0309 
0310 bool ServerManager::restartDriver(const QSharedPointer<DriverInfo> &driver)
0311 {
0312     auto cm = driver->getClientManager();
0313     const auto label = driver->getLabel();
0314 
0315     if (cm)
0316     {
0317         qCDebug(KSTARS_INDI) << "Restarting INDI Driver: " << label;
0318         // N.B. This MUST be called BEFORE stopping driver below
0319         // Since it requires the driver device pointer.
0320         cm->removeManagedDriver(driver);
0321 
0322         // Stop driver.
0323         stopDriver(driver);
0324     }
0325     else
0326     {
0327         qCDebug(KSTARS_INDI) << "restartDriver with no cm, and " << m_ManagedDrivers.size() << " drivers. Trying to remove: " <<
0328                              label;
0329         cm = DriverManager::Instance()->getClientManager(driver);
0330         const auto exec = driver->getExecutable();
0331         m_ManagedDrivers.erase(std::remove_if(m_ManagedDrivers.begin(), m_ManagedDrivers.end(), [exec](const auto & driver)
0332         {
0333             return driver->getExecutable() == exec;
0334         }));
0335     }
0336 
0337     // Wait 1 second before starting the driver again.
0338     QTimer::singleShot(1000, this, [this, label, cm]()
0339     {
0340         auto driver = DriverManager::Instance()->findDriverByLabel(label);
0341         if (!driver)
0342         {
0343             qCDebug(KSTARS_INDI) << "restartDriver timer, did not find driver with label: " << label;
0344             return;
0345         }
0346         cm->appendManagedDriver(driver);
0347         if (m_ManagedDrivers.contains(driver) == false)
0348             m_ManagedDrivers.append(driver);
0349         driver->setServerManager(this);
0350 
0351         QTextStream out(&indiFIFO);
0352 
0353         QString driversDir    = Options::indiDriversDir();
0354         QString indiServerDir = Options::indiServer();
0355 
0356 #ifdef Q_OS_OSX
0357         if (Options::indiServerIsInternal())
0358             indiServerDir = QCoreApplication::applicationDirPath();
0359         if (Options::indiDriversAreInternal())
0360             driversDir = QCoreApplication::applicationDirPath() + "/../Resources/DriverSupport";
0361         else
0362             indiServerDir = QFileInfo(Options::indiServer()).dir().path();
0363 #endif
0364 
0365         if (driver->getRemoteHost().isEmpty() == false)
0366         {
0367             QString driverString = driver->getName() + "@" + driver->getRemoteHost() + ":" + driver->getRemotePort();
0368             qCDebug(KSTARS_INDI) << "Restarting Remote INDI Driver" << driverString;
0369             out << "start " << driverString;
0370 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
0371             out << Qt::endl;
0372 #else
0373             out << Qt::endl;
0374 #endif
0375             out.flush();
0376         }
0377         else
0378         {
0379             QStringList paths;
0380             paths << "/usr/bin"
0381                   << "/usr/local/bin" << driversDir << indiServerDir;
0382 
0383             qCDebug(KSTARS_INDI) << "Starting INDI Driver " << driver->getExecutable();
0384 
0385             out << "start " << driver->getExecutable();
0386             if (driver->getUniqueLabel().isEmpty() == false)
0387                 out << " -n \"" << driver->getUniqueLabel() << "\"";
0388             if (driver->getSkeletonFile().isEmpty() == false)
0389                 out << " -s \"" << driversDir << QDir::separator() << driver->getSkeletonFile() << "\"";
0390             out << Qt::endl;
0391             out.flush();
0392 
0393             driver->setServerState(true);
0394             driver->setPort(port);
0395         }
0396     });
0397 
0398     emit driverRestarted(driver);
0399     return true;
0400 }
0401 
0402 void ServerManager::stop()
0403 {
0404     if (serverProcess.get() == nullptr)
0405         return;
0406 
0407     for (auto &device : m_ManagedDrivers)
0408     {
0409         device->reset();
0410     }
0411 
0412     qCDebug(KSTARS_INDI) << "Stopping INDI Server " << host << "@" << port;
0413 
0414     serverProcess->disconnect(this);
0415 
0416     serverBuffer.close();
0417 
0418     serverProcess->terminate();
0419 
0420     serverProcess->waitForFinished();
0421 
0422     serverProcess.reset();
0423 
0424     indiFIFO.close();
0425     QFile::remove(indiFIFO.fileName());
0426     emit stopped();
0427 
0428 }
0429 
0430 void ServerManager::processServerError(QProcess::ProcessError err)
0431 {
0432     Q_UNUSED(err)
0433     emit terminated(i18n("Connection to INDI server %1:%2 terminated: %3.",
0434                          getHost(), getPort(), serverProcess.get()->errorString()));
0435 }
0436 
0437 void ServerManager::processStandardError()
0438 {
0439 #ifdef Q_OS_WIN
0440     qWarning() << "INDI server is currently not supported on Windows.";
0441     return;
0442 #else
0443     QString stderr = serverProcess->readAllStandardError();
0444 
0445     for (auto &msg : stderr.split('\n'))
0446         qCDebug(KSTARS_INDI) << "INDI Server: " << msg;
0447 
0448     serverBuffer.write(stderr.toLatin1());
0449     emit newServerLog();
0450 
0451     //if (driverCrashed == false && (stderr.contains("stdin EOF") || stderr.contains("stderr EOF")))
0452     QRegularExpression re("Driver (.*): Terminated after #0 restarts");
0453     QRegularExpressionMatch match = re.match(stderr);
0454     if (match.hasMatch())
0455     {
0456         QString driverExec = match.captured(1);
0457         qCCritical(KSTARS_INDI) << "INDI driver " << driverExec << " crashed!";
0458 
0459         //KSNotification::info(i18n("KStars detected INDI driver %1 crashed. Please check INDI server log in the Device Manager.", driverName));
0460 
0461         auto crashedDriver = std::find_if(m_ManagedDrivers.begin(), m_ManagedDrivers.end(),
0462                                           [driverExec](QSharedPointer<DriverInfo> dv)
0463         {
0464             return dv->getExecutable() == driverExec;
0465         });
0466 
0467         if (crashedDriver != m_ManagedDrivers.end())
0468         {
0469             connect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, [this, crashedDriver]()
0470             {
0471                 //QObject::disconnect(KSMessageBox::Instance(), &KSMessageBox::accepted, this, nullptr);
0472                 KSMessageBox::Instance()->disconnect(this);
0473                 restartDriver(*crashedDriver);
0474             });
0475 
0476             QString label = (*crashedDriver)->getUniqueLabel();
0477             if (label.isEmpty())
0478                 label = (*crashedDriver)->getExecutable();
0479             KSMessageBox::Instance()->warningContinueCancel(i18n("INDI Driver <b>%1</b> crashed. Restart it?",
0480                     label), i18n("Driver crash"), 10);
0481         }
0482     }
0483 #endif
0484 }
0485 
0486 QString ServerManager::errorString()
0487 {
0488     if (serverProcess.get() != nullptr)
0489         return serverProcess->errorString();
0490 
0491     return nullptr;
0492 }
0493 
0494 QString ServerManager::getLogBuffer()
0495 {
0496     serverBuffer.flush();
0497     serverBuffer.close();
0498 
0499     serverBuffer.open();
0500     return serverBuffer.readAll();
0501 }