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

0001 /*
0002     SPDX-FileCopyrightText: 2001 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "indidriver.h"
0008 
0009 #include "config-kstars.h"
0010 
0011 #include "devicemanager.h"
0012 #include "indidevice.h"
0013 #include "indimenu.h"
0014 #include "kstars.h"
0015 #include "kstarsdata.h"
0016 #include "ksutils.h"
0017 #include "Options.h"
0018 #include "ui_indihostconf.h"
0019 #include "oal/log.h"
0020 #include "oal/scope.h"
0021 
0022 #include <KMessageBox>
0023 #include <KProcess>
0024 #include <KActionCollection>
0025 
0026 #include <QRadioButton>
0027 #include <QFile>
0028 #include <QDir>
0029 #include <QTextStream>
0030 #include <QTreeWidget>
0031 #include <QIcon>
0032 #include <QDialog>
0033 #include <QTcpServer>
0034 #include <QMenu>
0035 #include <QPushButton>
0036 #include <QLineEdit>
0037 #include <QAction>
0038 #include <QStandardPaths>
0039 
0040 #include <unistd.h>
0041 #include <sys/socket.h>
0042 #include <netinet/in.h>
0043 #include <netdb.h>
0044 
0045 #define MAX_RETRIES 3
0046 #define ERRMSG_SIZE 1024
0047 
0048 DeviceManagerUI::DeviceManagerUI(QWidget *parent) : QFrame(parent)
0049 {
0050 #ifdef Q_OS_OSX
0051     setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
0052 #endif
0053     setupUi(this);
0054 
0055     localTreeWidget->setSortingEnabled(false);
0056     localTreeWidget->setRootIsDecorated(true);
0057 
0058     clientTreeWidget->setSortingEnabled(false);
0059 
0060     runningPix = QIcon::fromTheme("system-run");
0061     stopPix    = QIcon::fromTheme("dialog-cancel");
0062     localMode  = QIcon::fromTheme("computer");
0063     serverMode = QIcon::fromTheme("network-server");
0064 
0065     connected    = QIcon::fromTheme("network-connect");
0066     disconnected = QIcon::fromTheme("network-disconnect");
0067 
0068     connect(localTreeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this,
0069             SLOT(makePortEditable(QTreeWidgetItem*,int)));
0070 }
0071 
0072 void DeviceManagerUI::makePortEditable(QTreeWidgetItem *selectedItem, int column)
0073 {
0074     // If it's the port column, then make it user-editable
0075     if (column == INDIDriver::LOCAL_PORT_COLUMN)
0076         selectedItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled);
0077 
0078     localTreeWidget->editItem(selectedItem, INDIDriver::LOCAL_PORT_COLUMN);
0079 }
0080 
0081 INDIDriver::INDIDriver(KStars *_ks) : QDialog(_ks), ksw(_ks)
0082 {
0083     currentPort = Options::serverPortStart().toInt() - 1;
0084     lastGroup   = nullptr;
0085     lastDevice  = nullptr;
0086 
0087     ui = new DeviceManagerUI(this);
0088     setMainWidget(ui);
0089     setCaption(xi18n("Device Manager"));
0090     setButtons(QDialog::Close);
0091 
0092     foreach (INDIHostsInfo *host, ksw->data()->INDIHostsList)
0093     {
0094         QTreeWidgetItem *item = new QTreeWidgetItem(ui->clientTreeWidget, lastGroup);
0095         lastGroup             = item;
0096         item->setIcon(HOST_STATUS_COLUMN, ui->disconnected);
0097         item->setText(HOST_NAME_COLUMN, host->name);
0098         item->setText(HOST_PORT_COLUMN, host->portnumber);
0099     }
0100 
0101     lastGroup = nullptr;
0102 
0103     QObject::connect(ui->addB, SIGNAL(clicked()), this, SLOT(addINDIHost()));
0104     QObject::connect(ui->modifyB, SIGNAL(clicked()), this, SLOT(modifyINDIHost()));
0105     QObject::connect(ui->removeB, SIGNAL(clicked()), this, SLOT(removeINDIHost()));
0106 
0107     //QObject::connect(ksw->indiMenu(), SIGNAL(driverDisconnected(int)), this, SLOT(shutdownHost(int)));
0108     QObject::connect(ui->connectHostB, SIGNAL(clicked()), this, SLOT(activateHostConnection()));
0109     QObject::connect(ui->disconnectHostB, SIGNAL(clicked()), this, SLOT(activateHostDisconnection()));
0110     QObject::connect(ui->runServiceB, SIGNAL(clicked()), this, SLOT(activateRunService()));
0111     QObject::connect(ui->stopServiceB, SIGNAL(clicked()), this, SLOT(activateStopService()));
0112     QObject::connect(ui->localTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(updateLocalTab()));
0113     QObject::connect(ui->clientTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(updateClientTab()));
0114     QObject::connect(ui->localTreeWidget, SIGNAL(expanded(QModelIndex)), this, SLOT(resizeDeviceColumn()));
0115 
0116     readXMLDrivers();
0117 }
0118 
0119 /*
0120 void INDIDriver::enableDevice(INDI_D *indi_device)
0121 {
0122    if (indi_device == nullptr)
0123     return;
0124 
0125    if (indi_device->deviceManager->mode == DeviceManager::M_CLIENT)
0126    {
0127     foreach (INDIHostsInfo * host, ksw->data()->INDIHostsList)
0128         {
0129 
0130             if (host->deviceManager == indi_device->deviceManager && host->isConnected == false)
0131             {
0132                 foreach (QTreeWidgetItem *item, ui->clientTreeWidget->findItems(host->name, Qt::MatchExactly, 1))
0133                                 item->setIcon(HOST_STATUS_COLUMN, ui->connected);
0134 
0135                     host->isConnected = true;
0136             updateClientTab();
0137             updateMenuActions();
0138             ksw->indiMenu()->show();
0139                     return;
0140             }
0141         }
0142     }
0143     else
0144       {
0145     foreach (IDevice *device, devices)
0146         {
0147         if (device->unique_label == indi_device->label)
0148         {
0149             foreach (QTreeWidgetItem *item, ui->localTreeWidget->findItems(device->tree_label, Qt::MatchExactly |  Qt::MatchRecursive))
0150             {
0151                                 item->setIcon(LOCAL_STATUS_COLUMN, ui->runningPix);
0152                                 item->setText(LOCAL_PORT_COLUMN, QString::number(indi_device->deviceManager->port));
0153             }
0154 
0155             updateLocalTab();
0156             updateMenuActions();
0157             ksw->indiMenu()->show();
0158             return;
0159         }
0160     }
0161       }
0162 
0163 }
0164 
0165 void INDIDriver::disableDevice(INDI_D *indi_device)
0166 {
0167     if (indi_device == nullptr)
0168     return;
0169 
0170    if (indi_device->deviceManager->mode == DeviceManager::M_CLIENT)
0171    {
0172     foreach (INDIHostsInfo * host, ksw->data()->INDIHostsList)
0173         {
0174             if (host->deviceManager == indi_device->deviceManager && host->isConnected == true)
0175             {
0176                 foreach (QTreeWidgetItem *item, ui->clientTreeWidget->findItems(host->name, Qt::MatchExactly,1))
0177                                 item->setIcon(HOST_STATUS_COLUMN, ui->disconnected);
0178 
0179             host->deviceManager = nullptr;
0180                 host->isConnected = false;
0181 
0182                 updateClientTab();
0183                     return;
0184             }
0185         }
0186     }
0187     else
0188       {
0189     foreach (IDevice *device, devices)
0190         {
0191         if (device->unique_label == indi_device->label)
0192         {
0193             foreach (QTreeWidgetItem *item, ui->localTreeWidget->findItems(device->tree_label, Qt::MatchExactly |  Qt::MatchRecursive))
0194             {
0195                                 item->setIcon(LOCAL_STATUS_COLUMN, ui->stopPix);
0196                                 item->setIcon(LOCAL_MODE_COLUMN, QIcon());
0197                                 item->setText(LOCAL_PORT_COLUMN, device->port);
0198             }
0199 
0200             device->clear();
0201             updateLocalTab();
0202 
0203             return;
0204         }
0205     }
0206       }
0207   }
0208   */
0209 void INDIDriver::activateRunService()
0210 {
0211     processLocalTree(IDevice::DEV_START);
0212 }
0213 
0214 void INDIDriver::activateStopService()
0215 {
0216     processLocalTree(IDevice::DEV_TERMINATE);
0217 }
0218 
0219 void INDIDriver::activateHostConnection()
0220 {
0221     processRemoteTree(IDevice::DEV_START);
0222 }
0223 
0224 void INDIDriver::activateHostDisconnection()
0225 {
0226     processRemoteTree(IDevice::DEV_TERMINATE);
0227 }
0228 
0229 void INDIDriver::updateLocalTab()
0230 {
0231     if (ui->localTreeWidget->currentItem() == nullptr)
0232         return;
0233 
0234     foreach (IDevice *device, devices)
0235     {
0236         if (ui->localTreeWidget->currentItem()->text(LOCAL_NAME_COLUMN) == device->tree_label)
0237         {
0238             ui->runServiceB->setEnabled(device->state == IDevice::DEV_TERMINATE);
0239             ui->stopServiceB->setEnabled(device->state == IDevice::DEV_START);
0240 
0241             ui->serverLogText->clear();
0242             ui->serverLogText->append(device->getServerBuffer());
0243 
0244             return;
0245         }
0246     }
0247 }
0248 
0249 void INDIDriver::updateClientTab()
0250 {
0251     if (ui->clientTreeWidget->currentItem() == nullptr)
0252         return;
0253 
0254     foreach (INDIHostsInfo *host, ksw->data()->INDIHostsList)
0255     {
0256         if (ui->clientTreeWidget->currentItem()->text(HOST_NAME_COLUMN) == host->name &&
0257             ui->clientTreeWidget->currentItem()->text(HOST_PORT_COLUMN) == host->portnumber)
0258         {
0259             ui->connectHostB->setEnabled(!host->isConnected);
0260             ui->disconnectHostB->setEnabled(host->isConnected);
0261             break;
0262         }
0263     }
0264 }
0265 
0266 void INDIDriver::processLocalTree(IDevice::DeviceStatus dev_request)
0267 {
0268     QList<IDevice *> processed_devices;
0269     DeviceManager *deviceManager = nullptr;
0270     int port                     = 0;
0271     bool portOK                  = false;
0272 
0273     foreach (QTreeWidgetItem *item, ui->localTreeWidget->selectedItems())
0274     {
0275         foreach (IDevice *device, devices)
0276         {
0277             //device->state = (dev_request == IDevice::DEV_TERMINATE) ? IDevice::DEV_START : IDevice::DEV_TERMINATE;
0278             if (item->text(LOCAL_NAME_COLUMN) == device->tree_label && device->state != dev_request)
0279             {
0280                 processed_devices.append(device);
0281 
0282                 // N.B. If multiple devices are selected to run under one device manager
0283                 // then we select the port for the first device that has a valid port
0284                 // entry, the rest are ignored.
0285                 if (port == 0 && item->text(LOCAL_PORT_COLUMN).isEmpty() == false)
0286                 {
0287                     port = item->text(LOCAL_PORT_COLUMN).toInt(&portOK);
0288                     // If we encounter conversion error, we abort
0289                     if (portOK == false)
0290                     {
0291                         KMessageBox::error(0, xi18n("Invalid port entry: %1", item->text(LOCAL_PORT_COLUMN)));
0292                         return;
0293                     }
0294                 }
0295             }
0296         }
0297     }
0298 
0299     if (processed_devices.empty())
0300         return;
0301 
0302     // Select random port within range is none specified.
0303     port = getINDIPort(port);
0304 
0305     if (dev_request == IDevice::DEV_START)
0306     {
0307         if (port <= 0)
0308         {
0309             KMessageBox::error(0, xi18n("Cannot start INDI server: port error."));
0310             return;
0311         }
0312 
0313         //deviceManager = ksw->indiMenu()->startDeviceManager(processed_devices, ui->localR->isChecked() ? DeviceManager::M_LOCAL : DeviceManager::M_SERVER, ((uint) port));
0314         deviceManager = ksw->indiMenu()->initDeviceManager(
0315             "localhost", ((uint)port), ui->localR->isChecked() ? DeviceManager::M_LOCAL : DeviceManager::M_SERVER);
0316 
0317         if (deviceManager == nullptr)
0318         {
0319             kWarning() << "Warning: device manager has not been established properly";
0320             return;
0321         }
0322 
0323         deviceManager->appendManagedDevices(processed_devices);
0324         deviceManager->startServer();
0325         connect(deviceManager, SIGNAL(newServerInput()), this, SLOT(updateLocalTab()));
0326     }
0327     else
0328         ksw->indiMenu()->stopDeviceManager(processed_devices);
0329 }
0330 
0331 void INDIDriver::processRemoteTree(IDevice::DeviceStatus dev_request)
0332 {
0333     DeviceManager *deviceManager = nullptr;
0334     QTreeWidgetItem *currentItem = ui->clientTreeWidget->currentItem();
0335     if (!currentItem)
0336         return;
0337     bool toConnect = (dev_request == IDevice::DEV_START);
0338 
0339     foreach (INDIHostsInfo *host, ksw->data()->INDIHostsList)
0340     {
0341         if (currentItem->text(HOST_NAME_COLUMN) == host->name &&
0342             currentItem->text(HOST_PORT_COLUMN) == host->portnumber)
0343         {
0344             // Nothing changed, return
0345             if (host->isConnected == toConnect)
0346                 return;
0347 
0348             // connect to host
0349             if (toConnect)
0350             {
0351                 deviceManager = ksw->indiMenu()->initDeviceManager(host->hostname, host->portnumber.toUInt(),
0352                                                                    DeviceManager::M_CLIENT);
0353 
0354                 if (deviceManager == nullptr)
0355                 {
0356                     // msgbox after freeze
0357                     kWarning() << "Warning: device manager has not been established properly";
0358                     return;
0359                 }
0360 
0361                 host->deviceManager = deviceManager;
0362                 deviceManager->connectToServer();
0363             }
0364             else
0365                 ksw->indiMenu()->removeDeviceManager(host->deviceManager);
0366 
0367             return;
0368         }
0369     }
0370 }
0371 
0372 void INDIDriver::newTelescopeDiscovered()
0373 {
0374     emit newTelescope();
0375 }
0376 
0377 void INDIDriver::newCCDDiscovered()
0378 {
0379     emit newCCD();
0380 }
0381 
0382 void INDIDriver::resizeDeviceColumn()
0383 {
0384     ui->localTreeWidget->resizeColumnToContents(0);
0385 }
0386 
0387 void INDIDriver::updateMenuActions()
0388 {
0389     // We iterate over devices, we enable INDI Control Panel if we have any active device
0390     // We enable capture image sequence if we have any imaging device
0391 
0392     QAction *tmpAction = nullptr;
0393     INDIMenu *devMenu  = ksw->indiMenu();
0394     bool activeDevice  = false;
0395     bool activeImaging = false;
0396     INDI_P *imgProp    = nullptr;
0397 
0398     if (devMenu == nullptr)
0399         return;
0400 
0401     if (devMenu->managers.count() > 0)
0402         activeDevice = true;
0403 
0404     foreach (DeviceManager *dev_managers, devMenu->managers)
0405     {
0406         foreach (INDI_D *device, dev_managers->indi_dev)
0407         {
0408             imgProp = device->findProp("CCD_EXPOSURE");
0409             if (imgProp && device->isOn())
0410             {
0411                 activeImaging = true;
0412                 break;
0413             }
0414         }
0415     }
0416 
0417     tmpAction = ksw->actionCollection()->action("capture_sequence");
0418 
0419     if (tmpAction != nullptr)
0420         tmpAction->setEnabled(activeImaging);
0421 
0422     tmpAction = ksw->actionCollection()->action("indi_cpl");
0423     if (tmpAction != nullptr)
0424         tmpAction->setEnabled(activeDevice);
0425 }
0426 
0427 bool INDIDriver::isDeviceRunning(const QString &deviceLabel)
0428 {
0429     foreach (IDevice *dev, devices)
0430     {
0431         if (deviceLabel == dev->tree_label)
0432             return dev->state == IDevice::DEV_START;
0433     }
0434     return false;
0435 }
0436 
0437 int INDIDriver::getINDIPort(int customPort)
0438 {
0439     int lastPort = Options::serverPortEnd().toInt();
0440     ;
0441     bool success = false;
0442     currentPort++;
0443 
0444     // recycle
0445     if (currentPort > lastPort)
0446         currentPort = Options::serverPortStart().toInt();
0447 
0448     QTcpServer temp_server;
0449 
0450     if (customPort != 0)
0451     {
0452         success = temp_server.listen(QHostAddress::LocalHost, customPort);
0453         if (success)
0454         {
0455             temp_server.close();
0456             return customPort;
0457         }
0458         else
0459             return -1;
0460     }
0461 
0462     for (; currentPort <= lastPort; currentPort++)
0463     {
0464         success = temp_server.listen(QHostAddress::LocalHost, currentPort);
0465         if (success)
0466         {
0467             temp_server.close();
0468             return currentPort;
0469         }
0470     }
0471     return -1;
0472 }
0473 
0474 bool INDIDriver::readXMLDrivers()
0475 {
0476     QDir indiDir;
0477     QString driverName;
0478 
0479     QString driversDir = Options::indiDriversDir();
0480 #ifdef Q_OS_OSX
0481     if (Options::indiDriversAreInternal())
0482         QCoreApplication::applicationDirPath() + "/../Resources/DriverSupport";
0483 #endif
0484 
0485     if (indiDir.cd(driversDir) == false)
0486     {
0487         KMessageBox::error(0, xi18n("Unable to find INDI drivers directory: %1\nPlease make sure to set the correct "
0488                                     "path in KStars configuration",
0489                                     driversDir));
0490         return false;
0491     }
0492 
0493     indiDir.setNameFilters(QStringList("*.xml"));
0494     indiDir.setFilter(QDir::Files | QDir::NoSymLinks);
0495     QFileInfoList list = indiDir.entryInfoList();
0496 
0497     foreach (QFileInfo fileInfo, list)
0498     {
0499         // libindi 0.7.1: Skip skeleton files
0500         if (fileInfo.fileName().endsWith(QLatin1String("_sk.xml")))
0501             continue;
0502 
0503         if (fileInfo.fileName() == "drivers.xml")
0504         {
0505             // Let first attempt to load the local version of drivers.xml
0506             driverName = QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("drivers.xml");
0507 
0508             // If found, we continue, otherwise, we load the system file
0509             if (driverName.isEmpty() == false && QFile(driverName).exists())
0510             {
0511                 processXMLDriver(driverName);
0512                 continue;
0513             }
0514         }
0515 
0516         driverName = QString("%1/%2").arg(driversDir).arg(fileInfo.fileName());
0517         processXMLDriver(driverName);
0518     }
0519 
0520     return true;
0521 }
0522 
0523 void INDIDriver::processXMLDriver(QString &driverName)
0524 {
0525     QFile file(driverName);
0526     if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
0527     {
0528         KMessageBox::error(0, xi18n("Failed to open INDI Driver file: %1", driverName));
0529         return;
0530     }
0531 
0532     char errmsg[ERRMSG_SIZE];
0533     char c;
0534     LilXML *xmlParser = newLilXML();
0535     XMLEle *root      = nullptr;
0536 
0537     if (driverName.endsWith(QLatin1String("drivers.xml")))
0538         xmlSource = IDevice::PRIMARY_XML;
0539     else
0540         xmlSource = IDevice::THIRD_PARTY_XML;
0541 
0542     while (file.getChar(&c))
0543     {
0544         root = readXMLEle(xmlParser, c, errmsg);
0545 
0546         if (root)
0547         {
0548             if (!buildDeviceGroup(root, errmsg))
0549                 prXMLEle(stderr, root, 0);
0550 
0551             delXMLEle(root);
0552         }
0553         else if (errmsg[0])
0554         {
0555             kDebug() << QString(errmsg) << endl;
0556             delLilXML(xmlParser);
0557             return;
0558         }
0559     }
0560 
0561     delLilXML(xmlParser);
0562 }
0563 
0564 bool INDIDriver::buildDeviceGroup(XMLEle *root, char errmsg[])
0565 {
0566     XMLAtt *ap;
0567     XMLEle *ep;
0568     QString groupName;
0569     QTreeWidgetItem *group;
0570     int groupType = KSTARS_TELESCOPE;
0571 
0572     // avoid overflow
0573     if (strlen(tagXMLEle(root)) > 1024)
0574         return false;
0575 
0576     // Get device grouping name
0577     ap = findXMLAtt(root, "group");
0578 
0579     if (!ap)
0580     {
0581         snprintf(errmsg, ERRMSG_SIZE, "Tag %.64s does not have a group attribute", tagXMLEle(root));
0582         return false;
0583     }
0584 
0585     groupName = valuXMLAtt(ap);
0586 
0587     if (groupName.indexOf("Telescopes") != -1)
0588         groupType = KSTARS_TELESCOPE;
0589     else if (groupName.indexOf("CCDs") != -1)
0590         groupType = KSTARS_CCD;
0591     else if (groupName.indexOf("Filter") != -1)
0592         groupType = KSTARS_FILTER;
0593     else if (groupName.indexOf("Video") != -1)
0594         groupType = KSTARS_VIDEO;
0595     else if (groupName.indexOf("Focusers") != -1)
0596         groupType = KSTARS_FOCUSER;
0597     else if (groupName.indexOf("Domes") != -1)
0598         groupType = KSTARS_DOME;
0599     else if (groupName.indexOf("Receivers") != -1)
0600         groupType = KSTARS_RECEIVERS;
0601     else if (groupName.indexOf("GPS") != -1)
0602         groupType = KSTARS_GPS;
0603 
0604 #ifndef HAVE_CFITSIO_H
0605     // We do not create these groups if we don't have CFITSIO support
0606     if (groupType == KSTARS_CCD || groupType == KSTARS_VIDEO)
0607         return true;
0608 #endif
0609 
0610     // Find if the group already exists
0611     QList<QTreeWidgetItem *> treeList = ui->localTreeWidget->findItems(groupName, Qt::MatchExactly);
0612     if (!treeList.isEmpty())
0613         group = treeList[0];
0614     else
0615         group = new QTreeWidgetItem(ui->localTreeWidget, lastGroup);
0616 
0617     group->setText(0, groupName);
0618     lastGroup = group;
0619 
0620     for (ep = nextXMLEle(root, 1); ep != nullptr; ep = nextXMLEle(root, 0))
0621         if (!buildDriverElement(ep, group, groupType, errmsg))
0622             return false;
0623 
0624     return true;
0625 }
0626 
0627 bool INDIDriver::buildDriverElement(XMLEle *root, QTreeWidgetItem *DGroup, int groupType, char errmsg[])
0628 {
0629     XMLAtt *ap;
0630     XMLEle *el;
0631     IDevice *dv;
0632     QString label;
0633     QString driver;
0634     QString version;
0635     QString name;
0636     QString port;
0637     double focal_length(-1), aperture(-1);
0638 
0639     ap = findXMLAtt(root, "label");
0640     if (!ap)
0641     {
0642         snprintf(errmsg, ERRMSG_SIZE, "Tag %.64s does not have a label attribute", tagXMLEle(root));
0643         return false;
0644     }
0645 
0646     label = valuXMLAtt(ap);
0647 
0648     // Search for optional port attribute
0649     ap = findXMLAtt(root, "port");
0650     if (ap)
0651         port = valuXMLAtt(ap);
0652 
0653     // Let's look for telescope-specific attributes: focal length and aperture
0654     ap = findXMLAtt(root, "focal_length");
0655     if (ap)
0656         focal_length = QString(valuXMLAtt(ap)).toDouble();
0657 
0658     ap = findXMLAtt(root, "aperture");
0659     if (ap)
0660         aperture = QString(valuXMLAtt(ap)).toDouble();
0661 
0662     el = findXMLEle(root, "driver");
0663 
0664     if (!el)
0665         return false;
0666 
0667     driver = pcdataXMLEle(el);
0668 
0669     ap = findXMLAtt(el, "name");
0670     if (!ap)
0671     {
0672         snprintf(errmsg, ERRMSG_SIZE, "Tag %.64s does not have a name attribute", tagXMLEle(el));
0673         return false;
0674     }
0675 
0676     name = valuXMLAtt(ap);
0677 
0678     el = findXMLEle(root, "version");
0679 
0680     if (!el)
0681         return false;
0682 
0683     version = pcdataXMLEle(el);
0684 
0685     QTreeWidgetItem *device = new QTreeWidgetItem(DGroup, lastDevice);
0686 
0687     device->setText(LOCAL_NAME_COLUMN, label);
0688     device->setIcon(LOCAL_STATUS_COLUMN, ui->stopPix);
0689     device->setText(LOCAL_VERSION_COLUMN, version);
0690     device->setText(LOCAL_PORT_COLUMN, port);
0691 
0692     lastDevice = device;
0693 
0694     if ((xmlSource == IDevice::PRIMARY_XML) && driversList.contains(driver) == false)
0695         driversList.insert(driver, name);
0696 
0697     dv            = new IDevice(name, label, driver, version);
0698     dv->type      = groupType;
0699     dv->xmlSource = xmlSource;
0700     //connect(dv, SIGNAL(newServerInput()), this, SLOT(updateLocalTab()));
0701     if (focal_length > 0)
0702         dv->focal_length = focal_length;
0703     if (aperture > 0)
0704         dv->aperture = aperture;
0705     dv->port = port;
0706 
0707     devices.append(dv);
0708 
0709     // SLOTS/SIGNAL, pop menu, indi server logic
0710     return true;
0711 }
0712 
0713 void INDIDriver::updateCustomDrivers()
0714 {
0715     QString label;
0716     QString driver;
0717     QString version;
0718     QString name;
0719     IDevice *dv;
0720     QTreeWidgetItem *group, *widgetDev;
0721     double focal_length(-1), aperture(-1);
0722 
0723     // Find if the group already exists
0724     QList<QTreeWidgetItem *> treeList = ui->localTreeWidget->findItems("Telescopes", Qt::MatchExactly);
0725     if (!treeList.isEmpty())
0726         group = treeList[0];
0727     else
0728         return;
0729 
0730     // Find custom telescope to ADD
0731     foreach (OAL::Scope *s, *(KStarsData::Instance()->logObject()->scopeList()))
0732     {
0733         label = s->vendor() + ' ' + s->model();
0734 
0735         if (s->driver() == xi18n("None") || findDeviceByLabel(label))
0736             continue;
0737 
0738         QHash<QString, QString>::const_iterator i = driversList.constFind(s->driver());
0739         if (i != driversList.constEnd() && i.key() == s->driver())
0740             name = i.value();
0741         else
0742             return;
0743         focal_length = s->focalLength();
0744         aperture     = s->aperture();
0745         driver       = s->driver();
0746         version      = QString("1.0");
0747 
0748         QTreeWidgetItem *device = new QTreeWidgetItem(group, lastDevice);
0749         device->setText(LOCAL_NAME_COLUMN, QString(label));
0750         device->setIcon(LOCAL_STATUS_COLUMN, ui->stopPix);
0751         device->setText(LOCAL_VERSION_COLUMN, QString(version));
0752 
0753         lastDevice = device;
0754 
0755         dv               = new IDevice(name, label, driver, version);
0756         dv->type         = KSTARS_TELESCOPE;
0757         dv->xmlSource    = IDevice::EM_XML;
0758         dv->focal_length = focal_length;
0759         dv->aperture     = aperture;
0760         dv->id           = s->id();
0761         devices.append(dv);
0762     }
0763 
0764     // Find custom telescope to REMOVE
0765     foreach (IDevice *dev, devices)
0766     {
0767         // If it's from primary xml file or it is in a running state, continue.
0768         if (dev->xmlSource != IDevice::EM_XML || dev->state == IDevice::DEV_START)
0769             continue;
0770 
0771         // See if log object has the device, if not delete it
0772         if (KStarsData::Instance()->logObject()->findScopeById(dev->id))
0773             continue;
0774 
0775         // Find if the group already exists
0776         QList<QTreeWidgetItem *> devList =
0777             ui->localTreeWidget->findItems(dev->tree_label, Qt::MatchExactly | Qt::MatchRecursive);
0778         if (!devList.isEmpty())
0779         {
0780             widgetDev = devList[0];
0781             group->removeChild(widgetDev);
0782         }
0783         else
0784             return;
0785 
0786         devices.removeOne(dev);
0787         delete (dev);
0788     }
0789 }
0790 
0791 void INDIDriver::addINDIHost()
0792 {
0793     QDialog hostConfDialog;
0794     Ui::INDIHostConf hostConf;
0795     hostConf.setupUi(&hostConfDialog);
0796     hostConfDialog.setWindowTitle(xi18n("Add Host"));
0797     bool portOk = false;
0798 
0799     if (hostConfDialog.exec() == QDialog::Accepted)
0800     {
0801         INDIHostsInfo *hostItem = new INDIHostsInfo;
0802         hostItem->name          = hostConf.nameIN->text();
0803         hostItem->hostname      = hostConf.hostname->text();
0804         hostItem->portnumber    = hostConf.portnumber->text();
0805         hostItem->isConnected   = false;
0806         hostItem->deviceManager = nullptr;
0807 
0808         hostItem->portnumber.toInt(&portOk);
0809 
0810         if (portOk == false)
0811         {
0812             KMessageBox::error(0, xi18n("Error: the port number is invalid."));
0813             delete hostItem;
0814             return;
0815         }
0816 
0817         //search for duplicates
0818         //for (uint i=0; i < ksw->data()->INDIHostsList.count(); i++)
0819         foreach (INDIHostsInfo *host, ksw->data()->INDIHostsList)
0820             if (hostItem->name == host->name && hostItem->portnumber == host->portnumber)
0821             {
0822                 KMessageBox::error(0, xi18n("Host: %1 Port: %2 already exists.", hostItem->name, hostItem->portnumber));
0823                 delete hostItem;
0824                 return;
0825             }
0826 
0827         ksw->data()->INDIHostsList.append(hostItem);
0828 
0829         QTreeWidgetItem *item = new QTreeWidgetItem(ui->clientTreeWidget);
0830         item->setIcon(HOST_STATUS_COLUMN, ui->disconnected);
0831         item->setText(HOST_NAME_COLUMN, hostConf.nameIN->text());
0832         item->setText(HOST_PORT_COLUMN, hostConf.portnumber->text());
0833     }
0834 
0835     saveHosts();
0836 }
0837 
0838 void INDIDriver::modifyINDIHost()
0839 {
0840     QDialog hostConfDialog;
0841     Ui::INDIHostConf hostConf;
0842     hostConf.setupUi(&hostConfDialog);
0843     hostConfDialog.setWindowTitle(xi18n("Modify Host"));
0844 
0845     QTreeWidgetItem *currentItem = ui->clientTreeWidget->currentItem();
0846 
0847     if (currentItem == nullptr)
0848         return;
0849 
0850     foreach (INDIHostsInfo *host, ksw->data()->INDIHostsList)
0851     {
0852         if (currentItem->text(HOST_NAME_COLUMN) == host->name &&
0853             currentItem->text(HOST_PORT_COLUMN) == host->portnumber)
0854         {
0855             hostConf.nameIN->setText(host->name);
0856             hostConf.hostname->setText(host->hostname);
0857             hostConf.portnumber->setText(host->portnumber);
0858 
0859             if (hostConfDialog.exec() == QDialog::Accepted)
0860             {
0861                 //INDIHostsInfo *hostItem = new INDIHostsInfo;
0862                 host->name       = hostConf.nameIN->text();
0863                 host->hostname   = hostConf.hostname->text();
0864                 host->portnumber = hostConf.portnumber->text();
0865 
0866                 currentItem->setText(HOST_NAME_COLUMN, hostConf.nameIN->text());
0867                 currentItem->setText(HOST_PORT_COLUMN, hostConf.portnumber->text());
0868 
0869                 //ksw->data()->INDIHostsList.replace(i, hostItem);
0870 
0871                 saveHosts();
0872                 return;
0873             }
0874         }
0875     }
0876 }
0877 
0878 void INDIDriver::removeINDIHost()
0879 {
0880     if (ui->clientTreeWidget->currentItem() == nullptr)
0881         return;
0882 
0883     for (int i = 0; i < ksw->data()->INDIHostsList.count(); i++)
0884     {
0885         if (ui->clientTreeWidget->currentItem()->text(HOST_NAME_COLUMN) == ksw->data()->INDIHostsList[i]->name &&
0886             ui->clientTreeWidget->currentItem()->text(HOST_PORT_COLUMN) == ksw->data()->INDIHostsList[i]->portnumber)
0887         {
0888             if (ksw->data()->INDIHostsList[i]->isConnected)
0889             {
0890                 KMessageBox::error(0, xi18n("You need to disconnect the client before removing it."));
0891                 return;
0892             }
0893 
0894             if (KMessageBox::warningContinueCancel(0,
0895                                                    xi18n("Are you sure you want to remove the %1 client?",
0896                                                          ui->clientTreeWidget->currentItem()->text(HOST_NAME_COLUMN)),
0897                                                    xi18n("Delete Confirmation"),
0898                                                    KStandardGuiItem::del()) != KMessageBox::Continue)
0899                 return;
0900 
0901             delete ksw->data()->INDIHostsList.takeAt(i);
0902             delete (ui->clientTreeWidget->currentItem());
0903             break;
0904         }
0905     }
0906     saveHosts();
0907 }
0908 
0909 void INDIDriver::saveHosts()
0910 {
0911     QFile file;
0912     QString hostData;
0913 
0914     //determine filename in local user KDE directory tree.
0915     file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("indihosts.xml"));
0916 
0917     if (!file.open(QIODevice::WriteOnly))
0918     {
0919         QString message = xi18n(
0920             "Unable to write to file 'indihosts.xml'\nAny changes to INDI hosts configurations will not be saved.");
0921         KMessageBox::sorry(0, message, xi18n("Could Not Open File"));
0922         return;
0923     }
0924 
0925     QTextStream outstream(&file);
0926 
0927     //for (uint i= 0; i < ksw->data()->INDIHostsList.count(); i++)
0928     foreach (INDIHostsInfo *host, ksw->data()->INDIHostsList)
0929     {
0930         hostData = "<INDIHost name='";
0931         hostData += host->name;
0932         hostData += "' hostname='";
0933         hostData += host->hostname;
0934         hostData += "' port='";
0935         hostData += host->portnumber;
0936         hostData += "' />\n";
0937 
0938         outstream << hostData;
0939     }
0940 
0941     file.close();
0942 }
0943 
0944 IDevice *INDIDriver::findDeviceByLabel(const QString &label)
0945 {
0946     foreach (IDevice *dev, devices)
0947     {
0948         if (dev->tree_label == label)
0949             return dev;
0950     }
0951     return nullptr;
0952 }
0953 
0954 INDIDriver::~INDIDriver()
0955 {
0956     //for (uint i=0; i < devices.size(); i++)
0957     //delete (devices[i]);
0958     while (!devices.isEmpty())
0959         delete devices.takeFirst();
0960 }
0961 
0962 IDevice::IDevice(const QString &inName, const QString &inLabel, const QString &inDriver, const QString &inVersion)
0963 {
0964     tree_label = inLabel;
0965     unique_label.clear();
0966     name    = inName;
0967     driver  = inDriver;
0968     version = inVersion;
0969 
0970     xmlSource = PRIMARY_XML;
0971 
0972     // Initially off
0973     state = IDevice::DEV_TERMINATE;
0974 
0975     // No port initially
0976     //indiPort = -1;
0977 
0978     // not yet managed by DeviceManager
0979     //managed = false;
0980 
0981     deviceManager = nullptr;
0982 
0983     focal_length = -1;
0984     aperture     = -1;
0985 
0986     //proc = nullptr;
0987 }
0988 
0989 IDevice::~IDevice()
0990 {
0991 }
0992 
0993 void IDevice::clear()
0994 {
0995     //managed = false;
0996     state         = IDevice::DEV_TERMINATE;
0997     deviceManager = nullptr;
0998 
0999     unique_label.clear();
1000 }
1001 
1002 QString IDevice::getServerBuffer()
1003 {
1004     if (deviceManager != nullptr)
1005         return deviceManager->getServerBuffer();
1006 
1007     return QString();
1008 }
1009 
1010 #include "indidriver.moc"