1 /*
2     SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "drivermanager.h"
8 
9 #include "config-kstars.h"
10 
11 #include "clientmanager.h"
12 #include "driverinfo.h"
13 #include "guimanager.h"
14 #include "indilistener.h"
15 #include "kspaths.h"
16 #include "kstars.h"
17 #include "indidbus.h"
18 #include "kstarsdata.h"
19 #include "Options.h"
20 #include "servermanager.h"
21 #include "ui_indihostconf.h"
22 #include "auxiliary/ksnotification.h"
23 
24 #include <basedevice.h>
25 
26 #ifndef KSTARS_LITE
27 #include <KMessageBox>
28 #include <KActionCollection>
29 #include <KNotifications/KNotification>
30 #endif
31 
32 #include <QTcpServer>
33 #include <indi_debug.h>
34 
35 #define INDI_MAX_TRIES 2
36 #define ERRMSG_SIZE    1024
37 
DriverManagerUI(QWidget * parent)38 DriverManagerUI::DriverManagerUI(QWidget *parent) : QFrame(parent)
39 {
40     setupUi(this);
41 
42     localTreeWidget->setSortingEnabled(false);
43     localTreeWidget->setRootIsDecorated(true);
44 
45     clientTreeWidget->setSortingEnabled(false);
46 
47     runningPix = QIcon::fromTheme("system-run");
48     stopPix    = QIcon::fromTheme("dialog-cancel");
49     localMode  = QIcon::fromTheme("computer");
50     serverMode = QIcon::fromTheme("network-server");
51 
52     connected    = QIcon::fromTheme("network-connect");
53     disconnected = QIcon::fromTheme("network-disconnect");
54 
55     connect(localTreeWidget, &QTreeWidget::itemDoubleClicked, this,
56             &DriverManagerUI::makePortEditable);
57 }
58 
makePortEditable(QTreeWidgetItem * selectedItem,int column)59 void DriverManagerUI::makePortEditable(QTreeWidgetItem *selectedItem, int column)
60 {
61     // If it's the port column, then make it user-editable
62     if (column == ::DriverManager::LOCAL_PORT_COLUMN)
63         selectedItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable |
64                                Qt::ItemIsEnabled);
65 
66     localTreeWidget->editItem(selectedItem, ::DriverManager::LOCAL_PORT_COLUMN);
67 }
68 
69 DriverManager *DriverManager::_DriverManager = nullptr;
70 
Instance()71 DriverManager *DriverManager::Instance()
72 {
73     if (_DriverManager == nullptr)
74     {
75         _DriverManager     = new DriverManager(KStars::Instance());
76         INDIDBus *indiDBUS = new INDIDBus(KStars::Instance());
77         Q_UNUSED(indiDBUS)
78     }
79 
80     return _DriverManager;
81 }
82 
DriverManager(QWidget * parent)83 DriverManager::DriverManager(QWidget *parent) : QDialog(parent)
84 {
85 #ifdef Q_OS_OSX
86     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
87 #endif
88 
89     currentPort = Options::serverPortStart() - 1;
90 
91     QVBoxLayout *mainLayout = new QVBoxLayout;
92     ui                      = new DriverManagerUI(this);
93     mainLayout->addWidget(ui);
94     setLayout(mainLayout);
95     setWindowTitle(i18nc("@title:window", "Device Manager"));
96 
97     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
98     mainLayout->addWidget(buttonBox);
99 
100     connect(buttonBox, &QDialogButtonBox::rejected, this, &DriverManager::close);
101 
102     connect(ui->addB, &QPushButton::clicked, this, &DriverManager::addINDIHost);
103     connect(ui->modifyB, &QPushButton::clicked, this, &DriverManager::modifyINDIHost);
104     connect(ui->removeB, &QPushButton::clicked, this, &DriverManager::removeINDIHost);
105 
106     connect(ui->connectHostB, &QPushButton::clicked, this, &DriverManager::activateHostConnection);
107     connect(ui->disconnectHostB, &QPushButton::clicked, this, &DriverManager::activateHostDisconnection);
108     connect(ui->runServiceB, &QPushButton::clicked, this, &DriverManager::activateRunService);
109     connect(ui->stopServiceB, &QPushButton::clicked, this, &DriverManager::activateStopService);
110     connect(ui->localTreeWidget,  &QTreeWidget::itemClicked, this, &DriverManager::updateLocalTab);
111     connect(ui->clientTreeWidget, &QTreeWidget::itemClicked, this, &DriverManager::updateClientTab);
112     connect(ui->localTreeWidget, &QTreeWidget::expanded, this, &DriverManager::resizeDeviceColumn);
113 
114     // Do not use KSPaths here, this is for INDI
115     if (Options::indiDriversDir().isEmpty())
116         Options::setIndiDriversDir(
117             QStandardPaths::locate(QStandardPaths::GenericDataLocation, "indi",
118                                    QStandardPaths::LocateDirectory));
119 
120     readXMLDrivers();
121 
122     readINDIHosts();
123 
124     m_CustomDrivers = new CustomDrivers(this, driversList);
125 
126     updateCustomDrivers();
127 
128 #ifdef Q_OS_WIN
129     ui->localTreeWidget->setEnabled(false);
130 #endif
131 }
132 
~DriverManager()133 DriverManager::~DriverManager()
134 {
135     clearServers();
136     qDeleteAll(driversList);
137 }
138 
processDeviceStatus(DriverInfo * dv)139 void DriverManager::processDeviceStatus(DriverInfo *dv)
140 {
141     if (dv == nullptr)
142         return;
143 
144     if (dv->getDriverSource() == GENERATED_SOURCE)
145         return;
146 
147     QString currentDriver;
148     ServerMode mode        = connectionMode;
149     ServerManager *manager = dv->getServerManager();
150     bool dState            = false;
151     bool cState            = false;
152 
153     if (dv->getDriverSource() != HOST_SOURCE)
154     {
155         if (ui->localTreeWidget->currentItem())
156             currentDriver = ui->localTreeWidget->currentItem()->text(LOCAL_NAME_COLUMN);
157 
158         for (auto &item : ui->localTreeWidget->findItems(
159                     dv->getLabel(), Qt::MatchExactly | Qt::MatchRecursive))
160         {
161             item->setText(LOCAL_VERSION_COLUMN, dv->getVersion());
162 
163             if (manager)
164                 mode = manager->getMode();
165 
166             dState = dv->getServerState();
167             cState = dv->getClientState() && dState;
168 
169             bool locallyAvailable = false;
170             if (dv->getAuxInfo().contains("LOCALLY_AVAILABLE"))
171                 locallyAvailable =
172                     dv->getAuxInfo().value("LOCALLY_AVAILABLE", false).toBool();
173 
174             switch (mode)
175             {
176                 case SERVER_ONLY:
177                     if (locallyAvailable)
178                     {
179                         ui->runServiceB->setEnabled(!dState);
180                         ui->stopServiceB->setEnabled(dState);
181                         item->setIcon(LOCAL_STATUS_COLUMN,
182                                       dState ? ui->runningPix : ui->stopPix);
183                     }
184                     else
185                     {
186                         ui->runServiceB->setEnabled(false);
187                         ui->stopServiceB->setEnabled(false);
188                     }
189 
190                     if (dState)
191                     {
192                         item->setIcon(LOCAL_MODE_COLUMN, ui->serverMode);
193                         if (manager)
194                             item->setText(LOCAL_PORT_COLUMN, QString(manager->getPort()));
195                     }
196                     else
197                     {
198                         item->setIcon(LOCAL_MODE_COLUMN, QIcon());
199                         item->setText(LOCAL_PORT_COLUMN, dv->getUserPort());
200                     }
201 
202                     break;
203 
204                 case SERVER_CLIENT:
205                     if (locallyAvailable)
206                     {
207                         ui->runServiceB->setEnabled(!cState);
208                         ui->stopServiceB->setEnabled(cState);
209                         item->setIcon(LOCAL_STATUS_COLUMN,
210                                       cState ? ui->runningPix : ui->stopPix);
211                     }
212                     else
213                     {
214                         ui->runServiceB->setEnabled(false);
215                         ui->stopServiceB->setEnabled(false);
216                     }
217 
218                     if (cState)
219                     {
220                         item->setIcon(LOCAL_MODE_COLUMN, ui->localMode);
221 
222                         if (manager)
223                             item->setText(LOCAL_PORT_COLUMN, QString(manager->getPort()));
224                     }
225                     else
226                     {
227                         item->setIcon(LOCAL_MODE_COLUMN, QIcon());
228                         item->setText(LOCAL_PORT_COLUMN,
229                                       dv->getUserPort() == "-1" ? "" : dv->getUserPort());
230                     }
231 
232                     break;
233             }
234 
235             // Only update the log if the current driver is selected
236             if (currentDriver == dv->getLabel())
237             {
238                 ui->serverLogText->clear();
239                 ui->serverLogText->append(dv->getServerBuffer());
240             }
241         }
242     }
243     else
244     {
245         for (auto &item : ui->clientTreeWidget->findItems(dv->getName(), Qt::MatchExactly,
246                 HOST_NAME_COLUMN))
247         {
248             if (dv->getClientState())
249             {
250                 item->setIcon(HOST_STATUS_COLUMN, ui->connected);
251                 ui->connectHostB->setEnabled(false);
252                 ui->disconnectHostB->setEnabled(true);
253             }
254             else
255             {
256                 item->setIcon(HOST_STATUS_COLUMN, ui->disconnected);
257                 ui->connectHostB->setEnabled(true);
258                 ui->disconnectHostB->setEnabled(false);
259             }
260         }
261     }
262 }
263 
getUniqueHosts(QList<DriverInfo * > & dList,QList<QList<DriverInfo * >> & uHosts)264 void DriverManager::getUniqueHosts(QList<DriverInfo *> &dList,
265                                    QList<QList<DriverInfo *>> &uHosts)
266 {
267     bool found = false;
268 
269     // Iterate over all drivers
270     for (DriverInfo *dv : dList)
271     {
272         QList<DriverInfo *> uList;
273 
274         // Let's see for drivers with identical hosts and ports
275         for (DriverInfo *idv : dList)
276         {
277             // If we get a match between port and hostname, we add it to the list
278             if ((dv->getHost() == idv->getHost() && dv->getPort() == idv->getPort()))
279             {
280                 // Check if running already
281                 if (dv->getClientState() || dv->getServerState())
282                 {
283                     int ans = KMessageBox::warningContinueCancel(
284                                   nullptr,
285                                   i18n("Driver %1 is already running, do you want to restart it?",
286                                        dv->getLabel()));
287                     if (ans == KMessageBox::Cancel)
288                         continue;
289                     else
290                     {
291                         QList<DriverInfo *> stopList;
292                         stopList.append(dv);
293                         stopDevices(stopList);
294                     }
295                 }
296 
297                 found = false;
298 
299                 // Check to see if the driver already been added elsewhere
300                 for (auto &qdi : uHosts)
301                 {
302                     for (DriverInfo *di : qdi)
303                     {
304                         if (di == idv)
305                         {
306                             found = true;
307                             break;
308                         }
309                     }
310                 }
311 
312                 if (found == false)
313                     uList.append(idv);
314             }
315         }
316 
317         if (uList.empty() == false)
318             uHosts.append(uList);
319     }
320 }
321 
startDevices(QList<DriverInfo * > & dList)322 bool DriverManager::startDevices(QList<DriverInfo *> &dList)
323 {
324     ServerManager *serverManager = nullptr;
325     ClientManager *clientManager = nullptr;
326     int port                     = -1;
327 
328     QList<QList<DriverInfo *>> uHosts;
329 
330     bool connectionToServer = false;
331 
332     getUniqueHosts(dList, uHosts);
333 
334     qCDebug(KSTARS_INDI) << "INDI: Starting local drivers...";
335 
336     for (auto &qdv : uHosts)
337     {
338         if (qdv.empty())
339             continue;
340 
341         port = qdv.at(0)->getPort().toInt();
342 
343         // Select random port within range is none specified.
344         if (port == -1)
345             port = getINDIPort(port);
346 
347         if (port <= 0)
348         {
349             KSNotification::error(i18n("Cannot start INDI server: port error."));
350             return false;
351         }
352 
353         serverManager = new ServerManager(qdv.at(0)->getHost(), port);
354 
355         if (serverManager == nullptr)
356         {
357             qWarning() << "Warning: device manager has not been established properly";
358             return false;
359         }
360 
361         serverManager->setMode(connectionMode);
362 
363         connect(serverManager, SIGNAL(newServerLog()), this, SLOT(updateLocalTab()));
364         connect(serverManager, &ServerManager::started, [&]()
365         {
366             emit serverStarted(qdv.at(0)->getHost(), QString::number(port));
367         });
368 
369         if (serverManager->start())
370             servers.append(serverManager);
371         else
372         {
373             delete serverManager;
374             return false;
375         }
376 
377         qCDebug(KSTARS_INDI) << "INDI: INDI Server started locally on port " << port;
378 
379         for (DriverInfo *dv : qdv)
380         {
381             if (serverManager->startDriver(dv) == false)
382             {
383                 servers.removeOne(serverManager);
384                 serverManager->stop();
385                 emit serverTerminated(serverManager->getHost(), serverManager->getPort());
386                 delete serverManager;
387                 return false;
388             }
389         }
390 
391         // Nothing to do more if SERVER ONLY
392         if (connectionMode == SERVER_ONLY)
393             continue;
394 
395         clientManager = new ClientManager();
396 
397         for (DriverInfo *dv : qdv)
398             clientManager->appendManagedDriver(dv);
399 
400         connect(clientManager, &ClientManager::connectionFailure, this,
401                 &DriverManager::processClientTermination);
402 
403         clientManager->setServer(qdv.at(0)->getHost().toLatin1().constData(), port);
404 
405         GUIManager::Instance()->addClient(clientManager);
406         INDIListener::Instance()->addClient(clientManager);
407 
408         for (int i = 0; i < INDI_MAX_TRIES; i++)
409         {
410             qCDebug(KSTARS_INDI)
411                     << "INDI: Connecting to local INDI server on port " << port << " ...";
412 
413             connectionToServer = clientManager->connectServer();
414 
415             if (connectionToServer)
416                 break;
417 
418             qApp->processEvents();
419 
420             //usleep(100000);
421             QThread::usleep(100000);
422         }
423 
424         if (connectionToServer)
425         {
426             qCDebug(KSTARS_INDI) << "Connection to INDI server is successful";
427 
428             clients.append(clientManager);
429             updateMenuActions();
430         }
431         else
432         {
433             qCDebug(KSTARS_INDI)
434                     << "INDI: Connection to local INDI server on port " << port << " failed!";
435 
436             KNotification::beep();
437             QPointer<QMessageBox> msgBox = new QMessageBox();
438             msgBox->setAttribute(Qt::WA_DeleteOnClose);
439             msgBox->setStandardButtons(QMessageBox::Ok);
440             msgBox->setWindowTitle(i18nc("@title:window", "Error"));
441             msgBox->setText(
442                 i18n("Connection to INDI server locally on port %1 failed.", port));
443             msgBox->setModal(false);
444             msgBox->setIcon(QMessageBox::Critical);
445             msgBox->show();
446 
447             for (DriverInfo *dv : qdv)
448                 processDeviceStatus(dv);
449 
450             GUIManager::Instance()->removeClient(clientManager);
451             INDIListener::Instance()->removeClient(clientManager);
452 
453             return false;
454         }
455     }
456 
457     return true;
458 }
459 
stopDevices(const QList<DriverInfo * > & dList)460 void DriverManager::stopDevices(const QList<DriverInfo *> &dList)
461 {
462     qCDebug(KSTARS_INDI) << "INDI: Stopping local drivers...";
463 
464     // #1 Disconnect all clients
465     for (DriverInfo *dv : dList)
466     {
467         ClientManager *cm = dv->getClientManager();
468 
469         if (cm == nullptr)
470             continue;
471 
472         cm->removeManagedDriver(dv);
473 
474         if (cm->count() == 0)
475         {
476             GUIManager::Instance()->removeClient(cm);
477             INDIListener::Instance()->removeClient(cm);
478             cm->disconnectServer();
479             clients.removeOne(cm);
480             cm->deleteLater();
481 
482             KStars::Instance()->slotSetTelescopeEnabled(false);
483             KStars::Instance()->slotSetDomeEnabled(false);
484         }
485     }
486 
487     // #2 Disconnect all servers
488     for (DriverInfo *dv : dList)
489     {
490         ServerManager *sm = dv->getServerManager();
491 
492         if (sm != nullptr)
493         {
494             sm->stopDriver(dv);
495 
496             if (sm->size() == 0)
497             {
498                 sm->stop();
499                 servers.removeOne(sm);
500                 sm->deleteLater();
501             }
502         }
503     }
504 
505     // Reset current port
506     currentPort = Options::serverPortStart() - 1;
507 
508     updateMenuActions();
509 }
510 
clearServers()511 void DriverManager::clearServers()
512 {
513     foreach (ServerManager *serverManager, servers)
514         serverManager->terminate();
515 
516     qDeleteAll(servers);
517 }
518 
activateRunService()519 void DriverManager::activateRunService()
520 {
521     processLocalTree(true);
522 }
523 
activateStopService()524 void DriverManager::activateStopService()
525 {
526     processLocalTree(false);
527 }
528 
activateHostConnection()529 void DriverManager::activateHostConnection()
530 {
531     processRemoteTree(true);
532 }
533 
activateHostDisconnection()534 void DriverManager::activateHostDisconnection()
535 {
536     processRemoteTree(false);
537 }
538 
getClientManager(DriverInfo * dv)539 ClientManager *DriverManager::getClientManager(DriverInfo *dv)
540 {
541     return dv->getClientManager();
542 }
543 
updateLocalTab()544 void DriverManager::updateLocalTab()
545 {
546     if (ui->localTreeWidget->currentItem() == nullptr)
547         return;
548 
549     QString currentDriver = ui->localTreeWidget->currentItem()->text(LOCAL_NAME_COLUMN);
550 
551     foreach (DriverInfo *device, driversList)
552     {
553         if (currentDriver == device->getLabel())
554         {
555             processDeviceStatus(device);
556             break;
557         }
558     }
559 }
560 
updateClientTab()561 void DriverManager::updateClientTab()
562 {
563     QTreeWidgetItem *item = ui->clientTreeWidget->currentItem();
564 
565     if (item == nullptr)
566         return;
567 
568     QString hostname = item->text(HOST_NAME_COLUMN);
569     QString hostport = item->text(HOST_PORT_COLUMN);
570 
571     for (auto &dv : driversList)
572     {
573         if (hostname == dv->getName() && hostport == dv->getPort())
574         {
575             processDeviceStatus(dv);
576             break;
577         }
578     }
579 }
580 
processLocalTree(bool dState)581 void DriverManager::processLocalTree(bool dState)
582 {
583     QList<DriverInfo *> processed_devices;
584 
585     int port    = -1;
586     bool portOK = false;
587 
588     connectionMode = ui->localR->isChecked() ? SERVER_CLIENT : SERVER_ONLY;
589 
590     foreach (QTreeWidgetItem *item, ui->localTreeWidget->selectedItems())
591     {
592         foreach (DriverInfo *device, driversList)
593         {
594             port = -1;
595 
596             //device->state = (dev_request == DriverInfo::DEV_TERMINATE) ? DriverInfo::DEV_START : DriverInfo::DEV_TERMINATE;
597             if (item->text(LOCAL_NAME_COLUMN) == device->getLabel() &&
598                     device->getServerState() != dState)
599             {
600                 processed_devices.append(device);
601 
602                 // N.B. If multiple devices are selected to run under one device manager
603                 // then we select the port for the first device that has a valid port
604                 // entry, the rest are ignored.
605                 if (port == -1 && item->text(LOCAL_PORT_COLUMN).isEmpty() == false)
606                 {
607                     port = item->text(LOCAL_PORT_COLUMN).toInt(&portOK);
608                     // If we encounter conversion error, we abort
609                     if (portOK == false)
610                     {
611                         KSNotification::error(i18n("Invalid port entry: %1",
612                                                    item->text(LOCAL_PORT_COLUMN)));
613                         return;
614                     }
615                 }
616 
617                 device->setHostParameters("localhost", QString("%1").arg(port));
618             }
619         }
620     }
621 
622     if (processed_devices.empty())
623         return;
624 
625     if (dState)
626         startDevices(processed_devices);
627     else
628         stopDevices(processed_devices);
629 }
630 
processClientTermination(ClientManager * client)631 void DriverManager::processClientTermination(ClientManager *client)
632 {
633     if (client == nullptr)
634         return;
635 
636     ServerManager *manager = client->getServerManager();
637 
638     if (manager)
639     {
640         servers.removeOne(manager);
641         delete manager;
642     }
643 
644     emit serverTerminated(client->getHost(), QString("%1").arg(client->getPort()));
645 
646     GUIManager::Instance()->removeClient(client);
647     INDIListener::Instance()->removeClient(client);
648 
649     KSNotification::event(
650         QLatin1String("IndiServerMessage"),
651         i18n("Connection to INDI host at %1 on port %2 lost. Server disconnected.",
652              client->getHost(), client->getPort()),
653         KSNotification::EVENT_ALERT);
654 
655     clients.removeOne(client);
656     client->deleteLater();
657 
658     updateMenuActions();
659     updateLocalTab();
660 }
661 
processServerTermination(ServerManager * server)662 void DriverManager::processServerTermination(ServerManager *server)
663 {
664     if (server == nullptr)
665         return;
666 
667     for (auto &dv : driversList)
668         if (dv->getServerManager() == server)
669         {
670             dv->reset();
671         }
672 
673     if (server->getMode() == SERVER_ONLY)
674     {
675         KSNotification::error(
676             i18n("Connection to INDI host at %1 on port %2 encountered an error: %3.",
677                  server->getHost(), server->getPort(), server->errorString()));
678     }
679 
680     emit serverTerminated(server->getHost(), server->getPort());
681     servers.removeOne(server);
682     server->deleteLater();
683 
684     updateLocalTab();
685 }
686 
processRemoteTree(bool dState)687 void DriverManager::processRemoteTree(bool dState)
688 {
689     QTreeWidgetItem *currentItem = ui->clientTreeWidget->currentItem();
690     if (!currentItem)
691         return;
692 
693     for (auto &dv : driversList)
694     {
695         if (dv->getDriverSource() != HOST_SOURCE)
696             continue;
697 
698         //qDebug() << "Current item port " << currentItem->text(HOST_PORT_COLUMN) << " current dev " << dv->getName() << " -- port " << dv->getPort() << endl;
699         //qDebug() << "dState is : " << (dState ? "True" : "False") << endl;
700 
701         if (currentItem->text(HOST_NAME_COLUMN) == dv->getName() &&
702                 currentItem->text(HOST_PORT_COLUMN) == dv->getPort())
703         {
704             // Nothing changed, return
705             if (dv->getClientState() == dState)
706                 return;
707 
708             // connect to host
709             if (dState)
710                 connectRemoteHost(dv);
711             // Disconnect form host
712             else
713                 disconnectRemoteHost(dv);
714         }
715     }
716 }
717 
connectRemoteHost(DriverInfo * dv)718 bool DriverManager::connectRemoteHost(DriverInfo *dv)
719 {
720     bool hostPortOk              = false;
721     bool connectionToServer      = false;
722     ClientManager *clientManager = nullptr;
723 
724     dv->getPort().toInt(&hostPortOk);
725 
726     if (hostPortOk == false)
727     {
728         KSNotification::error(i18n("Invalid host port %1", dv->getPort()));
729         return false;
730     }
731 
732     clientManager = new ClientManager();
733 
734     clientManager->appendManagedDriver(dv);
735 
736     connect(clientManager, &ClientManager::connectionFailure, this,
737             &DriverManager::processClientTermination);
738 
739     clientManager->setServer(dv->getHost().toLatin1().constData(),
740                              static_cast<uint>(dv->getPort().toInt()));
741 
742     GUIManager::Instance()->addClient(clientManager);
743     INDIListener::Instance()->addClient(clientManager);
744 
745     for (int i = 0; i < INDI_MAX_TRIES; i++)
746     {
747         connectionToServer = clientManager->connectServer();
748 
749         if (connectionToServer)
750             break;
751 
752         QThread::usleep(100000);
753     }
754 
755     if (connectionToServer)
756     {
757         clients.append(clientManager);
758         updateMenuActions();
759 
760         KSNotification::event(QLatin1String("ConnectionSuccessful"),
761                               i18n("Connected to INDI server"),
762                               KSNotification::EVENT_INFO);
763     }
764     else
765     {
766         GUIManager::Instance()->removeClient(clientManager);
767         INDIListener::Instance()->removeClient(clientManager);
768 
769         KSNotification::error(i18n("Connection to INDI server at host %1 with port %2 failed.",
770                                    dv->getHost(), dv->getPort()), i18nc("@title:window", "Error"), 30);
771         processDeviceStatus(dv);
772         return false;
773     }
774 
775     return true;
776 }
777 
disconnectRemoteHost(DriverInfo * dv)778 bool DriverManager::disconnectRemoteHost(DriverInfo *dv)
779 {
780     ClientManager *clientManager = dv->getClientManager();
781 
782     if (clientManager)
783     {
784         clientManager->removeManagedDriver(dv);
785         clientManager->disconnectAll();
786         GUIManager::Instance()->removeClient(clientManager);
787         INDIListener::Instance()->removeClient(clientManager);
788         clients.removeOne(clientManager);
789         clientManager->deleteLater();
790         updateMenuActions();
791         return true;
792     }
793 
794     return false;
795 }
796 
resizeDeviceColumn()797 void DriverManager::resizeDeviceColumn()
798 {
799     ui->localTreeWidget->resizeColumnToContents(0);
800 }
801 
updateMenuActions()802 void DriverManager::updateMenuActions()
803 {
804     // We iterate over devices, we enable INDI Control Panel if we have any active device
805     // We enable capture image sequence if we have any imaging device
806 
807     QAction *tmpAction = nullptr;
808     bool activeDevice  = false;
809 
810     if (clients.size() > 0)
811         activeDevice = true;
812 
813     tmpAction = KStars::Instance()->actionCollection()->action("indi_cpl");
814     if (tmpAction != nullptr)
815     {
816         //qDebug() << "indi_cpl action set to active" << endl;
817         tmpAction->setEnabled(activeDevice);
818     }
819 }
820 
getINDIPort(int customPort)821 int DriverManager::getINDIPort(int customPort)
822 {
823 #ifdef Q_OS_WIN
824     qWarning() << "INDI server is currently not supported on Windows.";
825     return -1;
826 #else
827     int lastPort = Options::serverPortEnd();
828     bool success = false;
829     currentPort++;
830 
831     // recycle
832     if (currentPort > lastPort)
833         currentPort = Options::serverPortStart();
834 
835     QTcpServer temp_server;
836 
837     if (customPort != -1)
838     {
839         success = temp_server.listen(QHostAddress::LocalHost, customPort);
840         if (success)
841         {
842             temp_server.close();
843             return customPort;
844         }
845         else
846             return -1;
847     }
848 
849     for (; currentPort <= lastPort; currentPort++)
850     {
851         success = temp_server.listen(QHostAddress::LocalHost, currentPort);
852         if (success)
853         {
854             temp_server.close();
855             return currentPort;
856         }
857     }
858     return -1;
859 #endif
860 }
861 
readINDIHosts()862 bool DriverManager::readINDIHosts()
863 {
864     QString indiFile("indihosts.xml");
865     //QFile localeFile;
866     QFile file;
867     char errmsg[1024];
868     char c;
869     LilXML *xmlParser = newLilXML();
870     XMLEle *root      = nullptr;
871     XMLAtt *ap        = nullptr;
872     QString hName, hHost, hPort;
873 
874     lastGroup = nullptr;
875     file.setFileName(KSPaths::locate(QStandardPaths::AppDataLocation, indiFile));
876     if (file.fileName().isEmpty() || !file.open(QIODevice::ReadOnly))
877     {
878         delLilXML(xmlParser);
879         return false;
880     }
881 
882     while (file.getChar(&c))
883     {
884         root = readXMLEle(xmlParser, c, errmsg);
885 
886         if (root)
887         {
888             // Get host name
889             ap = findXMLAtt(root, "name");
890             if (!ap)
891             {
892                 delLilXML(xmlParser);
893                 return false;
894             }
895 
896             hName = QString(valuXMLAtt(ap));
897 
898             // Get host name
899             ap = findXMLAtt(root, "hostname");
900 
901             if (!ap)
902             {
903                 delLilXML(xmlParser);
904                 return false;
905             }
906 
907             hHost = QString(valuXMLAtt(ap));
908 
909             ap = findXMLAtt(root, "port");
910 
911             if (!ap)
912             {
913                 delLilXML(xmlParser);
914                 return false;
915             }
916 
917             hPort = QString(valuXMLAtt(ap));
918 
919             DriverInfo *dv = new DriverInfo(hName);
920             dv->setHostParameters(hHost, hPort);
921             dv->setDriverSource(HOST_SOURCE);
922 
923             connect(dv, &DriverInfo::deviceStateChanged, this, &DriverManager::processDeviceStatus);
924 
925             driversList.append(dv);
926 
927             QTreeWidgetItem *item = new QTreeWidgetItem(ui->clientTreeWidget, lastGroup);
928             lastGroup             = item;
929             item->setIcon(HOST_STATUS_COLUMN, ui->disconnected);
930             item->setText(HOST_NAME_COLUMN, hName);
931             item->setText(HOST_PORT_COLUMN, hPort);
932 
933             delXMLEle(root);
934         }
935         else if (errmsg[0])
936         {
937             qDebug() << errmsg;
938             delLilXML(xmlParser);
939             return false;
940         }
941     }
942 
943     delLilXML(xmlParser);
944 
945     return true;
946 }
947 
readXMLDrivers()948 bool DriverManager::readXMLDrivers()
949 {
950     QDir indiDir;
951     QString driverName;
952 
953     // This is the XML file shipped with KStars that contains all supported INDI drivers.
954     /*QString indiDriversXML = KSPaths::locate(QStandardPaths::AppDataLocation, "indidrivers.xml");
955     if (indiDriversXML.isEmpty() == false)
956         processXMLDriver(indiDriversXML);
957     */
958 
959     processXMLDriver(QLatin1String(":/indidrivers.xml"));
960 
961     QString driversDir = Options::indiDriversDir();
962 #ifdef Q_OS_OSX
963     if (Options::indiDriversAreInternal())
964         driversDir =
965             QCoreApplication::applicationDirPath() + "/../Resources/DriverSupport";
966 #endif
967 
968     if (indiDir.cd(driversDir) == false)
969     {
970         KSNotification::error(i18n("Unable to find INDI drivers directory: %1\nPlease "
971                                    "make sure to set the correct "
972                                    "path in KStars configuration",
973                                    driversDir));
974         return false;
975     }
976 
977     indiDir.setNameFilters(QStringList() << "indi_*.xml"
978                            << "drivers.xml");
979     indiDir.setFilter(QDir::Files | QDir::NoSymLinks);
980     QFileInfoList list = indiDir.entryInfoList();
981 
982     for (auto &fileInfo : list)
983     {
984         // libindi 0.7.1: Skip skeleton files
985         if (fileInfo.fileName().endsWith(QLatin1String("_sk.xml")))
986             continue;
987 
988         //        if (fileInfo.fileName() == "drivers.xml")
989         //        {
990         //            // Let first attempt to load the local version of drivers.xml
991         //            driverName = QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("drivers.xml");
992 
993         //            // If found, we continue, otherwise, we load the system file
994         //            if (driverName.isEmpty() == false && QFile(driverName).exists())
995         //            {
996         //                processXMLDriver(driverName);
997         //                continue;
998         //            }
999         //        }
1000 
1001         processXMLDriver(fileInfo.absoluteFilePath());
1002     }
1003 
1004     return true;
1005 }
1006 
processXMLDriver(const QString & driverName)1007 void DriverManager::processXMLDriver(const QString &driverName)
1008 {
1009     QFile file(driverName);
1010     if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
1011     {
1012         KSNotification::error(i18n("Failed to open INDI Driver file: %1", driverName));
1013         return;
1014     }
1015 
1016     char errmsg[ERRMSG_SIZE];
1017     char c;
1018     LilXML *xmlParser = newLilXML();
1019     XMLEle *root      = nullptr;
1020     XMLEle *ep        = nullptr;
1021 
1022     if (driverName.endsWith(QLatin1String("drivers.xml")))
1023         driverSource = PRIMARY_XML;
1024     else
1025         driverSource = THIRD_PARTY_XML;
1026 
1027     while (file.getChar(&c))
1028     {
1029         root = readXMLEle(xmlParser, c, errmsg);
1030 
1031         if (root)
1032         {
1033             // If the XML file is using the INDI Library v1.3+ format
1034             if (!strcmp(tagXMLEle(root), "driversList"))
1035             {
1036                 for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
1037                 {
1038                     if (!buildDeviceGroup(ep, errmsg))
1039                         prXMLEle(stderr, ep, 0);
1040                 }
1041             }
1042             // If using the older format
1043             else
1044             {
1045                 if (!buildDeviceGroup(root, errmsg))
1046                     prXMLEle(stderr, root, 0);
1047             }
1048 
1049             delXMLEle(root);
1050         }
1051         else if (errmsg[0])
1052         {
1053             qCDebug(KSTARS_INDI) << QString(errmsg);
1054             delLilXML(xmlParser);
1055             return;
1056         }
1057     }
1058 
1059     delLilXML(xmlParser);
1060 }
1061 
buildDeviceGroup(XMLEle * root,char errmsg[])1062 bool DriverManager::buildDeviceGroup(XMLEle *root, char errmsg[])
1063 {
1064     XMLAtt *ap;
1065     XMLEle *ep;
1066     QString groupName;
1067     QTreeWidgetItem *group;
1068     DeviceFamily groupType = KSTARS_TELESCOPE;
1069 
1070     // avoid overflow
1071     if (strlen(tagXMLEle(root)) > 1024)
1072         return false;
1073 
1074     // Get device grouping name
1075     ap = findXMLAtt(root, "group");
1076 
1077     if (!ap)
1078     {
1079         snprintf(errmsg, ERRMSG_SIZE, "Tag %.64s does not have a group attribute",
1080                  tagXMLEle(root));
1081         return false;
1082     }
1083 
1084     groupName = valuXMLAtt(ap);
1085     groupType = DeviceFamilyLabels.key(groupName);
1086 
1087 #ifndef HAVE_CFITSIO
1088     // We do not create these groups if we don't have CFITSIO support
1089     if (groupType == KSTARS_CCD || groupType == KSTARS_VIDEO)
1090         return true;
1091 #endif
1092 
1093     // Find if the group already exists
1094     QList<QTreeWidgetItem *> treeList =
1095         ui->localTreeWidget->findItems(groupName, Qt::MatchExactly);
1096     if (!treeList.isEmpty())
1097         group = treeList[0];
1098     else
1099         group = new QTreeWidgetItem(ui->localTreeWidget, lastGroup);
1100 
1101     group->setText(0, groupName);
1102     lastGroup = group;
1103 
1104     for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
1105     {
1106         if (!buildDriverElement(ep, group, groupType, errmsg))
1107             return false;
1108     }
1109 
1110     return true;
1111 }
1112 
buildDriverElement(XMLEle * root,QTreeWidgetItem * DGroup,DeviceFamily groupType,char errmsg[])1113 bool DriverManager::buildDriverElement(XMLEle *root, QTreeWidgetItem *DGroup,
1114                                        DeviceFamily groupType, char errmsg[])
1115 {
1116     XMLAtt *ap;
1117     XMLEle *el;
1118     DriverInfo *dv;
1119     QString label;
1120     QString driver;
1121     QString version;
1122     // N.B. NOT an i18n string.
1123     QString manufacturer("Others");
1124     QString name;
1125     QString port;
1126     QString skel;
1127     QVariantMap vMap;
1128     //double focal_length(-1), aperture(-1);
1129 
1130     ap = findXMLAtt(root, "label");
1131     if (!ap)
1132     {
1133         snprintf(errmsg, ERRMSG_SIZE, "Tag %.64s does not have a label attribute",
1134                  tagXMLEle(root));
1135         return false;
1136     }
1137 
1138     label = valuXMLAtt(ap);
1139 
1140     // Label is unique, so if we have the same label, we simply ignore
1141     if (findDriverByLabel(label) != nullptr)
1142         return true;
1143 
1144     ap = findXMLAtt(root, "manufacturer");
1145     if (ap)
1146         manufacturer = valuXMLAtt(ap);
1147 
1148     // Search for optional port attribute
1149     ap = findXMLAtt(root, "port");
1150     if (ap)
1151         port = valuXMLAtt(ap);
1152 
1153     // Search for skel file, if any
1154     // Search for optional port attribute
1155     ap = findXMLAtt(root, "skel");
1156     if (ap)
1157         skel = valuXMLAtt(ap);
1158 
1159     // Let's look for telescope-specific attributes: focal length and aperture
1160     //    ap = findXMLAtt(root, "focal_length");
1161     //    if (ap)
1162     //    {
1163     //        focal_length = QString(valuXMLAtt(ap)).toDouble();
1164     //        if (focal_length > 0)
1165     //            vMap.insert("TELESCOPE_FOCAL_LENGTH", focal_length);
1166     //    }
1167 
1168     // Find MDPD: Multiple Devices Per Driver
1169     ap = findXMLAtt(root, "mdpd");
1170     if (ap)
1171     {
1172         bool mdpd = false;
1173         mdpd      = (QString(valuXMLAtt(ap)) == QString("true")) ? true : false;
1174         vMap.insert("mdpd", mdpd);
1175     }
1176 
1177     //    ap = findXMLAtt(root, "aperture");
1178     //    if (ap)
1179     //    {
1180     //        aperture = QString(valuXMLAtt(ap)).toDouble();
1181     //        if (aperture > 0)
1182     //            vMap.insert("TELESCOPE_APERTURE", aperture);
1183     //    }
1184 
1185     el = findXMLEle(root, "driver");
1186 
1187     if (!el)
1188         return false;
1189 
1190     driver = pcdataXMLEle(el);
1191 
1192     ap = findXMLAtt(el, "name");
1193     if (!ap)
1194     {
1195         snprintf(errmsg, ERRMSG_SIZE, "Tag %.64s does not have a name attribute",
1196                  tagXMLEle(el));
1197         return false;
1198     }
1199 
1200     name = valuXMLAtt(ap);
1201 
1202     el = findXMLEle(root, "version");
1203 
1204     if (!el)
1205         return false;
1206 
1207     version        = pcdataXMLEle(el);
1208     bool versionOK = false;
1209     version.toDouble(&versionOK);
1210     if (versionOK == false)
1211         version = "1.0";
1212 
1213     bool driverIsAvailable = checkDriverAvailability(driver);
1214 
1215     vMap.insert("LOCALLY_AVAILABLE", driverIsAvailable);
1216     QIcon remoteIcon = QIcon::fromTheme("network-modem");
1217 
1218     QTreeWidgetItem *device = new QTreeWidgetItem(DGroup);
1219 
1220     device->setText(LOCAL_NAME_COLUMN, label);
1221     if (driverIsAvailable)
1222         device->setIcon(LOCAL_STATUS_COLUMN, ui->stopPix);
1223     else
1224         device->setIcon(LOCAL_STATUS_COLUMN, remoteIcon);
1225     device->setText(LOCAL_VERSION_COLUMN, version);
1226     device->setText(LOCAL_PORT_COLUMN, port);
1227 
1228     //if ((driverSource == PRIMARY_XML) && driversStringList.contains(driver) == false)
1229     if (groupType == KSTARS_TELESCOPE && driversStringList.contains(driver) == false)
1230         driversStringList.append(driver);
1231 
1232     dv = new DriverInfo(name);
1233 
1234     dv->setLabel(label);
1235     dv->setVersion(version);
1236     dv->setExecutable(driver);
1237     dv->setManufacturer(manufacturer);
1238     dv->setSkeletonFile(skel);
1239     dv->setType(groupType);
1240     dv->setDriverSource(driverSource);
1241     dv->setUserPort(port);
1242     dv->setAuxInfo(vMap);
1243 
1244     connect(dv, &DriverInfo::deviceStateChanged, this, &DriverManager::processDeviceStatus);
1245 
1246     driversList.append(dv);
1247 
1248     return true;
1249 }
1250 
checkDriverAvailability(const QString & driver)1251 bool DriverManager::checkDriverAvailability(const QString &driver)
1252 {
1253     QString indiServerDir = Options::indiServer();
1254     if (Options::indiServerIsInternal())
1255         indiServerDir = QCoreApplication::applicationDirPath() + "/indi";
1256     else
1257         indiServerDir = QFileInfo(Options::indiServer()).dir().path();
1258 
1259     QFile driverFile(indiServerDir + '/' + driver);
1260 
1261     if (driverFile.exists() == false)
1262         return (!QStandardPaths::findExecutable(indiServerDir + '/' + driver).isEmpty());
1263 
1264     return true;
1265 }
1266 
updateCustomDrivers()1267 void DriverManager::updateCustomDrivers()
1268 {
1269     for (const QVariantMap &oneDriver : m_CustomDrivers->customDrivers())
1270     {
1271         DriverInfo *dv = new DriverInfo(oneDriver["Name"].toString());
1272         dv->setLabel(oneDriver["Label"].toString());
1273         dv->setUniqueLabel(dv->getLabel());
1274         dv->setExecutable(oneDriver["Exec"].toString());
1275         dv->setVersion(oneDriver["Version"].toString());
1276         dv->setManufacturer(oneDriver["Manufacturer"].toString());
1277         dv->setType(DeviceFamilyLabels.key(oneDriver["Family"].toString()));
1278         dv->setDriverSource(CUSTOM_SOURCE);
1279 
1280         bool driverIsAvailable = checkDriverAvailability(oneDriver["Exec"].toString());
1281         QVariantMap vMap;
1282         vMap.insert("LOCALLY_AVAILABLE", driverIsAvailable);
1283         dv->setAuxInfo(vMap);
1284 
1285         driversList.append(dv);
1286     }
1287 }
1288 
1289 // JM 2018-07-23: Disabling the old custom drivers method
1290 #if 0
1291 void DriverManager::updateCustomDrivers()
1292 {
1293     QString label;
1294     QString driver;
1295     QString version;
1296     QString name;
1297     QTreeWidgetItem *group     = nullptr;
1298     QTreeWidgetItem *widgetDev = nullptr;
1299     QVariantMap vMap;
1300     DriverInfo *drv = nullptr;
1301 
1302     // Find if the group already exists
1303     QList<QTreeWidgetItem *> treeList = ui->localTreeWidget->findItems("Telescopes", Qt::MatchExactly);
1304     if (!treeList.isEmpty())
1305         group = treeList[0];
1306     else
1307         return;
1308 
1309     KStarsData::Instance()->logObject()->readAll();
1310 
1311     // Find custom telescope to ADD/UPDATE
1312     foreach (OAL::Scope *s, *(KStarsData::Instance()->logObject()->scopeList()))
1313     {
1314         name = label = s->name();
1315 
1316         if (s->driver() == i18n("None"))
1317             continue;
1318 
1319         // If driver already exists, just update values
1320         if ((drv = findDriverByLabel(label)))
1321         {
1322             if (s->aperture() > 0 && s->focalLength() > 0)
1323             {
1324                 vMap.insert("TELESCOPE_APERTURE", s->aperture());
1325                 vMap.insert("TELESCOPE_FOCAL_LENGTH", s->focalLength());
1326                 drv->setAuxInfo(vMap);
1327             }
1328 
1329             drv->setExecutable(s->driver());
1330 
1331             continue;
1332         }
1333 
1334         driver  = s->driver();
1335         version = QString("1.0");
1336 
1337         QTreeWidgetItem *device = new QTreeWidgetItem(group);
1338         device->setText(LOCAL_NAME_COLUMN, QString(label));
1339         device->setIcon(LOCAL_STATUS_COLUMN, ui->stopPix);
1340         device->setText(LOCAL_VERSION_COLUMN, QString(version));
1341 
1342         DriverInfo *dv = new DriverInfo(name);
1343 
1344         dv->setLabel(label);
1345         dv->setExecutable(driver);
1346         dv->setVersion(version);
1347         dv->setType(KSTARS_TELESCOPE);
1348         dv->setDriverSource(EM_XML);
1349 
1350         if (s->aperture() > 0 && s->focalLength() > 0)
1351         {
1352             vMap.insert("TELESCOPE_APERTURE", s->aperture());
1353             vMap.insert("TELESCOPE_FOCAL_LENGTH", s->focalLength());
1354             dv->setAuxInfo(vMap);
1355         }
1356 
1357         connect(dv, SIGNAL(deviceStateChanged(DriverInfo*)), this, SLOT(processDeviceStatus(DriverInfo*)));
1358         driversList.append(dv);
1359     }
1360 
1361     // Find custom telescope to REMOVE
1362     foreach (DriverInfo *dev, driversList)
1363     {
1364         // If it's from primary xml file or it is in a running state, continue.
1365         if (dev->getDriverSource() != EM_XML || dev->getClientState())
1366             continue;
1367 
1368         if (KStarsData::Instance()->logObject()->findScopeByName(dev->getName()))
1369             continue;
1370 
1371         // Find if the group already exists
1372         QList<QTreeWidgetItem *> devList =
1373             ui->localTreeWidget->findItems(dev->getLabel(), Qt::MatchExactly | Qt::MatchRecursive);
1374         if (!devList.isEmpty())
1375         {
1376             widgetDev = devList[0];
1377             group->removeChild(widgetDev);
1378         }
1379         else
1380             return;
1381 
1382         driversList.removeOne(dev);
1383         delete (dev);
1384     }
1385 }
1386 #endif
1387 
addINDIHost()1388 void DriverManager::addINDIHost()
1389 {
1390     QDialog hostConfDialog;
1391     Ui::INDIHostConf hostConf;
1392     hostConf.setupUi(&hostConfDialog);
1393     hostConfDialog.setWindowTitle(i18nc("@title:window", "Add Host"));
1394     bool portOk = false;
1395 
1396     if (hostConfDialog.exec() == QDialog::Accepted)
1397     {
1398         DriverInfo *hostItem = new DriverInfo(hostConf.nameIN->text());
1399 
1400         hostConf.portnumber->text().toInt(&portOk);
1401 
1402         if (portOk == false)
1403         {
1404             KSNotification::error(i18n("Error: the port number is invalid."));
1405             delete hostItem;
1406             return;
1407         }
1408 
1409         hostItem->setHostParameters(hostConf.hostname->text(),
1410                                     hostConf.portnumber->text());
1411 
1412         //search for duplicates
1413         //for (uint i=0; i < ksw->data()->INDIHostsList.count(); i++)
1414         foreach (DriverInfo *host, driversList)
1415             if (hostItem->getName() == host->getName() &&
1416                     hostItem->getPort() == host->getPort())
1417             {
1418                 KSNotification::error(i18n("Host: %1 Port: %2 already exists.",
1419                                            hostItem->getName(), hostItem->getPort()));
1420                 delete hostItem;
1421                 return;
1422             }
1423 
1424         hostItem->setDriverSource(HOST_SOURCE);
1425 
1426         connect(hostItem, &DriverInfo::deviceStateChanged, this, &DriverManager::processDeviceStatus);
1427 
1428         driversList.append(hostItem);
1429 
1430         QTreeWidgetItem *item = new QTreeWidgetItem(ui->clientTreeWidget);
1431         item->setIcon(HOST_STATUS_COLUMN, ui->disconnected);
1432         item->setText(HOST_NAME_COLUMN, hostConf.nameIN->text());
1433         item->setText(HOST_PORT_COLUMN, hostConf.portnumber->text());
1434     }
1435 
1436     saveHosts();
1437 }
1438 
modifyINDIHost()1439 void DriverManager::modifyINDIHost()
1440 {
1441     QDialog hostConfDialog;
1442     Ui::INDIHostConf hostConf;
1443     hostConf.setupUi(&hostConfDialog);
1444     hostConfDialog.setWindowTitle(i18nc("@title:window", "Modify Host"));
1445 
1446     QTreeWidgetItem *currentItem = ui->clientTreeWidget->currentItem();
1447 
1448     if (currentItem == nullptr)
1449         return;
1450 
1451     foreach (DriverInfo *host, driversList)
1452     {
1453         if (currentItem->text(HOST_NAME_COLUMN) == host->getName() &&
1454                 currentItem->text(HOST_PORT_COLUMN) == host->getPort())
1455         {
1456             hostConf.nameIN->setText(host->getName());
1457             hostConf.hostname->setText(host->getHost());
1458             hostConf.portnumber->setText(host->getPort());
1459 
1460             if (hostConfDialog.exec() == QDialog::Accepted)
1461             {
1462                 //INDIHostsInfo *hostItem = new INDIHostsInfo;
1463                 host->setName(hostConf.nameIN->text());
1464                 host->setHostParameters(hostConf.hostname->text(),
1465                                         hostConf.portnumber->text());
1466 
1467                 currentItem->setText(HOST_NAME_COLUMN, hostConf.nameIN->text());
1468                 currentItem->setText(HOST_PORT_COLUMN, hostConf.portnumber->text());
1469 
1470                 //ksw->data()->INDIHostsList.replace(i, hostItem);
1471 
1472                 saveHosts();
1473                 return;
1474             }
1475         }
1476     }
1477 }
1478 
removeINDIHost()1479 void DriverManager::removeINDIHost()
1480 {
1481     if (ui->clientTreeWidget->currentItem() == nullptr)
1482         return;
1483 
1484     foreach (DriverInfo *host, driversList)
1485         if (ui->clientTreeWidget->currentItem()->text(HOST_NAME_COLUMN) ==
1486                 host->getName() &&
1487                 ui->clientTreeWidget->currentItem()->text(HOST_PORT_COLUMN) ==
1488                 host->getPort())
1489         {
1490             if (host->getClientState())
1491             {
1492                 KSNotification::error(
1493                     i18n("You need to disconnect the client before removing it."));
1494                 return;
1495             }
1496 
1497             if (KMessageBox::warningContinueCancel(
1498                         nullptr,
1499                         i18n("Are you sure you want to remove the %1 client?",
1500                              ui->clientTreeWidget->currentItem()->text(HOST_NAME_COLUMN)),
1501                         i18n("Delete Confirmation"),
1502                         KStandardGuiItem::del()) != KMessageBox::Continue)
1503                 return;
1504 
1505             driversList.removeOne(host);
1506             delete host;
1507             delete (ui->clientTreeWidget->currentItem());
1508             break;
1509         }
1510 
1511     saveHosts();
1512 }
1513 
saveHosts()1514 void DriverManager::saveHosts()
1515 {
1516     QFile file;
1517     QString hostData;
1518 
1519     //determine filename in local user KDE directory tree.
1520     file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation))
1521                      .filePath("indihosts.xml"));
1522 
1523     if (!file.open(QIODevice::WriteOnly))
1524     {
1525         KSNotification::sorry(i18n("Unable to write to file 'indihosts.xml'\nAny changes "
1526                                    "to INDI hosts configurations will not be saved."),
1527                               i18n("Could Not Open File"));
1528         return;
1529     }
1530 
1531     QTextStream outstream(&file);
1532 
1533     //for (uint i= 0; i < ksw->data()->INDIHostsList.count(); i++)
1534     foreach (DriverInfo *host, driversList)
1535     {
1536         if (host->getDriverSource() != HOST_SOURCE)
1537             continue;
1538 
1539         hostData = "<INDIHost name='";
1540         hostData += host->getName();
1541         hostData += "' hostname='";
1542         hostData += host->getHost();
1543         hostData += "' port='";
1544         hostData += host->getPort();
1545         hostData += "' />\n";
1546 
1547         outstream << hostData;
1548     }
1549 
1550     file.close();
1551 }
1552 
findDriverByName(const QString & name)1553 DriverInfo *DriverManager::findDriverByName(const QString &name)
1554 {
1555     for (auto &dv : driversList)
1556     {
1557         if (dv->getName() == name)
1558             return dv;
1559     }
1560 
1561     return nullptr;
1562 }
1563 
findDriverByLabel(const QString & label)1564 DriverInfo *DriverManager::findDriverByLabel(const QString &label)
1565 {
1566     for (auto &dv : driversList)
1567     {
1568         if (dv->getLabel() == label)
1569             return dv;
1570     }
1571 
1572     return nullptr;
1573 }
1574 
findDriverByExec(const QString & exec)1575 DriverInfo *DriverManager::findDriverByExec(const QString &exec)
1576 {
1577     for (auto &dv : driversList)
1578     {
1579         if (dv->getExecutable() == exec)
1580             return dv;
1581     }
1582 
1583     return nullptr;
1584 }
1585 
getUniqueDeviceLabel(const QString & label)1586 QString DriverManager::getUniqueDeviceLabel(const QString &label)
1587 {
1588     int nset            = 0;
1589     QString uniqueLabel = label;
1590 
1591     for (auto &cm : clients)
1592     {
1593         auto &devices = cm->getDevices();
1594 
1595         for (auto &dv : devices)
1596         {
1597             if (label == QString(dv->getDeviceName()))
1598                 nset++;
1599         }
1600     }
1601     if (nset > 0)
1602         uniqueLabel = QString("%1 %2").arg(label).arg(nset + 1);
1603 
1604     return uniqueLabel;
1605 }
1606 
getDriverList() const1607 QJsonArray DriverManager::getDriverList() const
1608 {
1609     QJsonArray driverArray;
1610 
1611     for (const auto &drv : driversList)
1612         driverArray.append(drv->toJson());
1613 
1614     return driverArray;
1615 }
1616 
restartDriver(DriverInfo * dv)1617 bool DriverManager::restartDriver(DriverInfo *dv)
1618 {
1619     for (auto &oneServer : servers)
1620     {
1621         if (oneServer->contains(dv))
1622         {
1623             return oneServer->restartDriver(dv);
1624         }
1625     }
1626 
1627     return false;
1628 }
1629