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 }