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

0001 /*
0002     SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "drivermanager.h"
0008 
0009 #include "config-kstars.h"
0010 
0011 #include "clientmanager.h"
0012 #include "driverinfo.h"
0013 #include "guimanager.h"
0014 #include "indilistener.h"
0015 #include "kspaths.h"
0016 #include "kstars.h"
0017 #include "indidbus.h"
0018 #include "kstarsdata.h"
0019 #include "Options.h"
0020 #include "servermanager.h"
0021 #include "ui_indihostconf.h"
0022 #include "auxiliary/ksnotification.h"
0023 
0024 #include <basedevice.h>
0025 
0026 #ifndef KSTARS_LITE
0027 #include <KMessageBox>
0028 #include <KActionCollection>
0029 #include <KNotifications/KNotification>
0030 #endif
0031 
0032 #include <QTcpServer>
0033 #include <QtConcurrent>
0034 #include <indi_debug.h>
0035 
0036 #define INDI_MAX_TRIES 2
0037 #define ERRMSG_SIZE    1024
0038 
0039 DriverManagerUI::DriverManagerUI(QWidget *parent) : QFrame(parent)
0040 {
0041     setupUi(this);
0042 
0043     localTreeWidget->setSortingEnabled(false);
0044     localTreeWidget->setRootIsDecorated(true);
0045 
0046     clientTreeWidget->setSortingEnabled(false);
0047 
0048     runningPix = QIcon::fromTheme("system-run");
0049     stopPix    = QIcon::fromTheme("dialog-cancel");
0050     localMode  = QIcon::fromTheme("computer");
0051     serverMode = QIcon::fromTheme("network-server");
0052 
0053     connected    = QIcon::fromTheme("network-connect");
0054     disconnected = QIcon::fromTheme("network-disconnect");
0055 
0056     connect(localTreeWidget, &QTreeWidget::itemDoubleClicked, this,
0057             &DriverManagerUI::makePortEditable);
0058 }
0059 
0060 void DriverManagerUI::makePortEditable(QTreeWidgetItem *selectedItem, int column)
0061 {
0062     // If it's the port column, then make it user-editable
0063     if (column == ::DriverManager::LOCAL_PORT_COLUMN)
0064         selectedItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable |
0065                                Qt::ItemIsEnabled);
0066 
0067     localTreeWidget->editItem(selectedItem, ::DriverManager::LOCAL_PORT_COLUMN);
0068 }
0069 
0070 DriverManager *DriverManager::_DriverManager = nullptr;
0071 INDIDBus *DriverManager::_INDIDBus = nullptr;
0072 
0073 DriverManager *DriverManager::Instance()
0074 {
0075     if (_DriverManager == nullptr)
0076     {
0077         _DriverManager = new DriverManager(KStars::Instance());
0078         _INDIDBus = new INDIDBus(KStars::Instance());
0079 
0080         qRegisterMetaType<QSharedPointer<DriverInfo>>("QSharedPointer<DriverInfo>");
0081     }
0082 
0083     return _DriverManager;
0084 }
0085 
0086 void DriverManager::release()
0087 {
0088     delete (_DriverManager);
0089     delete (_INDIDBus);
0090     _DriverManager = nullptr;
0091     _INDIDBus = nullptr;
0092 }
0093 
0094 DriverManager::DriverManager(QWidget *parent) : QDialog(parent)
0095 {
0096 #ifdef Q_OS_OSX
0097     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
0098 #endif
0099 
0100     currentPort = Options::serverPortStart() - 1;
0101 
0102     QVBoxLayout *mainLayout = new QVBoxLayout;
0103     ui                      = new DriverManagerUI(this);
0104     mainLayout->addWidget(ui);
0105     setLayout(mainLayout);
0106     setWindowTitle(i18nc("@title:window", "Device Manager"));
0107 
0108     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
0109     mainLayout->addWidget(buttonBox);
0110 
0111     connect(buttonBox, &QDialogButtonBox::rejected, this, &DriverManager::close);
0112 
0113     connect(ui->addB, &QPushButton::clicked, this, &DriverManager::addINDIHost);
0114     connect(ui->modifyB, &QPushButton::clicked, this, &DriverManager::modifyINDIHost);
0115     connect(ui->removeB, &QPushButton::clicked, this, &DriverManager::removeINDIHost);
0116 
0117     connect(ui->connectHostB, &QPushButton::clicked, this, &DriverManager::activateHostConnection);
0118     connect(ui->disconnectHostB, &QPushButton::clicked, this, &DriverManager::activateHostDisconnection);
0119     connect(ui->runServiceB, &QPushButton::clicked, this, &DriverManager::activateRunService);
0120     connect(ui->stopServiceB, &QPushButton::clicked, this, &DriverManager::activateStopService);
0121     connect(ui->localTreeWidget,  &QTreeWidget::itemClicked, this, &DriverManager::updateLocalTab);
0122     connect(ui->clientTreeWidget, &QTreeWidget::itemClicked, this, &DriverManager::updateClientTab);
0123     connect(ui->localTreeWidget, &QTreeWidget::expanded, this, &DriverManager::resizeDeviceColumn);
0124 
0125     // Do not use KSPaths here, this is for INDI
0126     if (Options::indiDriversDir().isEmpty())
0127         Options::setIndiDriversDir(
0128             QStandardPaths::locate(QStandardPaths::GenericDataLocation, "indi",
0129                                    QStandardPaths::LocateDirectory));
0130 
0131     readXMLDrivers();
0132 
0133     readINDIHosts();
0134 
0135     m_CustomDrivers = new CustomDrivers(this, driversList);
0136 
0137     updateCustomDrivers();
0138 
0139 #ifdef Q_OS_WIN
0140     ui->localTreeWidget->setEnabled(false);
0141 #endif
0142 }
0143 
0144 DriverManager::~DriverManager()
0145 {
0146     clearServers();
0147 }
0148 
0149 void DriverManager::processDeviceStatus(const QSharedPointer<DriverInfo> &driver)
0150 {
0151     if (driver.isNull() || driver->getDriverSource() == GENERATED_SOURCE)
0152         return;
0153 
0154     QString currentDriver;
0155     ServerMode mode        = connectionMode;
0156     ServerManager *manager = driver->getServerManager();
0157     bool dState            = false;
0158     bool cState            = false;
0159 
0160     if (driver->getDriverSource() != HOST_SOURCE)
0161     {
0162         if (ui->localTreeWidget->currentItem())
0163             currentDriver = ui->localTreeWidget->currentItem()->text(LOCAL_NAME_COLUMN);
0164 
0165         for (auto &item : ui->localTreeWidget->findItems(
0166                     driver->getLabel(), Qt::MatchExactly | Qt::MatchRecursive))
0167         {
0168             item->setText(LOCAL_VERSION_COLUMN, driver->getVersion());
0169 
0170             if (manager)
0171                 mode = manager->getMode();
0172 
0173             dState = driver->getServerState();
0174             cState = driver->getClientState() && dState;
0175 
0176             bool locallyAvailable = false;
0177             if (driver->getAuxInfo().contains("LOCALLY_AVAILABLE"))
0178                 locallyAvailable =
0179                     driver->getAuxInfo().value("LOCALLY_AVAILABLE", false).toBool();
0180 
0181             switch (mode)
0182             {
0183                 case SERVER_ONLY:
0184                     if (locallyAvailable)
0185                     {
0186                         ui->runServiceB->setEnabled(!dState);
0187                         ui->stopServiceB->setEnabled(dState);
0188                         item->setIcon(LOCAL_STATUS_COLUMN,
0189                                       dState ? ui->runningPix : ui->stopPix);
0190                     }
0191                     else
0192                     {
0193                         ui->runServiceB->setEnabled(false);
0194                         ui->stopServiceB->setEnabled(false);
0195                     }
0196 
0197                     if (dState)
0198                     {
0199                         item->setIcon(LOCAL_MODE_COLUMN, ui->serverMode);
0200                         if (manager)
0201                         {
0202                             item->setText(LOCAL_PORT_COLUMN, QString::number(manager->getPort()));
0203                         }
0204                     }
0205                     else
0206                     {
0207                         item->setIcon(LOCAL_MODE_COLUMN, QIcon());
0208                         item->setText(LOCAL_PORT_COLUMN, QString::number(driver->getUserPort()));
0209                     }
0210 
0211                     break;
0212 
0213                 case SERVER_CLIENT:
0214                     if (locallyAvailable)
0215                     {
0216                         ui->runServiceB->setEnabled(!cState);
0217                         ui->stopServiceB->setEnabled(cState);
0218                         item->setIcon(LOCAL_STATUS_COLUMN,
0219                                       cState ? ui->runningPix : ui->stopPix);
0220                     }
0221                     else
0222                     {
0223                         ui->runServiceB->setEnabled(false);
0224                         ui->stopServiceB->setEnabled(false);
0225                     }
0226 
0227                     if (cState)
0228                     {
0229                         item->setIcon(LOCAL_MODE_COLUMN, ui->localMode);
0230 
0231                         if (manager)
0232                         {
0233                             item->setText(LOCAL_PORT_COLUMN, QString::number(manager->getPort()));
0234                         }
0235                     }
0236                     else
0237                     {
0238                         item->setIcon(LOCAL_MODE_COLUMN, QIcon());
0239                         const auto port = driver->getUserPort() == -1 ? QString() : QString::number(driver->getUserPort());
0240                         item->setText(LOCAL_PORT_COLUMN, port);
0241                     }
0242 
0243                     break;
0244             }
0245 
0246             // Only update the log if the current driver is selected
0247             if (currentDriver == driver->getLabel())
0248             {
0249                 ui->serverLogText->clear();
0250                 ui->serverLogText->append(driver->getServerBuffer());
0251             }
0252         }
0253     }
0254     else
0255     {
0256         for (auto &item : ui->clientTreeWidget->findItems(driver->getName(), Qt::MatchExactly,
0257                 HOST_NAME_COLUMN))
0258         {
0259             if (driver->getClientState())
0260             {
0261                 item->setIcon(HOST_STATUS_COLUMN, ui->connected);
0262                 ui->connectHostB->setEnabled(false);
0263                 ui->disconnectHostB->setEnabled(true);
0264             }
0265             else
0266             {
0267                 item->setIcon(HOST_STATUS_COLUMN, ui->disconnected);
0268                 ui->connectHostB->setEnabled(true);
0269                 ui->disconnectHostB->setEnabled(false);
0270             }
0271         }
0272     }
0273 }
0274 
0275 void DriverManager::getUniqueHosts(const QList<QSharedPointer<DriverInfo>> &dList,
0276                                    QList<QList<QSharedPointer<DriverInfo>>> &uHosts)
0277 {
0278     bool found = false;
0279 
0280     // Iterate over all drivers
0281     for (auto &dv : dList)
0282     {
0283         QList<QSharedPointer<DriverInfo>> uList;
0284 
0285         // Let's see for drivers with identical hosts and ports
0286         for (auto &idv : dList)
0287         {
0288             // If we get a match between port and hostname, we add it to the list
0289             if ((dv->getHost() == idv->getHost() && dv->getPort() == idv->getPort()))
0290             {
0291                 // Check if running already
0292                 if (dv->getClientState() || dv->getServerState())
0293                 {
0294                     int ans = KMessageBox::warningContinueCancel(
0295                                   nullptr,
0296                                   i18n("Driver %1 is already running, do you want to restart it?",
0297                                        dv->getLabel()));
0298                     if (ans == KMessageBox::Cancel)
0299                         continue;
0300                     else
0301                     {
0302                         QList<QSharedPointer<DriverInfo>> stopList;
0303                         stopList.append(dv);
0304                         stopDevices(stopList);
0305                     }
0306                 }
0307 
0308                 found = false;
0309 
0310                 // Check to see if the driver already been added elsewhere
0311                 for (auto &qdi : uHosts)
0312                 {
0313                     for (QSharedPointer<DriverInfo>di : qdi)
0314                     {
0315                         if (di == idv)
0316                         {
0317                             found = true;
0318                             break;
0319                         }
0320                     }
0321                 }
0322 
0323                 if (found == false)
0324                     uList.append(idv);
0325             }
0326         }
0327 
0328         if (uList.empty() == false)
0329             uHosts.append(uList);
0330     }
0331 }
0332 
0333 void DriverManager::startDevices(const QList<QSharedPointer<DriverInfo>> &dList)
0334 {
0335     ServerManager *serverManager = nullptr;
0336     int port = -1;
0337 
0338     QList<QList<QSharedPointer<DriverInfo>>> uHosts;
0339     getUniqueHosts(dList, uHosts);
0340 
0341     qCDebug(KSTARS_INDI) << "INDI: Starting local drivers...";
0342 
0343     for (auto &qdv : uHosts)
0344     {
0345         if (qdv.empty())
0346             continue;
0347 
0348         port = qdv.at(0)->getPort();
0349         auto host = qdv.at(0)->getHost();
0350 
0351         // Select random port within range is none specified.
0352         if (port == -1)
0353             port = getINDIPort(port);
0354 
0355         if (port <= 0)
0356         {
0357             emit serverFailed(host, port, i18n("Cannot start INDI server: port error."));
0358             return;
0359         }
0360 
0361         serverManager = new ServerManager(host, port);
0362 
0363         if (serverManager == nullptr)
0364         {
0365             emit serverFailed(host, port, i18n("Failed to create local INDI server"));
0366             return;
0367         }
0368 
0369         servers.append(serverManager);
0370         serverManager->setPendingDrivers(qdv);
0371         serverManager->setMode(connectionMode);
0372 
0373         connect(serverManager, &ServerManager::newServerLog, this, &DriverManager::updateLocalTab, Qt::UniqueConnection);
0374         connect(serverManager, &ServerManager::started, this, &DriverManager::setServerStarted, Qt::UniqueConnection);
0375         connect(serverManager, &ServerManager::failed, this, &DriverManager::setServerFailed, Qt::UniqueConnection);
0376         connect(serverManager, &ServerManager::terminated, this, &DriverManager::setServerTerminated, Qt::UniqueConnection);
0377 
0378         serverManager->start();
0379     }
0380 }
0381 
0382 void DriverManager::startLocalDrivers(ServerManager *serverManager)
0383 {
0384     connect(serverManager, &ServerManager::driverStarted, this, &DriverManager::processDriverStartup, Qt::UniqueConnection);
0385     connect(serverManager, &ServerManager::driverFailed, this, &DriverManager::processDriverFailure, Qt::UniqueConnection);
0386     QtConcurrent::run(serverManager, &ServerManager::startDriver, serverManager->pendingDrivers().first());
0387 }
0388 
0389 void DriverManager::processDriverStartup(const QSharedPointer<DriverInfo> &driver)
0390 {
0391     emit driverStarted(driver);
0392 
0393     auto manager = driver->getServerManager();
0394     // Do we have more pending drivers?
0395     if (manager->pendingDrivers().count() > 0)
0396     {
0397         QtConcurrent::run(manager, &ServerManager::startDriver, manager->pendingDrivers().first());
0398         return;
0399     }
0400 
0401     // Nothing to do more if SERVER ONLY
0402     if (connectionMode == SERVER_ONLY)
0403     {
0404         return;
0405     }
0406 
0407     // Otherwise proceed to start Client Manager
0408     startClientManager(manager->managedDrivers(), manager->getHost(), manager->getPort());
0409 }
0410 
0411 void DriverManager::processDriverFailure(const QSharedPointer<DriverInfo> &driver, const QString &message)
0412 {
0413     emit driverFailed(driver, message);
0414 
0415     qCWarning(KSTARS_INDI) << "Driver" << driver->getName() << "failed to start. Retrying in 5 seconds...";
0416 
0417     QTimer::singleShot(5000, this, [driver]()
0418     {
0419         auto manager = driver->getServerManager();
0420         // Do we have more pending drivers?
0421         if (manager->pendingDrivers().count() > 0)
0422         {
0423             QtConcurrent::run(manager, &ServerManager::startDriver, manager->pendingDrivers().first());
0424             return;
0425         }
0426     });
0427 }
0428 
0429 void DriverManager::startClientManager(const QList<QSharedPointer<DriverInfo>> &qdv, const QString &host, int port)
0430 {
0431     auto clientManager = new ClientManager();
0432 
0433     for (auto &dv : qdv)
0434         clientManager->appendManagedDriver(dv);
0435 
0436     connect(clientManager, &ClientManager::started, this, &DriverManager::setClientStarted, Qt::UniqueConnection);
0437     connect(clientManager, &ClientManager::failed, this, &DriverManager::setClientFailed, Qt::UniqueConnection);
0438     connect(clientManager, &ClientManager::terminated, this, &DriverManager::setClientTerminated, Qt::UniqueConnection);
0439 
0440     clientManager->setServer(host.toLatin1().constData(), port);
0441 
0442     GUIManager::Instance()->addClient(clientManager);
0443     INDIListener::Instance()->addClient(clientManager);
0444 
0445     clientManager->establishConnection();
0446 }
0447 
0448 void DriverManager::stopDevices(const QList<QSharedPointer<DriverInfo>> &dList)
0449 {
0450     qCDebug(KSTARS_INDI) << "INDI: Stopping local drivers...";
0451 
0452     // #1 Disconnect all clients
0453     for (QSharedPointer<DriverInfo>dv : dList)
0454     {
0455         ClientManager *cm = dv->getClientManager();
0456 
0457         if (cm == nullptr)
0458             continue;
0459 
0460         cm->removeManagedDriver(dv);
0461 
0462         if (cm->count() == 0)
0463         {
0464             GUIManager::Instance()->removeClient(cm);
0465             INDIListener::Instance()->removeClient(cm);
0466             cm->disconnectServer();
0467             clients.removeOne(cm);
0468             cm->deleteLater();
0469 
0470             KStars::Instance()->slotSetTelescopeEnabled(false);
0471             KStars::Instance()->slotSetDomeEnabled(false);
0472         }
0473     }
0474 
0475     // #2 Disconnect all servers
0476     for (QSharedPointer<DriverInfo>dv : dList)
0477     {
0478         ServerManager *sm = dv->getServerManager();
0479 
0480         if (sm != nullptr)
0481         {
0482             sm->stopDriver(dv);
0483 
0484             if (sm->size() == 0)
0485             {
0486                 sm->stop();
0487                 servers.removeOne(sm);
0488                 sm->deleteLater();
0489             }
0490         }
0491     }
0492 
0493     // Reset current port
0494     currentPort = Options::serverPortStart() - 1;
0495 
0496     updateMenuActions();
0497 }
0498 
0499 void DriverManager::disconnectClients()
0500 {
0501     for (auto &clientManager : clients)
0502     {
0503         clientManager->disconnectAll();
0504         clientManager->disconnect();
0505     }
0506 }
0507 
0508 void DriverManager::clearServers()
0509 {
0510     for (auto &serverManager : servers)
0511         serverManager->stop();
0512 
0513     qDeleteAll(servers);
0514 }
0515 
0516 void DriverManager::activateRunService()
0517 {
0518     processLocalTree(true);
0519 }
0520 
0521 void DriverManager::activateStopService()
0522 {
0523     processLocalTree(false);
0524 }
0525 
0526 void DriverManager::activateHostConnection()
0527 {
0528     processRemoteTree(true);
0529 }
0530 
0531 void DriverManager::activateHostDisconnection()
0532 {
0533     processRemoteTree(false);
0534 }
0535 
0536 ClientManager *DriverManager::getClientManager(const QSharedPointer<DriverInfo> &driver)
0537 {
0538     auto cm = driver->getClientManager();
0539     if (cm)
0540         return cm;
0541     // If no client manager found for the driver, return the first client manager on the same host and port number
0542     else if (!clients.isEmpty())
0543     {
0544         auto host = driver->getHost();
0545         auto port = driver->getPort();
0546         auto it = std::find_if(clients.begin(), clients.end(), [host, port](const auto & oneClient)
0547         {
0548             return oneClient->getHost() == host && oneClient->getPort() == port;
0549         }
0550                               );
0551         if (it != clients.end())
0552             return *it;
0553     }
0554 
0555     return nullptr;
0556 }
0557 
0558 void DriverManager::updateLocalTab()
0559 {
0560     if (ui->localTreeWidget->currentItem() == nullptr)
0561         return;
0562 
0563     QString currentDriver = ui->localTreeWidget->currentItem()->text(LOCAL_NAME_COLUMN);
0564 
0565     auto device = std::find_if(driversList.begin(), driversList.end(), [currentDriver](const auto & oneDriver)
0566     {
0567         return currentDriver == oneDriver->getLabel();
0568     });
0569     if (device != driversList.end())
0570         processDeviceStatus(*device);
0571 }
0572 
0573 void DriverManager::updateClientTab()
0574 {
0575     QTreeWidgetItem *item = ui->clientTreeWidget->currentItem();
0576 
0577     if (item == nullptr)
0578         return;
0579 
0580     auto hostname = item->text(HOST_NAME_COLUMN);
0581     int port = item->text(HOST_PORT_COLUMN).toInt();
0582 
0583     auto device = std::find_if(driversList.begin(), driversList.end(), [hostname, port](const auto & oneDriver)
0584     {
0585         return hostname == oneDriver->getName() && port == oneDriver->getPort();
0586     });
0587     if (device != driversList.end())
0588         processDeviceStatus(*device);
0589 }
0590 
0591 void DriverManager::processLocalTree(bool dState)
0592 {
0593     QList<QSharedPointer<DriverInfo>> processed_devices;
0594 
0595     int port;
0596     bool portOK = false;
0597 
0598     connectionMode = ui->localR->isChecked() ? SERVER_CLIENT : SERVER_ONLY;
0599 
0600     for (auto &item : ui->localTreeWidget->selectedItems())
0601     {
0602         for (auto &device : driversList)
0603         {
0604             port = -1;
0605 
0606             //device->state = (dev_request == DriverInfo::DEV_TERMINATE) ? DriverInfo::DEV_START : DriverInfo::DEV_TERMINATE;
0607             if (item->text(LOCAL_NAME_COLUMN) == device->getLabel() &&
0608                     device->getServerState() != dState)
0609             {
0610                 processed_devices.append(device);
0611 
0612                 // N.B. If multiple devices are selected to run under one device manager
0613                 // then we select the port for the first device that has a valid port
0614                 // entry, the rest are ignored.
0615                 if (port == -1 && item->text(LOCAL_PORT_COLUMN).isEmpty() == false)
0616                 {
0617                     port = item->text(LOCAL_PORT_COLUMN).toInt(&portOK);
0618                     // If we encounter conversion error, we abort
0619                     if (portOK == false)
0620                     {
0621                         KSNotification::error(i18n("Invalid port entry: %1", item->text(LOCAL_PORT_COLUMN)));
0622                         return;
0623                     }
0624                 }
0625 
0626                 device->setHostParameters("localhost", port);
0627             }
0628         }
0629     }
0630 
0631     if (processed_devices.empty())
0632         return;
0633 
0634     if (dState)
0635         startDevices(processed_devices);
0636     else
0637         stopDevices(processed_devices);
0638 }
0639 
0640 void DriverManager::setClientFailed(const QString &message)
0641 {
0642     auto client = qobject_cast<ClientManager*>(sender());
0643 
0644     if (client == nullptr)
0645         return;
0646 
0647     auto host = client->getHost();
0648     auto port = client->getPort();
0649 
0650     KNotification::beep();
0651 
0652     emit clientFailed(host, port, message);
0653 
0654     GUIManager::Instance()->removeClient(client);
0655     INDIListener::Instance()->removeClient(client);
0656 
0657     KSNotification::event(QLatin1String("IndiServerMessage"), message, KSNotification::INDI);
0658 
0659     clients.removeOne(client);
0660     client->deleteLater();
0661 
0662     if (connectionMode == SERVER_CLIENT)
0663         activateStopService();
0664 
0665     updateMenuActions();
0666     updateLocalTab();
0667 }
0668 
0669 void DriverManager::setClientTerminated(const QString &message)
0670 {
0671     auto client = qobject_cast<ClientManager*>(sender());
0672 
0673     if (client == nullptr)
0674         return;
0675 
0676     auto host = client->getHost();
0677     auto port = client->getPort();
0678 
0679     KNotification::beep();
0680 
0681     emit clientTerminated(host, port, message);
0682 
0683     GUIManager::Instance()->removeClient(client);
0684     INDIListener::Instance()->removeClient(client);
0685 
0686     KSNotification::event(QLatin1String("IndiServerMessage"), message, KSNotification::INDI, KSNotification::Alert);
0687 
0688     clients.removeOne(client);
0689     client->deleteLater();
0690 
0691     updateMenuActions();
0692     updateLocalTab();
0693     updateClientTab();
0694 }
0695 
0696 void DriverManager::setServerStarted()
0697 {
0698     auto manager = qobject_cast<ServerManager*>(sender());
0699     qCDebug(KSTARS_INDI) << "INDI: INDI Server started locally on port " << manager->getPort();
0700     startLocalDrivers(manager);
0701     emit serverStarted(manager->getHost(), manager->getPort());
0702 }
0703 
0704 void DriverManager::setServerFailed(const QString &message)
0705 {
0706     auto server = qobject_cast<ServerManager *>(sender());
0707 
0708     if (server == nullptr)
0709         return;
0710 
0711     for (auto &dv : driversList)
0712     {
0713         if (dv->getServerManager() == server)
0714         {
0715             dv->reset();
0716         }
0717     }
0718 
0719     if (server->getMode() == SERVER_ONLY)
0720         KSNotification::error(message);
0721 
0722     emit serverFailed(server->getHost(), server->getPort(), message);
0723     servers.removeOne(server);
0724     server->deleteLater();
0725 
0726     updateLocalTab();
0727 }
0728 
0729 void DriverManager::setServerTerminated(const QString &message)
0730 {
0731     auto server = qobject_cast<ServerManager *>(sender());
0732 
0733     if (server == nullptr)
0734         return;
0735 
0736     for (auto &dv : driversList)
0737     {
0738         if (dv->getServerManager() == server)
0739         {
0740             dv->reset();
0741         }
0742     }
0743 
0744     if (server->getMode() == SERVER_ONLY)
0745         KSNotification::error(message);
0746 
0747     emit serverTerminated(server->getHost(), server->getPort(), message);
0748     servers.removeOne(server);
0749     server->deleteLater();
0750 
0751     updateLocalTab();
0752 }
0753 
0754 void DriverManager::processRemoteTree(bool dState)
0755 {
0756     QTreeWidgetItem *currentItem = ui->clientTreeWidget->currentItem();
0757     if (!currentItem)
0758         return;
0759 
0760     for (auto &dv : driversList)
0761     {
0762         if (dv->getDriverSource() != HOST_SOURCE)
0763             continue;
0764 
0765         //qDebug() << Q_FUNC_INFO << "Current item port " << currentItem->text(HOST_PORT_COLUMN) << " current dev " << dv->getName() << " -- port " << dv->getPort() << Qt::endl;
0766         //qDebug() << Q_FUNC_INFO << "dState is : " << (dState ? "True" : "False") << Qt::endl;
0767 
0768         if (currentItem->text(HOST_NAME_COLUMN) == dv->getName() &&
0769                 currentItem->text(HOST_PORT_COLUMN).toInt() == dv->getPort())
0770         {
0771             // Nothing changed, return
0772             if (dv->getClientState() == dState)
0773                 return;
0774 
0775             // connect to host
0776             if (dState)
0777                 connectRemoteHost(dv);
0778             // Disconnect form host
0779             else
0780                 disconnectRemoteHost(dv);
0781         }
0782     }
0783 }
0784 
0785 void DriverManager::connectRemoteHost(const QSharedPointer<DriverInfo> &driver)
0786 {
0787     auto host = driver->getHost();
0788     auto port = driver->getPort();
0789 
0790     auto clientManager = new ClientManager();
0791 
0792     clientManager->appendManagedDriver(driver);
0793 
0794     connect(clientManager, &ClientManager::started, this, &DriverManager::setClientStarted, Qt::UniqueConnection);
0795     connect(clientManager, &ClientManager::failed, this, &DriverManager::setClientFailed, Qt::UniqueConnection);
0796     connect(clientManager, &ClientManager::terminated, this, &DriverManager::setClientTerminated, Qt::UniqueConnection);
0797 
0798     clientManager->setServer(host.toLatin1().constData(), port);
0799 
0800     GUIManager::Instance()->addClient(clientManager);
0801     INDIListener::Instance()->addClient(clientManager);
0802 
0803     clientManager->establishConnection();
0804 }
0805 
0806 void DriverManager::setClientStarted()
0807 {
0808     auto clientManager = qobject_cast<ClientManager *>(sender());
0809     if (clientManager == nullptr)
0810         return;
0811 
0812     clients.append(clientManager);
0813     updateLocalTab();
0814     updateMenuActions();
0815 
0816     KSNotification::event(QLatin1String("ConnectionSuccessful"),
0817                           i18n("Connected to INDI server"));
0818 
0819     emit clientStarted(clientManager->getHost(), clientManager->getPort());
0820 }
0821 
0822 bool DriverManager::disconnectRemoteHost(const QSharedPointer<DriverInfo> &driver)
0823 {
0824     ClientManager *clientManager = driver->getClientManager();
0825 
0826     if (clientManager)
0827     {
0828         clientManager->removeManagedDriver(driver);
0829         clientManager->disconnectAll();
0830         GUIManager::Instance()->removeClient(clientManager);
0831         INDIListener::Instance()->removeClient(clientManager);
0832         clients.removeOne(clientManager);
0833         clientManager->deleteLater();
0834         updateMenuActions();
0835         return true;
0836     }
0837 
0838     return false;
0839 }
0840 
0841 void DriverManager::resizeDeviceColumn()
0842 {
0843     ui->localTreeWidget->resizeColumnToContents(0);
0844 }
0845 
0846 void DriverManager::updateMenuActions()
0847 {
0848     // We iterate over devices, we enable INDI Control Panel if we have any active device
0849     // We enable capture image sequence if we have any imaging device
0850 
0851     QAction *tmpAction = nullptr;
0852     bool activeDevice  = false;
0853 
0854     if (clients.size() > 0)
0855         activeDevice = true;
0856 
0857     tmpAction = KStars::Instance()->actionCollection()->action("indi_cpl");
0858     if (tmpAction != nullptr)
0859     {
0860         //qDebug() << Q_FUNC_INFO << "indi_cpl action set to active" << Qt::endl;
0861         tmpAction->setEnabled(activeDevice);
0862     }
0863 }
0864 
0865 int DriverManager::getINDIPort(int customPort)
0866 {
0867 #ifdef Q_OS_WIN
0868     qWarning() << "INDI server is currently not supported on Windows.";
0869     return -1;
0870 #else
0871     int lastPort = Options::serverPortEnd();
0872     bool success = false;
0873     currentPort++;
0874 
0875     // recycle
0876     if (currentPort > lastPort)
0877         currentPort = Options::serverPortStart();
0878 
0879     QTcpServer temp_server;
0880 
0881     if (customPort != -1)
0882     {
0883         success = temp_server.listen(QHostAddress::LocalHost, customPort);
0884         if (success)
0885         {
0886             temp_server.close();
0887             return customPort;
0888         }
0889         else
0890             return -1;
0891     }
0892 
0893     for (; currentPort <= lastPort; currentPort++)
0894     {
0895         success = temp_server.listen(QHostAddress::LocalHost, currentPort);
0896         if (success)
0897         {
0898             temp_server.close();
0899             return currentPort;
0900         }
0901     }
0902     return -1;
0903 #endif
0904 }
0905 
0906 bool DriverManager::readINDIHosts()
0907 {
0908     QString indiFile("indihosts.xml");
0909     //QFile localeFile;
0910     QFile file;
0911     char errmsg[1024];
0912     char c;
0913     LilXML *xmlParser = newLilXML();
0914     XMLEle *root      = nullptr;
0915     XMLAtt *ap        = nullptr;
0916     QString hName, hHost, hPort;
0917 
0918     lastGroup = nullptr;
0919     file.setFileName(KSPaths::locate(QStandardPaths::AppLocalDataLocation, indiFile));
0920     if (file.fileName().isEmpty() || !file.open(QIODevice::ReadOnly))
0921     {
0922         delLilXML(xmlParser);
0923         return false;
0924     }
0925 
0926     while (file.getChar(&c))
0927     {
0928         root = readXMLEle(xmlParser, c, errmsg);
0929 
0930         if (root)
0931         {
0932             // Get host name
0933             ap = findXMLAtt(root, "name");
0934             if (!ap)
0935             {
0936                 delLilXML(xmlParser);
0937                 return false;
0938             }
0939 
0940             hName = QString(valuXMLAtt(ap));
0941 
0942             // Get host name
0943             ap = findXMLAtt(root, "hostname");
0944 
0945             if (!ap)
0946             {
0947                 delLilXML(xmlParser);
0948                 return false;
0949             }
0950 
0951             hHost = QString(valuXMLAtt(ap));
0952 
0953             ap = findXMLAtt(root, "port");
0954 
0955             if (!ap)
0956             {
0957                 delLilXML(xmlParser);
0958                 return false;
0959             }
0960 
0961             hPort = QString(valuXMLAtt(ap));
0962 
0963             QSharedPointer<DriverInfo> dv(new DriverInfo(hName));
0964             dv->setHostParameters(hHost, hPort.toInt());
0965             dv->setDriverSource(HOST_SOURCE);
0966 
0967             connect(dv.get(), &DriverInfo::deviceStateChanged, this, [dv, this]()
0968             {
0969                 processDeviceStatus(dv);
0970             });
0971 
0972             driversList.append(dv);
0973 
0974             QTreeWidgetItem *item = new QTreeWidgetItem(ui->clientTreeWidget, lastGroup);
0975             lastGroup = item;
0976             item->setIcon(HOST_STATUS_COLUMN, ui->disconnected);
0977             item->setText(HOST_NAME_COLUMN, hName);
0978             item->setText(HOST_PORT_COLUMN, hPort);
0979 
0980             delXMLEle(root);
0981         }
0982         else if (errmsg[0])
0983         {
0984             qDebug() << Q_FUNC_INFO << errmsg;
0985             delLilXML(xmlParser);
0986             return false;
0987         }
0988     }
0989 
0990     delLilXML(xmlParser);
0991 
0992     return true;
0993 }
0994 
0995 bool DriverManager::readXMLDrivers()
0996 {
0997     QDir indiDir;
0998     QString driverName;
0999 
1000     QString driversDir = Options::indiDriversDir();
1001 #ifdef Q_OS_OSX
1002     if (Options::indiDriversAreInternal())
1003         driversDir =
1004             QCoreApplication::applicationDirPath() + "/../Resources/DriverSupport";
1005 #endif
1006 
1007     if (indiDir.cd(driversDir) == false)
1008     {
1009         KSNotification::error(i18n("Unable to find INDI drivers directory: %1\nPlease "
1010                                    "make sure to set the correct "
1011                                    "path in KStars configuration",
1012                                    driversDir));
1013         return false;
1014     }
1015 
1016     indiDir.setNameFilters(QStringList() << "indi_*.xml"
1017                            << "drivers.xml");
1018     indiDir.setFilter(QDir::Files | QDir::NoSymLinks);
1019     QFileInfoList list = indiDir.entryInfoList();
1020 
1021     for (auto &fileInfo : list)
1022     {
1023         if (fileInfo.fileName().endsWith(QLatin1String("_sk.xml")))
1024             continue;
1025 
1026         processXMLDriver(fileInfo.absoluteFilePath());
1027     }
1028 
1029     // JM 2022.08.24: Process local source last as INDI sources should have higher priority than KStars own database.
1030     processXMLDriver(QLatin1String(":/indidrivers.xml"));
1031 
1032     return true;
1033 }
1034 
1035 void DriverManager::processXMLDriver(const QString &driverName)
1036 {
1037     QFile file(driverName);
1038     if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
1039     {
1040         KSNotification::error(i18n("Failed to open INDI Driver file: %1", driverName));
1041         return;
1042     }
1043 
1044     char errmsg[ERRMSG_SIZE];
1045     char c;
1046     LilXML *xmlParser = newLilXML();
1047     XMLEle *root      = nullptr;
1048     XMLEle *ep        = nullptr;
1049 
1050     if (driverName.endsWith(QLatin1String("drivers.xml")))
1051         driverSource = PRIMARY_XML;
1052     else
1053         driverSource = THIRD_PARTY_XML;
1054 
1055     while (file.getChar(&c))
1056     {
1057         root = readXMLEle(xmlParser, c, errmsg);
1058 
1059         if (root)
1060         {
1061             // If the XML file is using the INDI Library v1.3+ format
1062             if (!strcmp(tagXMLEle(root), "driversList"))
1063             {
1064                 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
1065                 {
1066                     if (!buildDeviceGroup(ep, errmsg))
1067                         prXMLEle(stderr, ep, 0);
1068                 }
1069             }
1070             // If using the older format
1071             else
1072             {
1073                 if (!buildDeviceGroup(root, errmsg))
1074                     prXMLEle(stderr, root, 0);
1075             }
1076 
1077             delXMLEle(root);
1078         }
1079         else if (errmsg[0])
1080         {
1081             qCDebug(KSTARS_INDI) << QString(errmsg);
1082             delLilXML(xmlParser);
1083             return;
1084         }
1085     }
1086 
1087     delLilXML(xmlParser);
1088 }
1089 
1090 bool DriverManager::buildDeviceGroup(XMLEle *root, char errmsg[])
1091 {
1092     XMLAtt *ap;
1093     XMLEle *ep;
1094     QString groupName;
1095     QTreeWidgetItem *group;
1096     DeviceFamily groupType = KSTARS_TELESCOPE;
1097 
1098     // avoid overflow
1099     if (strlen(tagXMLEle(root)) > 1024)
1100         return false;
1101 
1102     // Get device grouping name
1103     ap = findXMLAtt(root, "group");
1104 
1105     if (!ap)
1106     {
1107         snprintf(errmsg, ERRMSG_SIZE, "Tag %.64s does not have a group attribute",
1108                  tagXMLEle(root));
1109         return false;
1110     }
1111 
1112     groupName = valuXMLAtt(ap);
1113     groupType = DeviceFamilyLabels.key(groupName);
1114 
1115 #ifndef HAVE_CFITSIO
1116     // We do not create these groups if we don't have CFITSIO support
1117     if (groupType == KSTARS_CCD || groupType == KSTARS_VIDEO)
1118         return true;
1119 #endif
1120 
1121     // Find if the group already exists
1122     QList<QTreeWidgetItem *> treeList =
1123         ui->localTreeWidget->findItems(groupName, Qt::MatchExactly);
1124     if (!treeList.isEmpty())
1125         group = treeList[0];
1126     else
1127         group = new QTreeWidgetItem(ui->localTreeWidget, lastGroup);
1128 
1129     group->setText(0, groupName);
1130     lastGroup = group;
1131 
1132     for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
1133     {
1134         if (!buildDriverElement(ep, group, groupType, errmsg))
1135             return false;
1136     }
1137 
1138     return true;
1139 }
1140 
1141 bool DriverManager::buildDriverElement(XMLEle *root, QTreeWidgetItem *DGroup,
1142                                        DeviceFamily groupType, char errmsg[])
1143 {
1144     XMLAtt *ap;
1145     XMLEle *el;
1146     QString label;
1147     QString driver;
1148     QString version;
1149     // N.B. NOT an i18n string.
1150     QString manufacturer("Others");
1151     QString name;
1152     QString port;
1153     QString skel;
1154     QVariantMap vMap;
1155     //double focal_length(-1), aperture(-1);
1156 
1157     ap = findXMLAtt(root, "label");
1158     if (!ap)
1159     {
1160         snprintf(errmsg, ERRMSG_SIZE, "Tag %.64s does not have a label attribute",
1161                  tagXMLEle(root));
1162         return false;
1163     }
1164 
1165     label = valuXMLAtt(ap);
1166 
1167     // Label is unique, so if we have the same label, we simply ignore
1168     if (findDriverByLabel(label) != nullptr)
1169         return true;
1170 
1171     ap = findXMLAtt(root, "manufacturer");
1172     if (ap)
1173         manufacturer = valuXMLAtt(ap);
1174 
1175     // Search for optional port attribute
1176     ap = findXMLAtt(root, "port");
1177     if (ap)
1178         port = valuXMLAtt(ap);
1179 
1180     // Search for skel file, if any
1181     // Search for optional port attribute
1182     ap = findXMLAtt(root, "skel");
1183     if (ap)
1184         skel = valuXMLAtt(ap);
1185 
1186     // Find MDPD: Multiple Devices Per Driver
1187     ap = findXMLAtt(root, "mdpd");
1188     if (ap)
1189     {
1190         bool mdpd = false;
1191         mdpd      = (QString(valuXMLAtt(ap)) == QString("true")) ? true : false;
1192         vMap.insert("mdpd", mdpd);
1193     }
1194 
1195     el = findXMLEle(root, "driver");
1196 
1197     if (!el)
1198         return false;
1199 
1200     driver = pcdataXMLEle(el);
1201 
1202     ap = findXMLAtt(el, "name");
1203     if (!ap)
1204     {
1205         snprintf(errmsg, ERRMSG_SIZE, "Tag %.64s does not have a name attribute",
1206                  tagXMLEle(el));
1207         return false;
1208     }
1209 
1210     name = valuXMLAtt(ap);
1211 
1212     el = findXMLEle(root, "version");
1213 
1214     if (!el)
1215         return false;
1216 
1217     version        = pcdataXMLEle(el);
1218     bool versionOK = false;
1219     version.toDouble(&versionOK);
1220     if (versionOK == false)
1221         version = "1.0";
1222 
1223     bool driverIsAvailable = checkDriverAvailability(driver);
1224 
1225     vMap.insert("LOCALLY_AVAILABLE", driverIsAvailable);
1226     QIcon remoteIcon = QIcon::fromTheme("network-modem");
1227 
1228     QTreeWidgetItem *device = new QTreeWidgetItem(DGroup);
1229 
1230     device->setText(LOCAL_NAME_COLUMN, label);
1231     if (driverIsAvailable)
1232         device->setIcon(LOCAL_STATUS_COLUMN, ui->stopPix);
1233     else
1234         device->setIcon(LOCAL_STATUS_COLUMN, remoteIcon);
1235     device->setText(LOCAL_VERSION_COLUMN, version);
1236     device->setText(LOCAL_PORT_COLUMN, port);
1237 
1238     //if ((driverSource == PRIMARY_XML) && driversStringList.contains(driver) == false)
1239     if (groupType == KSTARS_TELESCOPE && driversStringList.contains(driver) == false)
1240         driversStringList.append(driver);
1241 
1242     QSharedPointer<DriverInfo> dv(new DriverInfo(name));
1243 
1244     dv->setLabel(label);
1245     dv->setVersion(version);
1246     dv->setExecutable(driver);
1247     dv->setManufacturer(manufacturer);
1248     dv->setSkeletonFile(skel);
1249     dv->setType(groupType);
1250     dv->setDriverSource(driverSource);
1251     dv->setUserPort(port.isEmpty() ? 7624 : port.toInt());
1252     dv->setAuxInfo(vMap);
1253 
1254     connect(dv.get(), &DriverInfo::deviceStateChanged, this, [dv, this]()
1255     {
1256         processDeviceStatus(dv);
1257     });
1258 
1259     driversList.append(dv);
1260 
1261     return true;
1262 }
1263 
1264 bool DriverManager::checkDriverAvailability(const QString &driver)
1265 {
1266     QString indiServerDir = Options::indiServer();
1267     if (Options::indiServerIsInternal())
1268         indiServerDir = QCoreApplication::applicationDirPath();
1269     else
1270         indiServerDir = QFileInfo(Options::indiServer()).dir().path();
1271 
1272     QFile driverFile(indiServerDir + '/' + driver);
1273 
1274     if (driverFile.exists() == false)
1275         return (!QStandardPaths::findExecutable(indiServerDir + '/' + driver).isEmpty());
1276 
1277     return true;
1278 }
1279 
1280 void DriverManager::updateCustomDrivers()
1281 {
1282     for (const QVariantMap &oneDriver : m_CustomDrivers->customDrivers())
1283     {
1284         QSharedPointer<DriverInfo> dv(new DriverInfo(oneDriver["Name"].toString()));
1285         dv->setLabel(oneDriver["Label"].toString());
1286         dv->setUniqueLabel(dv->getLabel());
1287         dv->setExecutable(oneDriver["Exec"].toString());
1288         dv->setVersion(oneDriver["Version"].toString());
1289         dv->setManufacturer(oneDriver["Manufacturer"].toString());
1290         dv->setType(DeviceFamilyLabels.key(oneDriver["Family"].toString()));
1291         dv->setDriverSource(CUSTOM_SOURCE);
1292 
1293         bool driverIsAvailable = checkDriverAvailability(oneDriver["Exec"].toString());
1294         QVariantMap vMap;
1295         vMap.insert("LOCALLY_AVAILABLE", driverIsAvailable);
1296         dv->setAuxInfo(vMap);
1297 
1298         driversList.append(dv);
1299     }
1300 }
1301 
1302 // JM 2018-07-23: Disabling the old custom drivers method
1303 #if 0
1304 void DriverManager::updateCustomDrivers()
1305 {
1306     QString label;
1307     QString driver;
1308     QString version;
1309     QString name;
1310     QTreeWidgetItem *group     = nullptr;
1311     QTreeWidgetItem *widgetDev = nullptr;
1312     QVariantMap vMap;
1313     QSharedPointer<DriverInfo>drv = nullptr;
1314 
1315     // Find if the group already exists
1316     QList<QTreeWidgetItem *> treeList = ui->localTreeWidget->findItems("Telescopes", Qt::MatchExactly);
1317     if (!treeList.isEmpty())
1318         group = treeList[0];
1319     else
1320         return;
1321 
1322     KStarsData::Instance()->logObject()->readAll();
1323 
1324     // Find custom telescope to ADD/UPDATE
1325     foreach (OAL::Scope *s, *(KStarsData::Instance()->logObject()->scopeList()))
1326     {
1327         name = label = s->name();
1328 
1329         if (s->driver() == i18n("None"))
1330             continue;
1331 
1332         // If driver already exists, just update values
1333         if ((drv = findDriverByLabel(label)))
1334         {
1335             if (s->aperture() > 0 && s->focalLength() > 0)
1336             {
1337                 vMap.insert("TELESCOPE_APERTURE", s->aperture());
1338                 vMap.insert("TELESCOPE_FOCAL_LENGTH", s->focalLength());
1339                 drv->setAuxInfo(vMap);
1340             }
1341 
1342             drv->setExecutable(s->driver());
1343 
1344             continue;
1345         }
1346 
1347         driver  = s->driver();
1348         version = QString("1.0");
1349 
1350         QTreeWidgetItem *device = new QTreeWidgetItem(group);
1351         device->setText(LOCAL_NAME_COLUMN, QString(label));
1352         device->setIcon(LOCAL_STATUS_COLUMN, ui->stopPix);
1353         device->setText(LOCAL_VERSION_COLUMN, QString(version));
1354 
1355         QSharedPointer<DriverInfo>dv = new DriverInfo(name);
1356 
1357         dv->setLabel(label);
1358         dv->setExecutable(driver);
1359         dv->setVersion(version);
1360         dv->setType(KSTARS_TELESCOPE);
1361         dv->setDriverSource(EM_XML);
1362 
1363         if (s->aperture() > 0 && s->focalLength() > 0)
1364         {
1365             vMap.insert("TELESCOPE_APERTURE", s->aperture());
1366             vMap.insert("TELESCOPE_FOCAL_LENGTH", s->focalLength());
1367             dv->setAuxInfo(vMap);
1368         }
1369 
1370         connect(dv, SIGNAL(deviceStateChanged(DriverInfo*)), this, SLOT(processDeviceStatus(DriverInfo*)));
1371         driversList.append(dv);
1372     }
1373 
1374     // Find custom telescope to REMOVE
1375     foreach (QSharedPointer<DriverInfo>dev, driversList)
1376     {
1377         // If it's from primary xml file or it is in a running state, continue.
1378         if (dev->getDriverSource() != EM_XML || dev->getClientState())
1379             continue;
1380 
1381         if (KStarsData::Instance()->logObject()->findScopeByName(dev->getName()))
1382             continue;
1383 
1384         // Find if the group already exists
1385         QList<QTreeWidgetItem *> devList =
1386             ui->localTreeWidget->findItems(dev->getLabel(), Qt::MatchExactly | Qt::MatchRecursive);
1387         if (!devList.isEmpty())
1388         {
1389             widgetDev = devList[0];
1390             group->removeChild(widgetDev);
1391         }
1392         else
1393             return;
1394 
1395         driversList.removeOne(dev);
1396         delete (dev);
1397     }
1398 }
1399 #endif
1400 
1401 void DriverManager::addINDIHost()
1402 {
1403     QDialog hostConfDialog;
1404     Ui::INDIHostConf hostConf;
1405     hostConf.setupUi(&hostConfDialog);
1406     hostConfDialog.setWindowTitle(i18nc("@title:window", "Add Host"));
1407     bool portOk = false;
1408 
1409     if (hostConfDialog.exec() == QDialog::Accepted)
1410     {
1411         QSharedPointer<DriverInfo> hostItem(new DriverInfo(hostConf.nameIN->text()));
1412 
1413         hostConf.portnumber->text().toInt(&portOk);
1414 
1415         if (portOk == false)
1416         {
1417             KSNotification::error(i18n("Error: the port number is invalid."));
1418             return;
1419         }
1420 
1421         hostItem->setHostParameters(hostConf.hostname->text(),
1422                                     hostConf.portnumber->text().toInt());
1423 
1424         //search for duplicates
1425         //for (uint i=0; i < ksw->data()->INDIHostsList.count(); i++)
1426         foreach (QSharedPointer<DriverInfo>host, driversList)
1427             if (hostItem->getName() == host->getName() &&
1428                     hostItem->getPort() == host->getPort())
1429             {
1430                 KSNotification::error(i18n("Host: %1 Port: %2 already exists.",
1431                                            hostItem->getName(), hostItem->getPort()));
1432                 return;
1433             }
1434 
1435         hostItem->setDriverSource(HOST_SOURCE);
1436 
1437         connect(hostItem.get(), &DriverInfo::deviceStateChanged, this, [hostItem, this]()
1438         {
1439             processDeviceStatus(hostItem);
1440         });
1441 
1442         driversList.append(hostItem);
1443 
1444         QTreeWidgetItem *item = new QTreeWidgetItem(ui->clientTreeWidget);
1445         item->setIcon(HOST_STATUS_COLUMN, ui->disconnected);
1446         item->setText(HOST_NAME_COLUMN, hostConf.nameIN->text());
1447         item->setText(HOST_PORT_COLUMN, hostConf.portnumber->text());
1448     }
1449 
1450     saveHosts();
1451 }
1452 
1453 void DriverManager::modifyINDIHost()
1454 {
1455     QDialog hostConfDialog;
1456     Ui::INDIHostConf hostConf;
1457     hostConf.setupUi(&hostConfDialog);
1458     hostConfDialog.setWindowTitle(i18nc("@title:window", "Modify Host"));
1459 
1460     QTreeWidgetItem *currentItem = ui->clientTreeWidget->currentItem();
1461 
1462     if (currentItem == nullptr)
1463         return;
1464 
1465     for (auto &host : driversList)
1466     {
1467         if (currentItem->text(HOST_NAME_COLUMN) == host->getName() &&
1468                 currentItem->text(HOST_PORT_COLUMN).toInt() == host->getPort())
1469         {
1470             hostConf.nameIN->setText(host->getName());
1471             hostConf.hostname->setText(host->getHost());
1472             hostConf.portnumber->setText(QString::number(host->getPort()));
1473 
1474             if (hostConfDialog.exec() == QDialog::Accepted)
1475             {
1476                 host->setName(hostConf.nameIN->text());
1477                 host->setHostParameters(hostConf.hostname->text(),
1478                                         hostConf.portnumber->text().toInt());
1479 
1480                 currentItem->setText(HOST_NAME_COLUMN, hostConf.nameIN->text());
1481                 currentItem->setText(HOST_PORT_COLUMN, hostConf.portnumber->text());
1482 
1483                 saveHosts();
1484                 return;
1485             }
1486         }
1487     }
1488 }
1489 
1490 void DriverManager::removeINDIHost()
1491 {
1492     if (ui->clientTreeWidget->currentItem() == nullptr)
1493         return;
1494 
1495     foreach (QSharedPointer<DriverInfo>host, driversList)
1496         if (ui->clientTreeWidget->currentItem()->text(HOST_NAME_COLUMN) ==
1497                 host->getName() &&
1498                 ui->clientTreeWidget->currentItem()->text(HOST_PORT_COLUMN).toInt() ==
1499                 host->getPort())
1500         {
1501             if (host->getClientState())
1502             {
1503                 KSNotification::error(
1504                     i18n("You need to disconnect the client before removing it."));
1505                 return;
1506             }
1507 
1508             if (KMessageBox::warningContinueCancel(
1509                         nullptr,
1510                         i18n("Are you sure you want to remove the %1 client?",
1511                              ui->clientTreeWidget->currentItem()->text(HOST_NAME_COLUMN)),
1512                         i18n("Delete Confirmation"),
1513                         KStandardGuiItem::del()) != KMessageBox::Continue)
1514                 return;
1515 
1516             driversList.removeOne(host);
1517             delete (ui->clientTreeWidget->currentItem());
1518             break;
1519         }
1520 
1521     saveHosts();
1522 }
1523 
1524 void DriverManager::saveHosts()
1525 {
1526     QFile file;
1527     QString hostData;
1528 
1529     //determine filename in local user KDE directory tree.
1530     file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation))
1531                      .filePath("indihosts.xml"));
1532 
1533     if (!file.open(QIODevice::WriteOnly))
1534     {
1535         KSNotification::sorry(i18n("Unable to write to file 'indihosts.xml'\nAny changes "
1536                                    "to INDI hosts configurations will not be saved."),
1537                               i18n("Could Not Open File"));
1538         return;
1539     }
1540 
1541     QTextStream outstream(&file);
1542 
1543     //for (uint i= 0; i < ksw->data()->INDIHostsList.count(); i++)
1544     foreach (QSharedPointer<DriverInfo>host, driversList)
1545     {
1546         if (host->getDriverSource() != HOST_SOURCE)
1547             continue;
1548 
1549         hostData = "<INDIHost name='";
1550         hostData += host->getName();
1551         hostData += "' hostname='";
1552         hostData += host->getHost();
1553         hostData += "' port='";
1554         hostData += QString::number(host->getPort());
1555         hostData += "' />\n";
1556 
1557         outstream << hostData;
1558     }
1559 
1560     file.close();
1561 }
1562 
1563 QSharedPointer<DriverInfo> DriverManager::findDriverByName(const QString &name)
1564 {
1565     auto driver = std::find_if(driversList.begin(), driversList.end(), [name](auto & oneDriver)
1566     {
1567         return oneDriver->getName() == name;
1568     });
1569 
1570     if (driver != driversList.end())
1571         return *driver;
1572     else
1573         return QSharedPointer<DriverInfo>();
1574 }
1575 
1576 QSharedPointer<DriverInfo> DriverManager::findDriverByLabel(const QString &label)
1577 {
1578     auto driver = std::find_if(driversList.begin(), driversList.end(), [label](auto & oneDriver)
1579     {
1580         return oneDriver->getLabel() == label;
1581     });
1582 
1583     if (driver != driversList.end())
1584         return *driver;
1585     else
1586         return QSharedPointer<DriverInfo>();
1587 }
1588 
1589 QSharedPointer<DriverInfo> DriverManager::findDriverByExec(const QString &exec)
1590 {
1591     auto driver = std::find_if(driversList.begin(), driversList.end(), [exec](auto & oneDriver)
1592     {
1593         return oneDriver->getLabel() == exec;
1594     });
1595 
1596     if (driver != driversList.end())
1597         return *driver;
1598     else
1599         return QSharedPointer<DriverInfo>();
1600 }
1601 
1602 QString DriverManager::getUniqueDeviceLabel(const QString &label)
1603 {
1604     int nset            = 0;
1605     QString uniqueLabel = label;
1606 
1607     for (auto &cm : clients)
1608     {
1609         auto devices = cm->getDevices();
1610 
1611         for (auto &dv : devices)
1612         {
1613             if (label == QString(dv.getDeviceName()))
1614                 nset++;
1615         }
1616     }
1617     if (nset > 0)
1618         uniqueLabel = QString("%1 %2").arg(label).arg(nset + 1);
1619 
1620     return uniqueLabel;
1621 }
1622 
1623 QJsonArray DriverManager::getDriverList() const
1624 {
1625     QJsonArray driverArray;
1626 
1627     for (const auto &drv : driversList)
1628         driverArray.append(drv->toJson());
1629 
1630     return driverArray;
1631 }
1632 
1633 bool DriverManager::restartDriver(const QSharedPointer<DriverInfo> &driver)
1634 {
1635     for (auto &oneServer : servers)
1636     {
1637         if (oneServer->contains(driver))
1638         {
1639             return oneServer->restartDriver(driver);
1640         }
1641     }
1642 
1643     return false;
1644 }