From 711b86e91b25985ccdd3d487400b03019a33bd04 Mon Sep 17 00:00:00 2001 From: Phil Taylor Date: Wed, 5 Oct 2022 11:03:15 +0100 Subject: [PATCH] Add memory sqlite db for cluster spots. --- cluster.cpp | 79 ++++++++++++++++++++++------ cluster.h | 12 +++++ database.cpp | 103 ++++++++++++++++++++++++++++++++++++ database.h | 34 ++++++++++++ wfmain.cpp | 144 ++++++++++++++++++++++++++++++--------------------- wfmain.h | 11 ++-- wfview | 0 wfview.pro | 4 +- 8 files changed, 308 insertions(+), 79 deletions(-) create mode 100644 database.cpp create mode 100644 database.h create mode 100644 wfview diff --git a/cluster.cpp b/cluster.cpp index 04cbd71..0069219 100644 --- a/cluster.cpp +++ b/cluster.cpp @@ -13,6 +13,8 @@ dxClusterClient::~dxClusterClient() qInfo(logCluster()) << "closing dxClusterClient()"; enableUdp(false); enableTcp(false); + database db; + db.close(); } void dxClusterClient::enableUdp(bool enable) @@ -27,7 +29,6 @@ void dxClusterClient::enableUdp(bool enable) qInfo(logCluster()) << "Starting udpSocket() on:" << udpPort << "Result:" << result; connect(udpSocket, SIGNAL(readyRead()), this, SLOT(udpDataReceived()), Qt::QueuedConnection); - } } else { @@ -56,6 +57,7 @@ void dxClusterClient::enableTcp(bool enable) qInfo(logCluster()) << "Starting tcpSocket() on:" << tcpPort; connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(tcpDataReceived()), Qt::QueuedConnection); + connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(tcpDisconnected())); tcpCleanupTimer = new QTimer(this); tcpCleanupTimer->setInterval(1000 * 10); // Run once a minute @@ -76,7 +78,7 @@ void dxClusterClient::enableTcp(bool enable) tcpSocket->disconnect(); delete tcpSocket; tcpSocket = Q_NULLPTR; - emit deleteOldSpots(0); + //emit deleteOldSpots(0); } } } @@ -104,17 +106,26 @@ void dxClusterClient::udpDataReceived() data->timestamp = QDateTime::fromString(spot.firstChildElement("timestamp").text(),"yyyy-MM-dd hh:mm:ss"); data->mode = spot.firstChildElement("mode").text(); data->comment = spot.firstChildElement("comment").text(); - emit addSpot(data); - emit(sendOutput(QString("UDP: SPOT:%1 SPOTTER:%2 FREQ:%3 MODE:%4 DATE:%5 COMMENT:%6\n") - .arg(data->dxcall).arg(data->spottercall).arg(data->frequency).arg(data->mode) - .arg(data->timestamp.toString()).arg(data->comment))); + //emit addSpot(data); + + database db = database(); + db.query(QString("DELETE from spots where dxcall='%1'").arg(data->dxcall)); + QString query = QString("INSERT INTO spots(type,spottercall,frequency,dxcall,mode,comment,timestamp) VALUES('%1','%2',%3,'%4','%5','%6','%7')\n") + .arg("UDP").arg(data->spottercall).arg(data->frequency).arg(data->dxcall).arg(data->mode).arg(data->comment).arg(data->timestamp.toString("yyyy-MM-dd hh:mm:ss")); + db.query(query); + emit sendOutput(query); } else if (action == "delete") { QString dxcall = spot.firstChildElement("dxcall").text(); - emit deleteSpot(dxcall); - qInfo(logCluster()) << "DELETE DX=" << dxcall; + double frequency = spot.firstChildElement("frequency").text().toDouble() / 1000.0; + //emit deleteSpot(dxcall); + database db = database(); + QString query=QString("DELETE from spots where dxcall='%1' AND frequency=%2").arg(dxcall).arg(frequency); + db.query(query); + qInfo(logCluster()) << query; } + updateSpots(); } } } @@ -140,17 +151,21 @@ void dxClusterClient::tcpDataReceived() if (match.hasMatch()) { spotData* data = new spotData(); data->spottercall = match.captured(1); - data->frequency = match.captured(2).toFloat() / 1000.0; + data->frequency = match.captured(2).toDouble() / 1000.0; data->dxcall = match.captured(3); data->comment = match.captured(4).trimmed(); data->timestamp = QDateTime::currentDateTimeUtc(); //data.timestamp = QDateTime::fromString(match.captured(5), "hhmmZ"); - emit addSpot(data); - emit(sendOutput(QString("TCP: SPOT:%1 SPOTTER:%2 FREQ:%3 MODE:%4 DATE:%5 COMMENT:%6\n") - .arg(data->dxcall).arg(data->spottercall).arg(data->frequency).arg(data->mode) - .arg(data->timestamp.toString()).arg(data->comment))); + //emit addSpot(data); + database db = database(); + db.query(QString("DELETE from spots where dxcall='%1'").arg(data->dxcall)); + QString query = QString("INSERT INTO spots(type,spottercall,frequency,dxcall,comment,timestamp) VALUES('%1','%2',%3,'%4','%5','%6')\n") + .arg("TCP").arg(data->spottercall).arg(data->frequency).arg(data->dxcall).arg(data->comment).arg(data->timestamp.toString("yyyy-MM-dd hh:mm:ss")); + db.query(query); + emit sendOutput(query); } } + updateSpots(); } @@ -169,5 +184,39 @@ void dxClusterClient::sendTcpData(QString data) void dxClusterClient::tcpCleanup() { - emit deleteOldSpots(tcpTimeout); -} \ No newline at end of file + //emit deleteOldSpots(tcpTimeout); + database db = database(); + db.query(QString("DELETE FROM spots where timestamp < datetime('now', '-%1 minutes')").arg(tcpTimeout)); +} + +void dxClusterClient::tcpDisconnected() { + qWarning(logCluster()) << "TCP Cluster server disconnected..."; + // Need to start a timer and attempt reconnect. +} + +void dxClusterClient::freqRange(double low, double high) +{ + lowFreq = low; + highFreq = high; + //qInfo(logCluster) << "New range" << low << "-" << high; + updateSpots(); +} + +void dxClusterClient::updateSpots() +{ + // Set the required freqency range. + QString queryText = QString("SELECT * FROM spots WHERE frequency >= %1 AND frequency <= %2").arg(lowFreq).arg(highFreq); + //QString queryText = QString("SELECT * FROM spots"); + database db; + auto query = db.query(queryText); + + QList spots; + while (query.next()) { + // Step through all current spots within range + spotData s = spotData(); + s.dxcall = query.value(query.record().indexOf("dxcall")).toString(); + s.frequency = query.value(query.record().indexOf("frequency")).toDouble(); + spots.append(s); + } + emit sendSpots(spots); +} diff --git a/cluster.h b/cluster.h index 25cad70..0a54bed 100644 --- a/cluster.h +++ b/cluster.h @@ -11,7 +11,10 @@ #include #include #include +#include +#include #include +#include "database.h" struct spotData { QString dxcall; @@ -21,6 +24,7 @@ struct spotData { QString mode; QString comment; QCPItemText* text = Q_NULLPTR; + bool current = false; }; struct clusterSettings { @@ -45,10 +49,12 @@ signals: void deleteSpot(QString dxcall); void deleteOldSpots(int minutes); void sendOutput(QString text); + void sendSpots(QList spots); public slots: void udpDataReceived(); void tcpDataReceived(); + void tcpDisconnected(); void enableUdp(bool enable); void enableTcp(bool enable); void setUdpPort(int p) { udpPort = p; } @@ -58,9 +64,12 @@ public slots: void setTcpPassword(QString s) { tcpPassword = s; } void setTcpTimeout(int p) { tcpTimeout = p; } void tcpCleanup(); + void freqRange(double low, double high); private: void sendTcpData(QString data); + bool databaseOpen(); + void updateSpots(); bool udpEnable; bool tcpEnable; @@ -77,6 +86,9 @@ private: QMutex mutex; bool authenticated=false; QTimer* tcpCleanupTimer=Q_NULLPTR; + QSqlDatabase db; + double lowFreq; + double highFreq; }; #endif diff --git a/database.cpp b/database.cpp new file mode 100644 index 0000000..4ac2a6e --- /dev/null +++ b/database.cpp @@ -0,0 +1,103 @@ +#include "database.h" +#include "logcategories.h" + +database::database() +{ + open(); +} + +database::~database() +{ +} + +bool database::open() +{ + auto name = "my_db_" + QString::number((quint64)QThread::currentThread(), 16); + if (QSqlDatabase::contains(name)) + { + db = QSqlDatabase::database(name); + qu = QSqlQuery(db); + return true; + } + else { + qInfo(logCluster()) << "Creating new connection" << name; + db = QSqlDatabase::addDatabase("QSQLITE", name); + qu = QSqlQuery(db); + } + + //QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/" + "wfview.db"; + QString path = ":memory:"; + qInfo(logCluster()) << "DB Filename" << path; + db.setDatabaseName(path); + if (db.isValid()) + { + db.open(); + if (check()) { + return true; + } + } + qWarning(logCluster()) << "Database is not valid!"; + return false; +} + +void database::close() +{ + auto name = "my_db_" + QString::number((quint64)QThread::currentThread(), 16); + qInfo(logCluster()) << "Closing database connection:" << name; + db.close(); +} + + + +QSqlQuery database::query(QString query) +{ + if (!db.isOpen()) + { + qWarning(logCluster()) << "Query Database is not open!"; + db.open(); + } + qu.exec(query); + return qu; +} + + + +bool database::check() +{ + if (db.isOpen()) { + for (const auto& table : db.tables()) + { + if (table == "spots") + { + qInfo(logCluster()) << "DB Contains spots table"; + return true; + } + } + qInfo(logCluster()) << "Creating spots table"; + // Spots table does not exist, need to create it. + /* + QString dxcall; + double frequency; + QString spottercall; + QDateTime timestamp; + QString mode; + QString comment; + QCPItemText* text = Q_NULLPTR;*/ + qu.exec("CREATE TABLE spots " + "(id INTEGER PRIMARY KEY, " + "type VARCHAR(3)," + "dxcall VARCHAR(30)," + "spottercall VARCHAR(30)," + "frequency DOUBLE," + "timestamp DATETIME," + "mode VARCHAR(30)," + "comment VARCHAR(255) )"); + qu.exec("CREATE INDEX spots_index ON spots(type,dxcall,frequency,timestamp)"); + return true; + } + else { + qWarning(logCluster()) << "Database is not open"; + + } + return false; +} \ No newline at end of file diff --git a/database.h b/database.h new file mode 100644 index 0000000..7ddc9f2 --- /dev/null +++ b/database.h @@ -0,0 +1,34 @@ +#ifndef DATABASE_H +#define DATABASE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class database +{ +public: + explicit database(); + virtual ~database(); + bool open(); + void close(); + QSqlQuery query(QString query); + QSqlResult database::get(); + +signals: + +public slots: +private: + bool check(); + QSqlDatabase db; + QSqlQuery qu; + +}; + +#endif diff --git a/wfmain.cpp b/wfmain.cpp index 669e362..f1be150 100644 --- a/wfmain.cpp +++ b/wfmain.cpp @@ -69,9 +69,10 @@ wfmain::wfmain(const QString settingsFile, const QString logFile, bool debugMode qRegisterMetaType (); qRegisterMetaType(); qRegisterMetaType>(); + qRegisterMetaType>(); + qRegisterMetaType>(); qRegisterMetaType(); qRegisterMetaType(); - qRegisterMetaType(); haveRigCaps = false; @@ -125,10 +126,9 @@ wfmain::wfmain(const QString settingsFile, const QString logFile, bool debugMode connect(this, SIGNAL(setClusterUserName(QString)), cluster, SLOT(setTcpUserName(QString))); connect(this, SIGNAL(setClusterPassword(QString)), cluster, SLOT(setTcpPassword(QString))); connect(this, SIGNAL(setClusterTimeout(int)), cluster, SLOT(setTcpTimeout(int))); + connect(this, SIGNAL(setFrequencyRange(double, double)), cluster, SLOT(freqRange(double, double))); - connect(cluster, SIGNAL(addSpot(spotData*)), this, SLOT(addClusterSpot(spotData*))); - connect(cluster, SIGNAL(deleteSpot(QString)), this, SLOT(deleteClusterSpot(QString))); - connect(cluster, SIGNAL(deleteOldSpots(int)), this, SLOT(deleteOldClusterSpots(int))); + connect(cluster, SIGNAL(sendSpots(QList)), this, SLOT(receiveSpots(QList))); connect(cluster, SIGNAL(sendOutput(QString)), this, SLOT(receiveClusterOutput(QString))); connect(clusterThread, SIGNAL(finished()), cluster, SLOT(deleteLater())); @@ -3855,6 +3855,8 @@ void wfmain::receiveSpectrumData(QByteArray spectrum, double startFreq, double e // This will break if the button is ever moved or renamed. on_clearPeakBtn_clicked(); } + // Inform other threads (cluster) that the frequency range has changed. + emit setFrequencyRange(startFreq, endFreq); // TODO: Add clear-out for the buffer } @@ -4117,7 +4119,7 @@ void wfmain::handleWFDoubleClick(QMouseEvent *me) } } -void wfmain::handlePlotClick(QMouseEvent *me) +void wfmain::handlePlotClick(QMouseEvent* me) { double x = plot->xAxis->pixelToCoord(me->pos().x()); showStatusBarText(QString("Selected %1 MHz").arg(x)); @@ -7499,42 +7501,6 @@ void wfmain::receiveClusterOutput(QString text) { ui->clusterOutputTextEdit->moveCursor(QTextCursor::End); } -void wfmain::addClusterSpot(spotData* s) { - - s->text = new QCPItemText(plot); - s->text->setAntialiased(true); - s->text->setColor(QColor(Qt::red)); - s->text->setText(s->dxcall); - s->text->setFont(QFont(font().family(), 10)); - s->text->setPositionAlignment(Qt::AlignTop | Qt::AlignHCenter); - //s->text->setClipAxisRect(false); - s->text->position->setType(QCPItemPosition::ptPlotCoords); - //bool conflict = true; - //QCPAxisRect* rect = spot.text->position->axisRect(); - double left = s->frequency; - double top = rigCaps.spectAmpMax - 50.0; - - s->text->position->setCoords(left, top); - - //QList col_it = spot.text->item(Qt::IntersectsItemBoundingRect); - QMutexLocker locker(&clusterMutex); - clusterSpots.insert(s->dxcall, s); - //qInfo(logGui()) << "Number of cluster spots" << clusterSpots.size(); -} - -void wfmain::deleteClusterSpot(QString dxcall) { - QMutexLocker locker(&clusterMutex); - QMap::iterator spot = clusterSpots.find(dxcall); - while (spot != clusterSpots.end() && spot.key() == dxcall) { - if (spot.value()->text != Q_NULLPTR) - { - plot->removeItem(spot.value()->text); - } - delete spot.value(); - spot = clusterSpots.erase(spot); - } -} - void wfmain::on_clusterUdpEnable_clicked(bool enable) { prefs.clusterUdpEnable = enable; @@ -7661,26 +7627,86 @@ void wfmain::on_clusterTimeoutLineEdit_editingFinished() } -void wfmain::deleteOldClusterSpots(int timeout) +void wfmain::receiveSpots(QList spots) { - QMutexLocker locker(&clusterMutex); - QDateTime time=QDateTime::currentDateTimeUtc(); - //qDebug(logGui()) << "Deleting old Cluster spots"; - QMap::iterator spot = clusterSpots.begin(); - while (spot != clusterSpots.end()) { - if (spot.value()->timestamp.addSecs(timeout * 60) < time) { - if (spot.value()->text != Q_NULLPTR) - { - plot->removeItem(spot.value()->text); - } - //qDebug(logGui()) << "Deleting:" << spot.value()->dxcall << "Timestamp" << spot.value()->timestamp.addSecs(timeout * 60) << "is lower than" << time; - delete spot.value(); // Stop memory leak? - spot = clusterSpots.erase(spot); - } - else { - //qDebug(logGui()) << "Spot:" << spot.value()->dxcall << "Timestamp" << spot.value()->timestamp.addSecs(timeout * 60) << "not lower than" << time; + QElapsedTimer timer; + timer.start(); + + QMap::iterator spot1 = clusterSpots.begin(); + while (spot1 != clusterSpots.end()) { + spot1.value()->current = false; + ++spot1; + } + + foreach(spotData s, spots) + { + bool found = false; + QMap::iterator spot = clusterSpots.find(s.dxcall); + + while (spot != clusterSpots.end() && spot.key() == s.dxcall && spot.value()->frequency == s.frequency) { + spot.value()->current = true; + found = true; ++spot; } + + if (!found) + { + spotData* sp = new spotData(); + sp->dxcall = s.dxcall; + sp->frequency = s.frequency; + + //qDebug(logCluster()) << "ADD:" << sp->dxcall; + sp->current = true; + bool conflict = true; + double left = sp->frequency; + double top = rigCaps.spectAmpMax - 10; + sp->text = new QCPItemText(plot); + sp->text->setAntialiased(true); + sp->text->setColor(QColor(Qt::red)); + sp->text->setText(sp->dxcall); + sp->text->setFont(QFont(font().family(), 10)); + sp->text->setPositionAlignment(Qt::AlignVCenter | Qt::AlignHCenter); + sp->text->position->setType(QCPItemPosition::ptPlotCoords); + sp->text->setSelectable(true); + QMargins margin; + int width = (sp->text->right - sp->text->left) / 2; + margin.setLeft(width); + margin.setRight(width); + sp->text->setPadding(margin); + sp->text->position->setCoords(left, top); + sp->text->setVisible(false); + while (conflict) { + QCPItemText* textItem = plot->itemAt(sp->text->position->pixelPosition(), true); + if (textItem != nullptr && sp->text != textItem) { + //qInfo(logGui()) << "CONFLICT:" << textItem->text() << "SAME POSITION AS" << sp->dxcall << sp->text->position->pixelPosition(); + top = top - 5.0; + } + else { + //qInfo(logGui()) << "OK:" << sp->dxcall << sp->text->position->pixelPosition(); + conflict = false; + } + sp->text->position->setCoords(left, top); + } + + sp->text->setVisible(true); + clusterSpots.insert(sp->dxcall, sp); + } } -} \ No newline at end of file + QMap::iterator spot2 = clusterSpots.begin(); + while (spot2 != clusterSpots.end()) { + if (spot2.value()->current == false) { + plot->removeItem(spot2.value()->text); + //qDebug(logCluster()) << "REMOVE:" << spot2.value()->dxcall; + delete spot2.value(); // Stop memory leak? + spot2 = clusterSpots.erase(spot2); + } + else { + ++spot2; + } + + } + + //qDebug(logCluster()) << "Processing took" << timer.nsecsElapsed() / 1000 << "us"; +} + diff --git a/wfmain.h b/wfmain.h index a1b6431..ceb0e72 100644 --- a/wfmain.h +++ b/wfmain.h @@ -18,6 +18,8 @@ #include #include #include +#include +#include #include "logcategories.h" #include "commhandler.h" @@ -40,6 +42,7 @@ #include "colorprefs.h" #include "loggingwindow.h" #include "cluster.h" +#include "database.h" #include #include @@ -197,11 +200,9 @@ signals: void setClusterUserName(QString name); void setClusterPassword(QString pass); void setClusterTimeout(int timeout); + void setFrequencyRange(double low, double high); private slots: - void addClusterSpot(spotData* spot); - void deleteClusterSpot(QString dxcall); - void deleteOldClusterSpots(int timeout); void updateSizes(int tabIndex); void shortcutF1(); void shortcutF2(); @@ -675,7 +676,7 @@ private slots: void on_clusterTimeoutLineEdit_editingFinished(); void receiveClusterOutput(QString text); - + void receiveSpots(QList spots); private: Ui::wfmain *ui; @@ -1078,6 +1079,7 @@ private: QCPItemText* text=Q_NULLPTR; QList clusters; QMutex clusterMutex; + QSqlDatabase db; }; Q_DECLARE_METATYPE(struct rigCapabilities) @@ -1097,6 +1099,7 @@ Q_DECLARE_METATYPE(enum meterKind) Q_DECLARE_METATYPE(enum spectrumMode) Q_DECLARE_METATYPE(enum mode_kind) Q_DECLARE_METATYPE(QList) +Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(rigstate*) //void (*wfmain::logthistext)(QString text) = NULL; diff --git a/wfview b/wfview new file mode 100644 index 0000000..e69de29 diff --git a/wfview.pro b/wfview.pro index fc04e57..8353efe 100644 --- a/wfview.pro +++ b/wfview.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += core gui serialport network multimedia xml +QT += core gui serialport network multimedia xml sql greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport @@ -175,6 +175,7 @@ SOURCES += main.cpp\ selectradio.cpp \ tcpserver.cpp \ cluster.cpp \ + database.cpp \ aboutbox.cpp HEADERS += wfmain.h \ @@ -212,6 +213,7 @@ HEADERS += wfmain.h \ selectradio.h \ tcpserver.h \ cluster.h \ + database.h \ aboutbox.h