File indexing completed on 2024-05-19 15:01:36

0001 /***************************************************************************
0002 File        : LiveDataSource.cpp
0003 Project     : LabPlot
0004 Description : Represents live data source
0005 --------------------------------------------------------------------
0006 Copyright   : (C) 2009-2019 Alexander Semke (alexander.semke@web.de)
0007 Copyright   : (C) 2017 Fabian Kristof (fkristofszabolcs@gmail.com)
0008 Copyright   : (C) 2018 Stefan Gerlach (stefan.gerlach@uni.kn)
0009 
0010 ***************************************************************************/
0011 
0012 /***************************************************************************
0013 *                                                                         *
0014 *  This program is free software; you can redistribute it and/or modify   *
0015 *  it under the terms of the GNU General Public License as published by   *
0016 *  the Free Software Foundation; either version 2 of the License, or      *
0017 *  (at your option) any later version.                                    *
0018 *                                                                         *
0019 *  This program is distributed in the hope that it will be useful,        *
0020 *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
0021 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
0022 *  GNU General Public License for more details.                           *
0023 *                                                                         *
0024 *   You should have received a copy of the GNU General Public License     *
0025 *   along with this program; if not, write to the Free Software           *
0026 *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
0027 *   Boston, MA  02110-1301  USA                                           *
0028 *                                                                         *
0029 ***************************************************************************/
0030 
0031 #include "backend/datasources/LiveDataSource.h"
0032 #include "backend/datasources/filters/AsciiFilter.h"
0033 #include "backend/datasources/filters/FITSFilter.h"
0034 #include "backend/datasources/filters/BinaryFilter.h"
0035 #include "backend/datasources/filters/ROOTFilter.h"
0036 #include "backend/core/Project.h"
0037 #include "commonfrontend/spreadsheet/SpreadsheetView.h"
0038 #include "kdefrontend/spreadsheet/PlotDataDialog.h"
0039 
0040 #include <QAction>
0041 #include <QDateTime>
0042 #include <QDir>
0043 #include <QFileInfo>
0044 #include <QFileSystemWatcher>
0045 #include <QIcon>
0046 #include <QMenu>
0047 #include <QMessageBox>
0048 #include <QProcess>
0049 #include <QTimer>
0050 #include <QTcpSocket>
0051 #include <QUdpSocket>
0052 #ifdef HAVE_QTSERIALPORT
0053 #include <QSerialPortInfo>
0054 #endif
0055 
0056 #include <KLocalizedString>
0057 
0058 /*!
0059   \class LiveDataSource
0060   \brief Represents data stored in a file. Reading and writing is done with the help of appropriate I/O-filters.
0061 
0062   \ingroup datasources
0063 */
0064 LiveDataSource::LiveDataSource(const QString& name, bool loading) : Spreadsheet(name, loading, AspectType::LiveDataSource),
0065     m_updateTimer(new QTimer(this)), m_watchTimer(new QTimer(this)) {
0066 
0067     initActions();
0068 
0069     connect(m_updateTimer, &QTimer::timeout, this, &LiveDataSource::read);
0070     connect(m_watchTimer, &QTimer::timeout, this, &LiveDataSource::readOnUpdate);
0071 }
0072 
0073 LiveDataSource::~LiveDataSource() {
0074     //stop reading before deleting the objects
0075     pauseReading();
0076 
0077     delete m_filter;
0078     delete m_fileSystemWatcher;
0079     delete m_localSocket;
0080     delete m_tcpSocket;
0081 #ifdef HAVE_QTSERIALPORT
0082     delete m_serialPort;
0083 #endif
0084 }
0085 
0086 void LiveDataSource::initActions() {
0087     m_plotDataAction = new QAction(QIcon::fromTheme("office-chart-line"), i18n("Plot data"), this);
0088     connect(m_plotDataAction, &QAction::triggered, this, &LiveDataSource::plotData);
0089     m_watchTimer->setSingleShot(true);
0090     m_watchTimer->setInterval(100);
0091 }
0092 
0093 QWidget* LiveDataSource::view() const {
0094     if (!m_partView) {
0095         m_view = new SpreadsheetView(const_cast<LiveDataSource*>(this), true);
0096         m_partView = m_view;
0097     }
0098     return m_partView;
0099 }
0100 
0101 /*!
0102  * \brief Returns a list with the names of the available ports
0103  */
0104 QStringList LiveDataSource::availablePorts() {
0105     QStringList ports;
0106 //  qDebug() << "available ports count:" << QSerialPortInfo::availablePorts().size();
0107 
0108 #ifdef HAVE_QTSERIALPORT
0109     for (const QSerialPortInfo& sp : QSerialPortInfo::availablePorts()) {
0110         ports.append(sp.portName());
0111 
0112         DEBUG(" port " << STDSTRING(sp.portName()) << ": " << STDSTRING(sp.systemLocation()) << STDSTRING(sp.description())
0113             << ' ' << STDSTRING(sp.manufacturer()) << ' ' << STDSTRING(sp.serialNumber()));
0114     }
0115     // For Testing:
0116     // ports.append("/dev/pts/26");
0117 #endif
0118 
0119     return ports;
0120 }
0121 
0122 /*!
0123  * \brief Returns a list with the supported baud rates
0124  */
0125 QStringList LiveDataSource::supportedBaudRates() {
0126     QStringList baudRates;
0127 
0128 #ifdef HAVE_QTSERIALPORT
0129     for (const auto& baud : QSerialPortInfo::standardBaudRates())
0130         baudRates.append(QString::number(baud));
0131 #endif
0132     return baudRates;
0133 }
0134 
0135 /*!
0136  * \brief Updates this data source at this moment
0137  */
0138 void LiveDataSource::updateNow() {
0139     DEBUG("LiveDataSource::updateNow() update interval = " << m_updateInterval);
0140     if (m_updateType == UpdateType::TimeInterval)
0141         m_updateTimer->stop();
0142     else
0143         m_pending = false;
0144     read();
0145 
0146     //restart the timer after update
0147     if (m_updateType == UpdateType::TimeInterval && !m_paused)
0148         m_updateTimer->start(m_updateInterval);
0149 }
0150 
0151 /*!
0152  * \brief Continue reading from the live data source after it was paused
0153  */
0154 void LiveDataSource::continueReading() {
0155     m_paused = false;
0156     if (m_pending) {
0157         m_pending = false;
0158         updateNow();
0159     }
0160 }
0161 
0162 /*!
0163  * \brief Pause the reading of the live data source
0164  */
0165 void LiveDataSource::pauseReading() {
0166     m_paused = true;
0167     if (m_updateType == UpdateType::TimeInterval) {
0168         m_pending = true;
0169         m_updateTimer->stop();
0170     }
0171 }
0172 
0173 void LiveDataSource::setFileName(const QString& name) {
0174     m_fileName = name;
0175 }
0176 
0177 QString LiveDataSource::fileName() const {
0178     return m_fileName;
0179 }
0180 
0181 /*!
0182  * \brief Sets the local socket's server name to name
0183  * \param name
0184  */
0185 void LiveDataSource::setLocalSocketName(const QString& name) {
0186     m_localSocketName = name;
0187 }
0188 
0189 QString LiveDataSource::localSocketName() const {
0190     return m_localSocketName;
0191 }
0192 
0193 void LiveDataSource::setFileType(AbstractFileFilter::FileType type) {
0194     m_fileType = type;
0195 }
0196 
0197 AbstractFileFilter::FileType LiveDataSource::fileType() const {
0198     return m_fileType;
0199 }
0200 
0201 void LiveDataSource::setFilter(AbstractFileFilter* f) {
0202     delete m_filter;
0203     m_filter = f;
0204 }
0205 
0206 AbstractFileFilter* LiveDataSource::filter() const {
0207     return m_filter;
0208 }
0209 
0210 /*!
0211  * \brief Sets the serial port's baud rate
0212  * \param baudrate
0213  */
0214 void LiveDataSource::setBaudRate(int baudrate) {
0215     m_baudRate = baudrate;
0216 }
0217 
0218 int LiveDataSource::baudRate() const {
0219     return m_baudRate;
0220 }
0221 
0222 /*!
0223  * \brief Sets the source's update interval to \c interval
0224  * \param interval
0225  */
0226 void LiveDataSource::setUpdateInterval(int interval) {
0227     m_updateInterval = interval;
0228     if (!m_paused)
0229         m_updateTimer->start(m_updateInterval);
0230 }
0231 
0232 int LiveDataSource::updateInterval() const {
0233     return m_updateInterval;
0234 }
0235 
0236 /*!
0237  * \brief Sets how many values we should keep when keepLastValues is true
0238  * \param keepnvalues
0239  */
0240 void LiveDataSource::setKeepNValues(int keepnvalues) {
0241     m_keepNValues = keepnvalues;
0242 }
0243 
0244 int LiveDataSource::keepNValues() const {
0245     return m_keepNValues;
0246 }
0247 
0248 /*!
0249  * \brief Sets the network socket's port to port
0250  * \param port
0251  */
0252 void LiveDataSource::setPort(quint16 port) {
0253     m_port = port;
0254 }
0255 
0256 void LiveDataSource::setBytesRead(qint64 bytes) {
0257     m_bytesRead = bytes;
0258 }
0259 
0260 int LiveDataSource::bytesRead() const {
0261     return m_bytesRead;
0262 }
0263 
0264 int LiveDataSource::port() const {
0265     return m_port;
0266 }
0267 
0268 /*!
0269  * \brief Sets the serial port's name to name
0270  * \param name
0271  */
0272 void LiveDataSource::setSerialPort(const QString& name) {
0273     m_serialPortName = name;
0274 }
0275 
0276 QString LiveDataSource::serialPortName() const {
0277     return m_serialPortName;
0278 }
0279 
0280 bool LiveDataSource::isPaused() const {
0281     return m_paused;
0282 }
0283 
0284 /*!
0285  * \brief Sets the sample size to size
0286  * \param size
0287  */
0288 void LiveDataSource::setSampleSize(int size) {
0289     m_sampleSize = size;
0290 }
0291 
0292 int LiveDataSource::sampleSize() const {
0293     return m_sampleSize;
0294 }
0295 
0296 /*!
0297  * \brief Sets the source's type to sourcetype
0298  * \param sourcetype
0299  */
0300 void LiveDataSource::setSourceType(SourceType sourcetype) {
0301     m_sourceType = sourcetype;
0302 }
0303 
0304 LiveDataSource::SourceType LiveDataSource::sourceType() const {
0305     return m_sourceType;
0306 }
0307 
0308 /*!
0309  * \brief Sets the source's reading type to readingType
0310  * \param readingType
0311  */
0312 void LiveDataSource::setReadingType(ReadingType readingType) {
0313     m_readingType = readingType;
0314 }
0315 
0316 LiveDataSource::ReadingType LiveDataSource::readingType() const {
0317     return m_readingType;
0318 }
0319 
0320 /*!
0321  * \brief Sets the source's update type to updatetype and handles this change
0322  * \param updatetype
0323  */
0324 void LiveDataSource::setUpdateType(UpdateType updatetype) {
0325     switch (updatetype) {
0326     case UpdateType::NewData: {
0327         m_updateTimer->stop();
0328         if (!m_fileSystemWatcher)
0329             m_fileSystemWatcher = new QFileSystemWatcher(this);
0330 
0331         m_fileSystemWatcher->addPath(m_fileName);
0332         QFileInfo file(m_fileName);
0333         // If the watched file currently does not exist (because it is recreated for instance), watch its containing
0334         // directory instead. Once the file exists again, switch to watching the file in readOnUpdate().
0335         // Reading will only start 100ms after the last update, to prevent continuous re-reading while the file is updated.
0336         // If the watched file intentionally is updated more often than that, the user should switch to periodic reading.
0337         if (m_fileSystemWatcher->files().contains(m_fileName))
0338             m_fileSystemWatcher->removePath(file.absolutePath());
0339         else
0340             m_fileSystemWatcher->addPath(file.absolutePath());
0341 
0342         connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, [&](){ m_watchTimer->start(); });
0343         connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, [&](){ m_watchTimer->start(); });
0344         break;
0345     }
0346     case UpdateType::TimeInterval:
0347         delete m_fileSystemWatcher;
0348         m_fileSystemWatcher = nullptr;
0349         break;
0350     }
0351     m_updateType = updatetype;
0352 }
0353 
0354 LiveDataSource::UpdateType LiveDataSource::updateType() const {
0355     return m_updateType;
0356 }
0357 
0358 /*!
0359  * \brief Sets the network socket's host
0360  * \param host
0361  */
0362 void LiveDataSource::setHost(const QString& host) {
0363     m_host = host.simplified();
0364 }
0365 
0366 QString LiveDataSource::host() const {
0367     return m_host;
0368 }
0369 
0370 /*!
0371   sets whether only a link to the file is saved in the project file (\c b=true)
0372   or the whole content of the file (\c b=false).
0373 */
0374 void LiveDataSource::setFileLinked(bool b) {
0375     m_fileLinked = b;
0376 }
0377 
0378 /*!
0379   returns \c true if only a link to the file is saved in the project file.
0380   \c false otherwise.
0381 */
0382 bool LiveDataSource::isFileLinked() const {
0383     return m_fileLinked;
0384 }
0385 
0386 void LiveDataSource::setUseRelativePath(bool b) {
0387     m_relativePath = b;
0388 }
0389 
0390 bool LiveDataSource::useRelativePath() const {
0391     return m_relativePath;
0392 }
0393 
0394 QIcon LiveDataSource::icon() const {
0395     QIcon icon;
0396 
0397     switch (m_fileType) {
0398     case AbstractFileFilter::FileType::Ascii:
0399         icon = QIcon::fromTheme("text-plain");
0400         break;
0401     case AbstractFileFilter::FileType::Binary:
0402         icon = QIcon::fromTheme("application-octet-stream");
0403         break;
0404     case AbstractFileFilter::FileType::Image:
0405         icon = QIcon::fromTheme("image-x-generic");
0406         break;
0407     // TODO: missing icons
0408     case AbstractFileFilter::FileType::HDF5:
0409     case AbstractFileFilter::FileType::NETCDF:
0410         break;
0411     case AbstractFileFilter::FileType::FITS:
0412         icon = QIcon::fromTheme("kstars_fitsviewer");
0413         break;
0414     case AbstractFileFilter::FileType::JSON:
0415         icon = QIcon::fromTheme("application-json");
0416         break;
0417     case AbstractFileFilter::FileType::ROOT:
0418     case AbstractFileFilter::FileType::NgspiceRawAscii:
0419     case AbstractFileFilter::FileType::NgspiceRawBinary:
0420         break;
0421     }
0422 
0423     return icon;
0424 }
0425 
0426 QMenu* LiveDataSource::createContextMenu() {
0427     QMenu* menu = AbstractPart::createContextMenu();
0428 
0429     QAction* firstAction = nullptr;
0430     // if we're populating the context menu for the project explorer, then
0431     //there're already actions available there. Skip the first title-action
0432     //and insert the action at the beginning of the menu.
0433     if (menu->actions().size() > 1)
0434         firstAction = menu->actions().at(1);
0435 
0436     menu->insertAction(firstAction, m_plotDataAction);
0437     menu->insertSeparator(firstAction);
0438 
0439     return menu;
0440 }
0441 
0442 //##############################################################################
0443 //#################################  SLOTS  ####################################
0444 //##############################################################################
0445 
0446 /*
0447  * Called when the watch timer times out, i.e. when modifying the file or directory
0448  * presumably has finished. Also see LiveDataSource::setUpdateType().
0449  */
0450 void LiveDataSource::readOnUpdate() {
0451     if (!m_fileSystemWatcher->files().contains(m_fileName)) {
0452         m_fileSystemWatcher->addPath(m_fileName);
0453         QFileInfo file(m_fileName);
0454         if (m_fileSystemWatcher->files().contains(m_fileName))
0455             m_fileSystemWatcher->removePath(file.absolutePath());
0456         else {
0457             m_fileSystemWatcher->addPath(file.absolutePath());
0458             return;
0459         }
0460     }
0461     if (m_paused)
0462         // flag file for reading, once the user decides to continue reading
0463         m_pending = true;
0464     else
0465         read();
0466 }
0467 
0468 /*
0469  * called periodically or on new data changes (file changed, new data in the socket, etc.)
0470  */
0471 void LiveDataSource::read() {
0472     DEBUG("\nLiveDataSource::read()");
0473     if (!m_filter)
0474         return;
0475 
0476     if (m_reading)
0477         return;
0478 
0479     m_reading = true;
0480 
0481     //initialize the device (file, socket, serial port) when calling this function for the first time
0482     if (!m_prepared) {
0483         DEBUG(" Preparing device: update type = " << ENUM_TO_STRING(LiveDataSource, UpdateType, m_updateType));
0484         switch (m_sourceType) {
0485         case SourceType::FileOrPipe:
0486             delete m_device;
0487             m_device = new QFile(m_fileName);
0488             break;
0489         case SourceType::NetworkTcpSocket:
0490             m_tcpSocket = new QTcpSocket(this);
0491             m_device = m_tcpSocket;
0492             m_tcpSocket->connectToHost(m_host, m_port, QIODevice::ReadOnly);
0493 
0494             connect(m_tcpSocket, &QTcpSocket::readyRead, this, &LiveDataSource::readyRead);
0495             connect(m_tcpSocket, static_cast<void (QTcpSocket::*) (QAbstractSocket::SocketError)>(&QTcpSocket::error), this, &LiveDataSource::tcpSocketError);
0496 
0497             break;
0498         case SourceType::NetworkUdpSocket:
0499             m_udpSocket = new QUdpSocket(this);
0500             m_device = m_udpSocket;
0501             m_udpSocket->bind(QHostAddress(m_host), m_port);
0502             m_udpSocket->connectToHost(m_host, 0, QUdpSocket::ReadOnly);
0503 
0504             // only connect to readyRead when update is on new data
0505             if (m_updateType == UpdateType::NewData)
0506                 connect(m_udpSocket, &QUdpSocket::readyRead, this, &LiveDataSource::readyRead);
0507             connect(m_udpSocket, static_cast<void (QUdpSocket::*) (QAbstractSocket::SocketError)>(&QUdpSocket::error), this, &LiveDataSource::tcpSocketError);
0508 
0509             break;
0510         case SourceType::LocalSocket:
0511             m_localSocket = new QLocalSocket(this);
0512             m_device = m_localSocket;
0513             m_localSocket->connectToServer(m_localSocketName, QLocalSocket::ReadOnly);
0514 
0515             connect(m_localSocket, &QLocalSocket::readyRead, this, &LiveDataSource::readyRead);
0516             connect(m_localSocket, static_cast<void (QLocalSocket::*) (QLocalSocket::LocalSocketError)>(&QLocalSocket::error), this, &LiveDataSource::localSocketError);
0517 
0518             break;
0519         case SourceType::SerialPort:
0520 #ifdef HAVE_QTSERIALPORT
0521             m_serialPort = new QSerialPort(this);
0522             m_device = m_serialPort;
0523             DEBUG(" Serial: " << STDSTRING(m_serialPortName) << ", " << m_baudRate);
0524             m_serialPort->setBaudRate(m_baudRate);
0525             m_serialPort->setPortName(m_serialPortName);
0526             m_serialPort->open(QIODevice::ReadOnly);
0527 
0528             // only connect to readyRead when update is on new data
0529             if (m_updateType == UpdateType::NewData)
0530                 connect(m_serialPort, &QSerialPort::readyRead, this, &LiveDataSource::readyRead);
0531             connect(m_serialPort, static_cast<void (QSerialPort::*) (QSerialPort::SerialPortError)>(&QSerialPort::error), this, &LiveDataSource::serialPortError);
0532 #endif
0533             break;
0534         case SourceType::MQTT:
0535             break;
0536         }
0537         m_prepared = true;
0538     }
0539 
0540     qint64 bytes = 0;
0541 
0542     switch (m_sourceType) {
0543     case SourceType::FileOrPipe:
0544         DEBUG("Reading FileOrPipe. type = " << ENUM_TO_STRING(AbstractFileFilter, FileType, m_fileType));
0545         switch (m_fileType) {
0546         case AbstractFileFilter::FileType::Ascii:
0547             if (m_readingType == LiveDataSource::ReadingType::WholeFile) {
0548                 static_cast<AsciiFilter*>(m_filter)->readFromLiveDevice(*m_device, this, 0);
0549             } else {
0550                 bytes = static_cast<AsciiFilter*>(m_filter)->readFromLiveDevice(*m_device, this, m_bytesRead);
0551                 m_bytesRead += bytes;
0552                 DEBUG("Read " << bytes << " bytes, in total: " << m_bytesRead);
0553             }
0554             break;
0555         case AbstractFileFilter::FileType::Binary:
0556             //TODO: not implemented yet
0557             // bytes = qSharedPointerCast<BinaryFilter>(m_filter)->readFromLiveDevice(*m_file, this, m_bytesRead);
0558 //          m_bytesRead += bytes;
0559         case AbstractFileFilter::FileType::ROOT:
0560         case AbstractFileFilter::FileType::NgspiceRawAscii:
0561         case AbstractFileFilter::FileType::NgspiceRawBinary:
0562             //only re-reading of the whole file is supported
0563             m_filter->readDataFromFile(m_fileName, this);
0564             break;
0565         //TODO: other types not implemented yet
0566         case AbstractFileFilter::FileType::Image:
0567         case AbstractFileFilter::FileType::HDF5:
0568         case AbstractFileFilter::FileType::NETCDF:
0569         case AbstractFileFilter::FileType::FITS:
0570         case AbstractFileFilter::FileType::JSON:
0571             break;
0572         }
0573         break;
0574     case SourceType::NetworkTcpSocket:
0575         DEBUG("reading from TCP socket. state before abort = " << m_tcpSocket->state());
0576         m_tcpSocket->abort();
0577         m_tcpSocket->connectToHost(m_host, m_port, QIODevice::ReadOnly);
0578         DEBUG("reading from TCP socket. state after reconnect = " << m_tcpSocket->state());
0579         break;
0580     case SourceType::NetworkUdpSocket:
0581         DEBUG(" Reading from UDP socket. state = " << m_udpSocket->state());
0582 
0583         // reading data here
0584         if (m_fileType == AbstractFileFilter::FileType::Ascii)
0585             static_cast<AsciiFilter*>(m_filter)->readFromLiveDeviceNotFile(*m_device, this);
0586         break;
0587     case SourceType::LocalSocket:
0588         DEBUG(" Reading from local socket. state before abort = " << m_localSocket->state());
0589         if (m_localSocket->state() == QLocalSocket::ConnectingState)
0590             m_localSocket->abort();
0591         m_localSocket->connectToServer(m_localSocketName, QLocalSocket::ReadOnly);
0592         if (m_localSocket->waitForConnected())
0593             m_localSocket->waitForReadyRead();
0594         DEBUG(" Reading from local socket. state after reconnect = " << m_localSocket->state());
0595         break;
0596     case SourceType::SerialPort:
0597         DEBUG(" Reading from serial port");
0598 #ifdef HAVE_QTSERIALPORT
0599 
0600         // reading data here
0601         if (m_fileType == AbstractFileFilter::FileType::Ascii)
0602             static_cast<AsciiFilter*>(m_filter)->readFromLiveDeviceNotFile(*m_device, this);
0603 #endif
0604         break;
0605     case SourceType::MQTT:
0606         break;
0607     }
0608 
0609     m_reading = false;
0610 }
0611 
0612 /*!
0613  * Slot for the signal that is emitted once every time new data is available for reading from the device (not UDP or Serial).
0614  * It will only be emitted again once new data is available, such as when a new payload of network data has arrived on the network socket,
0615  * or when a new block of data has been appended to your device.
0616  */
0617 void LiveDataSource::readyRead() {
0618     DEBUG("LiveDataSource::readyRead() update type = " << ENUM_TO_STRING(LiveDataSource,UpdateType,m_updateType));
0619     DEBUG(" REMAINING TIME = " << m_updateTimer->remainingTime());
0620 
0621     if (m_fileType == AbstractFileFilter::FileType::Ascii)
0622         static_cast<AsciiFilter*>(m_filter)->readFromLiveDeviceNotFile(*m_device, this);
0623 
0624 //TODO: not implemented yet
0625 //  else if (m_fileType == AbstractFileFilter::FileType::Binary)
0626 //      dynamic_cast<BinaryFilter*>(m_filter)->readFromLiveDeviceNotFile(*m_device, this);
0627 
0628     //since we won't have the timer to call read() where we create new connections
0629     //for sequential devices in read() we just request data/connect to servers
0630     if (m_updateType == UpdateType::NewData)
0631         read();
0632 }
0633 
0634 void LiveDataSource::localSocketError(QLocalSocket::LocalSocketError socketError) {
0635     Q_UNUSED(socketError);
0636     /*disconnect(m_localSocket, SIGNAL(error(QLocalSocket::LocalSocketError)), this, SLOT(localSocketError(QLocalSocket::LocalSocketError)));
0637     disconnect(m_localSocket, SIGNAL(readyRead()), this, SLOT(readyRead()));*/
0638 
0639     /*switch (socketError) {
0640     case QLocalSocket::ServerNotFoundError:
0641         QMessageBox::critical(0, i18n("Local Socket Error"),
0642                               i18n("The socket was not found. Please check the socket name."));
0643         break;
0644     case QLocalSocket::ConnectionRefusedError:
0645         QMessageBox::critical(0, i18n("Local Socket Error"),
0646                               i18n("The connection was refused by the peer"));
0647         break;
0648     case QLocalSocket::PeerClosedError:
0649         QMessageBox::critical(0, i18n("Local Socket Error"),
0650                               i18n("The socket has closed the connection."));
0651         break;
0652     default:
0653         QMessageBox::critical(0, i18n("Local Socket Error"),
0654                               i18n("The following error occurred: %1.", m_localSocket->errorString()));
0655     }*/
0656 }
0657 
0658 void LiveDataSource::tcpSocketError(QAbstractSocket::SocketError socketError) {
0659     Q_UNUSED(socketError);
0660     /*switch (socketError) {
0661     case QAbstractSocket::ConnectionRefusedError:
0662         QMessageBox::critical(0, i18n("TCP Socket Error"),
0663                               i18n("The connection was refused by the peer. Make sure the server is running and check the host name and port settings."));
0664         break;
0665     case QAbstractSocket::RemoteHostClosedError:
0666         QMessageBox::critical(0, i18n("TCP Socket Error"),
0667                               i18n("The remote host closed the connection."));
0668         break;
0669     case QAbstractSocket::HostNotFoundError:
0670         QMessageBox::critical(0, i18n("TCP Socket Error"),
0671                               i18n("The host was not found. Please check the host name and port settings."));
0672         break;
0673     default:
0674         QMessageBox::critical(0, i18n("TCP Socket Error"),
0675                               i18n("The following error occurred: %1.", m_tcpSocket->errorString()));
0676     }*/
0677 }
0678 
0679 #ifdef HAVE_QTSERIALPORT
0680 void LiveDataSource::serialPortError(QSerialPort::SerialPortError serialPortError) {
0681     switch (serialPortError) {
0682     case QSerialPort::DeviceNotFoundError:
0683         QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to open the device."));
0684         break;
0685     case QSerialPort::PermissionError:
0686         QMessageBox::critical(nullptr, i18n("Serial Port Error"),
0687             i18n("Failed to open the device. Please check your permissions on this device."));
0688         break;
0689     case QSerialPort::OpenError:
0690         QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Device already opened."));
0691         break;
0692     case QSerialPort::NotOpenError:
0693         QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("The device is not opened."));
0694         break;
0695     case QSerialPort::ReadError:
0696         QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to read data."));
0697         break;
0698     case QSerialPort::ResourceError:
0699         QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("Failed to read data. The device is removed."));
0700         break;
0701     case QSerialPort::TimeoutError:
0702         QMessageBox::critical(nullptr, i18n("Serial Port Error"), i18n("The device timed out."));
0703         break;
0704 #ifndef _MSC_VER
0705     //MSVC complains about the usage of deprecated enums, g++ and clang complain about missing enums
0706     case QSerialPort::ParityError:
0707     case QSerialPort::FramingError:
0708     case QSerialPort::BreakConditionError:
0709 #endif
0710     case QSerialPort::WriteError:
0711     case QSerialPort::UnsupportedOperationError:
0712     case QSerialPort::UnknownError:
0713         QMessageBox::critical(nullptr, i18n("Serial Port Error"),
0714             i18n("The following error occurred: %1.", m_serialPort->errorString()));
0715         break;
0716     case QSerialPort::NoError:
0717         break;
0718     }
0719 }
0720 #endif
0721 
0722 void LiveDataSource::plotData() {
0723     auto* dlg = new PlotDataDialog(this);
0724     dlg->exec();
0725 }
0726 
0727 //##############################################################################
0728 //##################  Serialization/Deserialization  ###########################
0729 //##############################################################################
0730 /*!
0731   Saves as XML.
0732  */
0733 void LiveDataSource::save(QXmlStreamWriter* writer) const {
0734     writer->writeStartElement("liveDataSource");
0735     writeBasicAttributes(writer);
0736     writeCommentElement(writer);
0737 
0738     //general
0739     writer->writeStartElement("general");
0740 
0741     switch (m_sourceType) {
0742     case SourceType::FileOrPipe:
0743         writer->writeAttribute("fileType", QString::number(static_cast<int>(m_fileType)));
0744         writer->writeAttribute("fileLinked", QString::number(m_fileLinked));
0745         writer->writeAttribute("relativePath", QString::number(m_relativePath));
0746         if (m_relativePath) {
0747             //convert from the absolute to the relative path and save it
0748             const Project* p = const_cast<LiveDataSource*>(this)->project();
0749             QFileInfo fi(p->fileName());
0750             writer->writeAttribute("fileName", fi.dir().relativeFilePath(m_fileName));
0751         }else
0752             writer->writeAttribute("fileName", m_fileName);
0753 
0754         break;
0755     case SourceType::SerialPort:
0756         writer->writeAttribute("baudRate", QString::number(m_baudRate));
0757         writer->writeAttribute("serialPortName", m_serialPortName);
0758         break;
0759     case SourceType::NetworkTcpSocket:
0760     case SourceType::NetworkUdpSocket:
0761         writer->writeAttribute("host", m_host);
0762         writer->writeAttribute("port", QString::number(m_port));
0763         break;
0764     case SourceType::LocalSocket:
0765         break;
0766     case SourceType::MQTT:
0767         break;
0768     }
0769 
0770     writer->writeAttribute("updateType", QString::number(static_cast<int>(m_updateType)));
0771     writer->writeAttribute("readingType", QString::number(static_cast<int>(m_readingType)));
0772     writer->writeAttribute("sourceType", QString::number(static_cast<int>(m_sourceType)));
0773     writer->writeAttribute("keepNValues", QString::number(m_keepNValues));
0774 
0775     if (m_updateType == UpdateType::TimeInterval)
0776         writer->writeAttribute("updateInterval", QString::number(m_updateInterval));
0777 
0778     if (m_readingType != ReadingType::TillEnd)
0779         writer->writeAttribute("sampleSize", QString::number(m_sampleSize));
0780     writer->writeEndElement(); //general
0781 
0782     //filter
0783     m_filter->save(writer);
0784 
0785     //columns
0786     if (!m_fileLinked) {
0787         for (auto* col : children<Column>(ChildIndexFlag::IncludeHidden))
0788             col->save(writer);
0789     }
0790 
0791     writer->writeEndElement(); // "liveDataSource"
0792 }
0793 
0794 /*!
0795   Loads from XML.
0796 */
0797 bool LiveDataSource::load(XmlStreamReader* reader, bool preview) {
0798     if (!readBasicAttributes(reader))
0799         return false;
0800 
0801     KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used");
0802     QXmlStreamAttributes attribs;
0803     QString str;
0804 
0805     while (!reader->atEnd()) {
0806         reader->readNext();
0807         if (reader->isEndElement()
0808             && (reader->name() == "liveDataSource" || reader->name() == "LiveDataSource")) //TODO: remove "LiveDataSources" in couple of releases
0809             break;
0810 
0811         if (!reader->isStartElement())
0812             continue;
0813 
0814         if (reader->name() == "comment") {
0815             if (!readCommentElement(reader))
0816                 return false;
0817         } else if (reader->name() == "general") {
0818             attribs = reader->attributes();
0819 
0820             str = attribs.value("fileName").toString();
0821             if (str.isEmpty())
0822                 reader->raiseWarning(attributeWarning.subs("fileName").toString());
0823             else
0824                 m_fileName = str;
0825 
0826             str = attribs.value("fileType").toString();
0827             if (str.isEmpty())
0828                 reader->raiseWarning(attributeWarning.subs("fileType").toString());
0829             else
0830                 m_fileType = (AbstractFileFilter::FileType)str.toInt();
0831 
0832             str = attribs.value("fileLinked").toString();
0833             if (str.isEmpty())
0834                 reader->raiseWarning(attributeWarning.subs("fileLinked").toString());
0835             else
0836                 m_fileLinked = str.toInt();
0837 
0838             str = attribs.value("relativePath").toString();
0839             if (str.isEmpty())
0840                 reader->raiseWarning(attributeWarning.subs("relativePath").toString());
0841             else
0842                 m_relativePath = str.toInt();
0843 
0844             str = attribs.value("updateType").toString();
0845             if (str.isEmpty())
0846                 reader->raiseWarning(attributeWarning.subs("updateType").toString());
0847             else
0848                 m_updateType =  static_cast<UpdateType>(str.toInt());
0849 
0850             str = attribs.value("sourceType").toString();
0851             if (str.isEmpty())
0852                 reader->raiseWarning(attributeWarning.subs("sourceType").toString());
0853             else
0854                 m_sourceType =  static_cast<SourceType>(str.toInt());
0855 
0856             str = attribs.value("readingType").toString();
0857             if (str.isEmpty())
0858                 reader->raiseWarning(attributeWarning.subs("readingType").toString());
0859             else
0860                 m_readingType =  static_cast<ReadingType>(str.toInt());
0861 
0862             if (m_updateType == UpdateType::TimeInterval) {
0863                 str = attribs.value("updateInterval").toString();
0864                 if (str.isEmpty())
0865                     reader->raiseWarning(attributeWarning.subs("updateInterval").toString());
0866                 else
0867                     m_updateInterval = str.toInt();
0868             }
0869 
0870             if (m_readingType != ReadingType::TillEnd) {
0871                 str = attribs.value("sampleSize").toString();
0872                 if (str.isEmpty())
0873                     reader->raiseWarning(attributeWarning.subs("sampleSize").toString());
0874                 else
0875                     m_sampleSize = str.toInt();
0876             }
0877 
0878             switch (m_sourceType) {
0879             case SourceType::SerialPort:
0880                 str = attribs.value("baudRate").toString();
0881                 if (str.isEmpty())
0882                     reader->raiseWarning(attributeWarning.subs("baudRate").toString());
0883                 else
0884                     m_baudRate = str.toInt();
0885 
0886                 str = attribs.value("serialPortName").toString();
0887                 if (str.isEmpty())
0888                     reader->raiseWarning(attributeWarning.subs("serialPortName").toString());
0889                 else
0890                     m_serialPortName = str;
0891 
0892                 break;
0893             case SourceType::NetworkTcpSocket:
0894             case SourceType::NetworkUdpSocket:
0895                 str = attribs.value("host").toString();
0896                 if (str.isEmpty())
0897                     reader->raiseWarning(attributeWarning.subs("host").toString());
0898                 else
0899                     m_host = str;
0900 
0901                 str = attribs.value("port").toString();
0902                 if (str.isEmpty())
0903                     reader->raiseWarning(attributeWarning.subs("port").toString());
0904                 else
0905                     m_host = str;
0906                 break;
0907             case SourceType::MQTT:
0908                 break;
0909             case SourceType::FileOrPipe:
0910                 break;
0911             case SourceType::LocalSocket:
0912                 break;
0913             }
0914 
0915         } else if (reader->name() == "asciiFilter") {
0916             setFilter(new AsciiFilter);
0917             if (!m_filter->load(reader))
0918                 return false;
0919         } else if (reader->name() == "rootFilter") {
0920             setFilter(new ROOTFilter);
0921             if (!m_filter->load(reader))
0922                 return false;
0923         } else if (reader->name() == "column") {
0924             Column* column = new Column(QString(), AbstractColumn::ColumnMode::Text);
0925             if (!column->load(reader, preview)) {
0926                 delete column;
0927                 setColumnCount(0);
0928                 return false;
0929             }
0930             addChild(column);
0931         } else {// unknown element
0932             reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString()));
0933             if (!reader->skipToEndElement()) return false;
0934         }
0935     }
0936 
0937     return !reader->hasError();
0938 }
0939 
0940 void LiveDataSource::finalizeLoad() {
0941     //convert from the relative path saved in the project file to the absolute file to work with
0942     if (m_relativePath) {
0943         QFileInfo fi(project()->fileName());
0944         m_fileName = fi.dir().absoluteFilePath(m_fileName);
0945     }
0946 
0947     //read the content of the file if it was only linked
0948     if (m_fileLinked && QFile::exists(m_fileName))
0949         this->read();
0950 
0951     //call setUpdateType() to start watching the file for changes, is required
0952     setUpdateType(m_updateType);
0953 }