File indexing completed on 2024-05-12 15:26:57

0001 /***************************************************************************
0002 File        : MQTTTopic.cpp
0003 Project     : LabPlot
0004 Description : Represents a topic of a MQTTSubscription
0005 --------------------------------------------------------------------
0006 Copyright   : (C) 2018 Kovacs Ferencz (kferike98@gmail.com)
0007 
0008 ***************************************************************************/
0009 
0010 /***************************************************************************
0011 *                                                                         *
0012 *  This program is free software; you can redistribute it and/or modify   *
0013 *  it under the terms of the GNU General Public License as published by   *
0014 *  the Free Software Foundation; either version 2 of the License, or      *
0015 *  (at your option) any later version.                                    *
0016 *                                                                         *
0017 *  This program is distributed in the hope that it will be useful,        *
0018 *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
0019 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
0020 *  GNU General Public License for more details.                           *
0021 *                                                                         *
0022 *   You should have received a copy of the GNU General Public License     *
0023 *   along with this program; if not, write to the Free Software           *
0024 *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
0025 *   Boston, MA  02110-1301  USA                                           *
0026 *                                                                         *
0027 ***************************************************************************/
0028 #include "backend/datasources/MQTTTopic.h"
0029 
0030 #include "backend/datasources/MQTTSubscription.h"
0031 #include "backend/datasources/MQTTClient.h"
0032 #include "kdefrontend/spreadsheet/PlotDataDialog.h"
0033 #include "commonfrontend/spreadsheet/SpreadsheetView.h"
0034 #include "backend/datasources/filters/AsciiFilter.h"
0035 
0036 #include <QMenu>
0037 #include <QIcon>
0038 #include <QAction>
0039 #include <KLocalizedString>
0040 
0041 /*!
0042   \class MQTTTopic
0043   \brief  Represents a topic of a subscription made in MQTTClient.
0044 
0045   \ingroup datasources
0046 */
0047 MQTTTopic::MQTTTopic(const QString& name, MQTTSubscription* subscription, bool loading) :
0048     Spreadsheet(name, loading, AspectType::MQTTTopic),
0049     m_topicName(name),
0050     m_MQTTClient(subscription->mqttClient()),
0051     m_filter(new AsciiFilter) {
0052 
0053     auto mainFilter = m_MQTTClient->filter();
0054 
0055     m_filter->setAutoModeEnabled(mainFilter->isAutoModeEnabled());
0056     if (!mainFilter->isAutoModeEnabled()) {
0057         m_filter->setCommentCharacter(mainFilter->commentCharacter());
0058         m_filter->setSeparatingCharacter(mainFilter->separatingCharacter());
0059         m_filter->setDateTimeFormat(mainFilter->dateTimeFormat());
0060         m_filter->setCreateIndexEnabled(mainFilter->createIndexEnabled());
0061         m_filter->setCreateTimestampEnabled(mainFilter->createTimestampEnabled());
0062         m_filter->setSimplifyWhitespacesEnabled(mainFilter->simplifyWhitespacesEnabled());
0063         m_filter->setNaNValueToZero(mainFilter->NaNValueToZeroEnabled());
0064         m_filter->setRemoveQuotesEnabled(mainFilter->removeQuotesEnabled());
0065         m_filter->setSkipEmptyParts(mainFilter->skipEmptyParts());
0066         m_filter->setHeaderEnabled(mainFilter->isHeaderEnabled());
0067         QString vectorNames;
0068         const QStringList& filterVectorNames = mainFilter->vectorNames();
0069         for (int i = 0; i < filterVectorNames.size(); ++i) {
0070             vectorNames.append(filterVectorNames.at(i));
0071             if (i != vectorNames.size() - 1)
0072                 vectorNames.append(QLatin1String(" "));
0073         }
0074 
0075         m_filter->setVectorNames(vectorNames);
0076         m_filter->setStartRow(mainFilter->startRow());
0077         m_filter->setEndRow(mainFilter->endRow());
0078         m_filter->setStartColumn(mainFilter->startColumn());
0079         m_filter->setEndColumn(mainFilter->endColumn());
0080     }
0081 
0082     connect(m_MQTTClient, &MQTTClient::readFromTopics, this, &MQTTTopic::read);
0083     qDebug()<<"New MqttTopic: " << m_topicName;
0084     initActions();
0085 }
0086 
0087 MQTTTopic::~MQTTTopic() {
0088     qDebug()<<"MqttTopic destructor:"<<m_topicName;
0089     delete m_filter;
0090 }
0091 
0092 /*!
0093  *\brief Sets the MQTTTopic's filter
0094  * The ownership of the filter is passed to MQTTTopic.
0095  *
0096  * \param filter
0097  */
0098 void MQTTTopic::setFilter(AsciiFilter* f) {
0099     delete m_filter;
0100     m_filter = f;
0101 }
0102 
0103 /*!
0104  *\brief Returns the MQTTTopic's filter
0105  */
0106 AsciiFilter* MQTTTopic::filter() const {
0107     return m_filter;
0108 }
0109 
0110 /*!
0111  *\brief Returns the MQTTTopic's icon
0112  */
0113 QIcon MQTTTopic::icon() const {
0114     return QIcon::fromTheme("text-plain");
0115 }
0116 
0117 /*!
0118  *\brief Adds an action to the MQTTTopic's context menu in the project explorer
0119  */
0120 QMenu* MQTTTopic::createContextMenu() {
0121     QMenu* menu = AbstractPart::createContextMenu();
0122 
0123     QAction* firstAction = nullptr;
0124     // if we're populating the context menu for the project explorer, then
0125     //there're already actions available there. Skip the first title-action
0126     //and insert the action at the beginning of the menu.
0127     if (menu->actions().size() > 1)
0128         firstAction = menu->actions().at(1);
0129 
0130     menu->insertAction(firstAction, m_plotDataAction);
0131     menu->insertSeparator(firstAction);
0132 
0133     return menu;
0134 }
0135 
0136 QWidget* MQTTTopic::view() const {
0137     if (!m_partView)
0138         m_partView = new SpreadsheetView(const_cast<MQTTTopic*>(this), true);
0139     return m_partView;
0140 }
0141 
0142 /*!
0143  *\brief Adds a message received by the topic to the message puffer
0144  */
0145 void MQTTTopic::newMessage(const QString& message) {
0146     m_messagePuffer.push_back(message);
0147 }
0148 
0149 /*!
0150  *\brief Returns the name of the MQTTTopic
0151  */
0152 QString MQTTTopic::topicName() const {
0153     return m_topicName;
0154 }
0155 
0156 /*!
0157  *\brief Initializes the actions of MQTTTopic
0158  */
0159 void MQTTTopic::initActions() {
0160     m_plotDataAction = new QAction(QIcon::fromTheme("office-chart-line"), i18n("Plot data"), this);
0161     connect(m_plotDataAction, &QAction::triggered, this, &MQTTTopic::plotData);
0162 }
0163 
0164 /*!
0165  *\brief Returns the MQTTClient the topic belongs to
0166  */
0167 MQTTClient *MQTTTopic::mqttClient() const {
0168     return m_MQTTClient;
0169 }
0170 
0171 //##############################################################################
0172 //#################################  SLOTS  ####################################
0173 //##############################################################################
0174 
0175 /*!
0176  *\brief Plots the data stored in MQTTTopic
0177  */
0178 void MQTTTopic::plotData() {
0179     auto* dlg = new PlotDataDialog(this);
0180     dlg->exec();
0181 }
0182 
0183 /*!
0184  *\brief Reads every message from the message puffer
0185  */
0186 void MQTTTopic::read() {
0187     while (!m_messagePuffer.isEmpty()) {
0188         qDebug() << "Reading from topic " << m_topicName;
0189         const QString tempMessage = m_messagePuffer.takeFirst();
0190         m_filter->readMQTTTopic(tempMessage, this);
0191     }
0192 }
0193 
0194 //##############################################################################
0195 //##################  Serialization/Deserialization  ###########################
0196 //##############################################################################
0197 /*!
0198   Saves as XML.
0199  */
0200 void MQTTTopic::save(QXmlStreamWriter* writer) const {
0201     writer->writeStartElement("MQTTTopic");
0202     writeBasicAttributes(writer);
0203     writeCommentElement(writer);
0204 
0205     //general
0206     writer->writeStartElement("general");
0207     writer->writeAttribute("topicName", m_topicName);
0208     writer->writeAttribute("filterPrepared", QString::number(m_filter->isPrepared()));
0209     writer->writeAttribute("filterSeparator", m_filter->separator());
0210     writer->writeAttribute("messagePufferSize", QString::number(m_messagePuffer.size()));
0211     for (int i = 0; i < m_messagePuffer.count(); ++i)
0212         writer->writeAttribute("message"+QString::number(i), m_messagePuffer[i]);
0213     writer->writeEndElement();
0214 
0215     //filter
0216     m_filter->save(writer);
0217 
0218     //Columns
0219     for (auto* col : children<Column>(AbstractAspect::ChildIndexFlag::IncludeHidden))
0220         col->save(writer);
0221 
0222     writer->writeEndElement(); //MQTTTopic
0223 }
0224 
0225 /*!
0226   Loads from XML.
0227 */
0228 bool MQTTTopic::load(XmlStreamReader* reader, bool preview) {
0229     removeColumns(0, columnCount());
0230     if (!readBasicAttributes(reader))
0231         return false;
0232 
0233     bool isFilterPrepared = false;
0234     QString separator;
0235 
0236     QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used");
0237     QXmlStreamAttributes attribs;
0238     QString str;
0239 
0240     while (!reader->atEnd()) {
0241         reader->readNext();
0242         if (reader->isEndElement() && reader->name() == "MQTTTopic")
0243             break;
0244 
0245         if (!reader->isStartElement())
0246             continue;
0247 
0248         if (reader->name() == "comment") {
0249             if (!readCommentElement(reader))
0250                 return false;
0251         } else if (reader->name() == "general") {
0252             attribs = reader->attributes();
0253 
0254             str = attribs.value("topicName").toString();
0255             if (str.isEmpty())
0256                 reader->raiseWarning(attributeWarning.arg("'topicName'"));
0257             else {
0258                 m_topicName =  str;
0259                 setName(str);
0260             }
0261 
0262             str = attribs.value("filterPrepared").toString();
0263             if (str.isEmpty())
0264                 reader->raiseWarning(attributeWarning.arg("'filterPrepared'"));
0265             else {
0266                 isFilterPrepared =  str.toInt();
0267             }
0268 
0269             str = attribs.value("filterSeparator").toString();
0270             if (str.isEmpty())
0271                 reader->raiseWarning(attributeWarning.arg("'filterSeparator'"));
0272             else {
0273                 separator =  str;
0274             }
0275 
0276             int pufferSize = 0;
0277             str = attribs.value("messagePufferSize").toString();
0278             if (str.isEmpty())
0279                 reader->raiseWarning(attributeWarning.arg("'messagePufferSize'"));
0280             else
0281                 pufferSize = str.toInt();
0282             for (int i = 0; i < pufferSize; ++i) {
0283                 str = attribs.value("message"+QString::number(i)).toString();
0284                 if (str.isEmpty())
0285                     reader->raiseWarning(attributeWarning.arg("'message"+QString::number(i)+'\''));
0286                 else
0287                     m_messagePuffer.push_back(str);
0288             }
0289         } else if (reader->name() == "asciiFilter") {
0290             if (!m_filter->load(reader))
0291                 return false;
0292         } else if (reader->name() == "column") {
0293             Column* column = new Column(QString(), AbstractColumn::ColumnMode::Text);
0294             if (!column->load(reader, preview)) {
0295                 delete column;
0296                 setColumnCount(0);
0297                 return false;
0298             }
0299             addChild(column);
0300         } else {// unknown element
0301             reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString()));
0302             if (!reader->skipToEndElement())
0303                 return false;
0304         }
0305     }
0306 
0307     //prepare filter for reading
0308     m_filter->setPreparedForMQTT(isFilterPrepared, this, separator);
0309 
0310     return !reader->hasError();
0311 }