From 62442721193576ddd1516c8df0611918fa23a63f Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 3 Nov 2025 11:12:15 +0100 Subject: [PATCH 01/34] Add libshv dependency to enable QxEvent service implementation --- .clang-tidy | 1 + .gitmodules | 3 + 3rdparty/libshv | 1 + CMakeLists.txt | 4 + .../libqfgui/src/framework/datadialogwidget.h | 2 +- quickevent/app/quickevent/CMakeLists.txt | 8 + .../Event/src/services/qx/qxclientservice.cpp | 187 ++++++++++++++---- .../Event/src/services/qx/qxclientservice.h | 16 ++ .../Event/src/services/serviceswidget.cpp | 7 +- .../Event/src/services/servicewidget.cpp | 11 +- .../Event/src/services/servicewidget.h | 2 +- 11 files changed, 188 insertions(+), 54 deletions(-) create mode 160000 3rdparty/libshv diff --git a/.clang-tidy b/.clang-tidy index fd21b3531..64c3c28fa 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -40,6 +40,7 @@ Checks: -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-special-member-functions, + -cppcoreguidelines-use-enum-class -hicpp-avoid-c-arrays, -hicpp-avoid-goto, -hicpp-braces-around-statements, diff --git a/.gitmodules b/.gitmodules index 6e2bec6bb..5006631e0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "3rdparty/necrolog"] path = 3rdparty/necrolog url = https://github.com/fvacek/necrolog.git +[submodule "3rdparty/libshv"] + path = 3rdparty/libshv + url = https://github.com/silicon-heaven/libshv.git diff --git a/3rdparty/libshv b/3rdparty/libshv new file mode 160000 index 000000000..3cbe496ea --- /dev/null +++ b/3rdparty/libshv @@ -0,0 +1 @@ +Subproject commit 3cbe496ea4d2c48695994850a09c2cc794c3f80b diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a214da86..b5b73faa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.18.4) set(QF_BUILD_QML_PLUGINS ON CACHE BOOL "Build with QML Plugins support") +set(QF_WITH_LIBSHV OFF CACHE BOOL "Build with libshv") project(quickbox LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 20) @@ -34,6 +35,9 @@ endif (WIN32) if (NOT TARGET libnecrolog) add_subdirectory(3rdparty/necrolog) endif() +if (QF_WITH_LIBSHV) + add_subdirectory(3rdparty/libshv) +endif() set(USE_QT6 ON) find_package(Qt6 REQUIRED COMPONENTS Core Widgets Gui Sql Qml Xml LinguistTools PrintSupport Svg SerialPort Multimedia Network) diff --git a/libqf/libqfgui/src/framework/datadialogwidget.h b/libqf/libqfgui/src/framework/datadialogwidget.h index 37c4c1d91..ec4cca8e0 100644 --- a/libqf/libqfgui/src/framework/datadialogwidget.h +++ b/libqf/libqfgui/src/framework/datadialogwidget.h @@ -26,7 +26,7 @@ class QFGUI_DECL_EXPORT DataDialogWidget : public DialogWidget qf::gui::model::DataDocument* dataDocument(bool throw_exc = qf::core::Exception::Throw); - Q_SLOT virtual bool load(const QVariant &id = QVariant(), int mode = qf::gui::model::DataDocument::ModeEdit); + virtual bool load(const QVariant &id = QVariant(), int mode = qf::gui::model::DataDocument::ModeEdit); bool acceptDialogDone(int result) Q_DECL_OVERRIDE; diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index b6218790e..13549e64f 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -273,6 +273,14 @@ qt6_add_lupdate(quickevent TS_FILES target_sources(quickevent PRIVATE ${QM_FILES}) target_include_directories(quickevent PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src) target_link_libraries(quickevent PUBLIC libquickeventcore libquickeventgui libqfgui libsiut) +if (QF_WITH_LIBSHV) + target_link_libraries(quickevent PUBLIC libshviotqt) + target_compile_definitions(quickevent PRIVATE QF_WITH_LIBSHV) +endif() + +foreach(plugin IN ITEMS Classes Runs Relays Receipts shared) + install(DIRECTORY plugins/${plugin}/qml/reports DESTINATION ${CMAKE_INSTALL_BINDIR}/reports/${plugin}/qml) +endforeach() # Extract version from appversion.h file(READ "${CMAKE_CURRENT_SOURCE_DIR}/src/appversion.h" APP_VERSION_H) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp index 502483522..7a9acb8fd 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp @@ -9,6 +9,10 @@ #include #include +#include +#include +#include + #include #include #include @@ -24,6 +28,10 @@ #include #include +using namespace shv::chainpack; +using namespace shv::iotqt::rpc; +using namespace shv::iotqt::node; + using namespace qf::core; using namespace qf::gui; using namespace qf::gui::dialogs; @@ -66,28 +74,41 @@ QString QxClientService::serviceId() } void QxClientService::run() { + using namespace shv::iotqt::rpc; + auto ss = settings(); - auto *reply = getRemoteEventInfo(ss.exchangeServerUrl(), apiToken()); - connect(reply, &QNetworkReply::finished, this, [this, reply, ss]() { - if (reply->error() == QNetworkReply::NetworkError::NoError) { - auto data = reply->readAll(); - auto doc = QJsonDocument::fromJson(data); - EventInfo event_info(doc.toVariant().toMap()); - setStatusMessage(event_info.name() + (event_info.stage_count() > 1? QStringLiteral(" E%1").arg(event_info.stage()): QString())); - m_eventId = event_info.id(); - connectToSSE(m_eventId); - if (!m_pollChangesTimer) { - m_pollChangesTimer = new QTimer(this); - connect(m_pollChangesTimer, &QTimer::timeout, this, &QxClientService::pollQxChanges); - } - pollQxChanges(); - m_pollChangesTimer->start(10000); - Super::run(); - } - else { - qfWarning() << "Cannot run QX service, network error:" << reply->errorString(); - } - }); + + delete m_rpcConnection; + m_rpcConnection = new ClientConnection(this); + m_rpcConnection->setConnectionString(ss.exchangeServerUrl()); + + connect(m_rpcConnection, &ClientConnection::brokerConnectedChanged, this, &QxClientService::onBrokerConnectedChanged); + connect(m_rpcConnection, &ClientConnection::socketError, this, &QxClientService::onBrokerSocketError); + connect(m_rpcConnection, &ClientConnection::brokerLoginError, this, &QxClientService::onBrokerLoginError); + connect(m_rpcConnection, &ClientConnection::rpcMessageReceived, this, &QxClientService::onRpcMessageReceived); + + m_rpcConnection->open(); + +// connect(reply, &QNetworkReply::finished, this, [this, reply, ss]() { +// if (reply->error() == QNetworkReply::NetworkError::NoError) { +// auto data = reply->readAll(); +// auto doc = QJsonDocument::fromJson(data); +// EventInfo event_info(doc.toVariant().toMap()); +// setStatusMessage(event_info.name() + (event_info.stage_count() > 1? QStringLiteral(" E%1").arg(event_info.stage()): QString())); +// m_eventId = event_info.id(); +// connectToSSE(m_eventId); +// if (!m_pollChangesTimer) { +// m_pollChangesTimer = new QTimer(this); +// connect(m_pollChangesTimer, &QTimer::timeout, this, &QxClientService::pollQxChanges); +// } +// pollQxChanges(); +// m_pollChangesTimer->start(10000); +// Super::run(); +// } +// else { +// qfWarning() << "Cannot run QX service, network error:" << reply->errorString(); +// } +// }); } void QxClientService::stop() @@ -537,35 +558,115 @@ EventInfo QxClientService::eventInfo() const // qfInfo() << qf::core::Utils::qvariantToJson(ei, false); return ei; } -/* -namespace { -auto query_to_json_csv(QSqlQuery &q) +//namespace { +//auto query_to_json_csv(QSqlQuery &q) +//{ +// QVariantList csv; +// { +// // QStringList columns{"name", "control_count", "length", "climb", "start_time", "interval", "start_slot_count"}; +// QStringList columns; +// auto rec = q.record(); +// for (auto i = 0; i < rec.count(); ++i) { +// columns << rec.field(i).name(); +// } +// csv.insert(csv.length(), columns); +// } +// while (q.next()) { +// QVariantList values; +// auto rec = q.record(); +// for (auto i = 0; i < rec.count(); ++i) { +// values << q.value(i); +// } +// csv.insert(csv.length(), values); +// } +// return csv; +//} +//} +int QxClientService::currentConnectionId() { - QVariantList csv; - { - // QStringList columns{"name", "control_count", "length", "climb", "start_time", "interval", "start_slot_count"}; - QStringList columns; - auto rec = q.record(); - for (auto i = 0; i < rec.count(); ++i) { - columns << rec.field(i).name(); - } - csv.insert(csv.length(), columns); + return qf::core::sql::Connection::forName().connectionId(); +} + +void QxClientService::onBrokerConnectedChanged(bool is_connected) +{ + if(is_connected) { + setStatus(Status::Running); + QTimer::singleShot(0, this, [this]() { + subscribeChanges(); +// testRpcCall(); + }); + } else { + setStatus(Status::Stopped); } - while (q.next()) { - QVariantList values; - auto rec = q.record(); - for (auto i = 0; i < rec.count(); ++i) { - values << q.value(i); + +} + +void QxClientService::onBrokerSocketError(const QString &err) +{ + setStatusMessage(tr("Broker socket error: %1").arg(err)); +} + +void QxClientService::onBrokerLoginError(const shv::chainpack::RpcError &err) +{ + setStatusMessage(tr("Broker login error: %1").arg(err.toString())); +} + +void QxClientService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg) +{ +// shvLogFuncFrame() << msg.toCpon(); + if(msg.isRequest()) { + RpcRequest rq(msg); + qfMessage() << "RPC request received:" << rq.toPrettyString(); + if(m_shvTree->root()) { + m_shvTree->root()->handleRpcRequest(rq); } - csv.insert(csv.length(), values); } - return csv; + else if(msg.isResponse()) { + RpcResponse rp(msg); + qfMessage() << "RPC response received:" << rp.toPrettyString(); + } + else if(msg.isSignal()) { + RpcSignal nt(msg); + qfMessage() << "RPC signal received:" << nt.toPrettyString(); + } } + +void QxClientService::subscribeChanges() +{ + Q_ASSERT(m_rpcConnection); + QString shv_path = "test"; + QString signal_name = shv::chainpack::Rpc::SIG_VAL_CHANGED; + auto *rpc_call = RpcCall::createSubscriptionRequest(m_rpcConnection, shv_path, signal_name); + connect(rpc_call, &RpcCall::maybeResult, this, [this, shv_path, signal_name](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + if(error.isValid()) { + qfError() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribe error:" << error.toString(); + } + else { + qfMessage() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribed successfully" << result.toCpon(); + // generate data change without ret value check + m_rpcConnection->callShvMethod((shv_path + "/someInt").toStdString(), "set", 123); + QTimer::singleShot(500, this, [this, shv_path]() { + m_rpcConnection->callShvMethod((shv_path + "/someInt").toStdString(), "set", 321); + }); + } + }); + rpc_call->start(); } -*/ -int QxClientService::currentConnectionId() + +void QxClientService::testRpcCall() const { - return qf::core::sql::Connection::forName().connectionId(); + Q_ASSERT(m_rpcConnection); + auto *rpc_call = RpcCall::create(m_rpcConnection) + ->setShvPath("test") + ->setMethod("ls"); +// ->setTimeout(5000); + connect(rpc_call, &RpcCall::maybeResult, [](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + if(error.isValid()) + qfError() << "RPC call error:" << error.toString(); + else + qfInfo() << "Got RPC response, result:" << result.toCpon(); + }); + rpc_call->start(); } } // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h index 636a54c4d..7e1f046be 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h @@ -7,6 +7,10 @@ class QNetworkReply; class QUrlQuery; class QTimer; +namespace shv::iotqt::node { class ShvNodeTree; } +namespace shv::iotqt::rpc { class ClientConnection; } +namespace shv::chainpack { class RpcMessage; class RpcError; } + namespace Event::services::qx { class QxClientServiceSettings : public ServiceSettings @@ -68,6 +72,14 @@ class QxClientService : public Service QUrl exchangeServerUrl() const; int eventId() const; +private: // shv + void onBrokerConnectedChanged(bool is_connected); + void onRpcMessageReceived(const shv::chainpack::RpcMessage &msg); + void onBrokerSocketError(const QString &err); + void onBrokerLoginError(const shv::chainpack::RpcError &err); + + void subscribeChanges(); + void testRpcCall() const; private: void loadSettings() override; qf::gui::framework::DialogWidget *createDetailWidget() override; @@ -84,6 +96,10 @@ class QxClientService : public Service void pollQxChanges(); EventInfo eventInfo() const; +private: // shv + shv::iotqt::rpc::ClientConnection *m_rpcConnection = nullptr; + shv::iotqt::node::ShvNodeTree *m_shvTree = nullptr; + private: QNetworkAccessManager *m_networkManager = nullptr; QNetworkReply *m_replySSE = nullptr; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/serviceswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/serviceswidget.cpp index e4c9a604a..324dd0424 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/serviceswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/serviceswidget.cpp @@ -35,12 +35,7 @@ void ServicesWidget::reload() Service *svc = Service::serviceAt(i); auto *sw = new ServiceWidget(); - sw->setStatus(svc->status()); - connect(svc, &Service::statusChanged, sw, &ServiceWidget::setStatus); - sw->setServiceId(svc->serviceId(), svc->serviceDisplayName()); - sw->setMessage(svc->statusMessage()); - connect(svc, &Service::statusMessageChanged, sw, &ServiceWidget::setMessage); - connect(sw, &ServiceWidget::setRunningRequest, svc, &Service::setRunning); + sw->setService(svc); ly2->addWidget(sw); } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.cpp index dd4bf8233..3069eb848 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.cpp @@ -45,10 +45,15 @@ void ServiceWidget::setStatus(Service::Status st) } } -void ServiceWidget::setServiceId(const QString &id, const QString &display_name) +void ServiceWidget::setService(Service *service) { - m_serviceId = id; - ui->lblServiceName->setText(display_name.isEmpty()? id: display_name); + m_serviceId = service->serviceId(); + ui->lblServiceName->setText(service->serviceDisplayName()); + setStatus(service->status()); + connect(service, &Service::statusChanged, this, &ServiceWidget::setStatus); + setMessage(service->statusMessage()); + connect(service, &Service::statusMessageChanged, this, &ServiceWidget::setMessage); + connect(this, &ServiceWidget::setRunningRequest, service, &Service::setRunning); } QString ServiceWidget::serviceId() const diff --git a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.h b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.h index f659cafde..4d589d2fe 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.h @@ -21,7 +21,7 @@ class ServiceWidget : public QWidget ~ServiceWidget(); void setStatus(Service::Status st); - void setServiceId(const QString &id, const QString &display_name); + void setService(Service *service); QString serviceId() const; void setMessage(const QString &m); From dce19697a992c45780b2f869dcace7d2930cbdc9 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sat, 8 Nov 2025 11:47:27 +0100 Subject: [PATCH 02/34] Rename QxClientService to QxEventService --- quickevent/app/quickevent/CMakeLists.txt | 7 +- .../plugins/Event/src/eventplugin.cpp | 4 +- ...qxclientservice.cpp => qxeventservice.cpp} | 84 +++++++++---------- .../{qxclientservice.h => qxeventservice.h} | 10 +-- ...icewidget.cpp => qxeventservicewidget.cpp} | 46 +++++----- ...servicewidget.h => qxeventservicewidget.h} | 14 ++-- ...rvicewidget.ui => qxeventservicewidget.ui} | 4 +- .../services/qx/qxlateregistrationswidget.cpp | 6 +- .../services/qx/qxlateregistrationswidget.h | 4 +- .../Event/src/services/qx/runchangedialog.cpp | 8 +- .../Event/src/services/qx/runchangedialog.h | 4 +- 11 files changed, 94 insertions(+), 97 deletions(-) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxclientservice.cpp => qxeventservice.cpp} (87%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxclientservice.h => qxeventservice.h} (91%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxclientservicewidget.cpp => qxeventservicewidget.cpp} (76%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxclientservicewidget.h => qxeventservicewidget.h} (65%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxclientservicewidget.ui => qxeventservicewidget.ui} (97%) diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index 13549e64f..b502afa91 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -89,11 +89,8 @@ add_executable(quickevent plugins/Event/src/services/serviceswidget.cpp plugins/Event/src/services/servicewidget.cpp plugins/Event/src/services/servicewidget.ui - plugins/Event/src/services/qx/qxclientservice.cpp - plugins/Event/src/services/qx/qxclientservice.h - plugins/Event/src/services/qx/qxclientservicewidget.cpp - plugins/Event/src/services/qx/qxclientservicewidget.h - plugins/Event/src/services/qx/qxclientservicewidget.ui + plugins/Event/src/services/qx/qxeventservice.cpp plugins/Event/src/services/qx/qxeventservice.h plugins/Event/src/services/qx/qxeventservicewidget.cpp plugins/Event/src/services/qx/qxeventservicewidget.h + plugins/Event/src/services/qx/qxeventservicewidget.ui plugins/Event/src/services/qx/qxlateregistrationswidget.h plugins/Event/src/services/qx/qxlateregistrationswidget.cpp plugins/Event/src/services/qx/qxlateregistrationswidget.ui diff --git a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp index 796c38221..6f7accfcc 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp @@ -11,7 +11,7 @@ #include "services/serviceswidget.h" #include "services/emmaclient.h" -#include "services/qx/qxclientservice.h" +#include "services/qx/qxeventservice.h" #include #include @@ -380,7 +380,7 @@ void EventPlugin::onInstalled() auto *emma_client = new services::EmmaClient(this); services::Service::addService(emma_client); - auto shvapi_client = new services::qx::QxClientService(this); + auto shvapi_client = new services::qx::QxEventService(this); services::Service::addService(shvapi_client); { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp similarity index 87% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 7a9acb8fd..6b673328e 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -1,5 +1,5 @@ -#include "qxclientservice.h" -#include "qxclientservicewidget.h" +#include "qxeventservice.h" +#include "qxeventservicewidget.h" #include "../../eventplugin.h" #include "../../../../Runs/src/runsplugin.h" @@ -56,24 +56,24 @@ namespace Event::services::qx { //=============================================== // QxClientService //=============================================== -QxClientService::QxClientService(QObject *parent) - : Super(QxClientService::serviceId(), parent) +QxEventService::QxEventService(QObject *parent) + : Super(QxEventService::serviceId(), parent) { auto *event_plugin = getPlugin(); - connect(event_plugin, &Event::EventPlugin::dbEventNotify, this, &QxClientService::onDbEventNotify, Qt::QueuedConnection); + connect(event_plugin, &Event::EventPlugin::dbEventNotify, this, &QxEventService::onDbEventNotify, Qt::QueuedConnection); } -QString QxClientService::serviceDisplayName() const +QString QxEventService::serviceDisplayName() const { return tr("QE Exchange"); } -QString QxClientService::serviceId() +QString QxEventService::serviceId() { return QStringLiteral("qx"); } -void QxClientService::run() { +void QxEventService::run() { using namespace shv::iotqt::rpc; auto ss = settings(); @@ -82,10 +82,10 @@ void QxClientService::run() { m_rpcConnection = new ClientConnection(this); m_rpcConnection->setConnectionString(ss.exchangeServerUrl()); - connect(m_rpcConnection, &ClientConnection::brokerConnectedChanged, this, &QxClientService::onBrokerConnectedChanged); - connect(m_rpcConnection, &ClientConnection::socketError, this, &QxClientService::onBrokerSocketError); - connect(m_rpcConnection, &ClientConnection::brokerLoginError, this, &QxClientService::onBrokerLoginError); - connect(m_rpcConnection, &ClientConnection::rpcMessageReceived, this, &QxClientService::onRpcMessageReceived); + connect(m_rpcConnection, &ClientConnection::brokerConnectedChanged, this, &QxEventService::onBrokerConnectedChanged); + connect(m_rpcConnection, &ClientConnection::socketError, this, &QxEventService::onBrokerSocketError); + connect(m_rpcConnection, &ClientConnection::brokerLoginError, this, &QxEventService::onBrokerLoginError); + connect(m_rpcConnection, &ClientConnection::rpcMessageReceived, this, &QxEventService::onRpcMessageReceived); m_rpcConnection->open(); @@ -111,7 +111,7 @@ void QxClientService::run() { // }); } -void QxClientService::stop() +void QxEventService::stop() { disconnectSSE(); if (m_pollChangesTimer) { @@ -120,13 +120,13 @@ void QxClientService::stop() Super::stop(); } -qf::gui::framework::DialogWidget *QxClientService::createDetailWidget() +qf::gui::framework::DialogWidget *QxEventService::createDetailWidget() { - auto *w = new QxClientServiceWidget(); + auto *w = new QxEventServiceWidget(); return w; } -void QxClientService::loadSettings() +void QxEventService::loadSettings() { Super::loadSettings(); auto ss = settings(); @@ -136,7 +136,7 @@ void QxClientService::loadSettings() m_settings = ss; } -void QxClientService::onDbEventNotify(const QString &domain, int connection_id, const QVariant &data) +void QxEventService::onDbEventNotify(const QString &domain, int connection_id, const QVariant &data) { Q_UNUSED(connection_id) Q_UNUSED(data) @@ -183,7 +183,7 @@ void QxClientService::onDbEventNotify(const QString &domain, int connection_id, } } -QNetworkAccessManager *QxClientService::networkManager() +QNetworkAccessManager *QxEventService::networkManager() { if (!m_networkManager) { m_networkManager = new QNetworkAccessManager(this); @@ -191,7 +191,7 @@ QNetworkAccessManager *QxClientService::networkManager() return m_networkManager; } -QNetworkReply *QxClientService::getRemoteEventInfo(const QString &qxhttp_host, const QString &api_token) +QNetworkReply *QxEventService::getRemoteEventInfo(const QString &qxhttp_host, const QString &api_token) { auto *nm = networkManager(); QNetworkRequest request; @@ -202,7 +202,7 @@ QNetworkReply *QxClientService::getRemoteEventInfo(const QString &qxhttp_host, c return nm->get(request); } -QNetworkReply *QxClientService::postEventInfo(const QString &qxhttp_host, const QString &api_token) +QNetworkReply *QxEventService::postEventInfo(const QString &qxhttp_host, const QString &api_token) { auto *nm = networkManager(); QNetworkRequest request; @@ -218,7 +218,7 @@ QNetworkReply *QxClientService::postEventInfo(const QString &qxhttp_host, const return nm->post(request, data); } -void QxClientService::postStartListIofXml3(QObject *context, std::function call_back) +void QxEventService::postStartListIofXml3(QObject *context, std::function call_back) { auto *ep = getPlugin(); int current_stage = ep->currentStageId(); @@ -229,7 +229,7 @@ void QxClientService::postStartListIofXml3(QObject *context, std::function call_back) +void QxEventService::postRuns(QObject *context, std::function call_back) { auto *ep = getPlugin(); int current_stage = ep->currentStageId(); @@ -241,7 +241,7 @@ void QxClientService::postRuns(QObject *context, std::function c } } -void QxClientService::getHttpJson(const QString &path, const QUrlQuery &query, QObject *context, const std::function &call_back) +void QxEventService::getHttpJson(const QString &path, const QUrlQuery &query, QObject *context, const std::function &call_back) { auto url = exchangeServerUrl(); url.setPath(path); @@ -270,7 +270,7 @@ void QxClientService::getHttpJson(const QString &path, const QUrlQuery &query, Q }); } -QNetworkReply* QxClientService::getQxChangesReply(int from_id) +QNetworkReply* QxEventService::getQxChangesReply(int from_id) { auto url = exchangeServerUrl(); @@ -282,7 +282,7 @@ QNetworkReply* QxClientService::getQxChangesReply(int from_id) return networkManager()->get(request); } -int QxClientService::eventId() const +int QxEventService::eventId() const { if (m_eventId == 0) { throw qf::core::Exception(tr("Event ID is not loaded, service is not probably running.")); @@ -290,7 +290,7 @@ int QxClientService::eventId() const return m_eventId; } -QByteArray QxClientService::apiToken() const +QByteArray QxEventService::apiToken() const { // API token must not be cached to enable service point // always to current stage event on qxhttpd @@ -299,13 +299,13 @@ QByteArray QxClientService::apiToken() const return event_plugin->stageData(current_stage).qxApiToken().toUtf8(); } -QUrl QxClientService::exchangeServerUrl() const +QUrl QxEventService::exchangeServerUrl() const { auto ss = settings(); return QUrl(ss.exchangeServerUrl()); } -void QxClientService::postFileCompressed(std::optional path, std::optional name, QByteArray data, QObject *context , std::function call_back) +void QxEventService::postFileCompressed(std::optional path, std::optional name, QByteArray data, QObject *context , std::function call_back) { auto url = exchangeServerUrl(); @@ -333,7 +333,7 @@ void QxClientService::postFileCompressed(std::optional path, std::optio }); } -void QxClientService::uploadSpecFile(SpecFile file, QByteArray data, QObject *context, const std::function &call_back) +void QxEventService::uploadSpecFile(SpecFile file, QByteArray data, QObject *context, const std::function &call_back) { switch (file) { case SpecFile::StartListIofXml3: @@ -345,7 +345,7 @@ void QxClientService::uploadSpecFile(SpecFile file, QByteArray data, QObject *co } } -QByteArray QxClientService::zlibCompress(QByteArray data) +QByteArray QxEventService::zlibCompress(QByteArray data) { QByteArray compressedData = qCompress(data); // strip the 4-byte length put on by qCompress @@ -354,7 +354,7 @@ QByteArray QxClientService::zlibCompress(QByteArray data) return compressedData; } -void QxClientService::httpPostJson(const QString &path, const QString &query, QVariantMap json, QObject *context, const std::function &call_back) +void QxEventService::httpPostJson(const QString &path, const QString &query, QVariantMap json, QObject *context, const std::function &call_back) { if (!isRunning()) { return; @@ -394,7 +394,7 @@ void QxClientService::httpPostJson(const QString &path, const QString &query, QV } } -void QxClientService::connectToSSE(int event_id) +void QxEventService::connectToSSE(int event_id) { Q_UNUSED(event_id); // auto url = exchangeServerUrl(); @@ -417,7 +417,7 @@ void QxClientService::connectToSSE(int event_id) // }); } -void QxClientService::disconnectSSE() +void QxEventService::disconnectSSE() { if (m_replySSE) { qfInfo() << "Disconnecting SSE:" << m_replySSE; @@ -426,7 +426,7 @@ void QxClientService::disconnectSSE() } } -void QxClientService::pollQxChanges() +void QxEventService::pollQxChanges() { auto event_plugin = getPlugin(); if(!getPlugin()->isEventOpen()) { @@ -501,7 +501,7 @@ void QxClientService::pollQxChanges() } } -EventInfo QxClientService::eventInfo() const +EventInfo QxEventService::eventInfo() const { auto *event_plugin = getPlugin(); auto *event_config = event_plugin->eventConfig(); @@ -582,12 +582,12 @@ EventInfo QxClientService::eventInfo() const // return csv; //} //} -int QxClientService::currentConnectionId() +int QxEventService::currentConnectionId() { return qf::core::sql::Connection::forName().connectionId(); } -void QxClientService::onBrokerConnectedChanged(bool is_connected) +void QxEventService::onBrokerConnectedChanged(bool is_connected) { if(is_connected) { setStatus(Status::Running); @@ -601,17 +601,17 @@ void QxClientService::onBrokerConnectedChanged(bool is_connected) } -void QxClientService::onBrokerSocketError(const QString &err) +void QxEventService::onBrokerSocketError(const QString &err) { setStatusMessage(tr("Broker socket error: %1").arg(err)); } -void QxClientService::onBrokerLoginError(const shv::chainpack::RpcError &err) +void QxEventService::onBrokerLoginError(const shv::chainpack::RpcError &err) { setStatusMessage(tr("Broker login error: %1").arg(err.toString())); } -void QxClientService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg) +void QxEventService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg) { // shvLogFuncFrame() << msg.toCpon(); if(msg.isRequest()) { @@ -631,7 +631,7 @@ void QxClientService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg } } -void QxClientService::subscribeChanges() +void QxEventService::subscribeChanges() { Q_ASSERT(m_rpcConnection); QString shv_path = "test"; @@ -653,7 +653,7 @@ void QxClientService::subscribeChanges() rpc_call->start(); } -void QxClientService::testRpcCall() const +void QxEventService::testRpcCall() const { Q_ASSERT(m_rpcConnection); auto *rpc_call = RpcCall::create(m_rpcConnection) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h similarity index 91% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index 7e1f046be..1ab8d14b7 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -13,13 +13,13 @@ namespace shv::chainpack { class RpcMessage; class RpcError; } namespace Event::services::qx { -class QxClientServiceSettings : public ServiceSettings +class QxEventServiceSettings : public ServiceSettings { using Super = ServiceSettings; QF_VARIANTMAP_FIELD2(QString, e, setE, xchangeServerUrl, "http://localhost:8000") public: - QxClientServiceSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} + QxEventServiceSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} }; class EventInfo : public QVariantMap @@ -38,7 +38,7 @@ class EventInfo : public QVariantMap EventInfo(const QVariantMap &data = QVariantMap()) : QVariantMap(data) {} }; -class QxClientService : public Service +class QxEventService : public Service { Q_OBJECT @@ -46,14 +46,14 @@ class QxClientService : public Service public: static constexpr auto QX_API_TOKEN = "qx-api-token"; public: - QxClientService(QObject *parent); + QxEventService(QObject *parent); static QString serviceId(); QString serviceDisplayName() const override; void run() override; void stop() override; - QxClientServiceSettings settings() const {return QxClientServiceSettings(m_settings);} + QxEventServiceSettings settings() const {return QxEventServiceSettings(m_settings);} void onDbEventNotify(const QString &domain, int connection_id, const QVariant &data); QNetworkAccessManager* networkManager(); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp similarity index 76% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.cpp rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index e9a04902e..ede33ffb4 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -1,6 +1,6 @@ -#include "qxclientservicewidget.h" -#include "ui_qxclientservicewidget.h" -#include "qxclientservice.h" +#include "qxeventservicewidget.h" +#include "ui_qxeventservicewidget.h" +#include "qxeventservice.h" #include @@ -19,14 +19,14 @@ using qf::gui::framework::getPlugin; namespace Event::services::qx { -QxClientServiceWidget::QxClientServiceWidget(QWidget *parent) +QxEventServiceWidget::QxEventServiceWidget(QWidget *parent) : Super(parent) - , ui(new Ui::QxClientServiceWidget) + , ui(new Ui::QxEventServiceWidget) { setPersistentSettingsId("QxClientServiceWidget"); ui->setupUi(this); - connect(ui->edServerUrl, &QLineEdit::textChanged, this, &QxClientServiceWidget::updateOCheckListPostUrl); - connect(ui->edApiToken, &QLineEdit::textChanged, this, &QxClientServiceWidget::updateOCheckListPostUrl); + connect(ui->edServerUrl, &QLineEdit::textChanged, this, &QxEventServiceWidget::updateOCheckListPostUrl); + connect(ui->edApiToken, &QLineEdit::textChanged, this, &QxEventServiceWidget::updateOCheckListPostUrl); setMessage(""); @@ -38,18 +38,18 @@ QxClientServiceWidget::QxClientServiceWidget(QWidget *parent) ui->edServerUrl->setText(settings.exchangeServerUrl()); ui->edApiToken->setText(svc->apiToken()); ui->edCurrentStage->setValue(current_stage); - connect(ui->btTestConnection, &QAbstractButton::clicked, this, &QxClientServiceWidget::testConnection); - connect(ui->btExportEventInfo, &QAbstractButton::clicked, this, &QxClientServiceWidget::exportEventInfo); - connect(ui->btExportStartList, &QAbstractButton::clicked, this, &QxClientServiceWidget::exportStartList); - connect(ui->btExportRuns, &QAbstractButton::clicked, this, &QxClientServiceWidget::exportRuns); + connect(ui->btTestConnection, &QAbstractButton::clicked, this, &QxEventServiceWidget::testConnection); + connect(ui->btExportEventInfo, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportEventInfo); + connect(ui->btExportStartList, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportStartList); + connect(ui->btExportRuns, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportRuns); } -QxClientServiceWidget::~QxClientServiceWidget() +QxEventServiceWidget::~QxEventServiceWidget() { delete ui; } -void QxClientServiceWidget::setMessage(const QString &msg, MessageType msg_type) +void QxEventServiceWidget::setMessage(const QString &msg, MessageType msg_type) { if (msg.isEmpty()) { ui->lblStatus->setStyleSheet({}); @@ -70,7 +70,7 @@ void QxClientServiceWidget::setMessage(const QString &msg, MessageType msg_type) ui->lblStatus->setText(msg); } -bool QxClientServiceWidget::acceptDialogDone(int result) +bool QxEventServiceWidget::acceptDialogDone(int result) { if(result == QDialog::Accepted) { if(!saveSettings()) { @@ -80,14 +80,14 @@ bool QxClientServiceWidget::acceptDialogDone(int result) return true; } -QxClientService *QxClientServiceWidget::service() +QxEventService *QxEventServiceWidget::service() { - auto *svc = qobject_cast(Service::serviceByName(QxClientService::serviceId())); - QF_ASSERT(svc, QxClientService::serviceId() + " doesn't exist", return nullptr); + auto *svc = qobject_cast(Service::serviceByName(QxEventService::serviceId())); + QF_ASSERT(svc, QxEventService::serviceId() + " doesn't exist", return nullptr); return svc; } -bool QxClientServiceWidget::saveSettings() +bool QxEventServiceWidget::saveSettings() { auto *svc = service(); if(svc) { @@ -104,14 +104,14 @@ bool QxClientServiceWidget::saveSettings() return true; } -void QxClientServiceWidget::updateOCheckListPostUrl() +void QxEventServiceWidget::updateOCheckListPostUrl() { auto url = QStringLiteral("%1/api/event/current/oc").arg(ui->edServerUrl->text()); ui->edOChecklistUrl->setText(url); ui->edOChecklistUrlHeader->setText(QStringLiteral("qx-api-token=%1").arg(ui->edApiToken->text())); } -void QxClientServiceWidget::testConnection() +void QxEventServiceWidget::testConnection() { auto *svc = service(); Q_ASSERT(svc); @@ -131,7 +131,7 @@ void QxClientServiceWidget::testConnection() }); } -void QxClientServiceWidget::exportEventInfo() +void QxEventServiceWidget::exportEventInfo() { auto *svc = service(); Q_ASSERT(svc); @@ -151,7 +151,7 @@ void QxClientServiceWidget::exportEventInfo() }); } -void QxClientServiceWidget::exportStartList() +void QxEventServiceWidget::exportStartList() { auto *svc = service(); Q_ASSERT(svc); @@ -167,7 +167,7 @@ void QxClientServiceWidget::exportStartList() }); } -void QxClientServiceWidget::exportRuns() +void QxEventServiceWidget::exportRuns() { auto *svc = service(); Q_ASSERT(svc); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h similarity index 65% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.h rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h index f1b108a30..19454ad86 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.h @@ -5,23 +5,23 @@ namespace Event::services::qx { namespace Ui { -class QxClientServiceWidget; +class QxEventServiceWidget; } -class QxClientService; +class QxEventService; -class QxClientServiceWidget : public qf::gui::framework::DialogWidget +class QxEventServiceWidget : public qf::gui::framework::DialogWidget { Q_OBJECT using Super = qf::gui::framework::DialogWidget; public: - explicit QxClientServiceWidget(QWidget *parent = nullptr); - ~QxClientServiceWidget() override; + explicit QxEventServiceWidget(QWidget *parent = nullptr); + ~QxEventServiceWidget() override; private: enum class MessageType { Ok, Error, Progress }; void setMessage(const QString &msg = {}, MessageType msg_type = MessageType::Ok); - QxClientService* service(); + QxEventService* service(); bool saveSettings(); void updateOCheckListPostUrl(); void testConnection(); @@ -29,7 +29,7 @@ class QxClientServiceWidget : public qf::gui::framework::DialogWidget void exportStartList(); void exportRuns(); private: - Ui::QxClientServiceWidget *ui; + Ui::QxEventServiceWidget *ui; bool acceptDialogDone(int result) override; }; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui similarity index 97% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.ui rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui index d8824707e..261a65af9 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxclientservicewidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui @@ -1,7 +1,7 @@ - Event::services::qx::QxClientServiceWidget - + Event::services::qx::QxEventServiceWidget + 0 diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp index f0747e77a..083af5d69 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp @@ -1,7 +1,7 @@ #include "qxlateregistrationswidget.h" #include "ui_qxlateregistrationswidget.h" -#include "qxclientservice.h" +#include "qxeventservice.h" #include "runchangedialog.h" #include "runchange.h" @@ -152,9 +152,9 @@ void QxLateRegistrationsWidget::onVisibleChanged(bool is_visible) } } -QxClientService *QxLateRegistrationsWidget::service() +QxEventService *QxLateRegistrationsWidget::service() { - auto *svc = qobject_cast(Event::services::Service::serviceByName(QxClientService::serviceId())); + auto *svc = qobject_cast(Event::services::Service::serviceByName(QxEventService::serviceId())); Q_ASSERT(svc); return svc; } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h index 6c972c739..ea906b649 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h @@ -10,7 +10,7 @@ namespace Ui { class QxLateRegistrationsWidget; } -class QxClientService; +class QxEventService; class QxLateRegistrationsWidget : public QWidget { @@ -23,7 +23,7 @@ class QxLateRegistrationsWidget : public QWidget void onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload); void onVisibleChanged(bool is_visible); private: - QxClientService* service(); + QxEventService* service(); void reload(); void addQxChangeRow(int sql_id); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp index c44c38a94..789cb44e0 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp @@ -1,7 +1,7 @@ #include "runchangedialog.h" #include "ui_runchangedialog.h" -#include "qxclientservice.h" +#include "qxeventservice.h" #include "runchange.h" #include @@ -72,9 +72,9 @@ RunChangeDialog::~RunChangeDialog() delete ui; } -QxClientService *RunChangeDialog::service() +QxEventService *RunChangeDialog::service() { - auto *svc = qobject_cast(Service::serviceByName(QxClientService::serviceId())); + auto *svc = qobject_cast(Service::serviceByName(QxEventService::serviceId())); Q_ASSERT(svc); return svc; } @@ -220,7 +220,7 @@ void RunChangeDialog::resolveChanges(bool is_accepted) url.setQuery(query); request.setUrl(url); - request.setRawHeader(QxClientService::QX_API_TOKEN, svc->apiToken()); + request.setRawHeader(QxEventService::QX_API_TOKEN, svc->apiToken()); auto *reply = nm->get(request); connect(reply, &QNetworkReply::finished, this, [this, reply]() { if (reply->error() == QNetworkReply::NetworkError::NoError) { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h index d704a1d48..15a9bf147 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h @@ -14,7 +14,7 @@ class RunChangeDialog; } struct RunChange; -class QxClientService; +class QxEventService; class RunChangeDialog : public QDialog { @@ -25,7 +25,7 @@ class RunChangeDialog : public QDialog ~RunChangeDialog() override; private: - QxClientService* service(); + QxEventService* service(); void setMessage(const QString &msg, bool error); void loadOrigValues(); From 2b49c7c82854a1915ffba38f814d1d0951bc3a10 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 9 Nov 2025 18:34:22 +0100 Subject: [PATCH 03/34] Fix DesktopUtils::moveRectToVisibleDesktopScreen --- libqf/libqfgui/src/framework/dialogwidget.cpp | 15 ++++-------- libqf/libqfgui/src/framework/dialogwidget.h | 2 +- .../src/framework/ipersistentsettings.cpp | 23 ++++++++----------- 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/libqf/libqfgui/src/framework/dialogwidget.cpp b/libqf/libqfgui/src/framework/dialogwidget.cpp index f46cd8f23..8d5769997 100644 --- a/libqf/libqfgui/src/framework/dialogwidget.cpp +++ b/libqf/libqfgui/src/framework/dialogwidget.cpp @@ -9,13 +9,13 @@ using namespace qf::gui::framework; -DialogWidget::DialogWidget(QWidget *parent) : - Super(parent), IPersistentSettings(this) +DialogWidget::DialogWidget(QWidget *parent) + : Super(parent) + , IPersistentSettings(this) { } -DialogWidget::~DialogWidget() -= default; +DialogWidget::~DialogWidget() = default; bool DialogWidget::acceptDialogDone(int result) { @@ -23,12 +23,7 @@ bool DialogWidget::acceptDialogDone(int result) Q_UNUSED(result); return true; } -/* -QVariant DialogWidget::acceptDialogDone_qml(const QVariant &result) -{ - return acceptDialogDone(result.toBool()); -} -*/ + void DialogWidget::settleDownInDialog_qml(const QVariant &dlg) { auto *o = dlg.value(); diff --git a/libqf/libqfgui/src/framework/dialogwidget.h b/libqf/libqfgui/src/framework/dialogwidget.h index 13893c8a0..611be6b95 100644 --- a/libqf/libqfgui/src/framework/dialogwidget.h +++ b/libqf/libqfgui/src/framework/dialogwidget.h @@ -32,7 +32,7 @@ class QFGUI_DECL_EXPORT DialogWidget : public Frame, public IPersistentSettings typedef Frame Super; public: explicit DialogWidget(QWidget *parent = nullptr); - ~DialogWidget() Q_DECL_OVERRIDE; + ~DialogWidget() override; QF_PROPERTY_IMPL(QString, t, T, itle) QF_PROPERTY_IMPL(QString, i, I, conSource) diff --git a/libqf/libqfgui/src/framework/ipersistentsettings.cpp b/libqf/libqfgui/src/framework/ipersistentsettings.cpp index c639c374c..7fff48174 100644 --- a/libqf/libqfgui/src/framework/ipersistentsettings.cpp +++ b/libqf/libqfgui/src/framework/ipersistentsettings.cpp @@ -48,7 +48,7 @@ static void callMethodRecursively(QObject *obj, const char *method_name) QMetaMethod mm = obj->metaObject()->method(ix); mm.invoke(obj); } - Q_FOREACH(auto *o, obj->children()) { + for (auto *o : obj->children()) { //static int level = 0; //level++; //QString indent = QString(level, ' '); @@ -103,25 +103,20 @@ QString IPersistentSettings::rawPersistentSettingsPath() QString persistent_id = persistentSettingsId(); QStringList raw_path; if(!persistent_id.isEmpty()) { - for(QObject *obj=m_controlledObject->parent(); obj!=nullptr; obj=obj->parent()) { + for(QObject* obj = m_controlledObject->parent(); obj != nullptr; obj = obj->parent()) { auto *ps = dynamic_cast(obj); if(ps) { QString pp = ps->rawPersistentSettingsPath(); - if(!pp.isEmpty()) + if(!pp.isEmpty()) { raw_path.insert(0, pp); - //qfWarning() << "reading property 'persistentSettingsId' error" << obj << "casted to IPersistentSettings" << ps; - //qfWarning() << "\tcorrect value should be:" << parent_id; + } break; } - QVariant vid = obj->property("persistentSettingsId"); - QString parent_id = vid.toString(); - if(!parent_id.isEmpty()) { - raw_path.insert(0, parent_id); - } - - // reading property using QQmlProperty is crashing my app Qt 5.3.1 commit a83826dad0f62d7a96f5a6093240e4c8f7f2e06e - //QQmlProperty p(obj, "persistentSettingsId"); - //QVariant v2 = p.read(); + QVariant vid = obj->property("persistentSettingsId"); + QString parent_id = vid.toString(); + if(!parent_id.isEmpty()) { + raw_path.insert(0, parent_id); + } } raw_path.append(persistent_id); } From 4b1f96f3e85cb7f92002ca2c6558735ee0236c0b Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 9 Nov 2025 18:34:34 +0100 Subject: [PATCH 04/34] Update libshv --- 3rdparty/libshv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/libshv b/3rdparty/libshv index 3cbe496ea..40f0b3cf2 160000 --- a/3rdparty/libshv +++ b/3rdparty/libshv @@ -1 +1 @@ -Subproject commit 3cbe496ea4d2c48695994850a09c2cc794c3f80b +Subproject commit 40f0b3cf213ab799fb17bf6a31ce527a8851ee30 From 317424d02edd070e67ae6fde98f0db3e13c053c9 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 9 Nov 2025 18:35:51 +0100 Subject: [PATCH 05/34] Add QX event SHV nodes --- .../src/reportoptionsdialog.h | 8 +- quickevent/app/quickevent/CMakeLists.txt | 17 +- .../plugins/Event/src/services/qx/nodes.cpp | 300 ++++++++++++++++++ .../plugins/Event/src/services/qx/nodes.h | 103 ++++++ .../Event/src/services/qx/qxeventservice.cpp | 74 +++-- .../Event/src/services/qx/qxeventservice.h | 14 +- .../src/services/qx/qxeventservicewidget.cpp | 6 +- .../src/services/qx/qxeventservicewidget.ui | 2 +- .../plugins/Event/src/services/qx/qxnode.cpp | 36 +++ .../plugins/Event/src/services/qx/qxnode.h | 23 ++ .../Event/src/services/qx/runchangedialog.cpp | 2 +- .../Event/src/services/qx/sqlapinode.cpp | 119 +++++++ .../Event/src/services/qx/sqlapinode.h | 27 ++ 13 files changed, 682 insertions(+), 49 deletions(-) create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.cpp create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.h create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h diff --git a/libquickevent/libquickeventgui/src/reportoptionsdialog.h b/libquickevent/libquickeventgui/src/reportoptionsdialog.h index e4b22c6f9..bdccb558b 100644 --- a/libquickevent/libquickeventgui/src/reportoptionsdialog.h +++ b/libquickevent/libquickeventgui/src/reportoptionsdialog.h @@ -86,13 +86,13 @@ class QUICKEVENTGUI_DECL_EXPORT ReportOptionsDialog : public QDialog, public qf: explicit ReportOptionsDialog(QWidget *parent = nullptr); ~ReportOptionsDialog() override; - int exec() Q_DECL_OVERRIDE; + int exec() override; void setStartListPrintVacantsVisible(bool b); void setStartListForRelays(); - QString persistentSettingsPath() Q_DECL_OVERRIDE; - bool setPersistentSettingsId(const QString &id) Q_DECL_OVERRIDE; + QString persistentSettingsPath() override; + bool setPersistentSettingsId(const QString &id) override; Q_SIGNAL void persistentSettingsIdChanged(const QString &id); void setOptions(const Options &options); @@ -127,7 +127,7 @@ class QUICKEVENTGUI_DECL_EXPORT ReportOptionsDialog : public QDialog, public qf: static QString sqlWhereExpression(const Options &opts, const int stage_id); static QString getClassesForStartNumber(const int number, const int stage_id); protected: - //void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; + //void showEvent(QShowEvent *event) override; private: Ui::ReportOptionsDialog *ui; }; diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index b502afa91..33b7c3607 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -1,3 +1,5 @@ +find_package(Qt6 REQUIRED COMPONENTS Core) + add_executable(quickevent plugins/CardReader/src/cardchecker.cpp plugins/CardReader/src/cardcheckerclassiccpp.cpp @@ -89,11 +91,13 @@ add_executable(quickevent plugins/Event/src/services/serviceswidget.cpp plugins/Event/src/services/servicewidget.cpp plugins/Event/src/services/servicewidget.ui - plugins/Event/src/services/qx/qxeventservice.cpp plugins/Event/src/services/qx/qxeventservice.h plugins/Event/src/services/qx/qxeventservicewidget.cpp plugins/Event/src/services/qx/qxeventservicewidget.h - plugins/Event/src/services/qx/qxeventservicewidget.ui - plugins/Event/src/services/qx/qxlateregistrationswidget.h - plugins/Event/src/services/qx/qxlateregistrationswidget.cpp - plugins/Event/src/services/qx/qxlateregistrationswidget.ui + + plugins/Event/src/services/qx/qxnode.cpp + plugins/Event/src/services/qx/sqlapinode.cpp + plugins/Event/src/services/qx/nodes.cpp + plugins/Event/src/services/qx/qxeventservice.cpp plugins/Event/src/services/qx/qxeventservice.h + plugins/Event/src/services/qx/qxeventservicewidget.cpp plugins/Event/src/services/qx/qxeventservicewidget.h plugins/Event/src/services/qx/qxeventservicewidget.ui + plugins/Event/src/services/qx/qxlateregistrationswidget.h plugins/Event/src/services/qx/qxlateregistrationswidget.cpp plugins/Event/src/services/qx/qxlateregistrationswidget.ui plugins/Event/src/services/qx/runchangedialog.h plugins/Event/src/services/qx/runchangedialog.cpp plugins/Event/src/services/qx/runchangedialog.ui plugins/Event/src/services/qx/runchange.h plugins/Event/src/services/qx/runchange.cpp @@ -272,7 +276,8 @@ target_include_directories(quickevent PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAK target_link_libraries(quickevent PUBLIC libquickeventcore libquickeventgui libqfgui libsiut) if (QF_WITH_LIBSHV) target_link_libraries(quickevent PUBLIC libshviotqt) - target_compile_definitions(quickevent PRIVATE QF_WITH_LIBSHV) +target_link_libraries(quickevent PRIVATE Qt6::Core) +target_compile_definitions(quickevent PRIVATE QF_WITH_LIBSHV) endif() foreach(plugin IN ITEMS Classes Runs Relays Receipts shared) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp new file mode 100644 index 000000000..07c57d53d --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp @@ -0,0 +1,300 @@ +#include "nodes.h" + +#include "sqlapinode.h" +#include "../../eventplugin.h" +#include "../../../../Runs/src/runsplugin.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace qf::core::sql; +using namespace shv::chainpack; +using qf::gui::framework::getPlugin; +using Event::EventPlugin; +using Runs::RunsPlugin; + +namespace Event::services::qx { + +//========================================================= +// DotAppNode +//========================================================= +namespace { +auto METH_NAME = "name"; +} +const std::vector &DotAppNode::metaMethods() +{ + static std::vector meta_methods { + methods::DIR, + methods::LS, + {Rpc::METH_PING, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Browse}, + {METH_NAME, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Browse}, + }; + return meta_methods; +} + +RpcValue DotAppNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) +{ + qfLogFuncFrame() << shv_path.join('/') << method; + //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); + if(shv_path.empty()) { + if(method == Rpc::METH_PING) { + return nullptr; + } + if(method == METH_NAME) { + return "QuickEvent"; + } + } + return Super::callMethod(shv_path, method, params, user_id); +} + +//========================================================= +// EventNode +//========================================================= +static const auto METH_CURRENT_STAGE = "currentStage"; +static const auto METH_EVENT_CONFIG = "eventConfig"; + +EventNode::EventNode(shv::iotqt::node::ShvNode *parent) +: Super("event", parent) +{ +} + +const std::vector &EventNode::metaMethods() +{ + static std::vector meta_methods { + methods::DIR, + methods::LS, + {METH_NAME, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Read}, + {METH_CURRENT_STAGE, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Read}, + {METH_EVENT_CONFIG, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Read}, + }; + return meta_methods; +} + +RpcValue EventNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) +{ + qfLogFuncFrame() << shv_path.join('/') << method; + if(shv_path.empty()) { + if(method == METH_NAME) { + return getPlugin()->eventConfig()->eventName().toStdString(); + } + if(method == METH_CURRENT_STAGE) { + return getPlugin()->currentStageId(); + } + if(method == METH_EVENT_CONFIG) { + auto cfg = getPlugin()->eventConfig(); + return shv::coreqt::rpc::qVariantToRpcValue(cfg->values()); + } + } + return Super::callMethod(shv_path, method, params, user_id); +} + +//========================================================= +// StartListNode +//========================================================= +static const auto METH_TABLE = "table"; +static const auto METH_RECORD = "record"; +//static const auto SIG_REC_CHNG = "recchng"; + +void SqlViewNode::setQueryBuilder(const qf::core::sql::QueryBuilder &qb) +{ + m_queryBuilder = qb; +} + +qf::core::sql::QueryBuilder SqlViewNode::effectiveQueryBuilder() +{ + return m_queryBuilder; +} + +const std::vector &SqlViewNode::metaMethods() +{ + static std::vector meta_methods { + methods::DIR, + methods::LS, + {METH_TABLE, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Read}, + {METH_RECORD, MetaMethod::Flag::None, "RpcValue", "RpcValue", AccessLevel::Read }, + //{METH_SET_RECORD, MetaMethod::Flag::None, "RpcValue", {}, AccessLevel::Write}, + }; + return meta_methods; +} + +RpcValue SqlViewNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) +{ + qfLogFuncFrame() << shv_path.join('/') << method; + //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); + if(shv_path.empty()) { + if(method == METH_TABLE) { + const auto &m = params.asMap(); + auto qb = effectiveQueryBuilder(); + if (auto where = m.value("where").to(); !where.isEmpty()) { + qb.where(where); + } + if (auto order_by = m.value("orderBy").to(); !order_by.isEmpty()) { + qb.orderBy(order_by); + } + qf::core::sql::Query q; + QString qs = qb.toString(); + q.exec(qs, qf::core::Exception::Throw); + auto res = SqlApiNode::rpcSqlResultFromQuery(q); + return res.toRpcValue(); + } + if(method == METH_RECORD) { + auto id = params.toInt(); + auto qb = effectiveQueryBuilder(); + qb.where("runs.id = " + QString::number(id)); + qf::core::sql::Query q; + QString qs = qb.toString(); + qfDebug() << qs; + q.exec(qs, qf::core::Exception::Throw); + if (q.next()) { + return SqlApiNode::recordToMap(q.record()); + } + return RpcValue::Map{}; + } + } + return Super::callMethod(shv_path, method, params, user_id); +} + +//========================================================= +// CurrentStageConfigNode +//========================================================= +const std::vector &CurrentStageConfigNode::metaMethods() +{ + static std::vector meta_methods { + methods::DIR, + methods::LS, + {Rpc::METH_GET, MetaMethod::Flag::IsGetter, {}, "Map", AccessLevel::Read}, + }; + return meta_methods; +} + +RpcValue CurrentStageConfigNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) +{ + qfLogFuncFrame() << shv_path.join('/') << method; + //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); + if(shv_path.empty()) { + if(method == Rpc::METH_GET) { + auto *event_plugin = getPlugin(); + QVariantMap data = event_plugin->stageData(event_plugin->currentStageId()); + data.remove("drawingConfig"); // remove internal key + return shv::coreqt::rpc::qVariantToRpcValue(data); + } + } + return Super::callMethod(shv_path, method, params, user_id); +} + +//========================================================= +// CurrentStageStartListNode +//========================================================= +CurrentStageStartListNode::CurrentStageStartListNode(shv::iotqt::node::ShvNode *parent) + : Super("startList", parent) +{ + auto qb = getPlugin()->startListQuery(); + //qb.orderBy("runs.startTimeMs"); + setQueryBuilder(qb); +} + +qf::core::sql::QueryBuilder CurrentStageStartListNode::effectiveQueryBuilder() +{ + auto qb = Super::effectiveQueryBuilder(); + auto *event_plugin = getPlugin(); + qb.where(QStringLiteral("runs.stageId=%1").arg(event_plugin->currentStageId())); + return qb; +} + +//========================================================= +// CurrentStageRunsNode +//========================================================= +CurrentStageRunsNode::CurrentStageRunsNode(shv::iotqt::node::ShvNode *parent) + : Super("runs", parent) +{ +} + +QueryBuilder CurrentStageRunsNode::effectiveQueryBuilder() +{ + auto *event_plugin = getPlugin(); + auto qb = getPlugin()->runsQuery(event_plugin->currentStageId()); + return qb; +} + +static const auto METH_SET_RECORD = "setRecord"; +//static const auto METH_RUN_CHANGED = "runchng"; +static const auto SIG_RUN_CHANGED = "runchng"; + +const std::vector &CurrentStageRunsNode::metaMethods() +{ + static std::vector meta_methods { + methods::DIR, + methods::LS, + {METH_TABLE, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Read}, + {METH_RECORD, MetaMethod::Flag::None, "Int", "Map", AccessLevel::Read, {{SIG_RUN_CHANGED, "[Int, RpcValue]"}} }, + {METH_SET_RECORD, MetaMethod::Flag::None, "[Int, RpcValue]", {}, AccessLevel::Write}, + }; + return meta_methods; + static auto s_meta_methods = [this]() { + auto mm = Super::metaMethods(); + mm.push_back({METH_SET_RECORD, MetaMethod::Flag::None, "[int, Map]", {}, AccessLevel::Write }); + return mm; + }(); + return s_meta_methods; +} + +RpcValue CurrentStageRunsNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) +{ + qfLogFuncFrame() << shv_path.join('/') << method; + if(shv_path.empty()) { + if(method == METH_SET_RECORD) { + auto *plugin = getPlugin(); + const auto &lst = params.asList(); + plugin->setRunsRecord(lst.value(0).toInt(), shv::coreqt::rpc::rpcValueToQVariant(lst.value(1))); + return nullptr; + } + } + return Super::callMethod(shv_path, method, params, user_id); +} + +void CurrentStageRunsNode::sendRunChangedSignal(const QVariant &qparam) +{ + auto param = shv::coreqt::rpc::qVariantToRpcValue(qparam); + Q_ASSERT(param.isList()); + Q_ASSERT(param.asList().value(0).toInt() > 0); // run.id + RpcSignal sig; + sig.setShvPath(shvPath().asString()); + sig.setMethod(SIG_RUN_CHANGED); + sig.setSource(METH_RECORD); + sig.setParams(param); + qfDebug() << "emit:" << sig.toPrettyString(); + emitSendRpcMessage(sig); +} + +//========================================================= +// CurrentStageClassesNode +//========================================================= +CurrentStageClassesNode::CurrentStageClassesNode(shv::iotqt::node::ShvNode *parent) + : Super("classes", parent) +{ +} + +QueryBuilder CurrentStageClassesNode::effectiveQueryBuilder() +{ + auto *event_plugin = getPlugin(); + QueryBuilder qb; + qb.select2("classes", "*") + .select2("classdefs", "*") + .select2("courses", "id, name, length, climb") + .from("classes") + .joinRestricted("classes.id", "classdefs.classId", "classdefs.stageId=" QF_IARG(event_plugin->currentStageId())) + .join("classdefs.courseId", "courses.id") + .orderBy("classes.name");//.limit(10); + return qb; +} + + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h new file mode 100644 index 000000000..917a75f2c --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h @@ -0,0 +1,103 @@ +#pragma once + +#include "qxnode.h" + +#include + +namespace Event::services::qx { + +class DotAppNode : public QxNode +{ + Q_OBJECT + + using Super = QxNode; +public: + explicit DotAppNode(shv::iotqt::node::ShvNode *parent) : Super(".app", parent) {} +private: + //shv::chainpack::RpcValue callMethodRq(const shv::chainpack::RpcRequest &rq) override; + const std::vector &metaMethods() override; + shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; +}; + +class EventNode : public QxNode +{ + Q_OBJECT + + using Super = QxNode; +public: + explicit EventNode(shv::iotqt::node::ShvNode *parent); +private: + const std::vector &metaMethods() override; + shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; +}; + +class SqlViewNode : public QxNode +{ + Q_OBJECT + + using Super = QxNode; +public: + explicit SqlViewNode(const std::string &name, shv::iotqt::node::ShvNode *parent) + : Super(name, parent) + {} + void setQueryBuilder(const qf::core::sql::QueryBuilder &qb); +protected: + virtual qf::core::sql::QueryBuilder effectiveQueryBuilder(); + const std::vector &metaMethods() override; + shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; +private: + qf::core::sql::QueryBuilder m_queryBuilder; +}; + +class CurrentStageConfigNode : public QxNode +{ + Q_OBJECT + + using Super = QxNode; +public: + explicit CurrentStageConfigNode(shv::iotqt::node::ShvNode *parent) + : Super("config", parent) + {} +protected: + const std::vector &metaMethods() override; + shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; +}; + +class CurrentStageStartListNode : public SqlViewNode +{ + Q_OBJECT + + using Super = SqlViewNode; +public: + explicit CurrentStageStartListNode(shv::iotqt::node::ShvNode *parent); +protected: + qf::core::sql::QueryBuilder effectiveQueryBuilder() override; +}; + +class CurrentStageRunsNode : public SqlViewNode +{ + Q_OBJECT + + using Super = SqlViewNode; +public: + explicit CurrentStageRunsNode(shv::iotqt::node::ShvNode *parent); + void sendRunChangedSignal(const QVariant &qparam); +protected: + const std::vector &metaMethods() override; + shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; + qf::core::sql::QueryBuilder effectiveQueryBuilder() override; +}; + +class CurrentStageClassesNode : public SqlViewNode +{ + Q_OBJECT + + using Super = SqlViewNode; +public: + explicit CurrentStageClassesNode(shv::iotqt::node::ShvNode *parent); + +protected: + qf::core::sql::QueryBuilder effectiveQueryBuilder() override; +}; + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 6b673328e..e693f4b45 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -1,5 +1,7 @@ #include "qxeventservice.h" #include "qxeventservicewidget.h" +#include "nodes.h" +#include "sqlapinode.h" #include "../../eventplugin.h" #include "../../../../Runs/src/runsplugin.h" @@ -9,7 +11,7 @@ #include #include -#include +#include #include #include @@ -41,31 +43,39 @@ using Event::EventPlugin; using Runs::RunsPlugin; namespace Event::services::qx { -//=============================================== -// QxClientServiceSettings -//=============================================== -// QString QxClientServiceSettings::eventKey() const -// { -// auto *event_plugin = getPlugin(); -// auto *cfg = event_plugin->eventConfig(); -// auto key = cfg->apiKey(); -// auto current_stage = cfg->currentStageId(); -// return QStringLiteral("%1%2").arg(key).arg(current_stage); -// } //=============================================== // QxClientService //=============================================== QxEventService::QxEventService(QObject *parent) : Super(QxEventService::serviceId(), parent) + , m_rootNode(new shv::iotqt::node::ShvRootNode(this)) { auto *event_plugin = getPlugin(); - connect(event_plugin, &Event::EventPlugin::dbEventNotify, this, &QxEventService::onDbEventNotify, Qt::QueuedConnection); + // connect(event_plugin, &Event::EventPlugin::dbEventNotify, this, &QxEventService::onDbEventNotify, Qt::QueuedConnection); + + new DotAppNode(m_rootNode); + connect(event_plugin, &EventPlugin::eventOpenChanged, this, [this](bool is_open) { + if (is_open) { + new SqlApiNode(m_rootNode); + auto *event = new EventNode(m_rootNode); + auto *current_stage = new shv::iotqt::node::ShvNode("currentStage", event); + new CurrentStageConfigNode(current_stage); + //new CurrentStageStartListNode(current_stage); + new CurrentStageRunsNode(current_stage); + new CurrentStageClassesNode(current_stage); + } + else { + qDeleteAll(m_rootNode->findChildren()); + } + }); + + connect(m_rootNode, &shv::iotqt::node::ShvNode::sendRpcMessage, this, &QxEventService::sendRpcMessage); } QString QxEventService::serviceDisplayName() const { - return tr("QE Exchange"); + return tr("QX Event"); } QString QxEventService::serviceId() @@ -79,8 +89,13 @@ void QxEventService::run() { auto ss = settings(); delete m_rpcConnection; - m_rpcConnection = new ClientConnection(this); - m_rpcConnection->setConnectionString(ss.exchangeServerUrl()); + m_rpcConnection = new DeviceConnection(this); + m_rpcConnection->setConnectionString(ss.shvBrokerUrl()); + RpcValue::Map opts; + RpcValue::Map device; + device["mountPoint"] = "test/quickbox"; + opts["device"] = device; + m_rpcConnection->setConnectionOptions(opts); connect(m_rpcConnection, &ClientConnection::brokerConnectedChanged, this, &QxEventService::onBrokerConnectedChanged); connect(m_rpcConnection, &ClientConnection::socketError, this, &QxEventService::onBrokerSocketError); @@ -130,8 +145,8 @@ void QxEventService::loadSettings() { Super::loadSettings(); auto ss = settings(); - if (ss.exchangeServerUrl().isEmpty()) { - ss.setExchangeServerUrl("http://localhost:8000"); + if (ss.shvBrokerUrl().isEmpty()) { + ss.setShvBrokerUrl("tcp://localhost?user=test&password=test"); } m_settings = ss; } @@ -243,7 +258,7 @@ void QxEventService::postRuns(QObject *context, std::function ca void QxEventService::getHttpJson(const QString &path, const QUrlQuery &query, QObject *context, const std::function &call_back) { - auto url = exchangeServerUrl(); + auto url = shvBrokerUrl(); url.setPath(path); url.setQuery(query); // qfInfo() << url.toString(); @@ -272,7 +287,7 @@ void QxEventService::getHttpJson(const QString &path, const QUrlQuery &query, QO QNetworkReply* QxEventService::getQxChangesReply(int from_id) { - auto url = exchangeServerUrl(); + auto url = shvBrokerUrl(); url.setPath(QStringLiteral("/api/event/%1/changes").arg(eventId())); url.setQuery(QStringLiteral("from_id=%1").arg(from_id)); @@ -299,15 +314,15 @@ QByteArray QxEventService::apiToken() const return event_plugin->stageData(current_stage).qxApiToken().toUtf8(); } -QUrl QxEventService::exchangeServerUrl() const +QUrl QxEventService::shvBrokerUrl() const { auto ss = settings(); - return QUrl(ss.exchangeServerUrl()); + return QUrl(ss.shvBrokerUrl()); } void QxEventService::postFileCompressed(std::optional path, std::optional name, QByteArray data, QObject *context , std::function call_back) { - auto url = exchangeServerUrl(); + auto url = shvBrokerUrl(); url.setPath(path.value_or("/api/event/current/file")); if (name.has_value()) { @@ -359,7 +374,7 @@ void QxEventService::httpPostJson(const QString &path, const QString &query, QVa if (!isRunning()) { return; } - auto url = exchangeServerUrl(); + auto url = shvBrokerUrl(); url.setPath(path); url.setQuery(query); @@ -617,9 +632,7 @@ void QxEventService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg) if(msg.isRequest()) { RpcRequest rq(msg); qfMessage() << "RPC request received:" << rq.toPrettyString(); - if(m_shvTree->root()) { - m_shvTree->root()->handleRpcRequest(rq); - } + m_rootNode->handleRpcRequest(rq); } else if(msg.isResponse()) { RpcResponse rp(msg); @@ -631,6 +644,13 @@ void QxEventService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg) } } +void QxEventService::sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg) +{ + if(m_rpcConnection && m_rpcConnection->isBrokerConnected()) { + m_rpcConnection->sendRpcMessage(rpc_msg); + } +} + void QxEventService::subscribeChanges() { Q_ASSERT(m_rpcConnection); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index 1ab8d14b7..2a103a9d7 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -7,8 +7,8 @@ class QNetworkReply; class QUrlQuery; class QTimer; -namespace shv::iotqt::node { class ShvNodeTree; } -namespace shv::iotqt::rpc { class ClientConnection; } +namespace shv::iotqt::node { class ShvNodeTree; class ShvRootNode; } +namespace shv::iotqt::rpc { class DeviceConnection; } namespace shv::chainpack { class RpcMessage; class RpcError; } namespace Event::services::qx { @@ -17,7 +17,7 @@ class QxEventServiceSettings : public ServiceSettings { using Super = ServiceSettings; - QF_VARIANTMAP_FIELD2(QString, e, setE, xchangeServerUrl, "http://localhost:8000") + QF_VARIANTMAP_FIELD2(QString, s, setS, hvBrokerUrl, "http://localhost:8000") public: QxEventServiceSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} }; @@ -69,12 +69,13 @@ class QxEventService : public Service QByteArray apiToken() const; static int currentConnectionId(); - QUrl exchangeServerUrl() const; + QUrl shvBrokerUrl() const; int eventId() const; private: // shv void onBrokerConnectedChanged(bool is_connected); void onRpcMessageReceived(const shv::chainpack::RpcMessage &msg); + void sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg); void onBrokerSocketError(const QString &err); void onBrokerLoginError(const shv::chainpack::RpcError &err); @@ -97,9 +98,8 @@ class QxEventService : public Service EventInfo eventInfo() const; private: // shv - shv::iotqt::rpc::ClientConnection *m_rpcConnection = nullptr; - shv::iotqt::node::ShvNodeTree *m_shvTree = nullptr; - + shv::iotqt::rpc::DeviceConnection *m_rpcConnection = nullptr; + shv::iotqt::node::ShvRootNode *m_rootNode; private: QNetworkAccessManager *m_networkManager = nullptr; QNetworkReply *m_replySSE = nullptr; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index ede33ffb4..416bb25b4 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -23,7 +23,7 @@ QxEventServiceWidget::QxEventServiceWidget(QWidget *parent) : Super(parent) , ui(new Ui::QxEventServiceWidget) { - setPersistentSettingsId("QxClientServiceWidget"); + setPersistentSettingsId("QxEventServiceWidget"); ui->setupUi(this); connect(ui->edServerUrl, &QLineEdit::textChanged, this, &QxEventServiceWidget::updateOCheckListPostUrl); connect(ui->edApiToken, &QLineEdit::textChanged, this, &QxEventServiceWidget::updateOCheckListPostUrl); @@ -35,7 +35,7 @@ QxEventServiceWidget::QxEventServiceWidget(QWidget *parent) auto *event_plugin = getPlugin(); auto current_stage = event_plugin->currentStageId(); auto settings = svc->settings(); - ui->edServerUrl->setText(settings.exchangeServerUrl()); + ui->edServerUrl->setText(settings.shvBrokerUrl()); ui->edApiToken->setText(svc->apiToken()); ui->edCurrentStage->setValue(current_stage); connect(ui->btTestConnection, &QAbstractButton::clicked, this, &QxEventServiceWidget::testConnection); @@ -92,7 +92,7 @@ bool QxEventServiceWidget::saveSettings() auto *svc = service(); if(svc) { auto ss = svc->settings(); - ss.setExchangeServerUrl(ui->edServerUrl->text()); + ss.setShvBrokerUrl(ui->edServerUrl->text()); svc->setSettings(ss); auto *event_plugin = getPlugin(); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui index 261a65af9..5f4a41e1a 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.ui @@ -19,7 +19,7 @@ - Exchange server url + SHV broker url diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.cpp new file mode 100644 index 000000000..dfcea61fd --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.cpp @@ -0,0 +1,36 @@ +#include "qxnode.h" + +#include +#include + +#include + +using namespace shv::chainpack; + +namespace Event::services::qx { + +QxNode::QxNode(const std::string &name, shv::iotqt::node::ShvNode *parent) + : Super(name, parent) +{ + +} + +size_t QxNode::methodCount(const StringViewList &shv_path) +{ + if(shv_path.empty()) { + return metaMethods().size(); + } + return Super::methodCount(shv_path); +} + +const MetaMethod *QxNode::metaMethod(const StringViewList &shv_path, size_t ix) +{ + if(shv_path.empty()) { + if(metaMethods().size() <= ix) + QF_EXCEPTION("Invalid method index: " + QString::number(ix) + " of: " + QString::number(metaMethods().size())); + return &(metaMethods()[ix]); + } + return Super::metaMethod(shv_path, ix); +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.h new file mode 100644 index 000000000..79e42c174 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxnode.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace Event::services::qx { + +class QxNode : public shv::iotqt::node::ShvNode +{ + Q_OBJECT + + using Super = shv::iotqt::node::ShvNode; +public: + explicit QxNode(const std::string &name, shv::iotqt::node::ShvNode *parent); + + size_t methodCount(const StringViewList &shv_path) override; + const shv::chainpack::MetaMethod* metaMethod(const StringViewList &shv_path, size_t ix) override; +protected: + //shv::chainpack::RpcValue callMethodRq(const shv::chainpack::RpcRequest &rq) override; + //shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; + virtual const std::vector &metaMethods() = 0; +}; + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp index 789cb44e0..1c16121ce 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp @@ -208,7 +208,7 @@ void RunChangeDialog::resolveChanges(bool is_accepted) auto *svc = service(); auto *nm = svc->networkManager(); QNetworkRequest request; - auto url = svc->exchangeServerUrl(); + auto url = svc->shvBrokerUrl(); // qfInfo() << "url " << url.toString(); url.setPath("/api/event/current/changes/resolve-change"); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp new file mode 100644 index 000000000..ac90ce45f --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -0,0 +1,119 @@ +#include "sqlapinode.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include + +using namespace shv::chainpack; +using namespace shv::coreqt::data; + +namespace Event::services::qx { +namespace { + +/* + +RpcSqlResult RpcSqlResult::fromRpcValue(const shv::chainpack::RpcValue &rv) +{ + RpcSqlResult ret; + const RpcValue::Map &map = rv.asMap(); + const RpcValue::List &flds = map.value("fields").asList(); + if(flds.empty()) { + ret.numRowsAffected = map.value("numRowsAffected").toInt(); + ret.lastInsertId = map.value("lastInsertId").toInt(); + } + else { + + } + return ret; + +} +*/ +} + +SqlApiNode::SqlApiNode(shv::iotqt::node::ShvNode *parent) + : Super("sql", parent) +{ + +} + +shv::chainpack::RpcValue::Map SqlApiNode::recordToMap(const QSqlRecord &rec) +{ + RpcValue::Map record; + for (int i = 0; i < rec.count(); ++i) { + QSqlField fld = rec.field(i); + auto fld_name = fld.name(); + fld_name.replace("__", "."); + record[fld_name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(fld.value()); + } + return record; +} + +RpcSqlResult SqlApiNode::rpcSqlResultFromQuery(QSqlQuery &q) +{ + RpcSqlResult ret; + if(q.isSelect()) { + QSqlRecord rec = q.record(); + for (int i = 0; i < rec.count(); ++i) { + QSqlField fld = rec.field(i); + RpcSqlField rfld; + rfld.name = fld.name(); + rfld.name.replace("__", "."); + rfld.type = fld.metaType().id(); + ret.fields.append(rfld); + } + while(q.next()) { + RpcSqlResult::Row row; + for (int i = 0; i < rec.count(); ++i) { + const QVariant v = q.value(i); + if (v.isNull()) + row.append(QVariant()); + else + row.append(v); + //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); + } + ret.rows.insert(ret.rows.count(), row); + } + } + else { + ret.numRowsAffected = q.numRowsAffected(); + ret.lastInsertId = q.lastInsertId().toInt(); + } + return ret; +} + +static auto METH_EXEC_SQL = "execSql"; + +const std::vector &SqlApiNode::metaMethods() +{ + static std::vector meta_methods { + methods::DIR, + methods::LS, + {METH_EXEC_SQL, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Write}, + }; + return meta_methods; +} + +RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) +{ + qfLogFuncFrame() << shv_path.join('/') << method; + //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); + if(shv_path.empty()) { + if(method == METH_EXEC_SQL) { + qf::core::sql::Query q; + QString qs = params.to(); + q.exec(qs, qf::core::Exception::Throw); + auto res = rpcSqlResultFromQuery(q); + return res.toRpcValue(); + } + } + return Super::callMethod(shv_path, method, params, user_id); +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h new file mode 100644 index 000000000..c2e112f26 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h @@ -0,0 +1,27 @@ +#pragma once + +#include "qxnode.h" + +class QSqlQuery; +class QSqlRecord; + +namespace shv::coreqt::data { class RpcSqlResult; } + +namespace Event::services::qx { + +class SqlApiNode : public QxNode +{ + Q_OBJECT + + using Super = QxNode; +public: + explicit SqlApiNode(shv::iotqt::node::ShvNode *parent); + + static shv::coreqt::data::RpcSqlResult rpcSqlResultFromQuery(QSqlQuery &q); + static shv::chainpack::RpcValue::Map recordToMap(const QSqlRecord &rec); +protected: + const std::vector &metaMethods() override; + shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; +}; + +} From 8972a3ea4ce6a65fc9db08372a5de972020884b8 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 9 Nov 2025 18:43:07 +0100 Subject: [PATCH 06/34] Remove unused shv odes --- .../plugins/Event/src/services/qx/nodes.cpp | 249 ------------------ .../plugins/Event/src/services/qx/nodes.h | 81 ------ .../Event/src/services/qx/qxeventservice.cpp | 18 +- 3 files changed, 1 insertion(+), 347 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp index 07c57d53d..6a4db4533 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.cpp @@ -1,9 +1,5 @@ #include "nodes.h" -#include "sqlapinode.h" -#include "../../eventplugin.h" -#include "../../../../Runs/src/runsplugin.h" - #include #include #include @@ -17,9 +13,6 @@ using namespace qf::core::sql; using namespace shv::chainpack; -using qf::gui::framework::getPlugin; -using Event::EventPlugin; -using Runs::RunsPlugin; namespace Event::services::qx { @@ -55,246 +48,4 @@ RpcValue DotAppNode::callMethod(const StringViewList &shv_path, const std::strin return Super::callMethod(shv_path, method, params, user_id); } -//========================================================= -// EventNode -//========================================================= -static const auto METH_CURRENT_STAGE = "currentStage"; -static const auto METH_EVENT_CONFIG = "eventConfig"; - -EventNode::EventNode(shv::iotqt::node::ShvNode *parent) -: Super("event", parent) -{ -} - -const std::vector &EventNode::metaMethods() -{ - static std::vector meta_methods { - methods::DIR, - methods::LS, - {METH_NAME, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Read}, - {METH_CURRENT_STAGE, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Read}, - {METH_EVENT_CONFIG, MetaMethod::Flag::IsGetter, {}, "RpcValue", AccessLevel::Read}, - }; - return meta_methods; -} - -RpcValue EventNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) -{ - qfLogFuncFrame() << shv_path.join('/') << method; - if(shv_path.empty()) { - if(method == METH_NAME) { - return getPlugin()->eventConfig()->eventName().toStdString(); - } - if(method == METH_CURRENT_STAGE) { - return getPlugin()->currentStageId(); - } - if(method == METH_EVENT_CONFIG) { - auto cfg = getPlugin()->eventConfig(); - return shv::coreqt::rpc::qVariantToRpcValue(cfg->values()); - } - } - return Super::callMethod(shv_path, method, params, user_id); -} - -//========================================================= -// StartListNode -//========================================================= -static const auto METH_TABLE = "table"; -static const auto METH_RECORD = "record"; -//static const auto SIG_REC_CHNG = "recchng"; - -void SqlViewNode::setQueryBuilder(const qf::core::sql::QueryBuilder &qb) -{ - m_queryBuilder = qb; -} - -qf::core::sql::QueryBuilder SqlViewNode::effectiveQueryBuilder() -{ - return m_queryBuilder; -} - -const std::vector &SqlViewNode::metaMethods() -{ - static std::vector meta_methods { - methods::DIR, - methods::LS, - {METH_TABLE, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Read}, - {METH_RECORD, MetaMethod::Flag::None, "RpcValue", "RpcValue", AccessLevel::Read }, - //{METH_SET_RECORD, MetaMethod::Flag::None, "RpcValue", {}, AccessLevel::Write}, - }; - return meta_methods; -} - -RpcValue SqlViewNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) -{ - qfLogFuncFrame() << shv_path.join('/') << method; - //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); - if(shv_path.empty()) { - if(method == METH_TABLE) { - const auto &m = params.asMap(); - auto qb = effectiveQueryBuilder(); - if (auto where = m.value("where").to(); !where.isEmpty()) { - qb.where(where); - } - if (auto order_by = m.value("orderBy").to(); !order_by.isEmpty()) { - qb.orderBy(order_by); - } - qf::core::sql::Query q; - QString qs = qb.toString(); - q.exec(qs, qf::core::Exception::Throw); - auto res = SqlApiNode::rpcSqlResultFromQuery(q); - return res.toRpcValue(); - } - if(method == METH_RECORD) { - auto id = params.toInt(); - auto qb = effectiveQueryBuilder(); - qb.where("runs.id = " + QString::number(id)); - qf::core::sql::Query q; - QString qs = qb.toString(); - qfDebug() << qs; - q.exec(qs, qf::core::Exception::Throw); - if (q.next()) { - return SqlApiNode::recordToMap(q.record()); - } - return RpcValue::Map{}; - } - } - return Super::callMethod(shv_path, method, params, user_id); -} - -//========================================================= -// CurrentStageConfigNode -//========================================================= -const std::vector &CurrentStageConfigNode::metaMethods() -{ - static std::vector meta_methods { - methods::DIR, - methods::LS, - {Rpc::METH_GET, MetaMethod::Flag::IsGetter, {}, "Map", AccessLevel::Read}, - }; - return meta_methods; -} - -RpcValue CurrentStageConfigNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) -{ - qfLogFuncFrame() << shv_path.join('/') << method; - //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); - if(shv_path.empty()) { - if(method == Rpc::METH_GET) { - auto *event_plugin = getPlugin(); - QVariantMap data = event_plugin->stageData(event_plugin->currentStageId()); - data.remove("drawingConfig"); // remove internal key - return shv::coreqt::rpc::qVariantToRpcValue(data); - } - } - return Super::callMethod(shv_path, method, params, user_id); -} - -//========================================================= -// CurrentStageStartListNode -//========================================================= -CurrentStageStartListNode::CurrentStageStartListNode(shv::iotqt::node::ShvNode *parent) - : Super("startList", parent) -{ - auto qb = getPlugin()->startListQuery(); - //qb.orderBy("runs.startTimeMs"); - setQueryBuilder(qb); -} - -qf::core::sql::QueryBuilder CurrentStageStartListNode::effectiveQueryBuilder() -{ - auto qb = Super::effectiveQueryBuilder(); - auto *event_plugin = getPlugin(); - qb.where(QStringLiteral("runs.stageId=%1").arg(event_plugin->currentStageId())); - return qb; -} - -//========================================================= -// CurrentStageRunsNode -//========================================================= -CurrentStageRunsNode::CurrentStageRunsNode(shv::iotqt::node::ShvNode *parent) - : Super("runs", parent) -{ -} - -QueryBuilder CurrentStageRunsNode::effectiveQueryBuilder() -{ - auto *event_plugin = getPlugin(); - auto qb = getPlugin()->runsQuery(event_plugin->currentStageId()); - return qb; -} - -static const auto METH_SET_RECORD = "setRecord"; -//static const auto METH_RUN_CHANGED = "runchng"; -static const auto SIG_RUN_CHANGED = "runchng"; - -const std::vector &CurrentStageRunsNode::metaMethods() -{ - static std::vector meta_methods { - methods::DIR, - methods::LS, - {METH_TABLE, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Read}, - {METH_RECORD, MetaMethod::Flag::None, "Int", "Map", AccessLevel::Read, {{SIG_RUN_CHANGED, "[Int, RpcValue]"}} }, - {METH_SET_RECORD, MetaMethod::Flag::None, "[Int, RpcValue]", {}, AccessLevel::Write}, - }; - return meta_methods; - static auto s_meta_methods = [this]() { - auto mm = Super::metaMethods(); - mm.push_back({METH_SET_RECORD, MetaMethod::Flag::None, "[int, Map]", {}, AccessLevel::Write }); - return mm; - }(); - return s_meta_methods; -} - -RpcValue CurrentStageRunsNode::callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) -{ - qfLogFuncFrame() << shv_path.join('/') << method; - if(shv_path.empty()) { - if(method == METH_SET_RECORD) { - auto *plugin = getPlugin(); - const auto &lst = params.asList(); - plugin->setRunsRecord(lst.value(0).toInt(), shv::coreqt::rpc::rpcValueToQVariant(lst.value(1))); - return nullptr; - } - } - return Super::callMethod(shv_path, method, params, user_id); -} - -void CurrentStageRunsNode::sendRunChangedSignal(const QVariant &qparam) -{ - auto param = shv::coreqt::rpc::qVariantToRpcValue(qparam); - Q_ASSERT(param.isList()); - Q_ASSERT(param.asList().value(0).toInt() > 0); // run.id - RpcSignal sig; - sig.setShvPath(shvPath().asString()); - sig.setMethod(SIG_RUN_CHANGED); - sig.setSource(METH_RECORD); - sig.setParams(param); - qfDebug() << "emit:" << sig.toPrettyString(); - emitSendRpcMessage(sig); -} - -//========================================================= -// CurrentStageClassesNode -//========================================================= -CurrentStageClassesNode::CurrentStageClassesNode(shv::iotqt::node::ShvNode *parent) - : Super("classes", parent) -{ -} - -QueryBuilder CurrentStageClassesNode::effectiveQueryBuilder() -{ - auto *event_plugin = getPlugin(); - QueryBuilder qb; - qb.select2("classes", "*") - .select2("classdefs", "*") - .select2("courses", "id, name, length, climb") - .from("classes") - .joinRestricted("classes.id", "classdefs.classId", "classdefs.stageId=" QF_IARG(event_plugin->currentStageId())) - .join("classdefs.courseId", "courses.id") - .orderBy("classes.name");//.limit(10); - return qb; -} - - } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h index 917a75f2c..58cf1125e 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/nodes.h @@ -19,85 +19,4 @@ class DotAppNode : public QxNode shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; }; -class EventNode : public QxNode -{ - Q_OBJECT - - using Super = QxNode; -public: - explicit EventNode(shv::iotqt::node::ShvNode *parent); -private: - const std::vector &metaMethods() override; - shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; -}; - -class SqlViewNode : public QxNode -{ - Q_OBJECT - - using Super = QxNode; -public: - explicit SqlViewNode(const std::string &name, shv::iotqt::node::ShvNode *parent) - : Super(name, parent) - {} - void setQueryBuilder(const qf::core::sql::QueryBuilder &qb); -protected: - virtual qf::core::sql::QueryBuilder effectiveQueryBuilder(); - const std::vector &metaMethods() override; - shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; -private: - qf::core::sql::QueryBuilder m_queryBuilder; -}; - -class CurrentStageConfigNode : public QxNode -{ - Q_OBJECT - - using Super = QxNode; -public: - explicit CurrentStageConfigNode(shv::iotqt::node::ShvNode *parent) - : Super("config", parent) - {} -protected: - const std::vector &metaMethods() override; - shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; -}; - -class CurrentStageStartListNode : public SqlViewNode -{ - Q_OBJECT - - using Super = SqlViewNode; -public: - explicit CurrentStageStartListNode(shv::iotqt::node::ShvNode *parent); -protected: - qf::core::sql::QueryBuilder effectiveQueryBuilder() override; -}; - -class CurrentStageRunsNode : public SqlViewNode -{ - Q_OBJECT - - using Super = SqlViewNode; -public: - explicit CurrentStageRunsNode(shv::iotqt::node::ShvNode *parent); - void sendRunChangedSignal(const QVariant &qparam); -protected: - const std::vector &metaMethods() override; - shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; - qf::core::sql::QueryBuilder effectiveQueryBuilder() override; -}; - -class CurrentStageClassesNode : public SqlViewNode -{ - Q_OBJECT - - using Super = SqlViewNode; -public: - explicit CurrentStageClassesNode(shv::iotqt::node::ShvNode *parent); - -protected: - qf::core::sql::QueryBuilder effectiveQueryBuilder() override; -}; - } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index e693f4b45..8aa7f5642 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -51,24 +51,8 @@ QxEventService::QxEventService(QObject *parent) : Super(QxEventService::serviceId(), parent) , m_rootNode(new shv::iotqt::node::ShvRootNode(this)) { - auto *event_plugin = getPlugin(); - // connect(event_plugin, &Event::EventPlugin::dbEventNotify, this, &QxEventService::onDbEventNotify, Qt::QueuedConnection); - new DotAppNode(m_rootNode); - connect(event_plugin, &EventPlugin::eventOpenChanged, this, [this](bool is_open) { - if (is_open) { - new SqlApiNode(m_rootNode); - auto *event = new EventNode(m_rootNode); - auto *current_stage = new shv::iotqt::node::ShvNode("currentStage", event); - new CurrentStageConfigNode(current_stage); - //new CurrentStageStartListNode(current_stage); - new CurrentStageRunsNode(current_stage); - new CurrentStageClassesNode(current_stage); - } - else { - qDeleteAll(m_rootNode->findChildren()); - } - }); + new SqlApiNode(m_rootNode); connect(m_rootNode, &shv::iotqt::node::ShvNode::sendRpcMessage, this, &QxEventService::sendRpcMessage); } From f0d2a693f53c254b27d6c11db2357eae0d05be8c Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 9 Nov 2025 22:10:51 +0100 Subject: [PATCH 07/34] QX SQL API node implemented --- .../Event/src/services/qx/sqlapinode.cpp | 244 +++++++++++++++--- .../Event/src/services/qx/sqlapinode.h | 3 - 2 files changed, 203 insertions(+), 44 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index ac90ce45f..846e6c7e6 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -10,32 +11,12 @@ #include #include +#include using namespace shv::chainpack; using namespace shv::coreqt::data; namespace Event::services::qx { -namespace { - -/* - -RpcSqlResult RpcSqlResult::fromRpcValue(const shv::chainpack::RpcValue &rv) -{ - RpcSqlResult ret; - const RpcValue::Map &map = rv.asMap(); - const RpcValue::List &flds = map.value("fields").asList(); - if(flds.empty()) { - ret.numRowsAffected = map.value("numRowsAffected").toInt(); - ret.lastInsertId = map.value("lastInsertId").toInt(); - } - else { - - } - return ret; - -} -*/ -} SqlApiNode::SqlApiNode(shv::iotqt::node::ShvNode *parent) : Super("sql", parent) @@ -43,20 +24,60 @@ SqlApiNode::SqlApiNode(shv::iotqt::node::ShvNode *parent) } -shv::chainpack::RpcValue::Map SqlApiNode::recordToMap(const QSqlRecord &rec) +namespace { +class Transaction { - RpcValue::Map record; - for (int i = 0; i < rec.count(); ++i) { - QSqlField fld = rec.field(i); - auto fld_name = fld.name(); - fld_name.replace("__", "."); - record[fld_name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(fld.value()); +public: + Transaction(QSqlDatabase db) : m_db(db) { } - return record; -} + ~Transaction() { + if (m_inTransaction) { + m_db.rollback(); + } + } + void begin() { + if (!m_db.transaction()) { + qfWarning() << "BEGIN transaction error:" << m_db.lastError().text(); + throw std::runtime_error("BEGIN transaction error"); + } + m_inTransaction = true; + } + void commit() { + if (m_inTransaction) { + if (!m_db.commit()) { + qfWarning() << "COMMIT transaction error:" << m_db.lastError().text(); + throw std::runtime_error("COMMIT transaction error"); + } + m_inTransaction = false; + } + } +private: + QSqlDatabase m_db; + bool m_inTransaction = false; +}; -RpcSqlResult SqlApiNode::rpcSqlResultFromQuery(QSqlQuery &q) +RpcSqlResult rpcSqlQuery(const QString &query, const RpcValue ¶ms, bool in_transaction = false) { + auto conn = qf::core::sql::Connection::forName(); + Transaction tranaction(conn); + if (in_transaction) { + tranaction.begin(); + } + qf::core::sql::Query q(conn); + q.prepare(query, qf::core::Exception::Throw); + for (const auto &[k, v] : params.asMap()) { + bool ok; + QVariant val = shv::coreqt::rpc::rpcValueToQVariant(v, &ok); + if (!ok) { + QF_EXCEPTION(QStringLiteral("Cannot convert SHV type: %1 to QVariant").arg(v.typeName())); + } + q.bindValue(QString::fromStdString(k), val); + } + q.exec(qf::core::Exception::Throw); + if (in_transaction) { + tranaction.commit(); + } + RpcSqlResult ret; if(q.isSelect()) { QSqlRecord rec = q.record(); @@ -72,10 +93,12 @@ RpcSqlResult SqlApiNode::rpcSqlResultFromQuery(QSqlQuery &q) RpcSqlResult::Row row; for (int i = 0; i < rec.count(); ++i) { const QVariant v = q.value(i); - if (v.isNull()) - row.append(QVariant()); - else + if (v.isNull()) { + row.append(QVariant::fromValue(nullptr)); + } + else { row.append(v); + } //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); } ret.rows.insert(ret.rows.count(), row); @@ -88,14 +111,29 @@ RpcSqlResult SqlApiNode::rpcSqlResultFromQuery(QSqlQuery &q) return ret; } -static auto METH_EXEC_SQL = "execSql"; +auto METH_QUERY = "query"; +auto METH_EXEC = "exec"; +auto METH_TRANSACTION = "transaction"; +auto METH_LIST = "list"; +auto METH_CREATE = "create"; +auto METH_READ = "read"; +auto METH_UPDATE = "update"; +auto METH_DELETE = "delete"; +} const std::vector &SqlApiNode::metaMethods() { static std::vector meta_methods { methods::DIR, methods::LS, - {METH_EXEC_SQL, MetaMethod::Flag::None, {}, "RpcValue", AccessLevel::Write}, + {METH_QUERY, MetaMethod::Flag::LargeResultHint, "[s:query,{s|i|b|t|n}:params]", "{{s:name}:fields,[[s|i|b|t|n]]:rows}", AccessLevel::Read}, + {METH_EXEC, MetaMethod::Flag::None, "[s:query,{s|i|b|t|n}:params]", "{i:rows_affected,i|n:insert_id}", AccessLevel::Write}, + {METH_TRANSACTION, MetaMethod::Flag::None, "[s:query,{s|i|b|t|n}:params]", "n", AccessLevel::Write}, + {METH_LIST, MetaMethod::Flag::LargeResultHint, "{s:table,[s]|n:fields,i|n:ids_above,i|n:limit}", "[{s|i|b|t|n}]", AccessLevel::Write}, + {METH_CREATE, MetaMethod::Flag::None, "{s:table,{s|i|b|t|n}:record}", "i", AccessLevel::Write}, + {METH_READ, MetaMethod::Flag::None, "{s:table,i:id,{s}|n:fields}", "{s|i|b|t|n}|n", AccessLevel::Read}, + {METH_UPDATE, MetaMethod::Flag::None, "{s:table,i:id,{s|i|b|t|n}:record}", "b", AccessLevel::Write}, + {METH_DELETE, MetaMethod::Flag::None, "{s:table,i:id}", "b", AccessLevel::Write}, }; return meta_methods; } @@ -105,13 +143,137 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin qfLogFuncFrame() << shv_path.join('/') << method; //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); if(shv_path.empty()) { - if(method == METH_EXEC_SQL) { - qf::core::sql::Query q; - QString qs = params.to(); - q.exec(qs, qf::core::Exception::Throw); - auto res = rpcSqlResultFromQuery(q); + if(method == METH_EXEC) { + auto sql_query = params.asList().valref(0).to(); + const auto &sql_params = params.asList().valref(0); + auto res = rpcSqlQuery(sql_query, sql_params); + return res.toRpcValue(); + } + if(method == METH_QUERY) { + auto sql_query = params.asList().valref(0).to(); + const auto &sql_params = params.asList().valref(0); + auto res = rpcSqlQuery(sql_query, sql_params); return res.toRpcValue(); } + if(method == METH_TRANSACTION) { + auto sql_query = params.asList().valref(0).to(); + const auto &sql_params = params.asList().valref(0); + auto res = rpcSqlQuery(sql_query, sql_params, true); + return RpcValue(nullptr); + } + if(method == METH_LIST) { + const auto &map = params.asMap(); + QStringList fields; + for (const auto &fn : map.valref("fields").asList()) { + fields << fn.to(); + } + if (fields.isEmpty()) { + fields << "*"; + } + QString sql_query = QStringLiteral("SELECT %1 FROM %2").arg(fields.join(',')).arg(map.value("table").to()); + if (auto ids_above = map.value("ids_above"); ids_above.isInt()) { + sql_query += " WHERE id > " + QString::number(ids_above.toInt()); + } + if (auto limit = map.value("limit"); limit.isInt()) { + sql_query += " LIMIT " + QString::number(limit.toInt()); + } + auto res = rpcSqlQuery(sql_query, {}); + if (res.rows.isEmpty()) { + return RpcValue(nullptr); + } + RpcValue::List ret; + for (const auto &row : res.rows) { + auto cells = row.toList(); + RpcValue::Map rec; + int n = 0; + for (const auto &field : res.fields) { + rec[field.name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(cells.value(n++)); + } + ret.push_back(rec); + } + return ret; + } + if(method == METH_CREATE) { + const auto &map = params.asMap(); + auto table = map.valref("table").to(); + const auto &record = map.valref("record").asMap(); + QStringList fields; + QStringList placeholders; + for (const auto &[k, v] : record) { + auto name = QString::fromStdString(k); + fields << name; + placeholders << ':' + name; + } + QString sql_query = QStringLiteral("INSERT INTO %1 (%2) VALUES (%3)") + .arg(table) + .arg(fields.join(',')) + .arg(placeholders.join(',')); + qf::core::sql::Query q; + q.prepare(sql_query, qf::core::Exception::Throw); + for (const auto &[k, v] : record) { + q.bindValue(QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); + } + q.exec(qf::core::Exception::Throw); + return q.lastInsertId().toInt(); + } + if(method == METH_READ) { + const auto &map = params.asMap(); + QStringList fields; + for (const auto &fn : map.valref("fields").asList()) { + fields << fn.to(); + } + if (fields.isEmpty()) { + fields << "*"; + } + QString sql_query = QStringLiteral("SELECT %1 FROM %2 WHERE id = %3") + .arg(fields.join(',')) + .arg(map.value("table").to()) + .arg(map.value("id").toInt()) ; + auto res = rpcSqlQuery(sql_query, {}); + if (res.rows.isEmpty()) { + return RpcValue(nullptr); + } + RpcValue::Map rec; + auto cells = res.rows[0].toList(); + int n = 0; + for (const auto &field : res.fields) { + rec[field.name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(cells.value(n++)); + } + return rec; + } + if(method == METH_UPDATE) { + const auto &map = params.asMap(); + auto table = map.valref("table").to(); + auto id = map.valref("id").toInt(); + const auto &record = map.valref("record").asMap(); + QStringList fields; + for (const auto &[k, v] : record) { + auto name = QString::fromStdString(k); + fields << name + " = :" + name; + } + QString sql_query = QStringLiteral("UPDATE %1 SET %2 WHERE id = %3") + .arg(table) + .arg(fields.join(',')) + .arg(id); + qf::core::sql::Query q; + q.prepare(sql_query, qf::core::Exception::Throw); + for (const auto &[k, v] : record) { + q.bindValue(QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); + } + q.exec(qf::core::Exception::Throw); + return q.numRowsAffected() == 1; + } + if(method == METH_DELETE) { + const auto &map = params.asMap(); + auto table = map.valref("table").to(); + auto id = map.valref("id").toInt(); + QString sql_query = QStringLiteral("DELETE FROM %1 WHERE id = %2") + .arg(table) + .arg(id); + qf::core::sql::Query q; + q.exec(sql_query, qf::core::Exception::Throw); + return q.numRowsAffected() == 1; + } } return Super::callMethod(shv_path, method, params, user_id); } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h index c2e112f26..a36f7ed8c 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h @@ -16,9 +16,6 @@ class SqlApiNode : public QxNode using Super = QxNode; public: explicit SqlApiNode(shv::iotqt::node::ShvNode *parent); - - static shv::coreqt::data::RpcSqlResult rpcSqlResultFromQuery(QSqlQuery &q); - static shv::chainpack::RpcValue::Map recordToMap(const QSqlRecord &rec); protected: const std::vector &metaMethods() override; shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; From 9e325a7563bbb8988f7c7ec7f2c9fa9b081a0027 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 10 Nov 2025 23:10:32 +0100 Subject: [PATCH 08/34] QxSQL API in QE --- 3rdparty/libshv | 2 +- libqf/libqfgui/src/tableview.cpp | 2 +- quickevent/app/quickevent/CMakeLists.txt | 6 +- .../Event/src/services/qx/qxeventservice.cpp | 54 +-- .../Event/src/services/qx/qxeventservice.h | 6 +- .../src/services/qx/qxeventservicewidget.cpp | 34 +- .../plugins/Event/src/services/qx/sqlapi.cpp | 406 ++++++++++++++++++ .../plugins/Event/src/services/qx/sqlapi.h | 88 ++++ .../Event/src/services/qx/sqlapinode.cpp | 217 ++-------- .../plugins/Runs/src/runstablewidget.cpp | 28 ++ .../plugins/Runs/src/runstablewidget.h | 3 + .../quickevent/plugins/Runs/src/runswidget.h | 15 +- 12 files changed, 601 insertions(+), 260 deletions(-) create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp create mode 100644 quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h diff --git a/3rdparty/libshv b/3rdparty/libshv index 40f0b3cf2..f108e5d72 160000 --- a/3rdparty/libshv +++ b/3rdparty/libshv @@ -1 +1 @@ -Subproject commit 40f0b3cf213ab799fb17bf6a31ce527a8851ee30 +Subproject commit f108e5d72729c16ee5b27ee8a9096182a5ceb7fa diff --git a/libqf/libqfgui/src/tableview.cpp b/libqf/libqfgui/src/tableview.cpp index 72cae3801..bd3183e59 100644 --- a/libqf/libqfgui/src/tableview.cpp +++ b/libqf/libqfgui/src/tableview.cpp @@ -1146,7 +1146,7 @@ void TableView::rowExternallySaved(const QVariant &id) qf::core::sql::Query q; bool ok = q.exec(query_str); if (!ok) { - qfInfo() << "Query:" << query_str; + qfMessage() << "Query:" << query_str; qfWarning() << "SQL error:" << q.lastErrorText(); return; } diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index 33b7c3607..b7c6e2e75 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -1,5 +1,3 @@ -find_package(Qt6 REQUIRED COMPONENTS Core) - add_executable(quickevent plugins/CardReader/src/cardchecker.cpp plugins/CardReader/src/cardcheckerclassiccpp.cpp @@ -92,6 +90,7 @@ add_executable(quickevent plugins/Event/src/services/servicewidget.cpp plugins/Event/src/services/servicewidget.ui + plugins/Event/src/services/qx/sqlapi.h plugins/Event/src/services/qx/sqlapi.cpp plugins/Event/src/services/qx/qxnode.cpp plugins/Event/src/services/qx/sqlapinode.cpp plugins/Event/src/services/qx/nodes.cpp @@ -276,8 +275,7 @@ target_include_directories(quickevent PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAK target_link_libraries(quickevent PUBLIC libquickeventcore libquickeventgui libqfgui libsiut) if (QF_WITH_LIBSHV) target_link_libraries(quickevent PUBLIC libshviotqt) -target_link_libraries(quickevent PRIVATE Qt6::Core) -target_compile_definitions(quickevent PRIVATE QF_WITH_LIBSHV) + target_compile_definitions(quickevent PRIVATE QF_WITH_LIBSHV) endif() foreach(plugin IN ITEMS Classes Runs Relays Receipts shared) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 8aa7f5642..320548969 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -2,6 +2,7 @@ #include "qxeventservicewidget.h" #include "nodes.h" #include "sqlapinode.h" +#include "sqlapi.h" #include "../../eventplugin.h" #include "../../../../Runs/src/runsplugin.h" @@ -77,7 +78,7 @@ void QxEventService::run() { m_rpcConnection->setConnectionString(ss.shvBrokerUrl()); RpcValue::Map opts; RpcValue::Map device; - device["mountPoint"] = "test/quickbox"; + device["mountPoint"] = "test/quickevent"; opts["device"] = device; m_rpcConnection->setConnectionOptions(opts); @@ -86,28 +87,9 @@ void QxEventService::run() { connect(m_rpcConnection, &ClientConnection::brokerLoginError, this, &QxEventService::onBrokerLoginError); connect(m_rpcConnection, &ClientConnection::rpcMessageReceived, this, &QxEventService::onRpcMessageReceived); - m_rpcConnection->open(); + connect(SqlApi::instance(), &SqlApi::recchng, this, &QxEventService::onRecchg); -// connect(reply, &QNetworkReply::finished, this, [this, reply, ss]() { -// if (reply->error() == QNetworkReply::NetworkError::NoError) { -// auto data = reply->readAll(); -// auto doc = QJsonDocument::fromJson(data); -// EventInfo event_info(doc.toVariant().toMap()); -// setStatusMessage(event_info.name() + (event_info.stage_count() > 1? QStringLiteral(" E%1").arg(event_info.stage()): QString())); -// m_eventId = event_info.id(); -// connectToSSE(m_eventId); -// if (!m_pollChangesTimer) { -// m_pollChangesTimer = new QTimer(this); -// connect(m_pollChangesTimer, &QTimer::timeout, this, &QxClientService::pollQxChanges); -// } -// pollQxChanges(); -// m_pollChangesTimer->start(10000); -// Super::run(); -// } -// else { -// qfWarning() << "Cannot run QX service, network error:" << reply->errorString(); -// } -// }); + m_rpcConnection->open(); } void QxEventService::stop() @@ -615,6 +597,10 @@ void QxEventService::onRpcMessageReceived(const shv::chainpack::RpcMessage &msg) // shvLogFuncFrame() << msg.toCpon(); if(msg.isRequest()) { RpcRequest rq(msg); + if (rq.shvPath().asString().starts_with(".broker/")) { + // ignore broker discovery messages + return; + } qfMessage() << "RPC request received:" << rq.toPrettyString(); m_rootNode->handleRpcRequest(rq); } @@ -641,36 +627,22 @@ void QxEventService::subscribeChanges() QString shv_path = "test"; QString signal_name = shv::chainpack::Rpc::SIG_VAL_CHANGED; auto *rpc_call = RpcCall::createSubscriptionRequest(m_rpcConnection, shv_path, signal_name); - connect(rpc_call, &RpcCall::maybeResult, this, [this, shv_path, signal_name](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + connect(rpc_call, &RpcCall::maybeResult, this, [shv_path, signal_name](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { if(error.isValid()) { qfError() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribe error:" << error.toString(); } else { qfMessage() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribed successfully" << result.toCpon(); - // generate data change without ret value check - m_rpcConnection->callShvMethod((shv_path + "/someInt").toStdString(), "set", 123); - QTimer::singleShot(500, this, [this, shv_path]() { - m_rpcConnection->callShvMethod((shv_path + "/someInt").toStdString(), "set", 321); - }); } }); rpc_call->start(); } -void QxEventService::testRpcCall() const +void QxEventService::onRecchg(const QxRecChng &chng) { - Q_ASSERT(m_rpcConnection); - auto *rpc_call = RpcCall::create(m_rpcConnection) - ->setShvPath("test") - ->setMethod("ls"); -// ->setTimeout(5000); - connect(rpc_call, &RpcCall::maybeResult, [](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { - if(error.isValid()) - qfError() << "RPC call error:" << error.toString(); - else - qfInfo() << "Got RPC response, result:" << result.toCpon(); - }); - rpc_call->start(); + if (isRunning()) { + m_rpcConnection->sendShvSignal("sql", "recchng", chng.toRpcValue()); + } } } // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index 2a103a9d7..b25076626 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -13,11 +13,13 @@ namespace shv::chainpack { class RpcMessage; class RpcError; } namespace Event::services::qx { +struct QxRecChng; + class QxEventServiceSettings : public ServiceSettings { using Super = ServiceSettings; - QF_VARIANTMAP_FIELD2(QString, s, setS, hvBrokerUrl, "http://localhost:8000") + QF_VARIANTMAP_FIELD2(QString, s, setS, hvBrokerUrl, "tcp://localhost?user=test&password=test") public: QxEventServiceSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} }; @@ -78,9 +80,9 @@ class QxEventService : public Service void sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg); void onBrokerSocketError(const QString &err); void onBrokerLoginError(const shv::chainpack::RpcError &err); + void onRecchg(const QxRecChng &chng); void subscribeChanges(); - void testRpcCall() const; private: void loadSettings() override; qf::gui::framework::DialogWidget *createDetailWidget() override; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index 416bb25b4..125d70f16 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -1,5 +1,6 @@ #include "qxeventservicewidget.h" #include "ui_qxeventservicewidget.h" + #include "qxeventservice.h" #include @@ -8,6 +9,8 @@ #include #include +#include + #include #include #include @@ -113,22 +116,27 @@ void QxEventServiceWidget::updateOCheckListPostUrl() void QxEventServiceWidget::testConnection() { - auto *svc = service(); - Q_ASSERT(svc); - auto *reply = svc->getRemoteEventInfo(ui->edServerUrl->text(), ui->edApiToken->text()); - connect(reply, &QNetworkReply::finished, this, [this, reply]() { - if (reply->error() == QNetworkReply::NetworkError::NoError) { - auto data = reply->readAll(); - auto doc = QJsonDocument::fromJson(data); - EventInfo event_info(doc.toVariant().toMap()); - ui->edEventId->setValue(event_info.id()); + using namespace shv::iotqt::rpc; + using namespace shv::chainpack; + + auto *rpc = new DeviceConnection(this); + rpc->setConnectionString(ui->edServerUrl->text()); + + connect(rpc, &ClientConnection::brokerConnectedChanged, this, [this, rpc](bool is_connected) { + if (is_connected) { + rpc->deleteLater(); setMessage(tr("Connected OK")); } - else { - setMessage(tr("Connection error: %1").arg(reply->errorString()), MessageType::Error); - } - reply->deleteLater(); }); + connect(rpc, &ClientConnection::socketError, this, [this, rpc](const QString &error) { + rpc->deleteLater(); + setMessage(tr("Connection error: %1").arg(error), MessageType::Error); + }); + connect(rpc, &ClientConnection::brokerLoginError, this, [this, rpc](const auto &error) { + rpc->deleteLater(); + setMessage(tr("Login error: %1").arg(QString::fromStdString(error.toString())), MessageType::Error); + }); + rpc->open(); } void QxEventServiceWidget::exportEventInfo() diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp new file mode 100644 index 000000000..fde4ae512 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp @@ -0,0 +1,406 @@ +#include "sqlapi.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +using namespace shv::chainpack; + +namespace Event::services::qx { + +//============================================== +// RpcSqlField +//============================================== +RpcValue RpcSqlField::toRpcValue() const +{ + RpcValue::Map ret; + ret["name"] = name; + return RpcValue(std::move(ret)); +} + +RpcSqlField RpcSqlField::fromRpcValue(const shv::chainpack::RpcValue &rv) +{ + RpcSqlField ret; + const RpcValue::Map &map = rv.asMap(); + ret.name = map.value("name").asString(); + return ret; +} + +//============================================== +// RpcSqlResult +//============================================== +const RpcValue &RpcSqlResult::value(size_t row, size_t col) const +{ + if (row < rows.size()) { + const auto &cells = rows[row]; + if (col < cells.size()) { + return cells[col]; + } + } + static RpcValue s; + return s; +} + +const RpcValue& RpcSqlResult::value(size_t row, const std::string &name) const +{ + if (auto ix = columnIndex(name); ix.has_value()) { + return value(row, ix.value()); + } + static RpcValue s; + return s; +} + +void RpcSqlResult::setValue(size_t row, size_t col, const RpcValue &val) +{ + if (row < rows.size()) { + auto &r = rows[row]; + if (col < r.size()) { + r[col] = val; + } + } +} + +void RpcSqlResult::setValue(size_t row, const std::string &name, const RpcValue &val) +{ + if (auto ix = columnIndex(name); ix.has_value()) { + setValue(row, ix.value(), val); + } +} + +RpcValue::List RpcSqlResult::toRecordList() const +{ + RpcValue::List ret; + for (const auto &row : rows) { + SqlRecord rec; + int n = 0; + for (const auto &field : fields) { + rec[field.name] = row[n++]; + } + ret.push_back(rec); + } + return ret; +} + +std::optional RpcSqlResult::columnIndex(const std::string &name) const +{ + for (size_t col = 0; col < fields.size(); ++col) { + const auto &fld = fields[col]; + if (fld.name == name) { + return col; + } + } + return {}; +} + +RpcValue RpcSqlResult::toRpcValue() const +{ + RpcValue::Map ret; + if(isSelect()) { + RpcValue::List flds; + for(const auto &fld : this->fields) + flds.push_back(fld.toRpcValue()); + ret["fields"] = flds; + ret["rows"] = rows; + } + else { + ret["numRowsAffected"] = numRowsAffected; + ret["lastInsertId"] = lastInsertId.has_value()? RpcValue(lastInsertId.value()): RpcValue(nullptr); + } + return ret; +} + +RpcSqlResult RpcSqlResult::fromRpcValue(const RpcValue &rv) +{ + RpcSqlResult ret; + const auto &map = rv.asMap(); + const auto &flds = map.valref("fields").asList(); + if(flds.empty()) { + ret.numRowsAffected = map.value("numRowsAffected").toInt(); + ret.lastInsertId = map.value("lastInsertId").toInt(); + } + else { + for(const auto &fv : flds) { + ret.fields.push_back(RpcSqlField::fromRpcValue(fv)); + } + for (const auto &row : map.value("rows").asList()) { + ret.rows.push_back(row.asList()); + } + } + return ret; +} + +SqlQueryAndParams SqlQueryAndParams::fromRpcValue(const shv::chainpack::RpcValue &rv) +{ + auto sql_query = rv.asList().valref(0).asString(); + const auto &sql_params = rv.asList().valref(0); + return SqlQueryAndParams { .query = sql_query, .params = sql_params.asMap() }; +} + +RpcValue QxRecChng::toRpcValue() const +{ + RpcValue::Map ret; + ret["table"] = table.toStdString(); + ret["id"] = id; + ret["record"] = shv::coreqt::rpc::qVariantToRpcValue(record); + auto rec_op_string = [](RecOp op) { + switch (op) { + case RecOp::Insert: return "Insert"; + case RecOp::Update: return "Update"; + case RecOp::Delete: return "Delete"; + } + return ""; + }; + ret["op"] = rec_op_string(op); + return ret; +} + +//============================================== +// SqlApi +//============================================== +SqlApi::SqlApi(QObject *parent) + : QObject{parent} +{ + +} + +SqlApi *SqlApi::instance() +{ + static auto *api = new SqlApi(QCoreApplication::instance()); + return api; +} + +namespace { + +class Transaction +{ +public: + Transaction(QSqlDatabase db) : m_db(db) { + if (!m_db.transaction()) { + qfWarning() << "BEGIN transaction error:" << m_db.lastError().text(); + throw std::runtime_error("BEGIN transaction error"); + } + } + ~Transaction() { + if (m_inTransaction) { + m_db.rollback(); + } + } + void commit() { + if (!m_db.commit()) { + qfWarning() << "COMMIT transaction error:" << m_db.lastError().text(); + throw std::runtime_error("COMMIT transaction error"); + } + m_inTransaction = false; + } +private: + QSqlDatabase m_db; + bool m_inTransaction = true; +}; + +RpcSqlResult rpcSqlQuery(const SqlQueryAndParams ¶ms) +{ + qf::core::sql::Query q; + q.prepare(QString::fromUtf8(params.query), qf::core::Exception::Throw); + for (const auto &[k, v] : params.params) { + bool ok; + QVariant val = shv::coreqt::rpc::rpcValueToQVariant(v, &ok); + if (!ok) { + QF_EXCEPTION(QStringLiteral("Cannot convert SHV type: %1 to QVariant").arg(v.typeName())); + } + q.bindValue(':' + QString::fromStdString(k), val); + } + q.exec(qf::core::Exception::Throw); + RpcSqlResult ret; + if(q.isSelect()) { + QSqlRecord rec = q.record(); + for (int i = 0; i < rec.count(); ++i) { + QSqlField fld = rec.field(i); + RpcSqlField rfld; + rfld.name = fld.name().toStdString(); + // rfld.name.replace("__", "."); + ret.fields.push_back(rfld); + } + while(q.next()) { + RpcSqlResult::Row row; + for (int i = 0; i < rec.count(); ++i) { + const QVariant v = q.value(i); + if (v.isNull()) { + row.push_back(RpcValue(nullptr)); + } + else { + row.push_back(shv::coreqt::rpc::qVariantToRpcValue(v)); + } + //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); + } + ret.rows.push_back(row); + } + } + else { + ret.numRowsAffected = q.numRowsAffected(); + ret.lastInsertId = q.lastInsertId().toInt(); + } + return ret; +} + +} + +RpcSqlResult SqlApi::exec(const SqlQueryAndParams ¶ms) +{ + return rpcSqlQuery(params); +} + +RpcSqlResult SqlApi::query(const SqlQueryAndParams ¶ms) +{ + return rpcSqlQuery(params); +} + +void SqlApi::transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms) +{ + auto conn = qf::core::sql::Connection::forName(); + Transaction tranaction(conn); + qf::core::sql::Query q(conn); + q.prepare(QString::fromUtf8(query), qf::core::Exception::Throw); + for (const auto ¶m : params) { + for (const auto &[k, v] : param.asMap()) { + bool ok; + QVariant val = shv::coreqt::rpc::rpcValueToQVariant(v, &ok); + if (!ok) { + QF_EXCEPTION(QStringLiteral("Cannot convert SHV type: %1 to QVariant").arg(v.typeName())); + } + q.bindValue(':' + QString::fromStdString(k), val); + } + q.exec(qf::core::Exception::Throw); + } + tranaction.commit(); +} + +RpcSqlResult SqlApi::list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit) +{ + QStringList qfields; + for (const auto &fn : fields) { + qfields << QString::fromStdString(fn); + } + if (qfields.isEmpty()) { + qfields << "*"; + } + QString sql_query = QStringLiteral("SELECT %1 FROM %2").arg(qfields.join(',')).arg(QString::fromStdString(table)); + if (ids_above.has_value()) { + sql_query += " WHERE id > " + QString::number(ids_above.value()); + } + if (limit.has_value()) { + sql_query += " LIMIT " + QString::number(limit.value()); + } + auto res = rpcSqlQuery(SqlQueryAndParams { .query = sql_query.toStdString(), .params = {}}); + return res; +} + +int64_t SqlApi::create(const std::string &table, const SqlRecord &record) +{ + QStringList fields; + QStringList placeholders; + for (const auto &[k, v] : record) { + auto name = QString::fromStdString(k); + fields << name; + placeholders << ':' + name; + } + QString sql_query = QStringLiteral("INSERT INTO %1 (%2) VALUES (%3)") + .arg(table) + .arg(fields.join(',')) + .arg(placeholders.join(',')); + qf::core::sql::Query q; + q.prepare(sql_query, qf::core::Exception::Throw); + for (const auto &[k, v] : record) { + q.bindValue(':' + QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); + } + q.exec(qf::core::Exception::Throw); + auto id = q.lastInsertId().toInt(); + emit SqlApi::instance()->recchng(QxRecChng { + .table = QString::fromStdString(table), + .id = id, + .record = shv::coreqt::rpc::rpcValueToQVariant(record), + .op = RecOp::Insert + }); + return id; +} + +std::optional SqlApi::read(const std::string &table, int64_t id, const std::vector &fields) +{ + QStringList qfields; + for (const auto &fn : fields) { + qfields << QString::fromStdString(fn); + } + if (qfields.isEmpty()) { + qfields << "*"; + } + QString sql_query = QStringLiteral("SELECT %1 FROM %2 WHERE id = %3") + .arg(qfields.join(',')) + .arg(QString::fromStdString(table)) + .arg(id) ; + auto res = rpcSqlQuery(SqlQueryAndParams { .query = sql_query.toStdString(), .params = {}}); + auto lst = res.toRecordList(); + if (lst.empty()) { + return {}; + } + return lst[0].asMap(); +} + +bool SqlApi::update(const std::string &table, int64_t id, const SqlRecord &record) +{ + QStringList fields; + for (const auto &[k, v] : record) { + auto name = QString::fromStdString(k); + fields << name + " = :" + name; + } + QString sql_query = QStringLiteral("UPDATE %1 SET %2 WHERE id = %3") + .arg(table) + .arg(fields.join(',')) + .arg(id); + qf::core::sql::Query q; + q.prepare(sql_query, qf::core::Exception::Throw); + for (const auto &[k, v] : record) { + auto qv = shv::coreqt::rpc::rpcValueToQVariant(v); + q.bindValue(':' + QString::fromStdString(k), qv); + } + q.exec(qf::core::Exception::Throw); + bool updated = q.numRowsAffected() == 1; + if (updated) { + emit SqlApi::instance()->recchng(QxRecChng { + .table = QString::fromStdString(table), + .id = id, + .record = shv::coreqt::rpc::rpcValueToQVariant(record), + .op = RecOp::Update + }); + } + return updated; +} + +bool SqlApi::drop(const std::string &table, int64_t id) +{ + QString sql_query = QStringLiteral("DELETE FROM %1 WHERE id = %2") + .arg(table) + .arg(id); + qf::core::sql::Query q; + q.exec(sql_query, qf::core::Exception::Throw); + bool is_drop = q.numRowsAffected() == 1; + if (is_drop) { + emit SqlApi::instance()->recchng(QxRecChng { + .table = QString::fromStdString(table), + .id = id, + .record = {}, + .op = RecOp::Delete + }); + } + return is_drop; +} + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h new file mode 100644 index 000000000..3594ca510 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h @@ -0,0 +1,88 @@ +#pragma once + +#include + +#include +#include + +namespace Event::services::qx { + +struct RpcSqlField +{ + std::string name; + + //explicit RpcSqlField(const QJsonObject &jo = QJsonObject()) : Super(jo) {} + shv::chainpack::RpcValue toRpcValue() const; + // QVariant toVariant() const; + static RpcSqlField fromRpcValue(const shv::chainpack::RpcValue &rv); + // static RpcSqlField fromVariant(const QVariant &v); +}; + +struct RpcSqlResult +{ + int numRowsAffected = 0; + std::optional lastInsertId = 0; + std::vector fields; + using Row = shv::chainpack::RpcValue::List; + std::vector rows; + + RpcSqlResult() = default; + // explicit RpcSqlResult(const QSqlQuery &q); + + std::optional columnIndex(const std::string &name) const; + const shv::chainpack::RpcValue& value(size_t row, size_t col) const; + const shv::chainpack::RpcValue& value(size_t row, const std::string &name) const; + void setValue(size_t row, size_t col, const shv::chainpack::RpcValue &val); + void setValue(size_t row, const std::string &name, const shv::chainpack::RpcValue &val); + + bool isSelect() const {return !fields.empty();} + shv::chainpack::RpcValue toRpcValue() const; + shv::chainpack::RpcValue::List toRecordList() const; + // QVariant toVariant() const; + // static RpcSqlResult fromVariant(const QVariant &v); + static RpcSqlResult fromRpcValue(const shv::chainpack::RpcValue &rv); +}; + +using SqlRecord = shv::chainpack::RpcValue::Map; + +struct SqlQueryAndParams +{ + std::string query; + SqlRecord params; + + static SqlQueryAndParams fromRpcValue(const shv::chainpack::RpcValue &rv); +}; + +enum class RecOp { Insert, Update, Delete, }; + +struct QxRecChng +{ + QString table; + int64_t id; + QVariant record; + RecOp op; + + shv::chainpack::RpcValue toRpcValue() const; +}; + +class SqlApi : public QObject +{ + Q_OBJECT +public: + static SqlApi* instance(); + + Q_SIGNAL void recchng(const QxRecChng &chng); + + static RpcSqlResult exec(const SqlQueryAndParams ¶ms); + static RpcSqlResult query(const SqlQueryAndParams ¶ms); + static void transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms); + static RpcSqlResult list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); + static int64_t create(const std::string &table, const SqlRecord &record); + static std::optional read(const std::string &table, int64_t id, const std::vector &fields); + static bool update(const std::string &table, int64_t id, const SqlRecord &record); + static bool drop(const std::string &table, int64_t id); +private: + explicit SqlApi(QObject *parent = nullptr); +}; + +} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index 846e6c7e6..d12474363 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -1,4 +1,5 @@ #include "sqlapinode.h" +#include "sqlapi.h" #include #include @@ -21,96 +22,9 @@ namespace Event::services::qx { SqlApiNode::SqlApiNode(shv::iotqt::node::ShvNode *parent) : Super("sql", parent) { - } namespace { -class Transaction -{ -public: - Transaction(QSqlDatabase db) : m_db(db) { - } - ~Transaction() { - if (m_inTransaction) { - m_db.rollback(); - } - } - void begin() { - if (!m_db.transaction()) { - qfWarning() << "BEGIN transaction error:" << m_db.lastError().text(); - throw std::runtime_error("BEGIN transaction error"); - } - m_inTransaction = true; - } - void commit() { - if (m_inTransaction) { - if (!m_db.commit()) { - qfWarning() << "COMMIT transaction error:" << m_db.lastError().text(); - throw std::runtime_error("COMMIT transaction error"); - } - m_inTransaction = false; - } - } -private: - QSqlDatabase m_db; - bool m_inTransaction = false; -}; - -RpcSqlResult rpcSqlQuery(const QString &query, const RpcValue ¶ms, bool in_transaction = false) -{ - auto conn = qf::core::sql::Connection::forName(); - Transaction tranaction(conn); - if (in_transaction) { - tranaction.begin(); - } - qf::core::sql::Query q(conn); - q.prepare(query, qf::core::Exception::Throw); - for (const auto &[k, v] : params.asMap()) { - bool ok; - QVariant val = shv::coreqt::rpc::rpcValueToQVariant(v, &ok); - if (!ok) { - QF_EXCEPTION(QStringLiteral("Cannot convert SHV type: %1 to QVariant").arg(v.typeName())); - } - q.bindValue(QString::fromStdString(k), val); - } - q.exec(qf::core::Exception::Throw); - if (in_transaction) { - tranaction.commit(); - } - - RpcSqlResult ret; - if(q.isSelect()) { - QSqlRecord rec = q.record(); - for (int i = 0; i < rec.count(); ++i) { - QSqlField fld = rec.field(i); - RpcSqlField rfld; - rfld.name = fld.name(); - rfld.name.replace("__", "."); - rfld.type = fld.metaType().id(); - ret.fields.append(rfld); - } - while(q.next()) { - RpcSqlResult::Row row; - for (int i = 0; i < rec.count(); ++i) { - const QVariant v = q.value(i); - if (v.isNull()) { - row.append(QVariant::fromValue(nullptr)); - } - else { - row.append(v); - } - //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); - } - ret.rows.insert(ret.rows.count(), row); - } - } - else { - ret.numRowsAffected = q.numRowsAffected(); - ret.lastInsertId = q.lastInsertId().toInt(); - } - return ret; -} - auto METH_QUERY = "query"; auto METH_EXEC = "exec"; auto METH_TRANSACTION = "transaction"; @@ -128,7 +42,7 @@ const std::vector &SqlApiNode::metaMethods() methods::LS, {METH_QUERY, MetaMethod::Flag::LargeResultHint, "[s:query,{s|i|b|t|n}:params]", "{{s:name}:fields,[[s|i|b|t|n]]:rows}", AccessLevel::Read}, {METH_EXEC, MetaMethod::Flag::None, "[s:query,{s|i|b|t|n}:params]", "{i:rows_affected,i|n:insert_id}", AccessLevel::Write}, - {METH_TRANSACTION, MetaMethod::Flag::None, "[s:query,{s|i|b|t|n}:params]", "n", AccessLevel::Write}, + {METH_TRANSACTION, MetaMethod::Flag::None, "[s:query,[{s|i|b|t|n}]:params]", "n", AccessLevel::Write}, {METH_LIST, MetaMethod::Flag::LargeResultHint, "{s:table,[s]|n:fields,i|n:ids_above,i|n:limit}", "[{s|i|b|t|n}]", AccessLevel::Write}, {METH_CREATE, MetaMethod::Flag::None, "{s:table,{s|i|b|t|n}:record}", "i", AccessLevel::Write}, {METH_READ, MetaMethod::Flag::None, "{s:table,i:id,{s}|n:fields}", "{s|i|b|t|n}|n", AccessLevel::Read}, @@ -144,135 +58,66 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); if(shv_path.empty()) { if(method == METH_EXEC) { - auto sql_query = params.asList().valref(0).to(); - const auto &sql_params = params.asList().valref(0); - auto res = rpcSqlQuery(sql_query, sql_params); + auto res = SqlApi::exec(SqlQueryAndParams::fromRpcValue(params)); return res.toRpcValue(); } if(method == METH_QUERY) { - auto sql_query = params.asList().valref(0).to(); - const auto &sql_params = params.asList().valref(0); - auto res = rpcSqlQuery(sql_query, sql_params); + auto res = SqlApi::exec(SqlQueryAndParams::fromRpcValue(params)); return res.toRpcValue(); } if(method == METH_TRANSACTION) { - auto sql_query = params.asList().valref(0).to(); + auto sql_query = params.asList().valref(0).asString(); const auto &sql_params = params.asList().valref(0); - auto res = rpcSqlQuery(sql_query, sql_params, true); + SqlApi::transaction(sql_query, sql_params.asList()); return RpcValue(nullptr); } if(method == METH_LIST) { const auto &map = params.asMap(); - QStringList fields; + const auto &table = map.value("table").asString(); + std::vector fields; for (const auto &fn : map.valref("fields").asList()) { - fields << fn.to(); - } - if (fields.isEmpty()) { - fields << "*"; - } - QString sql_query = QStringLiteral("SELECT %1 FROM %2").arg(fields.join(',')).arg(map.value("table").to()); - if (auto ids_above = map.value("ids_above"); ids_above.isInt()) { - sql_query += " WHERE id > " + QString::number(ids_above.toInt()); - } - if (auto limit = map.value("limit"); limit.isInt()) { - sql_query += " LIMIT " + QString::number(limit.toInt()); - } - auto res = rpcSqlQuery(sql_query, {}); - if (res.rows.isEmpty()) { - return RpcValue(nullptr); + fields.push_back(fn.asString()); } - RpcValue::List ret; - for (const auto &row : res.rows) { - auto cells = row.toList(); - RpcValue::Map rec; - int n = 0; - for (const auto &field : res.fields) { - rec[field.name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(cells.value(n++)); - } - ret.push_back(rec); - } - return ret; + auto ids_above = map.contains("ids_above")? std::optional(map.value("ids_above").toInt64()): std::optional(); + auto limit = map.contains("limit")? std::optional(map.value("limit").toInt64()): std::optional(); + auto res = SqlApi::list(table, fields, ids_above, limit); + return res.toRecordList(); } if(method == METH_CREATE) { const auto &map = params.asMap(); - auto table = map.valref("table").to(); + const auto &table = map.value("table").asString(); const auto &record = map.valref("record").asMap(); - QStringList fields; - QStringList placeholders; - for (const auto &[k, v] : record) { - auto name = QString::fromStdString(k); - fields << name; - placeholders << ':' + name; - } - QString sql_query = QStringLiteral("INSERT INTO %1 (%2) VALUES (%3)") - .arg(table) - .arg(fields.join(',')) - .arg(placeholders.join(',')); - qf::core::sql::Query q; - q.prepare(sql_query, qf::core::Exception::Throw); - for (const auto &[k, v] : record) { - q.bindValue(QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); - } - q.exec(qf::core::Exception::Throw); - return q.lastInsertId().toInt(); + auto res = SqlApi::create(table, record); + return res; } if(method == METH_READ) { const auto &map = params.asMap(); - QStringList fields; + const auto &table = map.value("table").asString(); + auto id = map.value("id").toInt64(); + std::vector fields; for (const auto &fn : map.valref("fields").asList()) { - fields << fn.to(); - } - if (fields.isEmpty()) { - fields << "*"; - } - QString sql_query = QStringLiteral("SELECT %1 FROM %2 WHERE id = %3") - .arg(fields.join(',')) - .arg(map.value("table").to()) - .arg(map.value("id").toInt()) ; - auto res = rpcSqlQuery(sql_query, {}); - if (res.rows.isEmpty()) { - return RpcValue(nullptr); + fields.push_back(fn.asString()); } - RpcValue::Map rec; - auto cells = res.rows[0].toList(); - int n = 0; - for (const auto &field : res.fields) { - rec[field.name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(cells.value(n++)); + auto res = SqlApi::read(table, id, fields); + if (res.has_value()) { + return res.value(); } - return rec; + return RpcValue(nullptr); } if(method == METH_UPDATE) { const auto &map = params.asMap(); - auto table = map.valref("table").to(); - auto id = map.valref("id").toInt(); + const auto &table = map.value("table").asString(); + auto id = map.value("id").toInt64(); const auto &record = map.valref("record").asMap(); - QStringList fields; - for (const auto &[k, v] : record) { - auto name = QString::fromStdString(k); - fields << name + " = :" + name; - } - QString sql_query = QStringLiteral("UPDATE %1 SET %2 WHERE id = %3") - .arg(table) - .arg(fields.join(',')) - .arg(id); - qf::core::sql::Query q; - q.prepare(sql_query, qf::core::Exception::Throw); - for (const auto &[k, v] : record) { - q.bindValue(QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); - } - q.exec(qf::core::Exception::Throw); - return q.numRowsAffected() == 1; + auto res = SqlApi::update(table, id, record); + return res; } if(method == METH_DELETE) { const auto &map = params.asMap(); - auto table = map.valref("table").to(); + const auto &table = map.value("table").asString(); auto id = map.valref("id").toInt(); - QString sql_query = QStringLiteral("DELETE FROM %1 WHERE id = %2") - .arg(table) - .arg(id); - qf::core::sql::Query q; - q.exec(sql_query, qf::core::Exception::Throw); - return q.numRowsAffected() == 1; + auto res = SqlApi::drop(table, id); + return res; } } return Super::callMethod(shv_path, method, params, user_id); diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index a226ec7f0..4250dd0fe 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -20,6 +20,12 @@ #include #include +#include +#include +#include +#include +#include + #include #include #include @@ -130,6 +136,8 @@ RunsTableWidget::RunsTableWidget(QWidget *parent) : } } }, Qt::QueuedConnection); + + connect(Event::services::qx::SqlApi::instance(), &Event::services::qx::SqlApi::recchng, this, &RunsTableWidget::onQxRecChng); } RunsTableWidget::~RunsTableWidget() @@ -431,4 +439,24 @@ void RunsTableWidget::onBadTableDataInput(const QString &message) qf::gui::dialogs::MessageBox::showError(this, message); } +void RunsTableWidget::onQxRecChng(const Event::services::qx::QxRecChng &chng) +{ + std::optional run_id; + if (chng.table == "competitors") { + auto *m = m_runsModel; + for (auto i=0; irowCount(); ++i) { + if (m->table().row(i).value("competitors.id").toInt() == chng.id) { + run_id = m->value(i, "runs.id").toInt(); + break; + } + } + } + else if (chng.table == "runs") { + run_id = chng.id; + } + if (run_id.has_value()) { + ui->tblRuns->rowExternallySaved(run_id.value()); + } +} + diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h index 8ba2e0964..a5379f689 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h @@ -7,6 +7,7 @@ class RunsTableItemDelegate; class CourseItemDelegate; namespace qf::gui { class TableView; } +namespace Event::services::qx { struct QxRecChng; } namespace Ui { class RunsTableWidget; @@ -36,6 +37,8 @@ class RunsTableWidget : public QWidget void onCustomContextMenuRequest(const QPoint &pos); void onTableViewSqlException(const QString &what, const QString &where, const QString &stack_trace); void onBadTableDataInput(const QString &message); + + void onQxRecChng(const Event::services::qx::QxRecChng &chng); private: Ui::RunsTableWidget *ui; RunsTableModel *m_runsModel; diff --git a/quickevent/app/quickevent/plugins/Runs/src/runswidget.h b/quickevent/app/quickevent/plugins/Runs/src/runswidget.h index 9be9da658..9335be37d 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runswidget.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runswidget.h @@ -11,19 +11,11 @@ class QTextStream; class QLabel; namespace qf { -namespace core { -namespace model { -class SqlTableModel; -} -} -namespace gui { -class ForeignKeyComboBox; -} +namespace core { namespace model { class SqlTableModel; } } +namespace gui { class ForeignKeyComboBox; } } -namespace Event { -class EventPlugin; -} +namespace Event { class EventPlugin; } namespace Ui { class RunsWidget; @@ -69,7 +61,6 @@ class RunsWidget : public QFrame void onDrawRemoveClicked(); void onCbxStageCurrentIndexChanged(); private: - /** * @brief runnersInClubsHistogram * @return list of runs.id for each club sorted by their count, longest list of runners is first From ee2002f29fc06af35531e4248e56c60e72c52086 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Wed, 12 Nov 2025 21:15:09 +0100 Subject: [PATCH 09/34] Do not reload whole row on qxSqlApi update recchng --- libqf/libqfcore/src/utils/table.cpp | 4 +++- .../plugins/Event/src/services/qx/sqlapi.cpp | 14 +++++++------- .../plugins/Event/src/services/qx/sqlapi.h | 4 ++-- .../plugins/Runs/src/runstablewidget.cpp | 18 +++++++++++++++++- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/libqf/libqfcore/src/utils/table.cpp b/libqf/libqfcore/src/utils/table.cpp index 78d073307..c4210506b 100644 --- a/libqf/libqfcore/src/utils/table.cpp +++ b/libqf/libqfcore/src/utils/table.cpp @@ -1310,7 +1310,8 @@ QVariant Table::sumValue(int field_ix) const return ret; } -static void setDomElementText(QDomDocument &owner_doc, QDomElement &el, const QString &str) +namespace { +void setDomElementText(QDomDocument &owner_doc, QDomElement &el, const QString &str) { QDomNode nd = el.firstChild(); QDomText el_txt = nd.toText(); @@ -1322,6 +1323,7 @@ static void setDomElementText(QDomDocument &owner_doc, QDomElement &el, const QS el_txt.setData(str); } } +} QDomElement Table::toHtmlElement(QDomDocument &owner_doc, const QString & col_names, TextExportOptions opts) const { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp index fde4ae512..2c58823bf 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp @@ -152,11 +152,11 @@ RpcValue QxRecChng::toRpcValue() const ret["table"] = table.toStdString(); ret["id"] = id; ret["record"] = shv::coreqt::rpc::qVariantToRpcValue(record); - auto rec_op_string = [](RecOp op) { + auto rec_op_string = [](QxRecOp op) { switch (op) { - case RecOp::Insert: return "Insert"; - case RecOp::Update: return "Update"; - case RecOp::Delete: return "Delete"; + case QxRecOp::Insert: return "Insert"; + case QxRecOp::Update: return "Update"; + case QxRecOp::Delete: return "Delete"; } return ""; }; @@ -328,7 +328,7 @@ int64_t SqlApi::create(const std::string &table, const SqlRecord &record) .table = QString::fromStdString(table), .id = id, .record = shv::coreqt::rpc::rpcValueToQVariant(record), - .op = RecOp::Insert + .op = QxRecOp::Insert }); return id; } @@ -378,7 +378,7 @@ bool SqlApi::update(const std::string &table, int64_t id, const SqlRecord &recor .table = QString::fromStdString(table), .id = id, .record = shv::coreqt::rpc::rpcValueToQVariant(record), - .op = RecOp::Update + .op = QxRecOp::Update }); } return updated; @@ -397,7 +397,7 @@ bool SqlApi::drop(const std::string &table, int64_t id) .table = QString::fromStdString(table), .id = id, .record = {}, - .op = RecOp::Delete + .op = QxRecOp::Delete }); } return is_drop; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h index 3594ca510..b8a8f64b9 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h @@ -53,14 +53,14 @@ struct SqlQueryAndParams static SqlQueryAndParams fromRpcValue(const shv::chainpack::RpcValue &rv); }; -enum class RecOp { Insert, Update, Delete, }; +enum class QxRecOp { Insert, Update, Delete, }; struct QxRecChng { QString table; int64_t id; QVariant record; - RecOp op; + QxRecOp op; shv::chainpack::RpcValue toRpcValue() const; }; diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index 4250dd0fe..0011c3190 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -442,20 +442,36 @@ void RunsTableWidget::onBadTableDataInput(const QString &message) void RunsTableWidget::onQxRecChng(const Event::services::qx::QxRecChng &chng) { std::optional run_id; + int row = 0; if (chng.table == "competitors") { auto *m = m_runsModel; for (auto i=0; irowCount(); ++i) { if (m->table().row(i).value("competitors.id").toInt() == chng.id) { run_id = m->value(i, "runs.id").toInt(); + row = i; break; } } } else if (chng.table == "runs") { run_id = chng.id; + auto *m = m_runsModel; + for (auto i=0; irowCount(); ++i) { + if (m->table().row(i).value("runs.id").toInt() == chng.id) { + row = i; + break; + } + } } if (run_id.has_value()) { - ui->tblRuns->rowExternallySaved(run_id.value()); + if (chng.op == Event::services::qx::QxRecOp::Update) { + for (const auto &[k, v] : chng.record.toMap().asKeyValueRange()) { + m_runsModel->setValue(row, k, v); + m_runsModel->setDirty(row, k, false); + } + } else { + ui->tblRuns->rowExternallySaved(run_id.value()); + } } } From 46e5ff01866497ca18cbf935e890f133d3d02135 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 17 Nov 2025 22:05:33 +0100 Subject: [PATCH 10/34] Recchng is emitted on SqlTableModel::postRow --- .../libqfcore/include/qf/core/sql/qxrecchng.h | 1 + libqf/libqfcore/src/sql/qxrecchng.h | 17 +++++ libqf/libqfgui/src/model/sqldatadocument.h | 8 +- libqf/libqfgui/src/model/sqltablemodel.cpp | 73 ++++++++++++------- libqf/libqfgui/src/model/sqltablemodel.h | 14 ++-- quickevent/app/quickevent/CMakeLists.txt | 5 +- .../Competitors/src/competitordocument.h | 8 +- .../Event/src/services/qx/qxeventservice.cpp | 10 +-- .../Event/src/services/qx/qxeventservice.h | 5 +- .../Event/src/services/qx/sqlapinode.cpp | 18 ++--- .../plugins/Runs/src/runsplugin.cpp | 2 +- .../plugins/Runs/src/runstablemodel.cpp | 10 ++- .../plugins/Runs/src/runstablemodel.h | 6 +- .../plugins/Runs/src/runstablewidget.cpp | 29 +++++--- .../plugins/Runs/src/runstablewidget.h | 4 +- .../Event/src/services => src}/qx/sqlapi.cpp | 64 +++++++++++----- .../Event/src/services => src}/qx/sqlapi.h | 23 ++---- .../app/quickevent/src/qx/sqldatadocument.cpp | 17 +++++ .../app/quickevent/src/qx/sqldatadocument.h | 21 ++++++ .../app/quickevent/src/qx/sqltablemodel.cpp | 13 ++++ .../app/quickevent/src/qx/sqltablemodel.h | 17 +++++ 21 files changed, 252 insertions(+), 113 deletions(-) create mode 100644 libqf/libqfcore/include/qf/core/sql/qxrecchng.h create mode 100644 libqf/libqfcore/src/sql/qxrecchng.h rename quickevent/app/quickevent/{plugins/Event/src/services => src}/qx/sqlapi.cpp (86%) rename quickevent/app/quickevent/{plugins/Event/src/services => src}/qx/sqlapi.h (85%) create mode 100644 quickevent/app/quickevent/src/qx/sqldatadocument.cpp create mode 100644 quickevent/app/quickevent/src/qx/sqldatadocument.h create mode 100644 quickevent/app/quickevent/src/qx/sqltablemodel.cpp create mode 100644 quickevent/app/quickevent/src/qx/sqltablemodel.h diff --git a/libqf/libqfcore/include/qf/core/sql/qxrecchng.h b/libqf/libqfcore/include/qf/core/sql/qxrecchng.h new file mode 100644 index 000000000..7f444660c --- /dev/null +++ b/libqf/libqfcore/include/qf/core/sql/qxrecchng.h @@ -0,0 +1 @@ +#include "../../../../src/sql/qxrecchng.h" diff --git a/libqf/libqfcore/src/sql/qxrecchng.h b/libqf/libqfcore/src/sql/qxrecchng.h new file mode 100644 index 000000000..5ec42a70e --- /dev/null +++ b/libqf/libqfcore/src/sql/qxrecchng.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../core/coreglobal.h" + +namespace qf::core::sql { + +enum class QxRecOp { Insert, Update, Delete, }; + +struct QFCORE_DECL_EXPORT QxRecChng +{ + QString table; + int64_t id; + QVariant record; + QxRecOp op; +}; + +} diff --git a/libqf/libqfgui/src/model/sqldatadocument.h b/libqf/libqfgui/src/model/sqldatadocument.h index ee56ac5b5..2a2b755e1 100644 --- a/libqf/libqfgui/src/model/sqldatadocument.h +++ b/libqf/libqfgui/src/model/sqldatadocument.h @@ -3,9 +3,7 @@ #include "datadocument.h" #include "sqltablemodel.h" -namespace qf { -namespace gui { -namespace model { +namespace qf::gui::model { class QFGUI_DECL_EXPORT SqlDataDocument : public DataDocument { @@ -21,7 +19,7 @@ class QFGUI_DECL_EXPORT SqlDataDocument : public DataDocument qf::core::sql::QueryBuilder queryBuilder(); void setQueryBuilder(const qf::core::sql::QueryBuilder &qb); protected: - SqlTableModel* createModel(QObject *parent) Q_DECL_OVERRIDE; + SqlTableModel* createModel(QObject *parent) override; ///! load model persistent storage via model bool loadData() Q_DECL_OVERRIDE; @@ -35,5 +33,5 @@ class QFGUI_DECL_EXPORT SqlDataDocument : public DataDocument */ }; -}}} +} diff --git a/libqf/libqfgui/src/model/sqltablemodel.cpp b/libqf/libqfgui/src/model/sqltablemodel.cpp index 11075a550..a12705623 100644 --- a/libqf/libqfgui/src/model/sqltablemodel.cpp +++ b/libqf/libqfgui/src/model/sqltablemodel.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -150,8 +151,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) int serial_ix = -1; bool serial_ix_explicitly_set = false; int primary_ix = -1; - //QSqlIndex pri_ix = ti.primaryIndex(); - //bool has_blob_field = false; + QVariantMap qx_record; Q_FOREACH(const qf::core::utils::Table::Field &fld, row_ref.fields()) { i++; if(fld.tableId() != table_id) @@ -186,6 +186,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) new_fld.setValue(v); //qfInfo() << "\t\t" << "val is QString:" << (v.metaType().id() == QMetaType::QString); rec.append(new_fld); + qx_record[fld.shortName().toLower()] = v; } } @@ -201,40 +202,23 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) } else { qs = sqldrv->sqlStatement(QSqlDriver::InsertStatement, table, rec, false); - //qs = fixSerialDefaultValue(qs, serial_ix, rec); } - if(qs.isEmpty()) + if(qs.isEmpty()) { continue; - /* - qfDebug() << "\texecuting prepared query:" << qs; - bool ok = q.prepare(qs); - if(!ok) { - qfError() << "Cannot prepare query:" << qs; } - else { - for(int i=0; i= 0 && !serial_ix_explicitly_set) { - QVariant v = q.lastInsertId(); - qfDebug() << "\tsetting serial index:" << serial_ix << "to generated value:" << v; - if(v.isValid()) { - row_ref.setValue(serial_ix, v); + qfDebug() << "\tsetting serial index:" << serial_ix << "to generated value:" << insert_id; + if(insert_id.isValid()) { + row_ref.setValue(serial_ix, insert_id); row_ref.setDirty(serial_ix, false); } else { @@ -251,6 +235,15 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) qfDebug() << "\tsetting value of foreign key" << slave_key << "to value of master key:" << row_ref.value(master_key).toString(); row_ref.setValue(slave_key, row_ref.value(master_key)); } + if (!qx_record.isEmpty() && master_key == "id") { + qf::core::sql::QxRecChng chng { + .table = table_id, + .id = insert_id.toInt(), + .record = qx_record, + .op = qf::core::sql::QxRecOp::Insert, + }; + emit qxRecChng(chng); + } } } else { @@ -268,7 +261,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) QSqlDriver *sqldrv = sql_conn.driver(); Q_FOREACH(QString table_id, tableIds(m_table.fields())) { qfDebug() << "\ttableid:" << table_id; - //table = conn.fullTableNameToQtDriverTableName(table); + QVariantMap qx_record; QSqlRecord edit_rec; int i = -1; Q_FOREACH(qfu::Table::Field fld, row_ref.fields()) { @@ -286,12 +279,13 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) //qfDebug() << "\ttableid:" << tableid << "fullTableName:" << fld.fullTableName(); qfDebug() << "\tdirty field" << fld.name() << "type:" << fld.type().id() << "orig val:" << row_ref.origValue(i).toString() << "new val:" << v.toString(); //qfDebug().noSpace() << "\tdirty value: '" << v.toString() << "' isNull(): " << v.isNull() << " type(): " << v.type(); - QSqlField sqlfld(fld.shortName(), fld.type()); + QSqlField sqlfld(fld.shortName().toLower(), fld.type()); sqlfld.setValue(v); //if(sqlfld.type() == QVariant::ByteArray) // has_blob_field = true; qfDebug() << "\tfield is null: " << sqlfld.isNull(); edit_rec.append(sqlfld); + qx_record[fld.shortName()] = v; } if(!edit_rec.isEmpty()) { qfDebug() << "updating table edits:" << table_id; @@ -300,7 +294,9 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) query_str += " "; QSqlRecord where_rec; qfDebug() << "looking for primary index of table:" << table_id; - Q_FOREACH(auto fld_name, sql_conn.primaryIndexFieldNames(table_id)) { + std::optional id_pri_key_value; + auto pri_keys = sql_conn.primaryIndexFieldNames(table_id); + for (const auto &fld_name : pri_keys) { QString full_fld_name = table_id + '.' + fld_name; qfDebug() << "\t checking value of field:" << full_fld_name; int fld_ix = m_table.fields().fieldIndex(full_fld_name); @@ -313,6 +309,9 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) sqlfld.setValue(row_ref.origValue(fld_ix)); qfDebug() << "\tpri index field" << full_fld_name << "type:" << sqlfld.metaType().id() << "orig val:" << row_ref.origValue(fld_ix) << "current val:" << row_ref.value(fld_ix); where_rec.append(sqlfld); + if (auto id = sqlfld.value().toInt(); id > 0) { + id_pri_key_value = id; + } } QF_ASSERT(!where_rec.isEmpty(), QString("pri keys values not generated for table '%1'").arg(table_id), @@ -335,6 +334,15 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) ret = false; break; } + if (!qx_record.isEmpty() && pri_keys.size() == 1 && pri_keys[0] == "id" && id_pri_key_value.has_value()) { + qf::core::sql::QxRecChng chng { + .table = table_id, + .id = id_pri_key_value.value(), + .record = qx_record, + .op = qf::core::sql::QxRecOp::Update, + }; + emit qxRecChng(chng); + } } } } @@ -437,6 +445,17 @@ bool SqlTableModel::removeTableRow(int row_no, bool throw_exc) ret = false; break; } + if (where_rec.count() == 1 && where_rec.field(0).name() == "id") { + if (auto id = where_rec.field(0).value().toInt(); id < 0) { + qf::core::sql::QxRecChng chng { + .table = table_id, + .id = id, + .record = {}, + .op = qf::core::sql::QxRecOp::Delete, + }; + emit qxRecChng(chng); + } + } } } if(ret) { diff --git a/libqf/libqfgui/src/model/sqltablemodel.h b/libqf/libqfgui/src/model/sqltablemodel.h index 95a2cbebd..e055385f6 100644 --- a/libqf/libqfgui/src/model/sqltablemodel.h +++ b/libqf/libqfgui/src/model/sqltablemodel.h @@ -10,12 +10,10 @@ #include #include -namespace qf { -namespace gui { -namespace sql { -class Connection; -} -namespace model { +namespace qf::core::sql { struct QxRecChng; } +namespace qf::gui::sql { class Connection; } + +namespace qf::gui::model { class QFGUI_DECL_EXPORT SqlTableModel : public TableModel { @@ -51,6 +49,8 @@ class QFGUI_DECL_EXPORT SqlTableModel : public TableModel int reloadRow(int row_no) Q_DECL_OVERRIDE; int reloadInserts(const QString &id_column_name) Q_DECL_OVERRIDE; QString reloadRowQuery(const QVariant &record_id); + + Q_SIGNAL void qxRecChng(const qf::core::sql::QxRecChng &recchng); public: void setQueryBuilder(const qf::core::sql::QueryBuilder &qb, bool clear_columns = false); const qf::core::sql::QueryBuilder& queryBuilder() const; @@ -100,5 +100,5 @@ class QFGUI_DECL_EXPORT SqlTableModel : public TableModel QMap m_foreignKeyDependencies; }; -}}} +} diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index b7c6e2e75..989371c71 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -90,7 +90,6 @@ add_executable(quickevent plugins/Event/src/services/servicewidget.cpp plugins/Event/src/services/servicewidget.ui - plugins/Event/src/services/qx/sqlapi.h plugins/Event/src/services/qx/sqlapi.cpp plugins/Event/src/services/qx/qxnode.cpp plugins/Event/src/services/qx/sqlapinode.cpp plugins/Event/src/services/qx/nodes.cpp @@ -174,6 +173,10 @@ add_executable(quickevent plugins/Runs/Runs.qrc plugins/CardReader/CardReader.qrc + src/qx/sqlapi.cpp src/qx/sqlapi.h + src/qx/sqltablemodel.h src/qx/sqltablemodel.cpp + src/qx/sqldatadocument.h src/qx/sqldatadocument.cpp + src/appclioptions.cpp src/application.cpp src/loggerwidget.cpp diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h index a394edbe4..ebca84986 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h @@ -1,24 +1,22 @@ #ifndef COMPETITORS_COMPETITORDOCUMENT_H #define COMPETITORS_COMPETITORDOCUMENT_H -#include +#include "src/qx/sqldatadocument.h" #include namespace Competitors { -class CompetitorDocument : public qf::gui::model::SqlDataDocument +class CompetitorDocument : public qx::SqlDataDocument { Q_OBJECT private: - typedef qf::gui::model::SqlDataDocument Super; + typedef qx::SqlDataDocument Super; public: CompetitorDocument(QObject *parent = nullptr); - //bool isSaveSiidToRuns() const {return m_saveSiidToRuns;} void setEmitDbEventsOnSave(bool b) {m_isEmitDbEventsOnSave = b;} - //void setSiid(const QVariant &siid, bool save_siid_to_runs); void setSiid(const QVariant &siid); QVariant siid() const; const QVector& runsIds() const {return m_runsIds;} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 320548969..1b8b1d7ec 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -2,7 +2,7 @@ #include "qxeventservicewidget.h" #include "nodes.h" #include "sqlapinode.h" -#include "sqlapi.h" +#include "src/qx/sqlapi.h" #include "../../eventplugin.h" #include "../../../../Runs/src/runsplugin.h" @@ -78,7 +78,7 @@ void QxEventService::run() { m_rpcConnection->setConnectionString(ss.shvBrokerUrl()); RpcValue::Map opts; RpcValue::Map device; - device["mountPoint"] = "test/quickevent"; + device["mountPoint"] = "test/qx/event/1"; opts["device"] = device; m_rpcConnection->setConnectionOptions(opts); @@ -87,7 +87,7 @@ void QxEventService::run() { connect(m_rpcConnection, &ClientConnection::brokerLoginError, this, &QxEventService::onBrokerLoginError); connect(m_rpcConnection, &ClientConnection::rpcMessageReceived, this, &QxEventService::onRpcMessageReceived); - connect(SqlApi::instance(), &SqlApi::recchng, this, &QxEventService::onRecchg); + connect(::qx::SqlApi::instance(), &::qx::SqlApi::recchng, this, &QxEventService::sendRecchgShvSignal); m_rpcConnection->open(); } @@ -638,10 +638,10 @@ void QxEventService::subscribeChanges() rpc_call->start(); } -void QxEventService::onRecchg(const QxRecChng &chng) +void QxEventService::sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng) { if (isRunning()) { - m_rpcConnection->sendShvSignal("sql", "recchng", chng.toRpcValue()); + m_rpcConnection->sendShvSignal("sql", "recchng", ::qx::qxRecChngToRpcValue(chng)); } } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index b25076626..2b5e2e1de 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -10,11 +10,10 @@ class QTimer; namespace shv::iotqt::node { class ShvNodeTree; class ShvRootNode; } namespace shv::iotqt::rpc { class DeviceConnection; } namespace shv::chainpack { class RpcMessage; class RpcError; } +namespace qf::core::sql { struct QxRecChng; } namespace Event::services::qx { -struct QxRecChng; - class QxEventServiceSettings : public ServiceSettings { using Super = ServiceSettings; @@ -80,7 +79,7 @@ class QxEventService : public Service void sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg); void onBrokerSocketError(const QString &err); void onBrokerLoginError(const shv::chainpack::RpcError &err); - void onRecchg(const QxRecChng &chng); + void sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng); void subscribeChanges(); private: diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index d12474363..bbeeb3b08 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -1,5 +1,5 @@ #include "sqlapinode.h" -#include "sqlapi.h" +#include "src/qx/sqlapi.h" #include #include @@ -58,17 +58,17 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); if(shv_path.empty()) { if(method == METH_EXEC) { - auto res = SqlApi::exec(SqlQueryAndParams::fromRpcValue(params)); + auto res = ::qx::SqlApi::exec(::qx::SqlQueryAndParams::fromRpcValue(params)); return res.toRpcValue(); } if(method == METH_QUERY) { - auto res = SqlApi::exec(SqlQueryAndParams::fromRpcValue(params)); + auto res = ::qx::SqlApi::exec(::qx::SqlQueryAndParams::fromRpcValue(params)); return res.toRpcValue(); } if(method == METH_TRANSACTION) { auto sql_query = params.asList().valref(0).asString(); const auto &sql_params = params.asList().valref(0); - SqlApi::transaction(sql_query, sql_params.asList()); + ::qx::SqlApi::transaction(sql_query, sql_params.asList()); return RpcValue(nullptr); } if(method == METH_LIST) { @@ -80,14 +80,14 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin } auto ids_above = map.contains("ids_above")? std::optional(map.value("ids_above").toInt64()): std::optional(); auto limit = map.contains("limit")? std::optional(map.value("limit").toInt64()): std::optional(); - auto res = SqlApi::list(table, fields, ids_above, limit); + auto res = ::qx::SqlApi::list(table, fields, ids_above, limit); return res.toRecordList(); } if(method == METH_CREATE) { const auto &map = params.asMap(); const auto &table = map.value("table").asString(); const auto &record = map.valref("record").asMap(); - auto res = SqlApi::create(table, record); + auto res = ::qx::SqlApi::create(table, record); return res; } if(method == METH_READ) { @@ -98,7 +98,7 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin for (const auto &fn : map.valref("fields").asList()) { fields.push_back(fn.asString()); } - auto res = SqlApi::read(table, id, fields); + auto res = ::qx::SqlApi::read(table, id, fields); if (res.has_value()) { return res.value(); } @@ -109,14 +109,14 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin const auto &table = map.value("table").asString(); auto id = map.value("id").toInt64(); const auto &record = map.valref("record").asMap(); - auto res = SqlApi::update(table, id, record); + auto res = ::qx::SqlApi::update(table, id, record); return res; } if(method == METH_DELETE) { const auto &map = params.asMap(); const auto &table = map.value("table").asString(); auto id = map.valref("id").toInt(); - auto res = SqlApi::drop(table, id); + auto res = ::qx::SqlApi::drop(table, id); return res; } } diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index da276191f..6163682c3 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -1300,7 +1300,7 @@ qf::core::sql::QueryBuilder RunsPlugin::runsQuery(int stage_id, int class_id, bo qfs::QueryBuilder qb; qb.select2("runs", "*") .select2("classes", "name") - .select2("competitors", "id, iofId, registration, licence, ranking, siId, note") + .select2("competitors", "id, firstName, lastName, iofId, registration, licence, ranking, siId, note") .select("COALESCE(lastName, '') || ' ' || COALESCE(firstName, '') AS competitorName") .select("lentcards.siid IS NOT NULL AS cardInLentTable") diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp index fe183294e..5f2dfccda 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp @@ -1,8 +1,10 @@ #include "runstablemodel.h" +#include "../../Event/src/eventplugin.h" +#include "src/qx/sqlapi.h" + #include #include -#include "../../Event/src/eventplugin.h" #include #include @@ -81,6 +83,12 @@ QVariant RunsTableModel::data(const QModelIndex &index, int role) const QVariant RunsTableModel::value(int row_ix, int column_ix) const { + if(column_ix == col_competitorName) { + qf::core::utils::TableRow row = tableRow(row_ix); + auto first_name = row.value(QStringLiteral("firstName")).toString(); + auto last_name = row.value(QStringLiteral("lastName")).toString(); + return last_name + ' ' + first_name; + } if(column_ix == col_runFlags) { qf::core::utils::TableRow row = tableRow(row_ix); bool mis_punch = row.value(QStringLiteral("runs.misPunch")).toBool(); diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h index 9379bfebd..94b1ca389 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h @@ -1,13 +1,13 @@ #ifndef RUNSTABLEMODEL_H #define RUNSTABLEMODEL_H -#include +#include "src/qx/sqltablemodel.h" -class RunsTableModel : public quickevent::gui::og::SqlTableModel +class RunsTableModel : public ::qx::SqlTableModel { Q_OBJECT private: - using Super = quickevent::gui::og::SqlTableModel; + using Super = ::qx::SqlTableModel; public: enum Columns { col_runs_isRunning = 0, diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index 0011c3190..fd3ad352d 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -7,10 +7,14 @@ #include "runflagsdialog.h" #include "cardflagsdialog.h" +#include "src/qx/sqlapi.h" + #include -#include #include #include +#include +#include +#include #include #include @@ -20,11 +24,7 @@ #include #include -#include -#include -#include -#include -#include +#include #include #include @@ -137,7 +137,7 @@ RunsTableWidget::RunsTableWidget(QWidget *parent) : } }, Qt::QueuedConnection); - connect(Event::services::qx::SqlApi::instance(), &Event::services::qx::SqlApi::recchng, this, &RunsTableWidget::onQxRecChng); + connect(::qx::SqlApi::instance(), &::qx::SqlApi::recchng, this, &RunsTableWidget::onQxRecChng); } RunsTableWidget::~RunsTableWidget() @@ -439,7 +439,7 @@ void RunsTableWidget::onBadTableDataInput(const QString &message) qf::gui::dialogs::MessageBox::showError(this, message); } -void RunsTableWidget::onQxRecChng(const Event::services::qx::QxRecChng &chng) +void RunsTableWidget::onQxRecChng(const qf::core::sql::QxRecChng &chng) { std::optional run_id; int row = 0; @@ -464,10 +464,17 @@ void RunsTableWidget::onQxRecChng(const Event::services::qx::QxRecChng &chng) } } if (run_id.has_value()) { - if (chng.op == Event::services::qx::QxRecOp::Update) { + if (chng.op == qf::core::sql::QxRecOp::Update) { for (const auto &[k, v] : chng.record.toMap().asKeyValueRange()) { - m_runsModel->setValue(row, k, v); - m_runsModel->setDirty(row, k, false); + if (k == "firstname" || k == "lastname") { + auto &r = m_runsModel->tableRowRef(row); + r.setValue("competitors." + k, v); + auto ix = m_runsModel->index(row, RunsTableModel::col_competitorName); + m_runsModel->dataChanged(ix, ix); + } else { + m_runsModel->setValue(row, k, v); + m_runsModel->setDirty(row, k, false); + } } } else { ui->tblRuns->rowExternallySaved(run_id.value()); diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h index a5379f689..d7ed07e6e 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h @@ -7,7 +7,7 @@ class RunsTableItemDelegate; class CourseItemDelegate; namespace qf::gui { class TableView; } -namespace Event::services::qx { struct QxRecChng; } +namespace qf::core::sql { struct QxRecChng; } namespace Ui { class RunsTableWidget; @@ -38,7 +38,7 @@ class RunsTableWidget : public QWidget void onTableViewSqlException(const QString &what, const QString &where, const QString &stack_trace); void onBadTableDataInput(const QString &message); - void onQxRecChng(const Event::services::qx::QxRecChng &chng); + void onQxRecChng(const qf::core::sql::QxRecChng &chng); private: Ui::RunsTableWidget *ui; RunsTableModel *m_runsModel; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp b/quickevent/app/quickevent/src/qx/sqlapi.cpp similarity index 86% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp rename to quickevent/app/quickevent/src/qx/sqlapi.cpp index 2c58823bf..2fd65b9d8 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/src/qx/sqlapi.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -16,7 +17,7 @@ using namespace shv::chainpack; -namespace Event::services::qx { +namespace qx { //============================================== // RpcSqlField @@ -146,21 +147,22 @@ SqlQueryAndParams SqlQueryAndParams::fromRpcValue(const shv::chainpack::RpcValue return SqlQueryAndParams { .query = sql_query, .params = sql_params.asMap() }; } -RpcValue QxRecChng::toRpcValue() const + +RpcValue qxRecChngToRpcValue(const qf::core::sql::QxRecChng &chng) { RpcValue::Map ret; - ret["table"] = table.toStdString(); - ret["id"] = id; - ret["record"] = shv::coreqt::rpc::qVariantToRpcValue(record); - auto rec_op_string = [](QxRecOp op) { + ret["table"] = chng.table.toStdString(); + ret["id"] = chng.id; + ret["record"] = shv::coreqt::rpc::qVariantToRpcValue(chng.record); + auto rec_op_string = [](qf::core::sql::QxRecOp op) { switch (op) { - case QxRecOp::Insert: return "Insert"; - case QxRecOp::Update: return "Update"; - case QxRecOp::Delete: return "Delete"; + case qf::core::sql::QxRecOp::Insert: return "Insert"; + case qf::core::sql::QxRecOp::Update: return "Update"; + case qf::core::sql::QxRecOp::Delete: return "Delete"; } return ""; }; - ret["op"] = rec_op_string(op); + ret["op"] = rec_op_string(chng.op); return ret; } @@ -179,6 +181,12 @@ SqlApi *SqlApi::instance() return api; } +void SqlApi::emitRecChng(const qf::core::sql::QxRecChng &chng) +{ + qfInfo() << "REC_CHNG:" << qxRecChngToRpcValue(chng).toCpon(); + emit recchng(chng); +} + namespace { class Transaction @@ -303,7 +311,27 @@ RpcSqlResult SqlApi::list(const std::string &table, const std::vectorrecchng(QxRecChng { + SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { .table = QString::fromStdString(table), .id = id, - .record = shv::coreqt::rpc::rpcValueToQVariant(record), - .op = QxRecOp::Insert + .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)), + .op = qf::core::sql::QxRecOp::Insert }); return id; } @@ -374,11 +402,11 @@ bool SqlApi::update(const std::string &table, int64_t id, const SqlRecord &recor q.exec(qf::core::Exception::Throw); bool updated = q.numRowsAffected() == 1; if (updated) { - emit SqlApi::instance()->recchng(QxRecChng { + SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { .table = QString::fromStdString(table), .id = id, - .record = shv::coreqt::rpc::rpcValueToQVariant(record), - .op = QxRecOp::Update + .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)), + .op = qf::core::sql::QxRecOp::Update }); } return updated; @@ -393,11 +421,11 @@ bool SqlApi::drop(const std::string &table, int64_t id) q.exec(sql_query, qf::core::Exception::Throw); bool is_drop = q.numRowsAffected() == 1; if (is_drop) { - emit SqlApi::instance()->recchng(QxRecChng { + SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { .table = QString::fromStdString(table), .id = id, .record = {}, - .op = QxRecOp::Delete + .op = qf::core::sql::QxRecOp::Delete }); } return is_drop; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h b/quickevent/app/quickevent/src/qx/sqlapi.h similarity index 85% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h rename to quickevent/app/quickevent/src/qx/sqlapi.h index b8a8f64b9..80bee09b2 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h +++ b/quickevent/app/quickevent/src/qx/sqlapi.h @@ -1,11 +1,15 @@ #pragma once #include +// #include +#include #include #include -namespace Event::services::qx { +namespace qf::core::sql { struct QxRecChng; } + +namespace qx { struct RpcSqlField { @@ -27,7 +31,6 @@ struct RpcSqlResult std::vector rows; RpcSqlResult() = default; - // explicit RpcSqlResult(const QSqlQuery &q); std::optional columnIndex(const std::string &name) const; const shv::chainpack::RpcValue& value(size_t row, size_t col) const; @@ -38,8 +41,6 @@ struct RpcSqlResult bool isSelect() const {return !fields.empty();} shv::chainpack::RpcValue toRpcValue() const; shv::chainpack::RpcValue::List toRecordList() const; - // QVariant toVariant() const; - // static RpcSqlResult fromVariant(const QVariant &v); static RpcSqlResult fromRpcValue(const shv::chainpack::RpcValue &rv); }; @@ -53,17 +54,8 @@ struct SqlQueryAndParams static SqlQueryAndParams fromRpcValue(const shv::chainpack::RpcValue &rv); }; -enum class QxRecOp { Insert, Update, Delete, }; - -struct QxRecChng -{ - QString table; - int64_t id; - QVariant record; - QxRecOp op; +shv::chainpack::RpcValue qxRecChngToRpcValue(const qf::core::sql::QxRecChng &chng); - shv::chainpack::RpcValue toRpcValue() const; -}; class SqlApi : public QObject { @@ -71,7 +63,8 @@ class SqlApi : public QObject public: static SqlApi* instance(); - Q_SIGNAL void recchng(const QxRecChng &chng); + Q_SIGNAL void recchng(const qf::core::sql::QxRecChng &chng); + void emitRecChng(const qf::core::sql::QxRecChng &chng); static RpcSqlResult exec(const SqlQueryAndParams ¶ms); static RpcSqlResult query(const SqlQueryAndParams ¶ms); diff --git a/quickevent/app/quickevent/src/qx/sqldatadocument.cpp b/quickevent/app/quickevent/src/qx/sqldatadocument.cpp new file mode 100644 index 000000000..1f7040921 --- /dev/null +++ b/quickevent/app/quickevent/src/qx/sqldatadocument.cpp @@ -0,0 +1,17 @@ +#include "sqldatadocument.h" + + +namespace qx { + +SqlDataDocument::SqlDataDocument(QObject *parent) + : Super{parent} +{ + +} + +SqlTableModel *SqlDataDocument::createModel(QObject *parent) +{ + return new ::qx::SqlTableModel(parent); +} + +} // namespace qx diff --git a/quickevent/app/quickevent/src/qx/sqldatadocument.h b/quickevent/app/quickevent/src/qx/sqldatadocument.h new file mode 100644 index 000000000..cea298076 --- /dev/null +++ b/quickevent/app/quickevent/src/qx/sqldatadocument.h @@ -0,0 +1,21 @@ +#pragma once + +#include "sqltablemodel.h" + +#include + +namespace qx { + +class SqlDataDocument : public qf::gui::model::SqlDataDocument +{ + Q_OBJECT + + using Super = qf::gui::model::SqlDataDocument; +public: + explicit SqlDataDocument(QObject *parent = nullptr); +protected: + ::qx::SqlTableModel* createModel(QObject *parent) override; +}; + +} // namespace qx + diff --git a/quickevent/app/quickevent/src/qx/sqltablemodel.cpp b/quickevent/app/quickevent/src/qx/sqltablemodel.cpp new file mode 100644 index 000000000..0bad5b1fc --- /dev/null +++ b/quickevent/app/quickevent/src/qx/sqltablemodel.cpp @@ -0,0 +1,13 @@ +#include "sqltablemodel.h" +#include "sqlapi.h" + +namespace qx { + +SqlTableModel::SqlTableModel(QObject *parent) + : Super{parent} +{ + auto *sql_api = SqlApi::instance(); + connect(this, &qf::gui::model::SqlTableModel::qxRecChng, sql_api, &SqlApi::emitRecChng); +} + +} // namespace qx diff --git a/quickevent/app/quickevent/src/qx/sqltablemodel.h b/quickevent/app/quickevent/src/qx/sqltablemodel.h new file mode 100644 index 000000000..1581be19e --- /dev/null +++ b/quickevent/app/quickevent/src/qx/sqltablemodel.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace qx { + +class SqlTableModel : public quickevent::gui::og::SqlTableModel +{ + Q_OBJECT + + using Super = quickevent::gui::og::SqlTableModel; +public: + explicit SqlTableModel(QObject *parent = nullptr); +}; + +} // namespace qx + From 3581d8a42eb6ce42212f758e843500eaa74b5f47 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 23 Nov 2025 14:17:39 +0100 Subject: [PATCH 11/34] Fix CardReader model id column name --- .../app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp index 9fd066be2..f9a6a90fa 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp @@ -112,6 +112,7 @@ class Model : public quickevent::gui::og::SqlTableModel Model::Model(QObject *parent) : Super(parent) { + setIdColumnName("cards.id"); clearColumns(col_COUNT); setColumn(col_cards_id, ColumnDefinition("cards.id", "id").setReadOnly(true)); setColumn(col_cards_siId, ColumnDefinition("cards.siId", tr("SI")).setReadOnly(true).setCastType(qMetaTypeId())); From e780bf8d2604700cce6291302548c4ae95dd7d4b Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Sun, 30 Nov 2025 20:36:51 +0100 Subject: [PATCH 12/34] Change default SHV mount point --- .../quickevent/plugins/Event/src/services/qx/qxeventservice.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 1b8b1d7ec..e098be777 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -78,7 +78,7 @@ void QxEventService::run() { m_rpcConnection->setConnectionString(ss.shvBrokerUrl()); RpcValue::Map opts; RpcValue::Map device; - device["mountPoint"] = "test/qx/event/1"; + device["mountPoint"] = "test/hsh2025"; opts["device"] = device; m_rpcConnection->setConnectionOptions(opts); From 7904e27dc8819fffbde4cd4b7fbabcdd1040a9a2 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Fri, 19 Dec 2025 11:09:53 +0100 Subject: [PATCH 13/34] Enable QF_WITH_LIBSHV option in CMake action --- .github/actions/cmake/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/cmake/action.yml b/.github/actions/cmake/action.yml index 4ff81aa9e..ac208298e 100644 --- a/.github/actions/cmake/action.yml +++ b/.github/actions/cmake/action.yml @@ -66,6 +66,7 @@ runs: -B '${{github.workspace}}/build' \ -DCMAKE_BUILD_TYPE=Release \ -DQF_BUILD_QML_PLUGINS=ON \ + -DQF_WITH_LIBSHV=ON \ -DBUILD_TESTING=OFF \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DUSE_QT6=${{ inputs.use_qt6 }} \ From c91655c22a2a6882905b46335225b6501c31d722 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Wed, 24 Dec 2025 00:08:01 +0100 Subject: [PATCH 14/34] Make service status text expandable --- .../plugins/Event/src/services/qx/qxeventservice.cpp | 2 ++ .../app/quickevent/plugins/Event/src/services/servicewidget.ui | 3 +++ 2 files changed, 5 insertions(+) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index e098be777..de1c055b2 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -584,11 +584,13 @@ void QxEventService::onBrokerConnectedChanged(bool is_connected) void QxEventService::onBrokerSocketError(const QString &err) { + qfWarning() << "onBrokerSocketError:" << err; setStatusMessage(tr("Broker socket error: %1").arg(err)); } void QxEventService::onBrokerLoginError(const shv::chainpack::RpcError &err) { + qfWarning() << "onBrokerLoginError:" << err.toString(); setStatusMessage(tr("Broker login error: %1").arg(err.toString())); } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.ui index cdff60fa1..510d437a7 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/servicewidget.ui @@ -87,6 +87,9 @@ neco neco + + true + From 603522be6eb7a0db2b0c18dcec2d0b72f82f822f Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Fri, 30 Jan 2026 23:51:09 +0100 Subject: [PATCH 15/34] Rebased on multi-course --- .clang-tidy | 2 +- 3rdparty/libshv | 2 +- .../quickevent/plugins/Classes/src/classeswidget.cpp | 10 +++++++--- .../plugins/Event/src/services/qx/qxeventservice.cpp | 2 +- .../Event/src/services/qx/qxeventservicewidget.cpp | 2 +- .../quickevent/plugins/Runs/src/runstablewidget.cpp | 1 - 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 64c3c28fa..543d5ca53 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -40,7 +40,7 @@ Checks: -cppcoreguidelines-pro-type-union-access, -cppcoreguidelines-pro-type-vararg, -cppcoreguidelines-special-member-functions, - -cppcoreguidelines-use-enum-class + -cppcoreguidelines-use-enum-class, -hicpp-avoid-c-arrays, -hicpp-avoid-goto, -hicpp-braces-around-statements, diff --git a/3rdparty/libshv b/3rdparty/libshv index f108e5d72..f77c05674 160000 --- a/3rdparty/libshv +++ b/3rdparty/libshv @@ -1 +1 @@ -Subproject commit f108e5d72729c16ee5b27ee8a9096182a5ceb7fa +Subproject commit f77c05674413e5bd5a1a98c894385e89805406d0 diff --git a/quickevent/app/quickevent/plugins/Classes/src/classeswidget.cpp b/quickevent/app/quickevent/plugins/Classes/src/classeswidget.cpp index 8da479d89..c4750dadc 100644 --- a/quickevent/app/quickevent/plugins/Classes/src/classeswidget.cpp +++ b/quickevent/app/quickevent/plugins/Classes/src/classeswidget.cpp @@ -390,7 +390,8 @@ void ClassesWidget::importCourses(const QList &course_defs, con reload(); } -static QString normalize_course_name(const QString &course_name) +namespace { +QString normalize_course_name(const QString &course_name) { QString ret = qf::core::Collator::toAscii7(QLocale::Czech, course_name, false); ret.replace(' ', QString()); @@ -400,6 +401,7 @@ static QString normalize_course_name(const QString &course_name) ret.replace('-', '+'); return ret; } +} void ClassesWidget::import_ocad_txt() { @@ -566,7 +568,8 @@ void ClassesWidget::import_ocad_v8() } } -static QString element_text(const QDomElement &parent, const QString &tag_name) +namespace { +QString element_text(const QDomElement &parent, const QString &tag_name) { QDomElement el = parent.firstChildElement(tag_name); if(el.isNull()) @@ -574,13 +577,14 @@ static QString element_text(const QDomElement &parent, const QString &tag_name) return el.text(); } -static QString dump_element(const QDomElement &el) +QString dump_element(const QDomElement &el) { QString ret; QTextStream s(&ret); el.save(s, QDomNode::EncodingFromDocument); return ret; } +} void ClassesWidget::import_ocad_iofxml_2() { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index de1c055b2..e691cd9b8 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -74,7 +74,7 @@ void QxEventService::run() { auto ss = settings(); delete m_rpcConnection; - m_rpcConnection = new DeviceConnection(this); + m_rpcConnection = new DeviceConnection("QuickEvent", this); m_rpcConnection->setConnectionString(ss.shvBrokerUrl()); RpcValue::Map opts; RpcValue::Map device; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index 125d70f16..4d0e0bcbb 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -119,7 +119,7 @@ void QxEventServiceWidget::testConnection() using namespace shv::iotqt::rpc; using namespace shv::chainpack; - auto *rpc = new DeviceConnection(this); + auto *rpc = new DeviceConnection("QuickEventTest", this); rpc->setConnectionString(ui->edServerUrl->text()); connect(rpc, &ClientConnection::brokerConnectedChanged, this, [this, rpc](bool is_connected) { diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index fd3ad352d..217182124 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include From d542aaaf6248975cbe272de52bdeaf59745c1dea Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 2 Feb 2026 14:49:36 +0100 Subject: [PATCH 16/34] Update CI linter ubuntu version --- .github/workflows/lint.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d41591f4c..312183ca7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,8 +8,8 @@ on: jobs: clang-tidy: - name: clang-tidy / Ubuntu 22.04 - runs-on: ubuntu-22.04 + name: clang-tidy / Ubuntu 24.04 + runs-on: ubuntu-24.04 env: CC: clang CXX: clang++ @@ -26,8 +26,8 @@ jobs: exclude_files_pattern: ^quickevent/app/quickevent/plugins/Receipts/src/thirdparty/qrcodegen\.cpp$ clazy: - name: clazy / Ubuntu 22.04 - runs-on: ubuntu-22.04 + name: clazy / Ubuntu 24.04 + runs-on: ubuntu-24.04 env: CC: clang CXX: clang++ From 74aa2d0ca818083e6210a1d81cfb3d882fb42ae4 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 2 Feb 2026 14:50:26 +0100 Subject: [PATCH 17/34] Fix QxService login procedure --- .../Event/src/services/qx/qxeventservice.cpp | 48 ++++++++++++++----- .../Event/src/services/qx/qxeventservice.h | 3 +- .../src/services/qx/qxeventservicewidget.cpp | 35 +++++++++++--- .../Event/src/services/qx/runchangedialog.cpp | 2 +- 4 files changed, 67 insertions(+), 21 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index e691cd9b8..24f6bab19 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -74,11 +74,19 @@ void QxEventService::run() { auto ss = settings(); delete m_rpcConnection; + m_eventId = 0; + + auto api_token = apiToken(); + if (api_token.isEmpty()) { + setStatus(Status::Stopped); + setStatusMessage(tr("API token is not set.")); + } + m_rpcConnection = new DeviceConnection("QuickEvent", this); m_rpcConnection->setConnectionString(ss.shvBrokerUrl()); RpcValue::Map opts; RpcValue::Map device; - device["mountPoint"] = "test/hsh2025"; + device["deviceId"] = api_token.toStdString(); opts["device"] = device; m_rpcConnection->setConnectionOptions(opts); @@ -94,6 +102,7 @@ void QxEventService::run() { void QxEventService::stop() { + m_eventId = 0; disconnectSSE(); if (m_pollChangesTimer) { m_pollChangesTimer->stop(); @@ -265,19 +274,19 @@ QNetworkReply* QxEventService::getQxChangesReply(int from_id) int QxEventService::eventId() const { - if (m_eventId == 0) { - throw qf::core::Exception(tr("Event ID is not loaded, service is not probably running.")); - } + // if (m_eventId == 0) { + // throw qf::core::Exception(tr("Event ID is not loaded, service is not probably running.")); + // } return m_eventId; } -QByteArray QxEventService::apiToken() const +QString QxEventService::apiToken() const { - // API token must not be cached to enable service point + // API token must not be cached to enable service to point // always to current stage event on qxhttpd auto *event_plugin = getPlugin(); auto current_stage = event_plugin->currentStageId(); - return event_plugin->stageData(current_stage).qxApiToken().toUtf8(); + return event_plugin->stageData(current_stage).qxApiToken(); } QUrl QxEventService::shvBrokerUrl() const @@ -296,7 +305,7 @@ void QxEventService::postFileCompressed(std::optional path, std::option } QNetworkRequest request; request.setUrl(url); - request.setRawHeader(QX_API_TOKEN, apiToken()); + request.setRawHeader(QX_API_TOKEN, apiToken().toUtf8()); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/zip")); auto zdata = zlibCompress(data); QNetworkReply *reply = networkManager()->post(request, zdata); @@ -347,7 +356,7 @@ void QxEventService::httpPostJson(const QString &path, const QString &query, QVa QNetworkRequest request; request.setUrl(url); - request.setRawHeader(QX_API_TOKEN, apiToken()); + request.setRawHeader(QX_API_TOKEN, apiToken().toUtf8()); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json")); auto data = QJsonDocument::fromVariant(json).toJson(QJsonDocument::Compact); qfInfo() << "HTTP POST JSON:" << url.toString() << "data:" << QString::fromUtf8(data); @@ -571,11 +580,24 @@ int QxEventService::currentConnectionId() void QxEventService::onBrokerConnectedChanged(bool is_connected) { if(is_connected) { - setStatus(Status::Running); - QTimer::singleShot(0, this, [this]() { - subscribeChanges(); -// testRpcCall(); + auto *rpc_call = shv::iotqt::rpc::RpcCall::create(m_rpcConnection) + ->setShvPath(".broker/currentClient") + ->setMethod("info"); + connect(rpc_call, &shv::iotqt::rpc::RpcCall::maybeResult, this, [this](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + if (error.isValid()) { + setStatus(Status::Stopped); + setStatusMessage(tr("Client info discovery error: %1").arg(error.toString())); + } + else { + const auto &info = result.asMap(); + m_eventMountPoint = info.value("mountPoint").to(); + m_eventId = m_eventMountPoint.section('/', -1, -1).toInt(); + setStatus(Status::Running); + setStatusMessage(tr("Event ID: %1").arg(m_eventId)); + subscribeChanges(); + } }); + rpc_call->start(); } else { setStatus(Status::Stopped); } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index 2b5e2e1de..add24852e 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -68,7 +68,7 @@ class QxEventService : public Service QNetworkReply* getQxChangesReply(int from_id); - QByteArray apiToken() const; + QString apiToken() const; static int currentConnectionId(); QUrl shvBrokerUrl() const; @@ -105,6 +105,7 @@ class QxEventService : public Service QNetworkAccessManager *m_networkManager = nullptr; QNetworkReply *m_replySSE = nullptr; int m_eventId = 0; + QString m_eventMountPoint; QTimer *m_pollChangesTimer = nullptr; }; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp index 4d0e0bcbb..d18cd0e38 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservicewidget.cpp @@ -10,6 +10,8 @@ #include #include +#include +#include #include #include @@ -41,6 +43,7 @@ QxEventServiceWidget::QxEventServiceWidget(QWidget *parent) ui->edServerUrl->setText(settings.shvBrokerUrl()); ui->edApiToken->setText(svc->apiToken()); ui->edCurrentStage->setValue(current_stage); + ui->edEventId->setValue(svc->eventId()); connect(ui->btTestConnection, &QAbstractButton::clicked, this, &QxEventServiceWidget::testConnection); connect(ui->btExportEventInfo, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportEventInfo); connect(ui->btExportStartList, &QAbstractButton::clicked, this, &QxEventServiceWidget::exportStartList); @@ -119,21 +122,41 @@ void QxEventServiceWidget::testConnection() using namespace shv::iotqt::rpc; using namespace shv::chainpack; + delete findChild(); + auto *rpc = new DeviceConnection("QuickEventTest", this); rpc->setConnectionString(ui->edServerUrl->text()); + RpcValue::Map opts; + RpcValue::Map device; + device["deviceId"] = ui->edApiToken->text().toStdString(); + opts["device"] = device; + rpc->setConnectionOptions(opts); connect(rpc, &ClientConnection::brokerConnectedChanged, this, [this, rpc](bool is_connected) { if (is_connected) { - rpc->deleteLater(); - setMessage(tr("Connected OK")); + setMessage(tr("Broker connected OK")); + auto *rpc_call = shv::iotqt::rpc::RpcCall::create(rpc) + ->setShvPath(".broker/currentClient") + ->setMethod("info"); + connect(rpc_call, &shv::iotqt::rpc::RpcCall::maybeResult, this, [this](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + if (error.isValid()) { + setMessage(tr("Client info discovery error: %1").arg(error.toString()), MessageType::Error); + } + else { + const auto &info = result.asMap(); + auto mount_point = info.value("mountPoint").to(); + auto event_id = mount_point.section('/', -1, -1).toInt(); + ui->edEventId->setValue(event_id); + setMessage(tr("Event mounted at: %1, event id: %2").arg(mount_point).arg(event_id)); + } + }); + rpc_call->start(); } }); - connect(rpc, &ClientConnection::socketError, this, [this, rpc](const QString &error) { - rpc->deleteLater(); + connect(rpc, &ClientConnection::socketError, this, [this](const QString &error) { setMessage(tr("Connection error: %1").arg(error), MessageType::Error); }); - connect(rpc, &ClientConnection::brokerLoginError, this, [this, rpc](const auto &error) { - rpc->deleteLater(); + connect(rpc, &ClientConnection::brokerLoginError, this, [this](const auto &error) { setMessage(tr("Login error: %1").arg(QString::fromStdString(error.toString())), MessageType::Error); }); rpc->open(); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp index 1c16121ce..e2c50edd5 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp @@ -220,7 +220,7 @@ void RunChangeDialog::resolveChanges(bool is_accepted) url.setQuery(query); request.setUrl(url); - request.setRawHeader(QxEventService::QX_API_TOKEN, svc->apiToken()); + request.setRawHeader(QxEventService::QX_API_TOKEN, svc->apiToken().toUtf8()); auto *reply = nm->get(request); connect(reply, &QNetworkReply::finished, this, [this, reply]() { if (reply->error() == QNetworkReply::NetworkError::NoError) { From e338eedc4ef95ffdd7aa950f994dfd31b2edb0e0 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 2 Feb 2026 17:13:02 +0100 Subject: [PATCH 18/34] Fix CI linter --- .github/actions/run-linter/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/run-linter/action.yml b/.github/actions/run-linter/action.yml index ec63c078a..0d1ddaf1b 100644 --- a/.github/actions/run-linter/action.yml +++ b/.github/actions/run-linter/action.yml @@ -24,7 +24,7 @@ runs: - uses: mjp41/workaround8649@c8550b715ccdc17f89c8d5c28d7a48eeff9c94a8 if: runner.os == 'Linux' with: - os: ubuntu-latest + os: ubuntu-24.04 - name: Build autogenerated stuff shell: bash From 60c37b41d0a425b124b309b73aba0f8b81aa7e8b Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Wed, 8 Apr 2026 22:20:22 +0200 Subject: [PATCH 19/34] Rebased to origin/main --- quickevent/app/quickevent/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index 989371c71..3aae94906 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -114,10 +114,11 @@ add_executable(quickevent plugins/Receipts/src/receiptssettings.cpp plugins/Receipts/src/receiptssettingspage.cpp plugins/Receipts/src/receiptssettingspage.ui - plugins/Receipts/src/thirdparty/qrcodegen.cpp plugins/Receipts/src/receiptswidget.cpp plugins/Receipts/src/receiptswidget.ui + plugins/Receipts/src/thirdparty/qrcodegen.cpp + plugins/Relays/src/addlegdialogwidget.cpp plugins/Relays/src/addlegdialogwidget.ui plugins/Relays/src/relaydocument.cpp From 27fba468b2d278c6048e49a017073f2cc31bead6 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Wed, 8 Apr 2026 23:03:53 +0200 Subject: [PATCH 20/34] Remove duplicate include --- quickevent/app/quickevent/CMakeLists.txt | 4 ---- .../app/quickevent/plugins/Runs/src/runstablewidget.cpp | 1 - 2 files changed, 5 deletions(-) diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index 3aae94906..999a783d0 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -282,10 +282,6 @@ if (QF_WITH_LIBSHV) target_compile_definitions(quickevent PRIVATE QF_WITH_LIBSHV) endif() -foreach(plugin IN ITEMS Classes Runs Relays Receipts shared) - install(DIRECTORY plugins/${plugin}/qml/reports DESTINATION ${CMAKE_INSTALL_BINDIR}/reports/${plugin}/qml) -endforeach() - # Extract version from appversion.h file(READ "${CMAKE_CURRENT_SOURCE_DIR}/src/appversion.h" APP_VERSION_H) string(REGEX MATCH "#define APP_VERSION \"([0-9.]+)\"" _ ${APP_VERSION_H}) diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index 217182124..80e5cf2a3 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include From a3a1a13ad2b27054e324546d654bbd1c957660af Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Thu, 9 Apr 2026 09:15:40 +0200 Subject: [PATCH 21/34] Fix clang-tidy warnings --- libqf/libqfgui/src/framework/ipersistentsettings.cpp | 7 +++++-- libqf/libqfgui/src/framework/ipersistentsettings.h | 6 ++---- .../plugins/CardReader/src/cardreaderwidget.cpp | 8 +++++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/libqf/libqfgui/src/framework/ipersistentsettings.cpp b/libqf/libqfgui/src/framework/ipersistentsettings.cpp index 7fff48174..2d66f0235 100644 --- a/libqf/libqfgui/src/framework/ipersistentsettings.cpp +++ b/libqf/libqfgui/src/framework/ipersistentsettings.cpp @@ -5,7 +5,7 @@ #include #include -using namespace qf::gui::framework; +namespace qf::gui::framework { IPersistentSettings::IPersistentSettings(QObject *controlled_object) : m_controlledObject(controlled_object) @@ -39,7 +39,8 @@ QString IPersistentSettings::persistentSettingsPath() return m_persistentSettingsPath; } -static void callMethodRecursively(QObject *obj, const char *method_name) +namespace { +void callMethodRecursively(QObject *obj, const char *method_name) { if(!obj) return; @@ -57,6 +58,7 @@ static void callMethodRecursively(QObject *obj, const char *method_name) //level--; } } +} void IPersistentSettings::loadPersistentSettingsRecursively() { @@ -123,3 +125,4 @@ QString IPersistentSettings::rawPersistentSettingsPath() return raw_path.join('/'); } +} diff --git a/libqf/libqfgui/src/framework/ipersistentsettings.h b/libqf/libqfgui/src/framework/ipersistentsettings.h index 239dae588..272bd7efd 100644 --- a/libqf/libqfgui/src/framework/ipersistentsettings.h +++ b/libqf/libqfgui/src/framework/ipersistentsettings.h @@ -7,9 +7,7 @@ class QObject; -namespace qf { -namespace gui { -namespace framework { +namespace qf::gui::framework { class QFGUI_DECL_EXPORT IPersistentSettings { @@ -38,6 +36,6 @@ class QFGUI_DECL_EXPORT IPersistentSettings QString m_persistentSettingsPath; }; -}}} +} #endif diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp index f9a6a90fa..c5f3ac33a 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp @@ -842,13 +842,14 @@ CardReaderSettings::ReaderMode CardReaderWidget::currentReaderMode() const return s.readerModeEnum(); } -static int msecToSISec(int msec) +namespace { +int msecToSISec(int msec) { //static constexpr int secs_to_noon = 12 * 60 * 60; return (msec / 1000);// % secs_to_noon; } -static int obStringTosec(const QString &time_str) +int obStringTosec(const QString &time_str) { bool ok; int min = time_str.section('.', 0, 0).toInt(&ok); @@ -864,7 +865,7 @@ static int obStringTosec(const QString &time_str) return (60 * min + sec) * 1000; } -static QList codesForClassName(const QString &class_name, int stage_id) +QList codesForClassName(const QString &class_name, int stage_id) { QList ret; int course_id = 0; @@ -899,6 +900,7 @@ static QList codesForClassName(const QString &class_name, int stage_id) QF_ASSERT_EX(ret.count() > 0, QString("Cannot load codes for class %1 and stage %2").arg(class_name).arg(stage_id)); return ret; } +} void CardReaderWidget::importCards_lapsOnlyCsv() { From a6d3fbaecbcda97785b2c8206af977fc904db7f0 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Thu, 9 Apr 2026 12:01:53 +0200 Subject: [PATCH 22/34] Add workaround for clazy in action.yml Added a workaround step to remove gcc-14 for clazy. --- .github/actions/run-linter/action.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/actions/run-linter/action.yml b/.github/actions/run-linter/action.yml index 0d1ddaf1b..fe0e71558 100644 --- a/.github/actions/run-linter/action.yml +++ b/.github/actions/run-linter/action.yml @@ -20,6 +20,13 @@ runs: modules: qtserialport qtmultimedia additional_cmake_args: -DCMAKE_GLOBAL_AUTOGEN_TARGET=ON -DCMAKE_AUTOGEN_ORIGIN_DEPENDS=OFF + # https://bugs.launchpad.net/ubuntu/+source/clazy/+bug/2086665/comments/4 + - name: Workaround for clazy + shell: bash + run: | + sudo apt-get remove gcc-14 + sudo apt-get autoremove + # workaround for clang-tidy false positive - uses: mjp41/workaround8649@c8550b715ccdc17f89c8d5c28d7a48eeff9c94a8 if: runner.os == 'Linux' From 833dc773973279d245c6492454016b15cc0f012d Mon Sep 17 00:00:00 2001 From: lukaskett Date: Tue, 21 Apr 2026 22:28:32 +0200 Subject: [PATCH 23/34] fix: add missing -DQF_WITH_LIBSHV=ON for macOS --- .github/workflows/macos.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 204e971dc..56a5bf5af 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -37,6 +37,7 @@ jobs: -B '${{github.workspace}}/build' \ -DCMAKE_BUILD_TYPE=Release \ -DQF_BUILD_QML_PLUGINS=ON \ + -DQF_WITH_LIBSHV=ON \ -DBUILD_TESTING=OFF \ -DUSE_QT6=ON \ -DCOMMIT_SHA=${{ env.COMMIT_SHA }} \ From 98f57f26f25e069b43db1cf6d8e2441acb08eb88 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 1 Jun 2026 21:44:11 +0200 Subject: [PATCH 24/34] Update libshv --- 3rdparty/libshv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/libshv b/3rdparty/libshv index f77c05674..6d4367ff6 160000 --- a/3rdparty/libshv +++ b/3rdparty/libshv @@ -1 +1 @@ -Subproject commit f77c05674413e5bd5a1a98c894385e89805406d0 +Subproject commit 6d4367ff6d06de670ea1f49fe534730c3605fbf5 From b0f2e39b5a534ce499aa12753dcf0c9e551d224b Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 1 Jun 2026 21:44:42 +0200 Subject: [PATCH 25/34] Fix broken build --- libqf/libqfcore/src/utils/treetable.cpp | 8 ++-- libqf/libqfgui/src/model/sqltablemodel.cpp | 36 ++------------- libqf/libqfgui/src/model/sqltablemodel.h | 2 - quickevent/app/quickevent/CMakeLists.txt | 2 - .../Competitors/src/competitordocument.h | 10 ++-- .../plugins/Runs/src/runstablemodel.h | 10 ++-- .../plugins/Runs/src/runstablewidget.cpp | 46 ------------------- .../plugins/Runs/src/runstablewidget.h | 2 - quickevent/app/quickevent/src/qx/sqlapi.cpp | 29 ++++-------- .../app/quickevent/src/qx/sqldatadocument.cpp | 17 ------- .../app/quickevent/src/qx/sqldatadocument.h | 21 --------- .../app/quickevent/src/qx/sqltablemodel.cpp | 13 ------ .../app/quickevent/src/qx/sqltablemodel.h | 17 ------- 13 files changed, 26 insertions(+), 187 deletions(-) delete mode 100644 quickevent/app/quickevent/src/qx/sqldatadocument.cpp delete mode 100644 quickevent/app/quickevent/src/qx/sqldatadocument.h delete mode 100644 quickevent/app/quickevent/src/qx/sqltablemodel.cpp delete mode 100644 quickevent/app/quickevent/src/qx/sqltablemodel.h diff --git a/libqf/libqfcore/src/utils/treetable.cpp b/libqf/libqfcore/src/utils/treetable.cpp index c18b2fdee..5641dea4f 100644 --- a/libqf/libqfcore/src/utils/treetable.cpp +++ b/libqf/libqfcore/src/utils/treetable.cpp @@ -581,8 +581,8 @@ QVariantMap TreeTable::keyvals(int row_ix) const { return rows().value(row_ix).toMap().value(KEY_KEYVALS).toMap(); } - -static inline QString line_indent(const QString &ind, int level) +namespace { +QString line_indent(const QString &ind, int level) { QString s; for(int i=0; i= 0 && !serial_ix_explicitly_set) { - qfDebug() << "\tsetting serial index:" << serial_ix << "to generated value:" << insert_id; - if(insert_id.isValid()) { - row_ref.setValue(serial_ix, insert_id); + QVariant v = q.lastInsertId(); + qfDebug() << "\tsetting serial index:" << serial_ix << "to generated value:" << v; + if(v.isValid()) { + row_ref.setValue(serial_ix, v); row_ref.setDirty(serial_ix, false); if (auto *app = qobject_cast(QApplication::instance()); app) { app->emitDbRecInserted(table_id, v.value(), record_to_map(rec), this); @@ -250,15 +251,6 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) qfDebug() << "\tsetting value of foreign key" << slave_key << "to value of master key:" << row_ref.value(master_key).toString(); row_ref.setValue(slave_key, row_ref.value(master_key)); } - if (!qx_record.isEmpty() && master_key == "id") { - qf::core::sql::QxRecChng chng { - .table = table_id, - .id = insert_id.toInt(), - .record = qx_record, - .op = qf::core::sql::QxRecOp::Insert, - }; - emit qxRecChng(chng); - } } } else { @@ -355,15 +347,6 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) ret = false; break; } - if (!qx_record.isEmpty() && pri_keys.size() == 1 && pri_keys[0] == "id" && id_pri_key_value.has_value()) { - qf::core::sql::QxRecChng chng { - .table = table_id, - .id = id_pri_key_value.value(), - .record = qx_record, - .op = qf::core::sql::QxRecOp::Update, - }; - emit qxRecChng(chng); - } } } } @@ -472,17 +455,6 @@ bool SqlTableModel::removeTableRow(int row_no, bool throw_exc) ret = false; break; } - if (where_rec.count() == 1 && where_rec.field(0).name() == "id") { - if (auto id = where_rec.field(0).value().toInt(); id < 0) { - qf::core::sql::QxRecChng chng { - .table = table_id, - .id = id, - .record = {}, - .op = qf::core::sql::QxRecOp::Delete, - }; - emit qxRecChng(chng); - } - } } } if(ret) { diff --git a/libqf/libqfgui/src/model/sqltablemodel.h b/libqf/libqfgui/src/model/sqltablemodel.h index dc2f93082..ce9e2313f 100644 --- a/libqf/libqfgui/src/model/sqltablemodel.h +++ b/libqf/libqfgui/src/model/sqltablemodel.h @@ -49,8 +49,6 @@ class QFGUI_DECL_EXPORT SqlTableModel : public TableModel int reloadRow(int row_no) override; int reloadInserts(const QString &id_column_name) override; QString reloadRowQuery(const QVariant &record_id); - - Q_SIGNAL void qxRecChng(const qf::core::sql::QxRecChng &recchng); public: void setQueryBuilder(const qf::core::sql::QueryBuilder &qb, bool clear_columns = false); const qf::core::sql::QueryBuilder& queryBuilder() const; diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index cc071641a..0d89b41dc 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -178,8 +178,6 @@ add_executable(quickevent plugins/CardReader/CardReader.qrc src/qx/sqlapi.cpp src/qx/sqlapi.h - src/qx/sqltablemodel.h src/qx/sqltablemodel.cpp - src/qx/sqldatadocument.h src/qx/sqldatadocument.cpp src/appclioptions.cpp src/application.cpp diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h index ebca84986..b282d4ebb 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.h @@ -1,17 +1,16 @@ -#ifndef COMPETITORS_COMPETITORDOCUMENT_H -#define COMPETITORS_COMPETITORDOCUMENT_H +#pragma once -#include "src/qx/sqldatadocument.h" +#include #include namespace Competitors { -class CompetitorDocument : public qx::SqlDataDocument +class CompetitorDocument : public qf::gui::model::SqlDataDocument { Q_OBJECT private: - typedef qx::SqlDataDocument Super; + typedef qf::gui::model::SqlDataDocument Super; public: CompetitorDocument(QObject *parent = nullptr); @@ -31,4 +30,3 @@ class CompetitorDocument : public qx::SqlDataDocument } -#endif // COMPETITORS_COMPETITORDOCUMENT_H diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h index ffdcdfea9..08bc5a67c 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.h @@ -1,15 +1,14 @@ -#ifndef RUNSTABLEMODEL_H -#define RUNSTABLEMODEL_H +#pragma once -#include "src/qx/sqltablemodel.h" +#include namespace qf::core::sql { struct QxRecChng; } -class RunsTableModel : public ::qx::SqlTableModel +class RunsTableModel : public quickevent::gui::og::SqlTableModel { Q_OBJECT private: - using Super = ::qx::SqlTableModel; + using Super = quickevent::gui::og::SqlTableModel; public: enum Columns { col_runs_isRunning = 0, @@ -67,4 +66,3 @@ class RunsTableModel : public ::qx::SqlTableModel void onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); }; -#endif // RUNSTABLEMODEL_H diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index 80e5cf2a3..1b8604a65 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -134,8 +134,6 @@ RunsTableWidget::RunsTableWidget(QWidget *parent) : } } }, Qt::QueuedConnection); - - connect(::qx::SqlApi::instance(), &::qx::SqlApi::recchng, this, &RunsTableWidget::onQxRecChng); } RunsTableWidget::~RunsTableWidget() @@ -437,47 +435,3 @@ void RunsTableWidget::onBadTableDataInput(const QString &message) qf::gui::dialogs::MessageBox::showError(this, message); } -void RunsTableWidget::onQxRecChng(const qf::core::sql::QxRecChng &chng) -{ - std::optional run_id; - int row = 0; - if (chng.table == "competitors") { - auto *m = m_runsModel; - for (auto i=0; irowCount(); ++i) { - if (m->table().row(i).value("competitors.id").toInt() == chng.id) { - run_id = m->value(i, "runs.id").toInt(); - row = i; - break; - } - } - } - else if (chng.table == "runs") { - run_id = chng.id; - auto *m = m_runsModel; - for (auto i=0; irowCount(); ++i) { - if (m->table().row(i).value("runs.id").toInt() == chng.id) { - row = i; - break; - } - } - } - if (run_id.has_value()) { - if (chng.op == qf::core::sql::QxRecOp::Update) { - for (const auto &[k, v] : chng.record.toMap().asKeyValueRange()) { - if (k == "firstname" || k == "lastname") { - auto &r = m_runsModel->tableRowRef(row); - r.setValue("competitors." + k, v); - auto ix = m_runsModel->index(row, RunsTableModel::col_competitorName); - m_runsModel->dataChanged(ix, ix); - } else { - m_runsModel->setValue(row, k, v); - m_runsModel->setDirty(row, k, false); - } - } - } else { - ui->tblRuns->rowExternallySaved(run_id.value()); - } - } -} - - diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h index d7ed07e6e..263ddbd5c 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.h @@ -37,8 +37,6 @@ class RunsTableWidget : public QWidget void onCustomContextMenuRequest(const QPoint &pos); void onTableViewSqlException(const QString &what, const QString &where, const QString &stack_trace); void onBadTableDataInput(const QString &message); - - void onQxRecChng(const qf::core::sql::QxRecChng &chng); private: Ui::RunsTableWidget *ui; RunsTableModel *m_runsModel; diff --git a/quickevent/app/quickevent/src/qx/sqlapi.cpp b/quickevent/app/quickevent/src/qx/sqlapi.cpp index 2fd65b9d8..4782b259c 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/src/qx/sqlapi.cpp @@ -150,20 +150,8 @@ SqlQueryAndParams SqlQueryAndParams::fromRpcValue(const shv::chainpack::RpcValue RpcValue qxRecChngToRpcValue(const qf::core::sql::QxRecChng &chng) { - RpcValue::Map ret; - ret["table"] = chng.table.toStdString(); - ret["id"] = chng.id; - ret["record"] = shv::coreqt::rpc::qVariantToRpcValue(chng.record); - auto rec_op_string = [](qf::core::sql::QxRecOp op) { - switch (op) { - case qf::core::sql::QxRecOp::Insert: return "Insert"; - case qf::core::sql::QxRecOp::Update: return "Update"; - case qf::core::sql::QxRecOp::Delete: return "Delete"; - } - return ""; - }; - ret["op"] = rec_op_string(chng.op); - return ret; + auto m = chng.toVariantMap(); + return shv::coreqt::rpc::qVariantToRpcValue(m); } //============================================== @@ -355,8 +343,9 @@ int64_t SqlApi::create(const std::string &table, const SqlRecord &record) SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { .table = QString::fromStdString(table), .id = id, - .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)), - .op = qf::core::sql::QxRecOp::Insert + .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), + .op = qf::core::sql::RecOp::Insert, + .issuer = {} }); return id; } @@ -405,8 +394,9 @@ bool SqlApi::update(const std::string &table, int64_t id, const SqlRecord &recor SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { .table = QString::fromStdString(table), .id = id, - .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)), - .op = qf::core::sql::QxRecOp::Update + .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), + .op = qf::core::sql::RecOp::Update, + .issuer = {} }); } return updated; @@ -425,7 +415,8 @@ bool SqlApi::drop(const std::string &table, int64_t id) .table = QString::fromStdString(table), .id = id, .record = {}, - .op = qf::core::sql::QxRecOp::Delete + .op = qf::core::sql::RecOp::Delete, + .issuer = {} }); } return is_drop; diff --git a/quickevent/app/quickevent/src/qx/sqldatadocument.cpp b/quickevent/app/quickevent/src/qx/sqldatadocument.cpp deleted file mode 100644 index 1f7040921..000000000 --- a/quickevent/app/quickevent/src/qx/sqldatadocument.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "sqldatadocument.h" - - -namespace qx { - -SqlDataDocument::SqlDataDocument(QObject *parent) - : Super{parent} -{ - -} - -SqlTableModel *SqlDataDocument::createModel(QObject *parent) -{ - return new ::qx::SqlTableModel(parent); -} - -} // namespace qx diff --git a/quickevent/app/quickevent/src/qx/sqldatadocument.h b/quickevent/app/quickevent/src/qx/sqldatadocument.h deleted file mode 100644 index cea298076..000000000 --- a/quickevent/app/quickevent/src/qx/sqldatadocument.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "sqltablemodel.h" - -#include - -namespace qx { - -class SqlDataDocument : public qf::gui::model::SqlDataDocument -{ - Q_OBJECT - - using Super = qf::gui::model::SqlDataDocument; -public: - explicit SqlDataDocument(QObject *parent = nullptr); -protected: - ::qx::SqlTableModel* createModel(QObject *parent) override; -}; - -} // namespace qx - diff --git a/quickevent/app/quickevent/src/qx/sqltablemodel.cpp b/quickevent/app/quickevent/src/qx/sqltablemodel.cpp deleted file mode 100644 index 0bad5b1fc..000000000 --- a/quickevent/app/quickevent/src/qx/sqltablemodel.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "sqltablemodel.h" -#include "sqlapi.h" - -namespace qx { - -SqlTableModel::SqlTableModel(QObject *parent) - : Super{parent} -{ - auto *sql_api = SqlApi::instance(); - connect(this, &qf::gui::model::SqlTableModel::qxRecChng, sql_api, &SqlApi::emitRecChng); -} - -} // namespace qx diff --git a/quickevent/app/quickevent/src/qx/sqltablemodel.h b/quickevent/app/quickevent/src/qx/sqltablemodel.h deleted file mode 100644 index 1581be19e..000000000 --- a/quickevent/app/quickevent/src/qx/sqltablemodel.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -namespace qx { - -class SqlTableModel : public quickevent::gui::og::SqlTableModel -{ - Q_OBJECT - - using Super = quickevent::gui::og::SqlTableModel; -public: - explicit SqlTableModel(QObject *parent = nullptr); -}; - -} // namespace qx - From c6072f3a8682ae92153428fd489c1e7945e28b18 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 1 Jun 2026 21:54:47 +0200 Subject: [PATCH 26/34] Fix linter warnings --- quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp | 6 +++--- quickevent/app/quickevent/src/qx/sqlapi.cpp | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp index c4e14910b..5eeb13cd8 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp @@ -1362,8 +1362,8 @@ void EventPlugin::deleteEvent(const QString &event_name) .arg(event_name, q.lastErrorText())); } } - -static void cloneDbConnection(qfs::Connection &dst, const qfs::Connection &src) +namespace { +void cloneDbConnection(qfs::Connection &dst, const qfs::Connection &src) { dst.setHostName(src.hostName()); dst.setPort(src.port()); @@ -1371,7 +1371,7 @@ static void cloneDbConnection(qfs::Connection &dst, const qfs::Connection &src) dst.setPassword(src.password()); dst.setDatabaseName(src.databaseName()); } - +} bool EventPlugin::importEventFromFile(const QString &src_file, const QString &dest_event_name) { qfLogFuncFrame(); diff --git a/quickevent/app/quickevent/src/qx/sqlapi.cpp b/quickevent/app/quickevent/src/qx/sqlapi.cpp index 4782b259c..0581d77e0 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/src/qx/sqlapi.cpp @@ -14,6 +14,7 @@ #include #include #include +#include using namespace shv::chainpack; @@ -305,7 +306,7 @@ std::string to_lower(const std::string &s) std::string result; result.reserve(s.size()); - std::transform(s.begin(), s.end(), std::back_inserter(result), + std::ranges::transform(s, std::back_inserter(result), [](unsigned char c) { return std::tolower(c); }); return result; From 0d20550dd22c5901aca7bceb87b24ee12c80dd45 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 1 Jun 2026 22:11:12 +0200 Subject: [PATCH 27/34] Fix broken build --- 3rdparty/libshv | 2 +- .../Event/src/services/qx/qxeventservice.cpp | 2 +- quickevent/app/quickevent/src/qx/sqlapi.cpp | 55 ++++++++++--------- quickevent/app/quickevent/src/qx/sqlapi.h | 4 +- 4 files changed, 32 insertions(+), 31 deletions(-) diff --git a/3rdparty/libshv b/3rdparty/libshv index 6d4367ff6..b1c3721d5 160000 --- a/3rdparty/libshv +++ b/3rdparty/libshv @@ -1 +1 @@ -Subproject commit 6d4367ff6d06de670ea1f49fe534730c3605fbf5 +Subproject commit b1c3721d53f7390598e8f4fd8e5a3b225b58e5d0 diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 24f6bab19..92eb101c5 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -95,7 +95,7 @@ void QxEventService::run() { connect(m_rpcConnection, &ClientConnection::brokerLoginError, this, &QxEventService::onBrokerLoginError); connect(m_rpcConnection, &ClientConnection::rpcMessageReceived, this, &QxEventService::onRpcMessageReceived); - connect(::qx::SqlApi::instance(), &::qx::SqlApi::recchng, this, &QxEventService::sendRecchgShvSignal); + // connect(::qx::SqlApi::instance(), &::qx::SqlApi::recchng, this, &QxEventService::sendRecchgShvSignal); m_rpcConnection->open(); } diff --git a/quickevent/app/quickevent/src/qx/sqlapi.cpp b/quickevent/app/quickevent/src/qx/sqlapi.cpp index 0581d77e0..a3329ef51 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/src/qx/sqlapi.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -161,7 +162,7 @@ RpcValue qxRecChngToRpcValue(const qf::core::sql::QxRecChng &chng) SqlApi::SqlApi(QObject *parent) : QObject{parent} { - + // connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, ) } SqlApi *SqlApi::instance() @@ -170,11 +171,11 @@ SqlApi *SqlApi::instance() return api; } -void SqlApi::emitRecChng(const qf::core::sql::QxRecChng &chng) -{ - qfInfo() << "REC_CHNG:" << qxRecChngToRpcValue(chng).toCpon(); - emit recchng(chng); -} +// void SqlApi::emitRecChng(const qf::core::sql::QxRecChng &chng) +// { +// qfInfo() << "REC_CHNG:" << qxRecChngToRpcValue(chng).toCpon(); +// emit recchng(chng); +// } namespace { @@ -341,13 +342,13 @@ int64_t SqlApi::create(const std::string &table, const SqlRecord &record) } q.exec(qf::core::Exception::Throw); auto id = q.lastInsertId().toInt(); - SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { - .table = QString::fromStdString(table), - .id = id, - .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), - .op = qf::core::sql::RecOp::Insert, - .issuer = {} - }); + // SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { + // .table = QString::fromStdString(table), + // .id = id, + // .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), + // .op = qf::core::sql::RecOp::Insert, + // .issuer = {} + // }); return id; } @@ -392,13 +393,13 @@ bool SqlApi::update(const std::string &table, int64_t id, const SqlRecord &recor q.exec(qf::core::Exception::Throw); bool updated = q.numRowsAffected() == 1; if (updated) { - SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { - .table = QString::fromStdString(table), - .id = id, - .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), - .op = qf::core::sql::RecOp::Update, - .issuer = {} - }); + // SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { + // .table = QString::fromStdString(table), + // .id = id, + // .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), + // .op = qf::core::sql::RecOp::Update, + // .issuer = {} + // }); } return updated; } @@ -412,13 +413,13 @@ bool SqlApi::drop(const std::string &table, int64_t id) q.exec(sql_query, qf::core::Exception::Throw); bool is_drop = q.numRowsAffected() == 1; if (is_drop) { - SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { - .table = QString::fromStdString(table), - .id = id, - .record = {}, - .op = qf::core::sql::RecOp::Delete, - .issuer = {} - }); + // SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { + // .table = QString::fromStdString(table), + // .id = id, + // .record = {}, + // .op = qf::core::sql::RecOp::Delete, + // .issuer = {} + // }); } return is_drop; } diff --git a/quickevent/app/quickevent/src/qx/sqlapi.h b/quickevent/app/quickevent/src/qx/sqlapi.h index 80bee09b2..413b302dd 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.h +++ b/quickevent/app/quickevent/src/qx/sqlapi.h @@ -63,8 +63,8 @@ class SqlApi : public QObject public: static SqlApi* instance(); - Q_SIGNAL void recchng(const qf::core::sql::QxRecChng &chng); - void emitRecChng(const qf::core::sql::QxRecChng &chng); + // Q_SIGNAL void recchng(const qf::core::sql::QxRecChng &chng); + // void emitRecChng(const qf::core::sql::QxRecChng &chng); static RpcSqlResult exec(const SqlQueryAndParams ¶ms); static RpcSqlResult query(const SqlQueryAndParams ¶ms); From efe865e374b2948548a8c10eee88594537fa2938 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 1 Jun 2026 22:45:07 +0200 Subject: [PATCH 28/34] SqlApi rewrite --- .../Event/src/services/qx/sqlapinode.cpp | 2 +- quickevent/app/quickevent/src/qx/sqlapi.cpp | 242 ++++++++++-------- quickevent/app/quickevent/src/qx/sqlapi.h | 56 ++-- 3 files changed, 165 insertions(+), 135 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index bbeeb3b08..037061733 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -100,7 +100,7 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin } auto res = ::qx::SqlApi::read(table, id, fields); if (res.has_value()) { - return res.value(); + return shv::coreqt::rpc::qVariantToRpcValue(res.value()); } return RpcValue(nullptr); } diff --git a/quickevent/app/quickevent/src/qx/sqlapi.cpp b/quickevent/app/quickevent/src/qx/sqlapi.cpp index a3329ef51..d70dfc1a3 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/src/qx/sqlapi.cpp @@ -24,25 +24,45 @@ namespace qx { //============================================== // RpcSqlField //============================================== -RpcValue RpcSqlField::toRpcValue() const +RpcValue DbField::toRpcValue() const { RpcValue::Map ret; - ret["name"] = name; + ret["name"] = name.toStdString(); return RpcValue(std::move(ret)); } -RpcSqlField RpcSqlField::fromRpcValue(const shv::chainpack::RpcValue &rv) +DbField DbField::fromRpcValue(const shv::chainpack::RpcValue &rv) { - RpcSqlField ret; + DbField ret; const RpcValue::Map &map = rv.asMap(); - ret.name = map.value("name").asString(); + ret.name = QString::fromStdString(map.value("name").asString()); return ret; } //============================================== -// RpcSqlResult +// ExecResult //============================================== -const RpcValue &RpcSqlResult::value(size_t row, size_t col) const +RpcValue ExecResult::toRpcValue() const +{ + RpcValue::Map ret; + ret["numRowsAffected"] = numRowsAffected; + ret["lastInsertId"] = lastInsertId.has_value()? RpcValue(lastInsertId.value()): RpcValue(nullptr); + return ret; +} + +ExecResult ExecResult::fromRpcValue(const shv::chainpack::RpcValue &rv) +{ + ExecResult ret; + const auto &map = rv.asMap(); + ret.numRowsAffected = map.value("numRowsAffected").toInt(); + ret.lastInsertId = map.value("lastInsertId").toInt(); + return ret; +} + +//============================================== +// QueryResult +//============================================== +QVariant QueryResult::value(qsizetype row, qsizetype col) const { if (row < rows.size()) { const auto &cells = rows[row]; @@ -50,20 +70,18 @@ const RpcValue &RpcSqlResult::value(size_t row, size_t col) const return cells[col]; } } - static RpcValue s; - return s; + return {}; } -const RpcValue& RpcSqlResult::value(size_t row, const std::string &name) const +QVariant QueryResult::value(qsizetype row, const std::string &name) const { if (auto ix = columnIndex(name); ix.has_value()) { return value(row, ix.value()); } - static RpcValue s; - return s; + return {}; } -void RpcSqlResult::setValue(size_t row, size_t col, const RpcValue &val) +void QueryResult::setValue(qsizetype row, qsizetype col, const QVariant &val) { if (row < rows.size()) { auto &r = rows[row]; @@ -73,80 +91,81 @@ void RpcSqlResult::setValue(size_t row, size_t col, const RpcValue &val) } } -void RpcSqlResult::setValue(size_t row, const std::string &name, const RpcValue &val) +void QueryResult::setValue(qsizetype row, const std::string &name, const QVariant &val) { if (auto ix = columnIndex(name); ix.has_value()) { setValue(row, ix.value(), val); } } -RpcValue::List RpcSqlResult::toRecordList() const +RpcValue::List QueryResult::toRecordList() const { RpcValue::List ret; for (const auto &row : rows) { - SqlRecord rec; + RpcValue::Map rec; int n = 0; for (const auto &field : fields) { - rec[field.name] = row[n++]; + rec[field.name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(row.value(n++)); } ret.push_back(rec); } return ret; } -std::optional RpcSqlResult::columnIndex(const std::string &name) const +std::optional QueryResult::columnIndex(const std::string &name) const { for (size_t col = 0; col < fields.size(); ++col) { const auto &fld = fields[col]; - if (fld.name == name) { + if (fld.name.toStdString() == name) { return col; } } return {}; } -RpcValue RpcSqlResult::toRpcValue() const +RpcValue QueryResult::toRpcValue() const { RpcValue::Map ret; - if(isSelect()) { - RpcValue::List flds; - for(const auto &fld : this->fields) - flds.push_back(fld.toRpcValue()); - ret["fields"] = flds; - ret["rows"] = rows; - } - else { - ret["numRowsAffected"] = numRowsAffected; - ret["lastInsertId"] = lastInsertId.has_value()? RpcValue(lastInsertId.value()): RpcValue(nullptr); + RpcValue::List flds; + for(const auto &fld : this->fields) + flds.push_back(fld.toRpcValue()); + RpcValue::List rpc_rows; + for (const auto &row : rows) { + RpcValue::List rpc_row; + for (const auto &cell : row) { + rpc_row.push_back(shv::coreqt::rpc::qVariantToRpcValue(cell)); + } + rpc_rows.push_back(std::move(rpc_row)); } + ret["fields"] = flds; + ret["rows"] = std::move(rpc_rows); return ret; } -RpcSqlResult RpcSqlResult::fromRpcValue(const RpcValue &rv) +QueryResult QueryResult::fromRpcValue(const RpcValue &rv) { - RpcSqlResult ret; + QueryResult ret; const auto &map = rv.asMap(); const auto &flds = map.valref("fields").asList(); - if(flds.empty()) { - ret.numRowsAffected = map.value("numRowsAffected").toInt(); - ret.lastInsertId = map.value("lastInsertId").toInt(); + for(const auto &fv : flds) { + ret.fields.push_back(DbField::fromRpcValue(fv)); } - else { - for(const auto &fv : flds) { - ret.fields.push_back(RpcSqlField::fromRpcValue(fv)); - } - for (const auto &row : map.value("rows").asList()) { - ret.rows.push_back(row.asList()); + for (const auto &rpc_row : map.value("rows").asList()) { + QueryResult::Row row; + for (const auto &cell : rpc_row.asList()) { + row.push_back(shv::coreqt::rpc::rpcValueToQVariant(cell)); } + ret.rows.push_back(row); } return ret; } SqlQueryAndParams SqlQueryAndParams::fromRpcValue(const shv::chainpack::RpcValue &rv) { - auto sql_query = rv.asList().valref(0).asString(); - const auto &sql_params = rv.asList().valref(0); - return SqlQueryAndParams { .query = sql_query, .params = sql_params.asMap() }; + const auto &lst = rv.asList(); + auto sql_query = QString::fromStdString(lst.valref(0).asString()); + const auto &sql_params = lst.valref(1); + return SqlQueryAndParams { .query = sql_query, .params = shv::coreqt::rpc::rpcValueToQVariant(sql_params).toMap() }; } @@ -205,61 +224,63 @@ class Transaction bool m_inTransaction = true; }; -RpcSqlResult rpcSqlQuery(const SqlQueryAndParams ¶ms) +void bindParams(qf::core::sql::Query &q, const Record ¶ms) { - qf::core::sql::Query q; - q.prepare(QString::fromUtf8(params.query), qf::core::Exception::Throw); - for (const auto &[k, v] : params.params) { - bool ok; - QVariant val = shv::coreqt::rpc::rpcValueToQVariant(v, &ok); - if (!ok) { - QF_EXCEPTION(QStringLiteral("Cannot convert SHV type: %1 to QVariant").arg(v.typeName())); - } - q.bindValue(':' + QString::fromStdString(k), val); + for (const auto &[k, v] : params.asKeyValueRange()) { + q.bindValue(':' + k, v); } +} + +QueryResult sqlQuery(const SqlQueryAndParams ¶ms) +{ + qf::core::sql::Query q; + q.prepare(params.query, qf::core::Exception::Throw); + bindParams(q, params.params); q.exec(qf::core::Exception::Throw); - RpcSqlResult ret; - if(q.isSelect()) { - QSqlRecord rec = q.record(); + + QueryResult ret; + QSqlRecord rec = q.record(); + for (int i = 0; i < rec.count(); ++i) { + QSqlField fld = rec.field(i); + DbField rfld; + rfld.name = fld.name(); + // rfld.name.replace("__", "."); + ret.fields.push_back(rfld); + } + while(q.next()) { + QueryResult::Row row; for (int i = 0; i < rec.count(); ++i) { - QSqlField fld = rec.field(i); - RpcSqlField rfld; - rfld.name = fld.name().toStdString(); - // rfld.name.replace("__", "."); - ret.fields.push_back(rfld); + row.push_back(q.value(i)); + //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); } - while(q.next()) { - RpcSqlResult::Row row; - for (int i = 0; i < rec.count(); ++i) { - const QVariant v = q.value(i); - if (v.isNull()) { - row.push_back(RpcValue(nullptr)); - } - else { - row.push_back(shv::coreqt::rpc::qVariantToRpcValue(v)); - } - //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); - } - ret.rows.push_back(row); - } - } - else { - ret.numRowsAffected = q.numRowsAffected(); - ret.lastInsertId = q.lastInsertId().toInt(); + ret.rows.insert(ret.rows.size(), row); } return ret; } +ExecResult sqlExec(const SqlQueryAndParams ¶ms) +{ + qf::core::sql::Query q; + q.prepare(params.query, qf::core::Exception::Throw); + bindParams(q, params.params); + q.exec(qf::core::Exception::Throw); + + ExecResult ret; + ret.numRowsAffected = q.numRowsAffected(); + ret.lastInsertId = q.lastInsertId().toInt(); + return ret; } -RpcSqlResult SqlApi::exec(const SqlQueryAndParams ¶ms) +} + +ExecResult SqlApi::exec(const SqlQueryAndParams ¶ms) { - return rpcSqlQuery(params); + return sqlExec(params); } -RpcSqlResult SqlApi::query(const SqlQueryAndParams ¶ms) +QueryResult SqlApi::query(const SqlQueryAndParams ¶ms) { - return rpcSqlQuery(params); + return sqlQuery(params); } void SqlApi::transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms) @@ -282,7 +303,7 @@ void SqlApi::transaction(const std::string &query, const shv::chainpack::RpcValu tranaction.commit(); } -RpcSqlResult SqlApi::list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit) +QueryResult SqlApi::list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit) { QStringList qfields; for (const auto &fn : fields) { @@ -298,7 +319,7 @@ RpcSqlResult SqlApi::list(const std::string &table, const std::vector SqlApi::read(const std::string &table, int64_t id, const std::vector &fields) +std::optional SqlApi::read(const std::string &table, int64_t id, const std::vector &fields) { QStringList qfields; for (const auto &fn : fields) { @@ -365,30 +387,34 @@ std::optional SqlApi::read(const std::string &table, int64_t id, cons .arg(qfields.join(',')) .arg(QString::fromStdString(table)) .arg(id) ; - auto res = rpcSqlQuery(SqlQueryAndParams { .query = sql_query.toStdString(), .params = {}}); - auto lst = res.toRecordList(); - if (lst.empty()) { + auto res = sqlQuery(SqlQueryAndParams { .query = sql_query, .params = {}}); + if (res.rows.empty()) { return {}; } - return lst[0].asMap(); + Record ret; + const auto &row = res.rows.first(); + for (qsizetype i = 0; i < std::ssize(res.fields); ++i) { + ret[res.fields[i].name] = row.value(i); + } + return ret; } -bool SqlApi::update(const std::string &table, int64_t id, const SqlRecord &record) +bool SqlApi::update(const std::string &table, int64_t id, const RpcValue::Map &record) { QStringList fields; for (const auto &[k, v] : record) { - auto name = QString::fromStdString(k); - fields << name + " = :" + name; + Q_UNUSED(v) + auto qk = QString::fromStdString(k); + fields << qk + " = :" + qk; } QString sql_query = QStringLiteral("UPDATE %1 SET %2 WHERE id = %3") - .arg(table) + .arg(QString::fromStdString(table)) .arg(fields.join(',')) .arg(id); qf::core::sql::Query q; q.prepare(sql_query, qf::core::Exception::Throw); for (const auto &[k, v] : record) { - auto qv = shv::coreqt::rpc::rpcValueToQVariant(v); - q.bindValue(':' + QString::fromStdString(k), qv); + q.bindValue(':' + QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); } q.exec(qf::core::Exception::Throw); bool updated = q.numRowsAffected() == 1; @@ -407,7 +433,7 @@ bool SqlApi::update(const std::string &table, int64_t id, const SqlRecord &recor bool SqlApi::drop(const std::string &table, int64_t id) { QString sql_query = QStringLiteral("DELETE FROM %1 WHERE id = %2") - .arg(table) + .arg(QString::fromStdString(table)) .arg(id); qf::core::sql::Query q; q.exec(sql_query, qf::core::Exception::Throw); diff --git a/quickevent/app/quickevent/src/qx/sqlapi.h b/quickevent/app/quickevent/src/qx/sqlapi.h index 413b302dd..fe15ed6f2 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.h +++ b/quickevent/app/quickevent/src/qx/sqlapi.h @@ -11,45 +11,49 @@ namespace qf::core::sql { struct QxRecChng; } namespace qx { -struct RpcSqlField +struct DbField { - std::string name; + QString name; - //explicit RpcSqlField(const QJsonObject &jo = QJsonObject()) : Super(jo) {} + //explicit DbField(const QJsonObject &jo = QJsonObject()) : Super(jo) {} shv::chainpack::RpcValue toRpcValue() const; // QVariant toVariant() const; - static RpcSqlField fromRpcValue(const shv::chainpack::RpcValue &rv); - // static RpcSqlField fromVariant(const QVariant &v); + static DbField fromRpcValue(const shv::chainpack::RpcValue &rv); + // static DbField fromVariant(const QVariant &v); }; -struct RpcSqlResult +struct ExecResult { int numRowsAffected = 0; std::optional lastInsertId = 0; - std::vector fields; - using Row = shv::chainpack::RpcValue::List; - std::vector rows; - RpcSqlResult() = default; + shv::chainpack::RpcValue toRpcValue() const; + static ExecResult fromRpcValue(const shv::chainpack::RpcValue &rv); +}; + +struct QueryResult +{ + std::vector fields; + using Row = QVariantList; + QList rows; - std::optional columnIndex(const std::string &name) const; - const shv::chainpack::RpcValue& value(size_t row, size_t col) const; - const shv::chainpack::RpcValue& value(size_t row, const std::string &name) const; - void setValue(size_t row, size_t col, const shv::chainpack::RpcValue &val); - void setValue(size_t row, const std::string &name, const shv::chainpack::RpcValue &val); + std::optional columnIndex(const std::string &name) const; + QVariant value(qsizetype row, qsizetype col) const; + QVariant value(qsizetype row, const std::string &name) const; + void setValue(qsizetype row, qsizetype col, const QVariant &val); + void setValue(qsizetype row, const std::string &name, const QVariant &val); - bool isSelect() const {return !fields.empty();} shv::chainpack::RpcValue toRpcValue() const; + static QueryResult fromRpcValue(const shv::chainpack::RpcValue &rv); shv::chainpack::RpcValue::List toRecordList() const; - static RpcSqlResult fromRpcValue(const shv::chainpack::RpcValue &rv); }; -using SqlRecord = shv::chainpack::RpcValue::Map; +using Record = QVariantMap; struct SqlQueryAndParams { - std::string query; - SqlRecord params; + QString query; + Record params; static SqlQueryAndParams fromRpcValue(const shv::chainpack::RpcValue &rv); }; @@ -66,13 +70,13 @@ class SqlApi : public QObject // Q_SIGNAL void recchng(const qf::core::sql::QxRecChng &chng); // void emitRecChng(const qf::core::sql::QxRecChng &chng); - static RpcSqlResult exec(const SqlQueryAndParams ¶ms); - static RpcSqlResult query(const SqlQueryAndParams ¶ms); + static ExecResult exec(const SqlQueryAndParams ¶ms); + static QueryResult query(const SqlQueryAndParams ¶ms); static void transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms); - static RpcSqlResult list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); - static int64_t create(const std::string &table, const SqlRecord &record); - static std::optional read(const std::string &table, int64_t id, const std::vector &fields); - static bool update(const std::string &table, int64_t id, const SqlRecord &record); + static QueryResult list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); + static int64_t create(const std::string &table, const shv::chainpack::RpcValue::Map &record); + static std::optional read(const std::string &table, int64_t id, const std::vector &fields); + static bool update(const std::string &table, int64_t id, const shv::chainpack::RpcValue::Map &record); static bool drop(const std::string &table, int64_t id); private: explicit SqlApi(QObject *parent = nullptr); From 2b216441e15621b768e37ede64ed51f95a824ad3 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Wed, 3 Jun 2026 14:59:41 +0200 Subject: [PATCH 29/34] Move SqlApi to EventPlugin --- quickevent/app/quickevent/CMakeLists.txt | 3 +-- .../Event/src/services/qx/qxeventservice.cpp | 14 ++++++------- .../Event/src/services/qx/qxeventservice.h | 2 +- .../Event/src/services}/qx/sqlapi.cpp | 8 +------ .../Event/src/services}/qx/sqlapi.h | 21 +++++++++---------- .../Event/src/services/qx/sqlapinode.cpp | 19 +++++++++-------- .../Event/src/services/qx/sqlapinode.h | 4 ++++ .../plugins/Runs/src/runstablemodel.cpp | 1 - .../plugins/Runs/src/runstablewidget.cpp | 2 -- 9 files changed, 34 insertions(+), 40 deletions(-) rename quickevent/app/quickevent/{src => plugins/Event/src/services}/qx/sqlapi.cpp (98%) rename quickevent/app/quickevent/{src => plugins/Event/src/services}/qx/sqlapi.h (69%) diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index 0d89b41dc..94fbaec8c 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -95,6 +95,7 @@ add_executable(quickevent plugins/Event/src/services/qx/qxnode.cpp plugins/Event/src/services/qx/sqlapinode.cpp + plugins/Event/src/services/qx/sqlapi.cpp plugins/Event/src/services/qx/sqlapi.h plugins/Event/src/services/qx/nodes.cpp plugins/Event/src/services/qx/qxeventservice.cpp plugins/Event/src/services/qx/qxeventservice.h plugins/Event/src/services/qx/qxeventservicewidget.cpp plugins/Event/src/services/qx/qxeventservicewidget.h plugins/Event/src/services/qx/qxeventservicewidget.ui @@ -177,8 +178,6 @@ add_executable(quickevent plugins/Runs/Runs.qrc plugins/CardReader/CardReader.qrc - src/qx/sqlapi.cpp src/qx/sqlapi.h - src/appclioptions.cpp src/application.cpp src/loggerwidget.cpp diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 92eb101c5..be06570b9 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -2,7 +2,7 @@ #include "qxeventservicewidget.h" #include "nodes.h" #include "sqlapinode.h" -#include "src/qx/sqlapi.h" +#include "sqlapi.h" #include "../../eventplugin.h" #include "../../../../Runs/src/runsplugin.h" @@ -662,11 +662,11 @@ void QxEventService::subscribeChanges() rpc_call->start(); } -void QxEventService::sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng) -{ - if (isRunning()) { - m_rpcConnection->sendShvSignal("sql", "recchng", ::qx::qxRecChngToRpcValue(chng)); - } -} +// void QxEventService::sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng) +// { +// if (isRunning()) { +// m_rpcConnection->sendShvSignal("sql", "recchng", ::qx::qxRecChngToRpcValue(chng)); +// } +// } } // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index add24852e..fc5af4120 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -79,7 +79,7 @@ class QxEventService : public Service void sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg); void onBrokerSocketError(const QString &err); void onBrokerLoginError(const shv::chainpack::RpcError &err); - void sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng); + // void sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng); void subscribeChanges(); private: diff --git a/quickevent/app/quickevent/src/qx/sqlapi.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp similarity index 98% rename from quickevent/app/quickevent/src/qx/sqlapi.cpp rename to quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp index d70dfc1a3..859e29c82 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp @@ -19,7 +19,7 @@ using namespace shv::chainpack; -namespace qx { +namespace Event::services::qx { //============================================== // RpcSqlField @@ -184,12 +184,6 @@ SqlApi::SqlApi(QObject *parent) // connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, ) } -SqlApi *SqlApi::instance() -{ - static auto *api = new SqlApi(QCoreApplication::instance()); - return api; -} - // void SqlApi::emitRecChng(const qf::core::sql::QxRecChng &chng) // { // qfInfo() << "REC_CHNG:" << qxRecChngToRpcValue(chng).toCpon(); diff --git a/quickevent/app/quickevent/src/qx/sqlapi.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h similarity index 69% rename from quickevent/app/quickevent/src/qx/sqlapi.h rename to quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h index fe15ed6f2..d02966a7e 100644 --- a/quickevent/app/quickevent/src/qx/sqlapi.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h @@ -9,7 +9,7 @@ namespace qf::core::sql { struct QxRecChng; } -namespace qx { +namespace Event::services::qx { struct DbField { @@ -65,21 +65,20 @@ class SqlApi : public QObject { Q_OBJECT public: - static SqlApi* instance(); + explicit SqlApi(QObject *parent = nullptr); // Q_SIGNAL void recchng(const qf::core::sql::QxRecChng &chng); // void emitRecChng(const qf::core::sql::QxRecChng &chng); - static ExecResult exec(const SqlQueryAndParams ¶ms); - static QueryResult query(const SqlQueryAndParams ¶ms); - static void transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms); - static QueryResult list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); - static int64_t create(const std::string &table, const shv::chainpack::RpcValue::Map &record); - static std::optional read(const std::string &table, int64_t id, const std::vector &fields); - static bool update(const std::string &table, int64_t id, const shv::chainpack::RpcValue::Map &record); - static bool drop(const std::string &table, int64_t id); + ExecResult exec(const SqlQueryAndParams ¶ms); + QueryResult query(const SqlQueryAndParams ¶ms); + void transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms); + QueryResult list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); + int64_t create(const std::string &table, const shv::chainpack::RpcValue::Map &record); + std::optional read(const std::string &table, int64_t id, const std::vector &fields); + bool update(const std::string &table, int64_t id, const shv::chainpack::RpcValue::Map &record); + bool drop(const std::string &table, int64_t id); private: - explicit SqlApi(QObject *parent = nullptr); }; } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index 037061733..e18c89d97 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -1,5 +1,5 @@ #include "sqlapinode.h" -#include "src/qx/sqlapi.h" +#include "sqlapi.h" #include #include @@ -21,6 +21,7 @@ namespace Event::services::qx { SqlApiNode::SqlApiNode(shv::iotqt::node::ShvNode *parent) : Super("sql", parent) + , m_sqlApi(new SqlApi(this)) { } @@ -58,17 +59,17 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin //eyascore::utils::UserId user_id = eyascore::utils::UserId::makeUserName(QString::fromStdString(rq.userId().toMap().value("userName").toString())); if(shv_path.empty()) { if(method == METH_EXEC) { - auto res = ::qx::SqlApi::exec(::qx::SqlQueryAndParams::fromRpcValue(params)); + auto res = m_sqlApi->exec(SqlQueryAndParams::fromRpcValue(params)); return res.toRpcValue(); } if(method == METH_QUERY) { - auto res = ::qx::SqlApi::exec(::qx::SqlQueryAndParams::fromRpcValue(params)); + auto res = m_sqlApi->exec(SqlQueryAndParams::fromRpcValue(params)); return res.toRpcValue(); } if(method == METH_TRANSACTION) { auto sql_query = params.asList().valref(0).asString(); const auto &sql_params = params.asList().valref(0); - ::qx::SqlApi::transaction(sql_query, sql_params.asList()); + m_sqlApi->transaction(sql_query, sql_params.asList()); return RpcValue(nullptr); } if(method == METH_LIST) { @@ -80,14 +81,14 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin } auto ids_above = map.contains("ids_above")? std::optional(map.value("ids_above").toInt64()): std::optional(); auto limit = map.contains("limit")? std::optional(map.value("limit").toInt64()): std::optional(); - auto res = ::qx::SqlApi::list(table, fields, ids_above, limit); + auto res = m_sqlApi->list(table, fields, ids_above, limit); return res.toRecordList(); } if(method == METH_CREATE) { const auto &map = params.asMap(); const auto &table = map.value("table").asString(); const auto &record = map.valref("record").asMap(); - auto res = ::qx::SqlApi::create(table, record); + auto res = m_sqlApi->create(table, record); return res; } if(method == METH_READ) { @@ -98,7 +99,7 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin for (const auto &fn : map.valref("fields").asList()) { fields.push_back(fn.asString()); } - auto res = ::qx::SqlApi::read(table, id, fields); + auto res = m_sqlApi->read(table, id, fields); if (res.has_value()) { return shv::coreqt::rpc::qVariantToRpcValue(res.value()); } @@ -109,14 +110,14 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin const auto &table = map.value("table").asString(); auto id = map.value("id").toInt64(); const auto &record = map.valref("record").asMap(); - auto res = ::qx::SqlApi::update(table, id, record); + auto res = m_sqlApi->update(table, id, record); return res; } if(method == METH_DELETE) { const auto &map = params.asMap(); const auto &table = map.value("table").asString(); auto id = map.valref("id").toInt(); - auto res = ::qx::SqlApi::drop(table, id); + auto res = m_sqlApi->drop(table, id); return res; } } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h index a36f7ed8c..d461ae689 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.h @@ -9,6 +9,8 @@ namespace shv::coreqt::data { class RpcSqlResult; } namespace Event::services::qx { +class SqlApi; + class SqlApiNode : public QxNode { Q_OBJECT @@ -19,6 +21,8 @@ class SqlApiNode : public QxNode protected: const std::vector &metaMethods() override; shv::chainpack::RpcValue callMethod(const StringViewList &shv_path, const std::string &method, const shv::chainpack::RpcValue ¶ms, const shv::chainpack::RpcValue &user_id) override; +private: + SqlApi *m_sqlApi = nullptr; }; } diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp index 17e70cae1..52b93fad2 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp @@ -1,7 +1,6 @@ #include "runstablemodel.h" #include "../../Event/src/eventplugin.h" -#include "src/qx/sqlapi.h" #include #include diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp index 1b8604a65..9f7377b97 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablewidget.cpp @@ -7,8 +7,6 @@ #include "runflagsdialog.h" #include "cardflagsdialog.h" -#include "src/qx/sqlapi.h" - #include #include #include From ec77db0a99b71b7939ba53a8d160c164496d3201 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Fri, 5 Jun 2026 22:44:59 +0200 Subject: [PATCH 30/34] Move QxSqlApi from qf::gui::framework::Application to SqlApi class --- libqf/libqfcore/src/sql/qxsql.cpp | 98 +++++++++++++---- libqf/libqfcore/src/sql/qxsql.h | 35 ++++-- libqf/libqfgui/src/framework/application.cpp | 104 +++++++----------- libqf/libqfgui/src/framework/application.h | 19 ++-- libqf/libqfgui/src/model/sqltablemodel.cpp | 7 +- .../CardReader/src/cardreaderplugin.cpp | 13 ++- .../CardReader/src/cardreaderwidget.cpp | 7 +- .../Competitors/src/competitordocument.cpp | 7 +- .../plugins/Event/src/eventplugin.cpp | 5 +- .../Event/src/services/qx/qxeventservice.cpp | 41 ++++--- .../Event/src/services/qx/sqlapinode.cpp | 2 +- .../plugins/Runs/src/runstablemodel.cpp | 3 +- quickevent/app/quickevent/src/main.cpp | 3 +- 13 files changed, 200 insertions(+), 144 deletions(-) diff --git a/libqf/libqfcore/src/sql/qxsql.cpp b/libqf/libqfcore/src/sql/qxsql.cpp index 844ffbf2d..11601fa32 100644 --- a/libqf/libqfcore/src/sql/qxsql.cpp +++ b/libqf/libqfcore/src/sql/qxsql.cpp @@ -106,15 +106,9 @@ QList QxSqlApi::listOneOrMoreRecords(const QString &table, const std::op } //================================================================ -// QxSql +// QxSqlApiImpl //================================================================ -QxSql::QxSql(const QSqlDatabase &db) - : m_db(db) -{ - -} - -QueryResult QxSql::query(const QString &query, const QVariantMap ¶ms) +QueryResult QxSqlApiImpl::query(const QString &query, const QVariantMap ¶ms) { QSqlQuery q(m_db); q.prepare(query); @@ -138,7 +132,7 @@ QueryResult QxSql::query(const QString &query, const QVariantMap ¶ms) return result; } -ExecResult QxSql::exec(const QString &query, const QVariantMap ¶ms) +ExecResult QxSqlApiImpl::exec(const QString &query, const QVariantMap ¶ms) { QSqlQuery q(m_db); q.prepare(query); @@ -153,40 +147,102 @@ ExecResult QxSql::exec(const QString &query, const QVariantMap ¶ms) return result; } -qint64 QxSql::createRecord(const QString &table, const Record &record, const QString &issuer) +//================================================================ +// QxSql +//================================================================ +QxSql::QxSql(const QString &issuer, const QSqlDatabase &db, QObject *parent) + : QObject(parent) + , m_issuer(issuer) + , m_sqlApi(db) +{ +} + +QueryResult QxSql::query(const QString &query, const QVariantMap ¶ms) +{ + return m_sqlApi.query(query, params); +} + +ExecResult QxSql::exec(const QString &query, const QVariantMap ¶ms) { - auto id = QxSqlApi::createRecord(table, record, issuer); - emit dbRecChng(qf::core::sql::QxRecChng{.table = table, + return m_sqlApi.exec(query, params); +} + +qint64 QxSql::createRecord(const QString &table, const Record &record, QObject *source) +{ + auto id = m_sqlApi.createRecord(table, record, m_issuer); + emit recChng(qf::core::sql::QxRecChng{.table = table, .id = id, .record = record, .op = qf::core::sql::RecOp::Insert, - .issuer = issuer}); + .issuer = m_issuer}, + source); return id; } -bool QxSql::updateRecord(const QString &table, qint64 id, const Record &record, const QString &issuer) +bool QxSql::updateRecord(const QString &table, qint64 id, const Record &record, QObject *source) { - auto ok = QxSqlApi::updateRecord(table, id, record, issuer); + auto ok = m_sqlApi.updateRecord(table, id, record, m_issuer); if (ok) { - emit dbRecChng(qf::core::sql::QxRecChng{.table = table, + emit recChng(qf::core::sql::QxRecChng{.table = table, .id = id, .record = record, .op = qf::core::sql::RecOp::Update, - .issuer = issuer}); + .issuer = m_issuer}, + source); } return ok; } -bool QxSql::deleteRecord(const QString &table, qint64 id, const QString &issuer) +bool QxSql::deleteRecord(const QString &table, qint64 id, QObject *source) { - auto ok = QxSqlApi::deleteRecord(table, id, issuer); + auto ok = m_sqlApi.deleteRecord(table, id, m_issuer); if (ok) { - emit dbRecChng(qf::core::sql::QxRecChng{.table = table, + emit recChng(qf::core::sql::QxRecChng{.table = table, .id = id, .record = {}, .op = qf::core::sql::RecOp::Delete, - .issuer = issuer}); + .issuer = m_issuer}, + source); } return ok; } + +void QxSql::emitRecInserted(const QString &table, qint64 id, const QVariantMap &record, QObject *source) +{ + emit recChng(qf::core::sql::QxRecChng{ + .table = table, + .id = id, + .record = record, + .op = qf::core::sql::RecOp::Insert, + .issuer = m_issuer + }, source); +} + +void QxSql::emitRecUpdated(const QString &table, qint64 id, const QVariantMap &record, QObject *source) +{ + emit recChng(qf::core::sql::QxRecChng{ + .table = table, + .id = id, + .record = record, + .op = qf::core::sql::RecOp::Update, + .issuer = m_issuer + }, source); +} + +void QxSql::emitRecDeleted(const QString &table, qint64 id, QObject *source) +{ + emit recChng(qf::core::sql::QxRecChng{ + .table = table, + .id = id, + .record = {}, + .op = qf::core::sql::RecOp::Delete, + .issuer = m_issuer + }, source); +} + +void QxSql::emitRecChng(const core::sql::QxRecChng &recchng, QObject *source) +{ + emit recChng(recchng, source); +} + } diff --git a/libqf/libqfcore/src/sql/qxsql.h b/libqf/libqfcore/src/sql/qxsql.h index 51e7c6fd0..c1f6101f3 100644 --- a/libqf/libqfcore/src/sql/qxsql.h +++ b/libqf/libqfcore/src/sql/qxsql.h @@ -67,23 +67,40 @@ class QFCORE_DECL_EXPORT QxSqlApi const std::optional &limit); }; -class QFCORE_DECL_EXPORT QxSql : public QObject, public QxSqlApi +class QxSqlApiImpl : public QxSqlApi +{ +public: + QxSqlApiImpl(QSqlDatabase db) : m_db(db) {} + + QueryResult query(const QString &query, const QVariantMap ¶ms) override; + ExecResult exec(const QString &query, const QVariantMap ¶ms) override; +private: + QSqlDatabase m_db; +}; + +class QFCORE_DECL_EXPORT QxSql : public QObject { Q_OBJECT public: - QxSql(const QSqlDatabase &db = QSqlDatabase()); + QxSql(const QString &issuer, const QSqlDatabase &db = {}, QObject *parent = nullptr); ~QxSql() override = default; - Q_SIGNAL void dbRecChng(const qf::core::sql::QxRecChng &recchng); + Q_SIGNAL void recChng(const qf::core::sql::QxRecChng &recchng, QObject *source); - QueryResult query(const QString &query, const QVariantMap ¶ms) override; - ExecResult exec(const QString &query, const QVariantMap ¶ms) override; + QueryResult query(const QString &query, const QVariantMap ¶ms); + ExecResult exec(const QString &query, const QVariantMap ¶ms); + + qint64 createRecord(const QString &table, const Record &record, QObject *source); + bool updateRecord(const QString &table, qint64 id, const Record &record, QObject *source); + bool deleteRecord(const QString &table, qint64 id, QObject *source); - qint64 createRecord(const QString &table, const Record &record, const QString &issuer) override; - bool updateRecord(const QString &table, qint64 id, const Record &record, const QString &issuer) override; - bool deleteRecord(const QString &table, qint64 id, const QString &issuer) override; + void emitRecInserted(const QString &table, qint64 id, const QVariantMap &record, QObject *source); + void emitRecUpdated(const QString &table, qint64 id, const QVariantMap &record, QObject *source); + void emitRecDeleted(const QString &table, qint64 id, QObject *source); + void emitRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); private: - QSqlDatabase m_db; + QString m_issuer; + QxSqlApiImpl m_sqlApi; }; } // namespace qf::core::sql diff --git a/libqf/libqfgui/src/framework/application.cpp b/libqf/libqfgui/src/framework/application.cpp index 5f323c370..ea866c76b 100644 --- a/libqf/libqfgui/src/framework/application.cpp +++ b/libqf/libqfgui/src/framework/application.cpp @@ -19,80 +19,50 @@ Application::Application(int &argc, char **argv) : { } -qint64 Application::createDbRecord(const QString &table, const QVariantMap &record, QObject *source) +qf::core::sql::QxSql *Application::qxSql() { - using namespace qf::core::sql; - QxSql sql; - connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); - return sql.createRecord(table, record, uuidString()); -} - -std::optional Application::readDbRecord(const QString &table, qint64 id, const std::optional &fields) const -{ - using namespace qf::core::sql; - QxSql sql; - return sql.readRecord(table, id, fields); -} - -bool Application::updateDbRecord(const QString &table, qint64 id, const QVariantMap &record, QObject *source) -{ - using namespace qf::core::sql; - QxSql sql; - connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); - return sql.updateRecord(table, id, record, uuidString()); -} - -bool Application::deleteDbRecord(const QString &table, qint64 id, QObject *source) -{ - using namespace qf::core::sql; - QxSql sql; - connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); - return sql.deleteRecord(table, id, uuidString()); -} + if (m_qxSql == nullptr) { + m_qxSql = new qf::core::sql::QxSql(uuidString(), {}, this); + } + return m_qxSql; +} + +// qint64 Application::createDbRecord(const QString &table, const QVariantMap &record, QObject *source) +// { +// using namespace qf::core::sql; +// QxSql sql; +// connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); +// return sql.createRecord(table, record, uuidString()); +// } + +// std::optional Application::readDbRecord(const QString &table, qint64 id, const std::optional &fields) const +// { +// using namespace qf::core::sql; +// QxSql sql; +// return sql.readRecord(table, id, fields); +// } + +// bool Application::updateDbRecord(const QString &table, qint64 id, const QVariantMap &record, QObject *source) +// { +// using namespace qf::core::sql; +// QxSql sql; +// connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); +// return sql.updateRecord(table, id, record, uuidString()); +// } + +// bool Application::deleteDbRecord(const QString &table, qint64 id, QObject *source) +// { +// using namespace qf::core::sql; +// QxSql sql; +// connect(&sql, &QxSql::dbRecChng, this, [this, source](const qf::core::sql::QxRecChng &recchng) { emit qxRecChng(recchng, source); }); +// return sql.deleteRecord(table, id, uuidString()); +// } QString Application::versionString() const { return QCoreApplication::applicationVersion(); } -void Application::emitDbRecInserted(const QString &table, qint64 id, const QVariantMap &record, QObject *source) -{ - emit qxRecChng(qf::core::sql::QxRecChng{ - .table = table, - .id = id, - .record = record, - .op = qf::core::sql::RecOp::Insert, - .issuer = uuidString() - }, source); -} - -void Application::emitDbRecUpdated(const QString &table, qint64 id, const QVariantMap &record, QObject *source) -{ - emit qxRecChng(qf::core::sql::QxRecChng{ - .table = table, - .id = id, - .record = record, - .op = qf::core::sql::RecOp::Update, - .issuer = uuidString() - }, source); -} - -void Application::emitDbRecDeleted(const QString &table, qint64 id, QObject *source) -{ - emit qxRecChng(qf::core::sql::QxRecChng{ - .table = table, - .id = id, - .record = {}, - .op = qf::core::sql::RecOp::Delete, - .issuer = uuidString() - }, source); -} - -void Application::emitQxRecChng(const core::sql::QxRecChng &recchng, QObject *source) -{ - emit qxRecChng(recchng, source); -} - Application *Application::instance(bool must_exist) { auto *ret = qobject_cast(Super::instance()); diff --git a/libqf/libqfgui/src/framework/application.h b/libqf/libqfgui/src/framework/application.h index 6b3893a8c..513007e7d 100644 --- a/libqf/libqfgui/src/framework/application.h +++ b/libqf/libqfgui/src/framework/application.h @@ -12,6 +12,8 @@ class QQmlEngine; +namespace qf::core::sql { class QxSql; } + namespace qf { namespace gui { namespace framework { @@ -28,15 +30,13 @@ class QFGUI_DECL_EXPORT Application : public QApplication explicit Application(int & argc, char ** argv); ~Application() override = default; - Q_SIGNAL void qxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); - qint64 createDbRecord(const QString &table, const QVariantMap &record, QObject *source); - std::optional readDbRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt) const; - bool updateDbRecord(const QString &table, qint64 id, const QVariantMap &record, QObject *source); - bool deleteDbRecord(const QString &table, qint64 id, QObject *source); - void emitDbRecInserted(const QString &table, qint64 id, const QVariantMap &record, QObject *source); - void emitDbRecUpdated(const QString &table, qint64 id, const QVariantMap &record, QObject *source); - void emitDbRecDeleted(const QString &table, qint64 id, QObject *source); - void emitQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + qf::core::sql::QxSql *qxSql(); + + // Q_SIGNAL void qxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + // qint64 createDbRecord(const QString &table, const QVariantMap &record, QObject *source); + // std::optional readDbRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt) const; + // bool updateDbRecord(const QString &table, qint64 id, const QVariantMap &record, QObject *source); + // bool deleteDbRecord(const QString &table, qint64 id, QObject *source); Q_INVOKABLE QString versionString() const; public: @@ -53,6 +53,7 @@ class QFGUI_DECL_EXPORT Application : public QApplication static QString uuidString(); protected: MainWindow* m_frameWork = nullptr; + qf::core::sql::QxSql *m_qxSql = nullptr; }; }}} diff --git a/libqf/libqfgui/src/model/sqltablemodel.cpp b/libqf/libqfgui/src/model/sqltablemodel.cpp index 91ff50340..e61a1e0d9 100644 --- a/libqf/libqfgui/src/model/sqltablemodel.cpp +++ b/libqf/libqfgui/src/model/sqltablemodel.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -234,7 +235,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) row_ref.setValue(serial_ix, v); row_ref.setDirty(serial_ix, false); if (auto *app = qobject_cast(QApplication::instance()); app) { - app->emitDbRecInserted(table_id, v.value(), record_to_map(rec), this); + app->qxSql()->emitRecInserted(table_id, v.value(), record_to_map(rec), this); } } else { @@ -338,7 +339,7 @@ bool SqlTableModel::postRow(int row_no, bool throw_exc) if (num_rows_affected == 1) { if (where_rec.count() == 1) { if (auto *app = qobject_cast(QApplication::instance()); app) { - app->emitDbRecUpdated(table_id, where_rec.value(0).value(), record_to_map(edit_rec), this); + app->qxSql()->emitRecUpdated(table_id, where_rec.value(0).value(), record_to_map(edit_rec), this); } } } @@ -447,7 +448,7 @@ bool SqlTableModel::removeTableRow(int row_no, bool throw_exc) if (num_rows_affected == 1) { if (where_rec.count() == 1) { if (auto *app = qobject_cast(QApplication::instance()); app) { - app->emitDbRecDeleted(table_id, where_rec.value(0).value(), this); + app->qxSql()->emitRecDeleted(table_id, where_rec.value(0).value(), this); } } } else { diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderplugin.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderplugin.cpp index 418ecf9dd..9ebf1820b 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderplugin.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderplugin.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -280,7 +281,7 @@ int CardReaderPlugin::saveCardToSql(const quickevent::core::si::ReadCard &read_c {"runIdAssignError", read_card.runIdAssignError()}, {"data", qf::core::Utils::qvariantToJson(read_card.data())}, }; - auto id = app->createDbRecord("cards", rec, this); + auto id = app->qxSql()->createRecord("cards", rec, this); return id; } catch (const std::exception &e) { @@ -333,7 +334,7 @@ int CardReaderPlugin::savePunchRecordToSql(const quickevent::core::si::PunchReco {"timeMs", punch.timems()}, {"runTimeMs", punch.runtimems_isset()? punch.runtimems(): QVariant()}, }; - auto id = app->createDbRecord("punches", rec, this); + auto id = app->qxSql()->createRecord("punches", rec, this); return id; } catch (const std::exception &e) { @@ -406,7 +407,7 @@ void CardReaderPlugin::updateCheckedCardValuesSql(int card_id, const quickevent: {"notStart", false}, {"penaltyTimeMs", {}}, }; - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); } if (auto missing_codes = checked_card.missingCodes(); !missing_codes.isEmpty()) { QStringList missing_str; @@ -417,7 +418,7 @@ void CardReaderPlugin::updateCheckedCardValuesSql(int card_id, const quickevent: QVariantMap rec { {"runIdAssignError", missing_str.join(',')}, }; - app->updateDbRecord("cards", card_id, rec, this); + app->qxSql()->updateRecord("cards", card_id, rec, this); } } catch (const std::exception &e) { @@ -463,7 +464,7 @@ bool CardReaderPlugin::saveCardAssignedRunnerIdSql(int card_id, int run_id) {"runId", run_id}, {"runIdAssignTS", QDateTime::currentDateTime()}, }; - app->updateDbRecord("cards", card_id, rec, this); + app->qxSql()->updateRecord("cards", card_id, rec, this); return true; } catch (const std::exception &e) { @@ -540,7 +541,7 @@ void CardReaderPlugin::setStartTime(int relay_id, int leg, int start_time) { {"startTimeMs", start_time}, }; auto run_id = q.value(0).toInt(); - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); } catch (const std::exception &e) { qfError() << "setStartTime(): Update runs error, query:" << e.what(); diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp index 9701c0e10..2c3db126d 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -262,7 +263,7 @@ CardReaderWidget::CardReaderWidget(QWidget *parent) } }, Qt::QueuedConnection); - connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, this, &CardReaderWidget::onQxRecChng, Qt::QueuedConnection); + connect(qf::gui::framework::Application::instance()->qxSql(), &qf::core::sql::QxSql::recChng, this, &CardReaderWidget::onQxRecChng, Qt::QueuedConnection); } CardReaderWidget::~CardReaderWidget() @@ -818,7 +819,7 @@ void CardReaderWidget::assignRunnerToSelectedCard() QVariantMap rec { {"isRunning", true}, }; - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); // QString qs = "UPDATE runs SET isRunning=true WHERE competitorId=" QF_IARG(competitor_id) " AND stageId=" QF_IARG(stage_id); // q.execThrow(qs); } @@ -835,7 +836,7 @@ void CardReaderWidget::assignRunnerToSelectedCard() QVariantMap rec { {"siId", si_id}, }; auto run_id = q.value(0).toInt(); if (n++ == 0 || use_si_in_next_stages) { - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); } } // QString qs = "UPDATE runs SET siId=" QF_IARG(si_id) " WHERE competitorId=" QF_IARG(competitor_id) " AND stageId=" QF_IARG(stage_id); diff --git a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp index 3c678da43..98ed30b64 100644 --- a/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp +++ b/quickevent/app/quickevent/plugins/Competitors/src/competitordocument.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -92,7 +93,7 @@ bool CompetitorDocument::saveData() getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_RUN_CHANGED, QVariantList {run_id, rec}); // new update API auto *app = qf::gui::framework::Application::instance(); - app->emitDbRecInserted( "runs", run_id, rec, this); + app->qxSql()->emitRecInserted( "runs", run_id, rec, this); } } } @@ -103,7 +104,7 @@ bool CompetitorDocument::saveData() auto *app = qf::gui::framework::Application::instance(); QVariantMap rec { {"siId", siid()}, }; for (const auto &[run_id, _] : old_records.asKeyValueRange()) { - app->updateDbRecord("runs", run_id, rec, this); + app->qxSql()->updateRecord("runs", run_id, rec, this); } // int competitor_id = dataId().toInt(); // qf::core::sql::Query q(sqlModel()->connectionName()); @@ -171,7 +172,7 @@ bool CompetitorDocument::dropData() getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_RUN_CHANGED, QVariantList {run_id, {}}); // new update API auto *app = qf::gui::framework::Application::instance(); - app->emitDbRecDeleted("runs", run_id, this); + app->qxSql()->emitRecDeleted("runs", run_id, this); } } } diff --git a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp index 5eeb13cd8..1a124970e 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -194,7 +195,7 @@ EventPlugin::EventPlugin(QObject *parent) setEventOpen(!event_name.isEmpty()); }); connect(this, &Event::EventPlugin::dbEventNotify, this, &Event::EventPlugin::onDbEventNotify, Qt::QueuedConnection); - connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, this, &EventPlugin::onRecChng); + connect(qf::gui::framework::Application::instance()->qxSql(), &qf::core::sql::QxSql::recChng, this, &EventPlugin::onRecChng); } void EventPlugin::initEventConfig() @@ -669,7 +670,7 @@ void EventPlugin::onDbEvent(const QString &name, QSqlDriver::NotificationSource qfWarning() << "RecChng loopback detected, issuer:" << recchng.issuer; return; } - qf::gui::framework::Application::instance()->emitQxRecChng(recchng, this); + qf::gui::framework::Application::instance()->qxSql()->emitRecChng(recchng, this); return; } qfMessage() << "emitting domain:" << domain << "data:" << data; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index be06570b9..6d43c3b4c 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -580,18 +580,23 @@ int QxEventService::currentConnectionId() void QxEventService::onBrokerConnectedChanged(bool is_connected) { if(is_connected) { + auto *event_plugin = getPlugin(); + auto current_stage = event_plugin->currentStageId(); + auto stage_data = event_plugin->stageData(current_stage); + auto api_token = stage_data.qxApiToken().toStdString(); auto *rpc_call = shv::iotqt::rpc::RpcCall::create(m_rpcConnection) - ->setShvPath(".broker/currentClient") - ->setMethod("info"); + ->setShvPath("test/qx/qxeventd/eventctl") + ->setMethod("openEventApiKey") + ->setParams(RpcValue(api_token)); connect(rpc_call, &shv::iotqt::rpc::RpcCall::maybeResult, this, [this](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { if (error.isValid()) { setStatus(Status::Stopped); setStatusMessage(tr("Client info discovery error: %1").arg(error.toString())); } else { - const auto &info = result.asMap(); - m_eventMountPoint = info.value("mountPoint").to(); - m_eventId = m_eventMountPoint.section('/', -1, -1).toInt(); + const auto &info = result.asList(); + m_eventId = info.value(0).toInt(); + m_eventMountPoint = info.value(1).to(); setStatus(Status::Running); setStatusMessage(tr("Event ID: %1").arg(m_eventId)); subscribeChanges(); @@ -647,19 +652,19 @@ void QxEventService::sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg) void QxEventService::subscribeChanges() { - Q_ASSERT(m_rpcConnection); - QString shv_path = "test"; - QString signal_name = shv::chainpack::Rpc::SIG_VAL_CHANGED; - auto *rpc_call = RpcCall::createSubscriptionRequest(m_rpcConnection, shv_path, signal_name); - connect(rpc_call, &RpcCall::maybeResult, this, [shv_path, signal_name](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { - if(error.isValid()) { - qfError() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribe error:" << error.toString(); - } - else { - qfMessage() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribed successfully" << result.toCpon(); - } - }); - rpc_call->start(); + // Q_ASSERT(m_rpcConnection); + // QString shv_path = "test"; + // QString signal_name = shv::chainpack::Rpc::SIG_VAL_CHANGED; + // auto *rpc_call = RpcCall::createSubscriptionRequest(m_rpcConnection, shv_path, signal_name); + // connect(rpc_call, &RpcCall::maybeResult, this, [shv_path, signal_name](const ::shv::chainpack::RpcValue &result, const shv::chainpack::RpcError &error) { + // if(error.isValid()) { + // qfError() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribe error:" << error.toString(); + // } + // else { + // qfMessage() << "Signal:" << signal_name << "on SHV path:" << shv_path << "subscribed successfully" << result.toCpon(); + // } + // }); + // rpc_call->start(); } // void QxEventService::sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index e18c89d97..9e4b5845f 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -63,7 +63,7 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin return res.toRpcValue(); } if(method == METH_QUERY) { - auto res = m_sqlApi->exec(SqlQueryAndParams::fromRpcValue(params)); + auto res = m_sqlApi->query(SqlQueryAndParams::fromRpcValue(params)); return res.toRpcValue(); } if(method == METH_TRANSACTION) { diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp index 52b93fad2..ab322c97c 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -51,7 +52,7 @@ RunsTableModel::RunsTableModel(QObject *parent) setColumn(col_competitors_note, ColumnDefinition("competitors.note", tr("Note"))); connect(this, &RunsTableModel::dataChanged, this, &RunsTableModel::onDataChanged, Qt::QueuedConnection); - connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, this, &RunsTableModel::onQxRecChng, Qt::QueuedConnection); + connect(qf::gui::framework::Application::instance()->qxSql(), &qf::core::sql::QxSql::recChng, this, &RunsTableModel::onQxRecChng, Qt::QueuedConnection); } Qt::ItemFlags RunsTableModel::flags(const QModelIndex &index) const diff --git a/quickevent/app/quickevent/src/main.cpp b/quickevent/app/quickevent/src/main.cpp index 3e6036e3d..efb9704a4 100644 --- a/quickevent/app/quickevent/src/main.cpp +++ b/quickevent/app/quickevent/src/main.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -189,7 +190,7 @@ int main(int argc, char *argv[]) main_window.show(); emit main_window.applicationLaunched(); - QObject::connect(&app, &Application::qxRecChng, &app, [](const qf::core::sql::QxRecChng &recchng) { + QObject::connect(app.qxSql(), &qf::core::sql::QxSql::recChng, &app, [](const qf::core::sql::QxRecChng &recchng) { auto dump_map = [](const QVariantMap &m) { QStringList rows; for (const auto &[k, v] : m.asKeyValueRange()) { From 1dac94131922177ef3652062fd97cacc2cebefc5 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 8 Jun 2026 21:02:07 +0200 Subject: [PATCH 31/34] Change qxchanges table structure, QE 3.6.0 --- libqf/libqfcore/src/sql/qxsql.cpp | 77 +++- libqf/libqfcore/src/sql/qxsql.h | 31 +- quickevent/app/quickevent/CMakeLists.txt | 2 +- .../quickevent/plugins/Event/qml/DbSchema.qml | 14 +- .../plugins/Event/sql/create_db_psql.sql | 10 +- .../plugins/Event/sql/create_db_sqlite.sql | 10 +- ...ionswidget.cpp => qxlateentrieswidget.cpp} | 50 +- ...trationswidget.h => qxlateentrieswidget.h} | 10 +- ...ationswidget.ui => qxlateentrieswidget.ui} | 4 +- .../plugins/Event/src/services/qx/sqlapi.cpp | 428 +++++------------- .../plugins/Event/src/services/qx/sqlapi.h | 51 +-- .../Event/src/services/qx/sqlapinode.cpp | 8 +- .../plugins/Runs/src/runsplugin.cpp | 12 +- quickevent/app/quickevent/src/appversion.h | 2 +- 14 files changed, 289 insertions(+), 420 deletions(-) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxlateregistrationswidget.cpp => qxlateentrieswidget.cpp} (84%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxlateregistrationswidget.h => qxlateentrieswidget.h} (75%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{qxlateregistrationswidget.ui => qxlateentrieswidget.ui} (96%) diff --git a/libqf/libqfcore/src/sql/qxsql.cpp b/libqf/libqfcore/src/sql/qxsql.cpp index 11601fa32..dac711b02 100644 --- a/libqf/libqfcore/src/sql/qxsql.cpp +++ b/libqf/libqfcore/src/sql/qxsql.cpp @@ -1,6 +1,8 @@ #include "qxsql.h" #include +#include +#include #include #include @@ -11,6 +13,49 @@ namespace qf::core::sql { //================================================================ // QxSqlApi //================================================================ +namespace { + +class Transaction +{ +public: + Transaction(QSqlDatabase db) : m_db(db) { + if (!m_db.transaction()) { + qfWarning() << "BEGIN transaction error:" << m_db.lastError().text(); + throw std::runtime_error("BEGIN transaction error"); + } + } + ~Transaction() { + if (m_inTransaction) { + m_db.rollback(); + } + } + void commit() { + if (!m_db.commit()) { + qfWarning() << "COMMIT transaction error:" << m_db.lastError().text(); + throw std::runtime_error("COMMIT transaction error"); + } + m_inTransaction = false; + } +private: + QSqlDatabase m_db; + bool m_inTransaction = true; +}; +} +void QxSqlApi::transaction(const QString &query, const QVariantList ¶ms, QSqlDatabase db) +{ + Transaction transaction(db); + qf::core::sql::Query q(db); + q.prepare(query, qf::core::Exception::Throw); + for (const auto ¶m : params) { + auto m = param.toMap(); + for (const auto &[k, v] : m.asKeyValueRange()) { + q.bindValue(':' + k, v); + } + q.exec(qf::core::Exception::Throw); + } + transaction.commit(); +} + qint64 QxSqlApi::createRecord(const QString &table, const Record &record, const QString &issuer) { Q_UNUSED(issuer) @@ -63,7 +108,7 @@ bool QxSqlApi::updateRecord(const QString &table, qint64 id, const Record &recor .arg(table, keyVals.join(", "), QString::number(id)); ExecResult res = exec(qs, record); - return res.rowsAffected == 1; + return res.numRowsAffected == 1; } bool QxSqlApi::deleteRecord(const QString &table, qint64 id, const QString &issuer) @@ -72,7 +117,7 @@ bool QxSqlApi::deleteRecord(const QString &table, qint64 id, const QString &issu QString qs = QString("DELETE FROM %1 WHERE id = %2").arg(table, QString::number(id)); ExecResult res = exec(qs, {}); - return res.rowsAffected == 1; + return res.numRowsAffected == 1; } QList QxSqlApi::listOneOrMoreRecords(const QString &table, const std::optional &fields, const std::optional &id, const std::optional &limit) @@ -110,6 +155,7 @@ QList QxSqlApi::listOneOrMoreRecords(const QString &table, const std::op //================================================================ QueryResult QxSqlApiImpl::query(const QString &query, const QVariantMap ¶ms) { + // qDebug() << query << params; QSqlQuery q(m_db); q.prepare(query); for (const auto &[key, val] : params.asKeyValueRange()) { @@ -120,7 +166,7 @@ QueryResult QxSqlApiImpl::query(const QString &query, const QVariantMap ¶ms) } QueryResult result; for (int i = 0; i < q.record().count(); ++i) { - result.columns.append(q.record().fieldName(i).toLower()); + result.fields.append(DbField{.name = q.record().fieldName(i).toLower()}); } while (q.next()) { QList row; @@ -143,10 +189,18 @@ ExecResult QxSqlApiImpl::exec(const QString &query, const QVariantMap ¶ms) throw qf::core::Exception(q.lastError().text()); } ExecResult result; - result.rowsAffected = q.numRowsAffected(); + result.numRowsAffected = q.numRowsAffected(); + if (auto id = q.lastInsertId().toLongLong(); id > 0) { + result.lastInsertId = id; + } return result; } +void QxSqlApiImpl::transaction(const QString &query, const QVariantList ¶ms) +{ + QxSqlApi::transaction(query, params, m_db); +} + //================================================================ // QxSql //================================================================ @@ -167,6 +221,16 @@ ExecResult QxSql::exec(const QString &query, const QVariantMap ¶ms) return m_sqlApi.exec(query, params); } +QList QxSql::listRecords(const QString &table, const std::optional &fields, const std::optional &fromId, const std::optional &limit) +{ + return m_sqlApi.listRecords(table, fields, fromId, limit); +} + +void QxSql::transaction(const QString &query, const QVariantList ¶ms) +{ + m_sqlApi.transaction(query, params); +} + qint64 QxSql::createRecord(const QString &table, const Record &record, QObject *source) { auto id = m_sqlApi.createRecord(table, record, m_issuer); @@ -179,6 +243,11 @@ qint64 QxSql::createRecord(const QString &table, const Record &record, QObject * return id; } +std::optional QxSql::readRecord(const QString &table, qint64 id, const std::optional &fields) +{ + return m_sqlApi.readRecord(table, id, fields); +} + bool QxSql::updateRecord(const QString &table, qint64 id, const Record &record, QObject *source) { auto ok = m_sqlApi.updateRecord(table, id, record, m_issuer); diff --git a/libqf/libqfcore/src/sql/qxsql.h b/libqf/libqfcore/src/sql/qxsql.h index c1f6101f3..04d493eeb 100644 --- a/libqf/libqfcore/src/sql/qxsql.h +++ b/libqf/libqfcore/src/sql/qxsql.h @@ -15,9 +15,14 @@ namespace qf::core::sql { using Record = QVariantMap; +struct DbField +{ + QString name; +}; + struct QFCORE_DECL_EXPORT QueryResult { - QStringList columns; + QList fields; QList> rows; std::optional record(int i) const @@ -27,8 +32,8 @@ struct QFCORE_DECL_EXPORT QueryResult } Record r; const auto &row = rows[i]; - for (int j = 0; j < columns.size(); ++j) { - r[columns[j]] = row.value(j); + for (int j = 0; j < fields.size(); ++j) { + r[fields[j].name] = row.value(j); } return r; } @@ -36,7 +41,8 @@ struct QFCORE_DECL_EXPORT QueryResult struct QFCORE_DECL_EXPORT ExecResult { - qint64 rowsAffected = 0; + qint64 numRowsAffected = 0; + std::optional lastInsertId = 0; }; class QFCORE_DECL_EXPORT QxSqlApi @@ -54,11 +60,12 @@ class QFCORE_DECL_EXPORT QxSqlApi { return listOneOrMoreRecords(table, fields, fromId, limit); } + void transaction(const QString &query, const QVariantList ¶ms, QSqlDatabase db); - virtual qint64 createRecord(const QString &table, const Record &record, const QString &issuer); - virtual std::optional readRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt); - virtual bool updateRecord(const QString &table, qint64 id, const Record &record, const QString &issuer); - virtual bool deleteRecord(const QString &table, qint64 id, const QString &issuer); + qint64 createRecord(const QString &table, const Record &record, const QString &issuer); + std::optional readRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt); + bool updateRecord(const QString &table, qint64 id, const Record &record, const QString &issuer); + bool deleteRecord(const QString &table, qint64 id, const QString &issuer); protected: QList listOneOrMoreRecords( const QString &table, @@ -74,6 +81,7 @@ class QxSqlApiImpl : public QxSqlApi QueryResult query(const QString &query, const QVariantMap ¶ms) override; ExecResult exec(const QString &query, const QVariantMap ¶ms) override; + void transaction(const QString &query, const QVariantList ¶ms); private: QSqlDatabase m_db; }; @@ -89,8 +97,15 @@ class QFCORE_DECL_EXPORT QxSql : public QObject QueryResult query(const QString &query, const QVariantMap ¶ms); ExecResult exec(const QString &query, const QVariantMap ¶ms); + QList listRecords( + const QString &table, + const std::optional &fields = std::nullopt, + const std::optional &fromId = std::nullopt, + const std::optional &limit = std::nullopt); + void transaction(const QString &query, const QVariantList ¶ms); qint64 createRecord(const QString &table, const Record &record, QObject *source); + std::optional readRecord(const QString &table, qint64 id, const std::optional &fields = std::nullopt); bool updateRecord(const QString &table, qint64 id, const Record &record, QObject *source); bool deleteRecord(const QString &table, qint64 id, QObject *source); diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index 94fbaec8c..bd86ad635 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -99,7 +99,7 @@ add_executable(quickevent plugins/Event/src/services/qx/nodes.cpp plugins/Event/src/services/qx/qxeventservice.cpp plugins/Event/src/services/qx/qxeventservice.h plugins/Event/src/services/qx/qxeventservicewidget.cpp plugins/Event/src/services/qx/qxeventservicewidget.h plugins/Event/src/services/qx/qxeventservicewidget.ui - plugins/Event/src/services/qx/qxlateregistrationswidget.h plugins/Event/src/services/qx/qxlateregistrationswidget.cpp plugins/Event/src/services/qx/qxlateregistrationswidget.ui + plugins/Event/src/services/qx/qxlateentrieswidget.h plugins/Event/src/services/qx/qxlateentrieswidget.cpp plugins/Event/src/services/qx/qxlateentrieswidget.ui plugins/Event/src/services/qx/runchangedialog.h plugins/Event/src/services/qx/runchangedialog.cpp plugins/Event/src/services/qx/runchangedialog.ui plugins/Event/src/services/qx/runchange.h plugins/Event/src/services/qx/runchange.cpp diff --git a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml index c06846d00..55222da4a 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml @@ -494,22 +494,26 @@ Schema { fields: [ Field { name: 'id'; type: Serial { primaryKey: true } }, Field { name: 'stage_id'; type: Int { } }, - Field { name: 'change_id'; type: Int { } }, - Field { name: 'data_id'; type: Int { } }, + // Field { name: 'change_id'; type: Int { } }, // not used + Field { name: 'data_id'; type: Int { } }, // not used Field { name: 'data_type'; type: String { } }, Field { name: 'data'; type: String { } }, Field { name: 'orig_data'; type: String { } comment: 'Store data overriden by change here to enable change rollback.' }, - Field { name: 'source'; type: String { } }, + Field { name: 'source'; type: String { } }, // issuer Field { name: 'user_id'; type: String { } }, Field { name: 'status'; type: String { } }, Field { name: 'status_message'; type: String { } }, - Field { name: 'created'; type: DateTime { } }, + Field { name: 'created'; type: DateTime { } + notNull: true + defaultValue: 'CURRENT_TIMESTAMP' + }, Field { name: 'lock_number'; type: Int { } } ] indexes: [ - Index {fields: ['stage_id', 'change_id']; unique: true } + Index {fields: ['data_type', 'data_id']; unique: false }, + Index {fields: ['status']; unique: false } ] } ] diff --git a/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql b/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql index 93a1837cb..f294be580 100644 --- a/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql +++ b/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql @@ -290,7 +290,6 @@ CREATE TABLE {{eventId}}.lentcards ( CREATE TABLE {{eventId}}.qxchanges ( id serial PRIMARY KEY, stage_id integer, - change_id integer, data_id integer, data_type character varying, data character varying, @@ -299,10 +298,11 @@ CREATE TABLE {{eventId}}.qxchanges ( user_id character varying, status character varying, status_message character varying, - created timestamp with time zone, - lock_number integer, - CONSTRAINT qxchanges_unique0 UNIQUE (stage_id, change_id) + created timestamp with time zone NOT NULL DEFAULT 'CURRENT_TIMESTAMP', + lock_number integer ); +CREATE INDEX qxchanges_ix0 ON {{eventId}}.qxchanges (data_type, data_id); +CREATE INDEX qxchanges_ix1 ON {{eventId}}.qxchanges (status); COMMENT ON COLUMN {{eventId}}.qxchanges.orig_data IS 'Store data overriden by change here to enable change rollback.'; ; ------------------------------------; @@ -311,4 +311,4 @@ COMMENT ON COLUMN {{eventId}}.qxchanges.orig_data IS 'Store data overriden by ch ; -- insert into table: {{eventId}}.config; INSERT INTO {{eventId}}.config (ckey, cname, cvalue, ctype) VALUES -('db.version', 'Data version', '30500', 'int'); +('db.version', 'Data version', '30600', 'int'); diff --git a/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql b/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql index f358a6663..f4f050f6d 100644 --- a/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql +++ b/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql @@ -317,7 +317,6 @@ CREATE TABLE lentcards ( CREATE TABLE qxchanges ( id integer PRIMARY KEY, stage_id integer, - change_id integer, data_id integer, data_type character varying, data character varying, @@ -326,10 +325,11 @@ CREATE TABLE qxchanges ( user_id character varying, status character varying, status_message character varying, - created timestamp, - lock_number integer, - CONSTRAINT qxchanges_unique0 UNIQUE (stage_id, change_id) + created timestamp NOT NULL DEFAULT 'CURRENT_TIMESTAMP', + lock_number integer ); +CREATE INDEX qxchanges_ix0 ON qxchanges (data_type, data_id); +CREATE INDEX qxchanges_ix1 ON qxchanges (status); -- comments not suported for driver: SQLITE -- COMMENT ON COLUMN qxchanges.orig_data IS 'Store data overriden by change here to enable change rollback.'; ; @@ -339,4 +339,4 @@ CREATE TABLE qxchanges ( ; -- insert into table: config; INSERT INTO config (ckey, cname, cvalue, ctype) VALUES -('db.version', 'Data version', '30500', 'int'); +('db.version', 'Data version', '30600', 'int'); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp similarity index 84% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp index 083af5d69..b596faa15 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp @@ -1,5 +1,5 @@ -#include "qxlateregistrationswidget.h" -#include "ui_qxlateregistrationswidget.h" +#include "qxlateentrieswidget.h" +#include "ui_qxlateentrieswidget.h" #include "qxeventservice.h" #include "runchangedialog.h" @@ -42,18 +42,18 @@ constexpr auto DATA_TYPE_RUN_UPDATE_REQUEST = "RunUpdateRequest"; constexpr auto SOURCE_WWW = "www"; -QxLateRegistrationsWidget::QxLateRegistrationsWidget(QWidget *parent) : +QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : QWidget(parent), - ui(new Ui::QxLateRegistrationsWidget) + ui(new Ui::QxLateEntriesWidget) { ui->setupUi(this); ui->tableView->setReadOnly(true); ui->tableView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->tableView, &qfw::TableView::customContextMenuRequested, this, &QxLateRegistrationsWidget::onTableCustomContextMenuRequest); - connect(ui->tableView, &qfw::TableView::doubleClicked, this, &QxLateRegistrationsWidget::onTableDoubleClicked); + connect(ui->tableView, &qfw::TableView::customContextMenuRequested, this, &QxLateEntriesWidget::onTableCustomContextMenuRequest); + connect(ui->tableView, &qfw::TableView::doubleClicked, this, &QxLateEntriesWidget::onTableDoubleClicked); - ui->tableView->setPersistentSettingsId("tblQxLateRegistrations"); + ui->tableView->setPersistentSettingsId("tblQxLateEntries"); ui->tableView->setInsertRowEnabled(false); ui->tableView->setCloneRowEnabled(false); ui->tableView->setRemoveRowEnabled(false); @@ -93,7 +93,7 @@ QxLateRegistrationsWidget::QxLateRegistrationsWidget(QWidget *parent) : } }); - connect(getPlugin(), &Event::EventPlugin::dbEventNotify, this, &QxLateRegistrationsWidget::onDbEventNotify, Qt::QueuedConnection); + connect(getPlugin(), &Event::EventPlugin::dbEventNotify, this, &QxLateEntriesWidget::onDbEventNotify, Qt::QueuedConnection); { auto *lst = ui->lstType; @@ -104,13 +104,13 @@ QxLateRegistrationsWidget::QxLateRegistrationsWidget(QWidget *parent) : lst->addItem("RadioPunch"); lst->addItem("CardReadout"); lst->setCurrentIndex(0); - connect(lst, &QComboBox::currentIndexChanged, this, &QxLateRegistrationsWidget::reload); + connect(lst, &QComboBox::currentIndexChanged, this, &QxLateEntriesWidget::reload); } - connect(ui->chkNull, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); - connect(ui->chkPending, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); - connect(ui->chkLocked, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); - connect(ui->chkAccepted, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); - connect(ui->chkRejected, &QCheckBox::checkStateChanged, this, &QxLateRegistrationsWidget::reload); + connect(ui->chkNull, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); + connect(ui->chkPending, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); + connect(ui->chkLocked, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); + connect(ui->chkAccepted, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); + connect(ui->chkRejected, &QCheckBox::checkStateChanged, this, &QxLateEntriesWidget::reload); connect(ui->btAll, &QPushButton::clicked, this, [this]() { QSignalBlocker sb1(ui->lstType); @@ -131,12 +131,12 @@ QxLateRegistrationsWidget::QxLateRegistrationsWidget(QWidget *parent) : }); } -QxLateRegistrationsWidget::~QxLateRegistrationsWidget() +QxLateEntriesWidget::~QxLateEntriesWidget() { delete ui; } -void QxLateRegistrationsWidget::onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload) +void QxLateEntriesWidget::onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload) { Q_UNUSED(connection_id) if(domain == QLatin1String(Event::EventPlugin::DBEVENT_QX_CHANGE_RECEIVED)) { @@ -145,27 +145,27 @@ void QxLateRegistrationsWidget::onDbEventNotify(const QString &domain, int conne } } -void QxLateRegistrationsWidget::onVisibleChanged(bool is_visible) +void QxLateEntriesWidget::onVisibleChanged(bool is_visible) { if (is_visible && isEnabled()) { reload(); } } -QxEventService *QxLateRegistrationsWidget::service() +QxEventService *QxLateEntriesWidget::service() { auto *svc = qobject_cast(Event::services::Service::serviceByName(QxEventService::serviceId())); Q_ASSERT(svc); return svc; } -void QxLateRegistrationsWidget::resizeColumns() +void QxLateEntriesWidget::resizeColumns() { auto *tv = ui->tableView; tv->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); } -void QxLateRegistrationsWidget::showMessage(const QString &msg, bool is_error) +void QxLateEntriesWidget::showMessage(const QString &msg, bool is_error) { if (msg.isEmpty()) { ui->lblErrorMsg->hide(); @@ -182,7 +182,7 @@ void QxLateRegistrationsWidget::showMessage(const QString &msg, bool is_error) ui->lblErrorMsg->setText(msg); } -void QxLateRegistrationsWidget::reload() +void QxLateEntriesWidget::reload() { if(!isEnabled()) { return; @@ -225,7 +225,7 @@ void QxLateRegistrationsWidget::reload() m_model->reload(); } -void QxLateRegistrationsWidget::addQxChangeRow(int sql_id) +void QxLateEntriesWidget::addQxChangeRow(int sql_id) { qfDebug() << "reloading qxchanges row id:" << sql_id << "col id:" << COL_ID; if(sql_id <= 0) { @@ -251,7 +251,7 @@ void QxLateRegistrationsWidget::addQxChangeRow(int sql_id) } -void QxLateRegistrationsWidget::applyCurrentChange() +void QxLateEntriesWidget::applyCurrentChange() { auto row = ui->tableView->currentIndex().row(); if (row < 0) { @@ -267,7 +267,7 @@ void QxLateRegistrationsWidget::applyCurrentChange() } -void QxLateRegistrationsWidget::onTableCustomContextMenuRequest(const QPoint &pos) +void QxLateEntriesWidget::onTableCustomContextMenuRequest(const QPoint &pos) { QAction a_neco(tr("Neco"), nullptr); QList lst; @@ -278,7 +278,7 @@ void QxLateRegistrationsWidget::onTableCustomContextMenuRequest(const QPoint &po } } -void QxLateRegistrationsWidget::onTableDoubleClicked(const QModelIndex &ix) +void QxLateEntriesWidget::onTableDoubleClicked(const QModelIndex &ix) { auto row = ix.row(); auto data_type = m_model->value(row, COL_DATA_TYPE).toString(); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h similarity index 75% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h index ea906b649..2caed12b8 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h @@ -7,18 +7,18 @@ namespace qf::gui::model { class SqlTableModel; } namespace Event::services::qx { namespace Ui { -class QxLateRegistrationsWidget; +class QxLateEntriesWidget; } class QxEventService; -class QxLateRegistrationsWidget : public QWidget +class QxLateEntriesWidget : public QWidget { Q_OBJECT public: - explicit QxLateRegistrationsWidget(QWidget *parent = nullptr); - ~QxLateRegistrationsWidget() override; + explicit QxLateEntriesWidget(QWidget *parent = nullptr); + ~QxLateEntriesWidget() override; void onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload); void onVisibleChanged(bool is_visible); @@ -35,7 +35,7 @@ class QxLateRegistrationsWidget : public QWidget void onTableDoubleClicked(const QModelIndex &ix); private: - Ui::QxLateRegistrationsWidget *ui; + Ui::QxLateEntriesWidget *ui; qf::gui::model::SqlTableModel *m_model; }; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui similarity index 96% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.ui rename to quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui index 07bf69ed4..70e933293 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateregistrationswidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui @@ -1,7 +1,7 @@ - Event::services::qx::QxLateRegistrationsWidget - + Event::services::qx::QxLateEntriesWidget + 0 diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp index 859e29c82..3e99c1e50 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp @@ -1,6 +1,7 @@ #include "sqlapi.h" #include +#include #include #include #include @@ -21,116 +22,30 @@ using namespace shv::chainpack; namespace Event::services::qx { -//============================================== -// RpcSqlField -//============================================== -RpcValue DbField::toRpcValue() const +RpcValue dbFieldToRpcValue(const DbField &fld) { RpcValue::Map ret; - ret["name"] = name.toStdString(); + ret["name"] = fld.name.toStdString(); return RpcValue(std::move(ret)); } -DbField DbField::fromRpcValue(const shv::chainpack::RpcValue &rv) -{ - DbField ret; - const RpcValue::Map &map = rv.asMap(); - ret.name = QString::fromStdString(map.value("name").asString()); - return ret; -} - -//============================================== -// ExecResult -//============================================== -RpcValue ExecResult::toRpcValue() const +RpcValue execResultToRpcValue(const ExecResult &res) { RpcValue::Map ret; - ret["numRowsAffected"] = numRowsAffected; - ret["lastInsertId"] = lastInsertId.has_value()? RpcValue(lastInsertId.value()): RpcValue(nullptr); - return ret; -} - -ExecResult ExecResult::fromRpcValue(const shv::chainpack::RpcValue &rv) -{ - ExecResult ret; - const auto &map = rv.asMap(); - ret.numRowsAffected = map.value("numRowsAffected").toInt(); - ret.lastInsertId = map.value("lastInsertId").toInt(); - return ret; -} - -//============================================== -// QueryResult -//============================================== -QVariant QueryResult::value(qsizetype row, qsizetype col) const -{ - if (row < rows.size()) { - const auto &cells = rows[row]; - if (col < cells.size()) { - return cells[col]; - } - } - return {}; -} - -QVariant QueryResult::value(qsizetype row, const std::string &name) const -{ - if (auto ix = columnIndex(name); ix.has_value()) { - return value(row, ix.value()); - } - return {}; -} - -void QueryResult::setValue(qsizetype row, qsizetype col, const QVariant &val) -{ - if (row < rows.size()) { - auto &r = rows[row]; - if (col < r.size()) { - r[col] = val; - } - } -} - -void QueryResult::setValue(qsizetype row, const std::string &name, const QVariant &val) -{ - if (auto ix = columnIndex(name); ix.has_value()) { - setValue(row, ix.value(), val); - } -} - -RpcValue::List QueryResult::toRecordList() const -{ - RpcValue::List ret; - for (const auto &row : rows) { - RpcValue::Map rec; - int n = 0; - for (const auto &field : fields) { - rec[field.name.toStdString()] = shv::coreqt::rpc::qVariantToRpcValue(row.value(n++)); - } - ret.push_back(rec); - } + ret["numRowsAffected"] = res.numRowsAffected; + ret["lastInsertId"] = res.lastInsertId.has_value()? RpcValue(res.lastInsertId.value()): RpcValue(nullptr); return ret; } -std::optional QueryResult::columnIndex(const std::string &name) const -{ - for (size_t col = 0; col < fields.size(); ++col) { - const auto &fld = fields[col]; - if (fld.name.toStdString() == name) { - return col; - } - } - return {}; -} - -RpcValue QueryResult::toRpcValue() const +RpcValue queryResultToRpcValue(const QueryResult &res) { RpcValue::Map ret; RpcValue::List flds; - for(const auto &fld : this->fields) - flds.push_back(fld.toRpcValue()); + for(const auto &fld : res.fields) { + flds.push_back(dbFieldToRpcValue(fld)); + } RpcValue::List rpc_rows; - for (const auto &row : rows) { + for (const auto &row : res.rows) { RpcValue::List rpc_row; for (const auto &cell : row) { rpc_row.push_back(shv::coreqt::rpc::qVariantToRpcValue(cell)); @@ -142,23 +57,24 @@ RpcValue QueryResult::toRpcValue() const return ret; } -QueryResult QueryResult::fromRpcValue(const RpcValue &rv) +namespace { +RpcValue::List toShvRecordList(const QList &records) { - QueryResult ret; - const auto &map = rv.asMap(); - const auto &flds = map.valref("fields").asList(); - for(const auto &fv : flds) { - ret.fields.push_back(DbField::fromRpcValue(fv)); - } - for (const auto &rpc_row : map.value("rows").asList()) { - QueryResult::Row row; - for (const auto &cell : rpc_row.asList()) { - row.push_back(shv::coreqt::rpc::rpcValueToQVariant(cell)); - } - ret.rows.push_back(row); + RpcValue::List ret; + for (const auto &rec : records) { + ret.push_back(shv::coreqt::rpc::qVariantToRpcValue(rec)); } return ret; } +QStringList toQStringList(const std::vector &sl) +{ + QStringList qsl; + for (const auto &s : sl) { + qsl << QString::fromStdString(s); + } + return qsl; +} +} SqlQueryAndParams SqlQueryAndParams::fromRpcValue(const shv::chainpack::RpcValue &rv) { @@ -168,7 +84,6 @@ SqlQueryAndParams SqlQueryAndParams::fromRpcValue(const shv::chainpack::RpcValue return SqlQueryAndParams { .query = sql_query, .params = shv::coreqt::rpc::rpcValueToQVariant(sql_params).toMap() }; } - RpcValue qxRecChngToRpcValue(const qf::core::sql::QxRecChng &chng) { auto m = chng.toVariantMap(); @@ -190,131 +105,110 @@ SqlApi::SqlApi(QObject *parent) // emit recchng(chng); // } -namespace { - -class Transaction -{ -public: - Transaction(QSqlDatabase db) : m_db(db) { - if (!m_db.transaction()) { - qfWarning() << "BEGIN transaction error:" << m_db.lastError().text(); - throw std::runtime_error("BEGIN transaction error"); - } - } - ~Transaction() { - if (m_inTransaction) { - m_db.rollback(); - } - } - void commit() { - if (!m_db.commit()) { - qfWarning() << "COMMIT transaction error:" << m_db.lastError().text(); - throw std::runtime_error("COMMIT transaction error"); - } - m_inTransaction = false; - } -private: - QSqlDatabase m_db; - bool m_inTransaction = true; -}; +// namespace { -void bindParams(qf::core::sql::Query &q, const Record ¶ms) -{ - for (const auto &[k, v] : params.asKeyValueRange()) { - q.bindValue(':' + k, v); - } -} +// class Transaction +// { +// public: +// Transaction(QSqlDatabase db) : m_db(db) { +// if (!m_db.transaction()) { +// qfWarning() << "BEGIN transaction error:" << m_db.lastError().text(); +// throw std::runtime_error("BEGIN transaction error"); +// } +// } +// ~Transaction() { +// if (m_inTransaction) { +// m_db.rollback(); +// } +// } +// void commit() { +// if (!m_db.commit()) { +// qfWarning() << "COMMIT transaction error:" << m_db.lastError().text(); +// throw std::runtime_error("COMMIT transaction error"); +// } +// m_inTransaction = false; +// } +// private: +// QSqlDatabase m_db; +// bool m_inTransaction = true; +// }; + +// void bindParams(qf::core::sql::Query &q, const Record ¶ms) +// { +// for (const auto &[k, v] : params.asKeyValueRange()) { +// q.bindValue(':' + k, v); +// } +// } -QueryResult sqlQuery(const SqlQueryAndParams ¶ms) -{ - qf::core::sql::Query q; - q.prepare(params.query, qf::core::Exception::Throw); - bindParams(q, params.params); - q.exec(qf::core::Exception::Throw); - - QueryResult ret; - QSqlRecord rec = q.record(); - for (int i = 0; i < rec.count(); ++i) { - QSqlField fld = rec.field(i); - DbField rfld; - rfld.name = fld.name(); - // rfld.name.replace("__", "."); - ret.fields.push_back(rfld); - } - while(q.next()) { - QueryResult::Row row; - for (int i = 0; i < rec.count(); ++i) { - row.push_back(q.value(i)); - //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); - } - ret.rows.insert(ret.rows.size(), row); - } - return ret; -} +// QueryResult sqlQuery(const SqlQueryAndParams ¶ms) +// { +// qf::core::sql::Query q; +// q.prepare(params.query, qf::core::Exception::Throw); +// bindParams(q, params.params); +// q.exec(qf::core::Exception::Throw); + +// QueryResult ret; +// QSqlRecord rec = q.record(); +// for (int i = 0; i < rec.count(); ++i) { +// QSqlField fld = rec.field(i); +// DbField rfld; +// rfld.name = fld.name(); +// // rfld.name.replace("__", "."); +// ret.fields.push_back(rfld); +// } +// while(q.next()) { +// QueryResult::Row row; +// for (int i = 0; i < rec.count(); ++i) { +// row.push_back(q.value(i)); +// //shvError() << v << v.isNull() << jsv.toVariant() << jsv.toVariant().isNull(); +// } +// ret.rows.insert(ret.rows.size(), row); +// } +// return ret; +// } -ExecResult sqlExec(const SqlQueryAndParams ¶ms) -{ - qf::core::sql::Query q; - q.prepare(params.query, qf::core::Exception::Throw); - bindParams(q, params.params); - q.exec(qf::core::Exception::Throw); - - ExecResult ret; - ret.numRowsAffected = q.numRowsAffected(); - ret.lastInsertId = q.lastInsertId().toInt(); - return ret; -} +// ExecResult sqlExec(const SqlQueryAndParams ¶ms) +// { +// qf::core::sql::Query q; +// q.prepare(params.query, qf::core::Exception::Throw); +// bindParams(q, params.params); +// q.exec(qf::core::Exception::Throw); + +// ExecResult ret; +// ret.numRowsAffected = q.numRowsAffected(); +// ret.lastInsertId = q.lastInsertId().toInt(); +// return ret; +// } -} +// } ExecResult SqlApi::exec(const SqlQueryAndParams ¶ms) { - return sqlExec(params); + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->exec(params.query, params.params); } QueryResult SqlApi::query(const SqlQueryAndParams ¶ms) { - return sqlQuery(params); + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->query(params.query, params.params); } void SqlApi::transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms) { - auto conn = qf::core::sql::Connection::forName(); - Transaction tranaction(conn); - qf::core::sql::Query q(conn); - q.prepare(QString::fromUtf8(query), qf::core::Exception::Throw); - for (const auto ¶m : params) { - for (const auto &[k, v] : param.asMap()) { - bool ok; - QVariant val = shv::coreqt::rpc::rpcValueToQVariant(v, &ok); - if (!ok) { - QF_EXCEPTION(QStringLiteral("Cannot convert SHV type: %1 to QVariant").arg(v.typeName())); - } - q.bindValue(':' + QString::fromStdString(k), val); - } - q.exec(qf::core::Exception::Throw); + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + QVariantList qparams; + for (const auto &p : params) { + qparams << shv::coreqt::rpc::rpcValueToQVariant(p); } - tranaction.commit(); + return qxsql->transaction(QString::fromStdString(query), qparams); } -QueryResult SqlApi::list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit) +RpcValue::List SqlApi::list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit) { - QStringList qfields; - for (const auto &fn : fields) { - qfields << QString::fromStdString(fn); - } - if (qfields.isEmpty()) { - qfields << "*"; - } - QString sql_query = QStringLiteral("SELECT %1 FROM %2").arg(qfields.join(',')).arg(QString::fromStdString(table)); - if (ids_above.has_value()) { - sql_query += " WHERE id > " + QString::number(ids_above.value()); - } - if (limit.has_value()) { - sql_query += " LIMIT " + QString::number(limit.value()); - } - auto res = sqlQuery(SqlQueryAndParams { .query = sql_query, .params = {}}); - return res; + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + auto records = qxsql->listRecords(QString::fromStdString(table), toQStringList(fields), ids_above, limit); + return toShvRecordList(records); } namespace { std::string to_lower(const std::string &s) @@ -339,109 +233,29 @@ std::string to_lower(const std::string &s) } int64_t SqlApi::create(const std::string &table, const RpcValue::Map &record) { - QStringList fields; - QStringList placeholders; - for (const auto &[k, v] : record) { - Q_UNUSED(v) - auto qk = QString::fromStdString(k); - fields << qk; - placeholders << ':' + qk; - } - QString sql_query = QStringLiteral("INSERT INTO %1 (%2) VALUES (%3)") - .arg(QString::fromStdString(table)) - .arg(fields.join(',')) - .arg(placeholders.join(',')); - qf::core::sql::Query q; - q.prepare(sql_query, qf::core::Exception::Throw); - for (const auto &[k, v] : record) { - q.bindValue(':' + QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); - } - q.exec(qf::core::Exception::Throw); - auto id = q.lastInsertId().toInt(); - // SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { - // .table = QString::fromStdString(table), - // .id = id, - // .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), - // .op = qf::core::sql::RecOp::Insert, - // .issuer = {} - // }); - return id; + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->createRecord(QString::fromStdString(table), shv::coreqt::rpc::rpcValueToQVariant(record).toMap(), this); } -std::optional SqlApi::read(const std::string &table, int64_t id, const std::vector &fields) +std::optional SqlApi::read(const std::string &table, int64_t id, const std::vector &fields) { - QStringList qfields; - for (const auto &fn : fields) { - qfields << QString::fromStdString(fn); - } - if (qfields.isEmpty()) { - qfields << "*"; - } - QString sql_query = QStringLiteral("SELECT %1 FROM %2 WHERE id = %3") - .arg(qfields.join(',')) - .arg(QString::fromStdString(table)) - .arg(id) ; - auto res = sqlQuery(SqlQueryAndParams { .query = sql_query, .params = {}}); - if (res.rows.empty()) { - return {}; - } - Record ret; - const auto &row = res.rows.first(); - for (qsizetype i = 0; i < std::ssize(res.fields); ++i) { - ret[res.fields[i].name] = row.value(i); + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + if (auto rec = qxsql->readRecord(QString::fromStdString(table), id, toQStringList(fields)); rec.has_value()) { + return shv::coreqt::rpc::qVariantToRpcValue(rec.value()).asMap(); } - return ret; + return {}; } bool SqlApi::update(const std::string &table, int64_t id, const RpcValue::Map &record) { - QStringList fields; - for (const auto &[k, v] : record) { - Q_UNUSED(v) - auto qk = QString::fromStdString(k); - fields << qk + " = :" + qk; - } - QString sql_query = QStringLiteral("UPDATE %1 SET %2 WHERE id = %3") - .arg(QString::fromStdString(table)) - .arg(fields.join(',')) - .arg(id); - qf::core::sql::Query q; - q.prepare(sql_query, qf::core::Exception::Throw); - for (const auto &[k, v] : record) { - q.bindValue(':' + QString::fromStdString(k), shv::coreqt::rpc::rpcValueToQVariant(v)); - } - q.exec(qf::core::Exception::Throw); - bool updated = q.numRowsAffected() == 1; - if (updated) { - // SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { - // .table = QString::fromStdString(table), - // .id = id, - // .record = shv::coreqt::rpc::rpcValueToQVariant(normalizeFieldNames(record)).toMap(), - // .op = qf::core::sql::RecOp::Update, - // .issuer = {} - // }); - } - return updated; + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->updateRecord(QString::fromStdString(table), id, shv::coreqt::rpc::rpcValueToQVariant(record).toMap(), this); } bool SqlApi::drop(const std::string &table, int64_t id) { - QString sql_query = QStringLiteral("DELETE FROM %1 WHERE id = %2") - .arg(QString::fromStdString(table)) - .arg(id); - qf::core::sql::Query q; - q.exec(sql_query, qf::core::Exception::Throw); - bool is_drop = q.numRowsAffected() == 1; - if (is_drop) { - // SqlApi::instance()->emitRecChng(qf::core::sql::QxRecChng { - // .table = QString::fromStdString(table), - // .id = id, - // .record = {}, - // .op = qf::core::sql::RecOp::Delete, - // .issuer = {} - // }); - } - return is_drop; + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + return qxsql->deleteRecord(QString::fromStdString(table), id, this); } } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h index d02966a7e..c0d9334b7 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h @@ -1,7 +1,7 @@ #pragma once #include -// #include +#include #include #include @@ -11,42 +11,13 @@ namespace qf::core::sql { struct QxRecChng; } namespace Event::services::qx { -struct DbField -{ - QString name; - - //explicit DbField(const QJsonObject &jo = QJsonObject()) : Super(jo) {} - shv::chainpack::RpcValue toRpcValue() const; - // QVariant toVariant() const; - static DbField fromRpcValue(const shv::chainpack::RpcValue &rv); - // static DbField fromVariant(const QVariant &v); -}; - -struct ExecResult -{ - int numRowsAffected = 0; - std::optional lastInsertId = 0; - - shv::chainpack::RpcValue toRpcValue() const; - static ExecResult fromRpcValue(const shv::chainpack::RpcValue &rv); -}; - -struct QueryResult -{ - std::vector fields; - using Row = QVariantList; - QList rows; +using DbField = qf::core::sql::DbField; +using ExecResult = qf::core::sql::ExecResult; +using QueryResult = qf::core::sql::QueryResult; - std::optional columnIndex(const std::string &name) const; - QVariant value(qsizetype row, qsizetype col) const; - QVariant value(qsizetype row, const std::string &name) const; - void setValue(qsizetype row, qsizetype col, const QVariant &val); - void setValue(qsizetype row, const std::string &name, const QVariant &val); - - shv::chainpack::RpcValue toRpcValue() const; - static QueryResult fromRpcValue(const shv::chainpack::RpcValue &rv); - shv::chainpack::RpcValue::List toRecordList() const; -}; +shv::chainpack::RpcValue dbFieldToRpcValue(const DbField &fld); +shv::chainpack::RpcValue execResultToRpcValue(const ExecResult &res); +shv::chainpack::RpcValue queryResultToRpcValue(const QueryResult &res); using Record = QVariantMap; @@ -67,18 +38,14 @@ class SqlApi : public QObject public: explicit SqlApi(QObject *parent = nullptr); - // Q_SIGNAL void recchng(const qf::core::sql::QxRecChng &chng); - // void emitRecChng(const qf::core::sql::QxRecChng &chng); - ExecResult exec(const SqlQueryAndParams ¶ms); QueryResult query(const SqlQueryAndParams ¶ms); void transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms); - QueryResult list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); + shv::chainpack::RpcValue::List list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit); int64_t create(const std::string &table, const shv::chainpack::RpcValue::Map &record); - std::optional read(const std::string &table, int64_t id, const std::vector &fields); + std::optional read(const std::string &table, int64_t id, const std::vector &fields); bool update(const std::string &table, int64_t id, const shv::chainpack::RpcValue::Map &record); bool drop(const std::string &table, int64_t id); -private: }; } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index 9e4b5845f..a495e24b1 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -60,11 +60,11 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin if(shv_path.empty()) { if(method == METH_EXEC) { auto res = m_sqlApi->exec(SqlQueryAndParams::fromRpcValue(params)); - return res.toRpcValue(); + return execResultToRpcValue(res); } if(method == METH_QUERY) { auto res = m_sqlApi->query(SqlQueryAndParams::fromRpcValue(params)); - return res.toRpcValue(); + return queryResultToRpcValue(res); } if(method == METH_TRANSACTION) { auto sql_query = params.asList().valref(0).asString(); @@ -82,7 +82,7 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin auto ids_above = map.contains("ids_above")? std::optional(map.value("ids_above").toInt64()): std::optional(); auto limit = map.contains("limit")? std::optional(map.value("limit").toInt64()): std::optional(); auto res = m_sqlApi->list(table, fields, ids_above, limit); - return res.toRecordList(); + return res; } if(method == METH_CREATE) { const auto &map = params.asMap(); @@ -101,7 +101,7 @@ RpcValue SqlApiNode::callMethod(const StringViewList &shv_path, const std::strin } auto res = m_sqlApi->read(table, id, fields); if (res.has_value()) { - return shv::coreqt::rpc::qVariantToRpcValue(res.value()); + return res.value(); } return RpcValue(nullptr); } diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index 3964ce8de..efa0248b1 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -11,7 +11,7 @@ // #include "../../Competitors/src/competitorwidget.h" #include "../../CardReader/src/cardreaderplugin.h" #include "../../Event/src/eventplugin.h" -#include "../../Event/src/services/qx/qxlateregistrationswidget.h" +#include "../../Event/src/services/qx/qxlateentrieswidget.h" #include #include @@ -142,15 +142,15 @@ void RunsPlugin::onInstalled() } { auto *dw = new qff::DockWidget(nullptr); - dw->setObjectName("qxLateRegistrationsDockWidget"); - dw->setPersistentSettingsId("Runs/qxLateRegistrations"); - dw->setWindowTitle(tr("Late registrations")); - auto *ew = new Event::services::qx::QxLateRegistrationsWidget(); + dw->setObjectName("qxLateEntriesDockWidget"); + dw->setPersistentSettingsId("Runs/qxLateEntries"); + dw->setWindowTitle(tr("Late entries")); + auto *ew = new Event::services::qx::QxLateEntriesWidget(); dw->setWidget(ew); fwk->addDockWidget(Qt::RightDockWidgetArea, dw); dw->hide(); - connect(dw, &qff::DockWidget::visibilityChanged, ew, &Event::services::qx::QxLateRegistrationsWidget::onVisibleChanged); + connect(dw, &qff::DockWidget::visibilityChanged, ew, &Event::services::qx::QxLateEntriesWidget::onVisibleChanged); auto *a = dw->toggleViewAction(); // a->setShortcut(QKeySequence("ctrl+shift+E")); diff --git a/quickevent/app/quickevent/src/appversion.h b/quickevent/app/quickevent/src/appversion.h index d6b84b113..a08b78be1 100644 --- a/quickevent/app/quickevent/src/appversion.h +++ b/quickevent/app/quickevent/src/appversion.h @@ -1,4 +1,4 @@ #pragma once -#define APP_VERSION "3.5.6" +#define APP_VERSION "3.6.0" From 30f6b3a737fff89cdd91ff3ce82ae8ede780587b Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Mon, 8 Jun 2026 21:06:36 +0200 Subject: [PATCH 32/34] Fix ofeed client compiler warnings --- .../quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp | 2 +- .../plugins/Event/src/services/ofeed/ofeedclientwidget.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp index 54d699543..6153547a9 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclient.cpp @@ -1373,7 +1373,7 @@ void OFeedClient::markChangelogEntryAsProcessed(int protocolId) variables["processedByType"] = QString("SYSTEM"); variables["processedBySource"] = QString("IT"); - sendGraphQLRequest(mutation, variables, [this, protocolId](QJsonObject data) { + sendGraphQLRequest(mutation, variables, [protocolId](QJsonObject data) { if (data.isEmpty()) { qfWarning() << serviceName().toStdString() + " Failed to mark changelog entry " << protocolId << " as processed"; } else { diff --git a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclientwidget.h b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclientwidget.h index feef6e8d5..242409451 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclientwidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/ofeed/ofeedclientwidget.h @@ -40,9 +40,9 @@ class OFeedClientWidget : public qf::gui::framework::DialogWidget QString defaultReceiptEventLink() const; OFeedClient* service(); bool saveSettings(); + bool acceptDialogDone(int result) override; private: Ui::OFeedClientWidget *ui; - bool acceptDialogDone(int result); bool m_isTestConnectionRunning = false; bool m_isImageRefreshRunning = false; QString m_lastAutoReceiptEventLink; From 03ab4d6eb84ae67cbe8430eb8fbd86ff337d0b28 Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Tue, 9 Jun 2026 11:34:13 +0200 Subject: [PATCH 33/34] Fix create SQL scripts --- libqf/libqfgui/src/framework/dockwidget.h | 4 ++-- quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml | 4 ++-- .../app/quickevent/plugins/Event/qml/sql/def/Field.qml | 2 ++ .../app/quickevent/plugins/Event/sql/create_db_psql.sql | 2 +- .../app/quickevent/plugins/Event/sql/create_db_sqlite.sql | 2 +- .../plugins/Event/src/services/qx/qxlateentrieswidget.cpp | 8 +++++--- .../plugins/Event/src/services/qx/qxlateentrieswidget.h | 2 +- .../plugins/Event/src/services/qx/qxlateentrieswidget.ui | 2 +- 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/libqf/libqfgui/src/framework/dockwidget.h b/libqf/libqfgui/src/framework/dockwidget.h index 1364ec5ee..bc4067236 100644 --- a/libqf/libqfgui/src/framework/dockwidget.h +++ b/libqf/libqfgui/src/framework/dockwidget.h @@ -22,12 +22,12 @@ class QFGUI_DECL_EXPORT DockWidget : public QDockWidget, public framework::IPers public: explicit DockWidget(const QString &window_title, QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); explicit DockWidget(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()) : DockWidget(QString(), parent, flags) {} - ~DockWidget() Q_DECL_OVERRIDE; + ~DockWidget() override; // visibilityChanged() exists already in QDockWidget //Q_SIGNAL void visibleChanged(bool visible); protected: - bool event(QEvent *ev) Q_DECL_OVERRIDE; + bool event(QEvent *ev) override; //void showEvent(QShowEvent *ev) Q_DECL_OVERRIDE; private: void setQmlWidget(QWidget *w); diff --git a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml index 55222da4a..00bcbb863 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml @@ -512,8 +512,8 @@ Schema { Field { name: 'lock_number'; type: Int { } } ] indexes: [ - Index {fields: ['data_type', 'data_id']; unique: false }, - Index {fields: ['status']; unique: false } + Index {fields: ['stage_id', 'data_type', 'data_id']; unique: false }, + Index {fields: ['stage_id', 'status']; unique: false } ] } ] diff --git a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Field.qml b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Field.qml index 6d43184fe..c3fae0e09 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Field.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Field.qml @@ -21,6 +21,8 @@ QtObject { def += ' DEFAULT '; if(typeof defaultValue === 'boolean' && driver_name.endsWith("SQLITE")) def += (defaultValue)? 1: 0; + else if(defaultValue === 'CURRENT_TIMESTAMP') + def += defaultValue; else if(typeof defaultValue === 'string') def += "'" + defaultValue + "'"; else diff --git a/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql b/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql index f294be580..6937756b4 100644 --- a/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql +++ b/quickevent/app/quickevent/plugins/Event/sql/create_db_psql.sql @@ -298,7 +298,7 @@ CREATE TABLE {{eventId}}.qxchanges ( user_id character varying, status character varying, status_message character varying, - created timestamp with time zone NOT NULL DEFAULT 'CURRENT_TIMESTAMP', + created timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP, lock_number integer ); CREATE INDEX qxchanges_ix0 ON {{eventId}}.qxchanges (data_type, data_id); diff --git a/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql b/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql index f4f050f6d..cbc337538 100644 --- a/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql +++ b/quickevent/app/quickevent/plugins/Event/sql/create_db_sqlite.sql @@ -325,7 +325,7 @@ CREATE TABLE qxchanges ( user_id character varying, status character varying, status_message character varying, - created timestamp NOT NULL DEFAULT 'CURRENT_TIMESTAMP', + created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, lock_number integer ); CREATE INDEX qxchanges_ix0 ON qxchanges (data_type, data_id); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp index b596faa15..1a25ce21b 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp @@ -76,17 +76,19 @@ QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : m_model->addColumn(COL_ORIG_DATA, tr("Orig data"));//.setToolTip(tr("Locked for drawing")); ui->tableView->setTableModel(m_model); - showMessage({}); + showMessage(tr("QxEvent service is disabled"), true); setEnabled(false); - auto *svc = service(); + auto *svc = qxEventService(); connect(svc, &Service::statusChanged, this, [this](Service::Status new_status){ switch (new_status) { case Service::Status::Unknown: case Service::Status::Stopped: + showMessage(tr("QxEvent service is disabled"), true); setEnabled(false); break; case Service::Status::Running: + showMessage({}); setEnabled(true); reload(); break; @@ -152,7 +154,7 @@ void QxLateEntriesWidget::onVisibleChanged(bool is_visible) } } -QxEventService *QxLateEntriesWidget::service() +QxEventService *QxLateEntriesWidget::qxEventService() { auto *svc = qobject_cast(Event::services::Service::serviceByName(QxEventService::serviceId())); Q_ASSERT(svc); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h index 2caed12b8..5ae69ac66 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h @@ -23,7 +23,7 @@ class QxLateEntriesWidget : public QWidget void onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload); void onVisibleChanged(bool is_visible); private: - QxEventService* service(); + QxEventService* qxEventService(); void reload(); void addQxChangeRow(int sql_id); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui index 70e933293..30abe1ed9 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.ui @@ -11,7 +11,7 @@ - Form + Late entries From 3fa9d282ed3725b21889311dacf5d7df3a03232a Mon Sep 17 00:00:00 2001 From: Fanda Vacek Date: Wed, 17 Jun 2026 23:12:33 +0200 Subject: [PATCH 34/34] Add LateEntry support and update qxchanges schema Also add recChng signal forwarding to RPC node, check QSqlQuery::prepare and throw on failure, and rename handleQxRecChng to applyQxRecChng across models and widgets --- libqf/libqfcore/src/sql/qxrecchng.cpp | 1 + libqf/libqfcore/src/sql/qxsql.cpp | 8 +- libqf/libqfgui/src/model/tablemodel.cpp | 30 +++- libqf/libqfgui/src/model/tablemodel.h | 2 +- quickevent/app/quickevent/CMakeLists.txt | 2 +- .../CardReader/src/cardreaderwidget.cpp | 2 +- .../plugins/CardReader/src/cardreaderwidget.h | 2 +- .../quickevent/plugins/Event/qml/DbSchema.qml | 14 +- .../plugins/Event/qml/sql/def/Index.qml | 21 ++- .../plugins/Event/qml/sql/def/Table.qml | 20 +-- .../plugins/Event/sql/create_db_psql.sql | 8 +- .../plugins/Event/sql/create_db_sqlite.sql | 8 +- .../plugins/Event/src/eventplugin.cpp | 19 +-- .../plugins/Event/src/eventplugin.h | 21 ++- ...unchangedialog.cpp => lateentrydialog.cpp} | 135 +++++++----------- .../{runchangedialog.h => lateentrydialog.h} | 21 ++- ...{runchangedialog.ui => lateentrydialog.ui} | 33 ++--- .../Event/src/services/qx/qxeventservice.cpp | 109 -------------- .../Event/src/services/qx/qxeventservice.h | 6 - .../src/services/qx/qxlateentrieswidget.cpp | 79 +++------- .../src/services/qx/qxlateentrieswidget.h | 6 +- .../Event/src/services/qx/runchange.cpp | 30 +++- .../plugins/Event/src/services/qx/runchange.h | 24 +++- .../plugins/Event/src/services/qx/sqlapi.cpp | 5 +- .../plugins/Event/src/services/qx/sqlapi.h | 2 + .../Event/src/services/qx/sqlapinode.cpp | 10 ++ .../plugins/Runs/src/runstablemodel.cpp | 2 +- 27 files changed, 258 insertions(+), 362 deletions(-) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{runchangedialog.cpp => lateentrydialog.cpp} (63%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{runchangedialog.h => lateentrydialog.h} (55%) rename quickevent/app/quickevent/plugins/Event/src/services/qx/{runchangedialog.ui => lateentrydialog.ui} (95%) diff --git a/libqf/libqfcore/src/sql/qxrecchng.cpp b/libqf/libqfcore/src/sql/qxrecchng.cpp index fc0ae36b2..b534d9ed5 100644 --- a/libqf/libqfcore/src/sql/qxrecchng.cpp +++ b/libqf/libqfcore/src/sql/qxrecchng.cpp @@ -48,6 +48,7 @@ QString QxRecChng::recopToString(RecOp op) case RecOp::Update: return "Update"; case RecOp::Delete: return "Delete"; } + return QString(); } } diff --git a/libqf/libqfcore/src/sql/qxsql.cpp b/libqf/libqfcore/src/sql/qxsql.cpp index dac711b02..45e55d111 100644 --- a/libqf/libqfcore/src/sql/qxsql.cpp +++ b/libqf/libqfcore/src/sql/qxsql.cpp @@ -157,7 +157,9 @@ QueryResult QxSqlApiImpl::query(const QString &query, const QVariantMap ¶ms) { // qDebug() << query << params; QSqlQuery q(m_db); - q.prepare(query); + if (!q.prepare(query)) { + throw qf::core::Exception(q.lastError().text()); + } for (const auto &[key, val] : params.asKeyValueRange()) { q.bindValue(QStringLiteral(":%1").arg(key), val); } @@ -181,7 +183,9 @@ QueryResult QxSqlApiImpl::query(const QString &query, const QVariantMap ¶ms) ExecResult QxSqlApiImpl::exec(const QString &query, const QVariantMap ¶ms) { QSqlQuery q(m_db); - q.prepare(query); + if (!q.prepare(query)) { + throw qf::core::Exception(q.lastError().text()); + } for (const auto &[key, val] : params.asKeyValueRange()) { q.bindValue(QStringLiteral(":%1").arg(key), val); } diff --git a/libqf/libqfgui/src/model/tablemodel.cpp b/libqf/libqfgui/src/model/tablemodel.cpp index 2fff88940..1eebdbfca 100644 --- a/libqf/libqfgui/src/model/tablemodel.cpp +++ b/libqf/libqfgui/src/model/tablemodel.cpp @@ -672,7 +672,7 @@ QColor TableModel::contrastTextColor(const QColor &background_color) return (qGray(background_color.rgb()) < 128)? QColor(Qt::white) : QColor(Qt::black); } -void TableModel::handleQxRecChng(const core::sql::QxRecChng &recchng, QObject *source) +void TableModel::applyQxRecChng(const core::sql::QxRecChng &recchng, QObject *source) { // qfInfo() << __FUNCTION__ << source; if (source == this) { @@ -685,12 +685,28 @@ void TableModel::handleQxRecChng(const core::sql::QxRecChng &recchng, QObject *s return i; } } + qfMessage() << "cannot find table row with id:" << id; } else { - qfWarning() << "cannot find table column:" << (table_name + ".id"); + // not every change is addressing every table model + qfMessage() << metaObject()->className() << objectName() << "cannot find table column:" << (table_name + ".id"); } return -1; }; - if (recchng.op == qf::core::sql::RecOp::Update) { + if (recchng.op == qf::core::sql::RecOp::Insert) { + if (int row = find_row_by_id(recchng.table, recchng.id); row >= 0) { + // row exists already + return; + } + if (auto fld_ix = table().fields().fieldIndex(recchng.table + ".id"); fld_ix >= 0) { + beginInsertRows({}, 0, 0); + auto row = insertTableRow(0); + tableRowRef(row).setValue(fld_ix, recchng.id); + endInsertRows(); + qfMessage() << "reloading inserted row:" << row << "table:" << recchng.table; + reloadRow(row); + } + } + else if (recchng.op == qf::core::sql::RecOp::Update) { if (int row = find_row_by_id(recchng.table, recchng.id); row >= 0) { for (const auto &[k, v] : recchng.record.asKeyValueRange()) { if (auto col_ix = columnIndexOfTableField(recchng.table + '.' + k); col_ix.has_value()) { @@ -707,12 +723,18 @@ void TableModel::handleQxRecChng(const core::sql::QxRecChng &recchng, QObject *s } } else { // calculated column - qfInfo() << "reloading calculated field, row:" << row << (recchng.table + '.' + k) << "-->" << v; + qfMessage() << "reloading calculated field, row:" << row << (recchng.table + '.' + k) << "-->" << v; reloadRow(row); } } } } + else if (recchng.op == qf::core::sql::RecOp::Delete) { + if (int row = find_row_by_id(recchng.table, recchng.id); row >= 0) { + qfMessage() << "deleteing row:" << row << (recchng.table + ".id") << recchng.id; + removeRowNoOverload(row, !qf::core::Exception::Throw); + } + } } QVariant TableModel::rawValueToEdit(int column_index, const QVariant &val) const diff --git a/libqf/libqfgui/src/model/tablemodel.h b/libqf/libqfgui/src/model/tablemodel.h index fc2c4b805..fded27d00 100644 --- a/libqf/libqfgui/src/model/tablemodel.h +++ b/libqf/libqfgui/src/model/tablemodel.h @@ -209,7 +209,7 @@ class QFGUI_DECL_EXPORT TableModel : public QAbstractTableModel static QColor contrastTextColor(const QColor &background_color); - void handleQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + void applyQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); protected: virtual void checkColumns(); void createColumnsFromTableFields(); diff --git a/quickevent/app/quickevent/CMakeLists.txt b/quickevent/app/quickevent/CMakeLists.txt index b0cf1f556..1d7ada5a4 100644 --- a/quickevent/app/quickevent/CMakeLists.txt +++ b/quickevent/app/quickevent/CMakeLists.txt @@ -107,7 +107,7 @@ add_executable(quickevent plugins/Event/src/services/qx/qxeventservice.cpp plugins/Event/src/services/qx/qxeventservice.h plugins/Event/src/services/qx/qxeventservicewidget.cpp plugins/Event/src/services/qx/qxeventservicewidget.h plugins/Event/src/services/qx/qxeventservicewidget.ui plugins/Event/src/services/qx/qxlateentrieswidget.h plugins/Event/src/services/qx/qxlateentrieswidget.cpp plugins/Event/src/services/qx/qxlateentrieswidget.ui - plugins/Event/src/services/qx/runchangedialog.h plugins/Event/src/services/qx/runchangedialog.cpp plugins/Event/src/services/qx/runchangedialog.ui + plugins/Event/src/services/qx/lateentrydialog.h plugins/Event/src/services/qx/lateentrydialog.cpp plugins/Event/src/services/qx/lateentrydialog.ui plugins/Event/src/services/qx/runchange.h plugins/Event/src/services/qx/runchange.cpp plugins/Oris/src/chooseoriseventdialog.cpp diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp index 2c3db126d..45c6db000 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.cpp @@ -522,7 +522,7 @@ void CardReaderWidget::reload() void CardReaderWidget::onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source) { if(isVisible()) { - m_cardsModel->handleQxRecChng(recchng, source); + m_cardsModel->applyQxRecChng(recchng, source); } } diff --git a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h index 03b7a6aa5..3e2f312b7 100644 --- a/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h +++ b/quickevent/app/quickevent/plugins/CardReader/src/cardreaderwidget.h @@ -68,7 +68,7 @@ class CardReaderWidget : public QFrame Q_SLOT void reload(); private: - void onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + void onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); void onDbEventNotify(const QString &domain, int connection_id, const QVariant &data); void appendLog(NecroLog::Level level, const QString &msg); void processDriverInfo(NecroLog::Level level, const QString &msg); diff --git a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml index 00bcbb863..df5393e31 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/DbSchema.qml @@ -494,14 +494,13 @@ Schema { fields: [ Field { name: 'id'; type: Serial { primaryKey: true } }, Field { name: 'stage_id'; type: Int { } }, - // Field { name: 'change_id'; type: Int { } }, // not used - Field { name: 'data_id'; type: Int { } }, // not used + // Field { name: 'foreign_table'; type: String { } }, + Field { name: 'foreign_id'; type: Int { } }, Field { name: 'data_type'; type: String { } }, Field { name: 'data'; type: String { } }, Field { name: 'orig_data'; type: String { } comment: 'Store data overriden by change here to enable change rollback.' }, - Field { name: 'source'; type: String { } }, // issuer Field { name: 'user_id'; type: String { } }, Field { name: 'status'; type: String { } }, Field { name: 'status_message'; type: String { } }, @@ -512,8 +511,13 @@ Schema { Field { name: 'lock_number'; type: Int { } } ] indexes: [ - Index {fields: ['stage_id', 'data_type', 'data_id']; unique: false }, - Index {fields: ['stage_id', 'status']; unique: false } + Index {fields: ['stage_id', 'data_type', 'foreign_id']; unique: false }, + Index {fields: ['stage_id', 'status']; unique: false }, + Index {fields: ['stage_id', 'foreign_id'] + where: "status = 'Pending' AND data_type = 'LateEntry' AND foreign_id IS NOT NULL" + comment: 'Only one pending late entry can be present for single runs.id (foreign_id).' + unique: true + } ] } ] diff --git a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Index.qml b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Index.qml index bc72921c5..12986f898 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Index.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Index.qml @@ -9,6 +9,7 @@ QtObject property bool primary: false property bool unique: false property ForeignKeyReference references: null + property string where property string comment function indexName(cnt, table_name) @@ -55,21 +56,29 @@ QtObject else if(primary) { ret += '\tCONSTRAINT ' + indexName(constr_no, options.tableName) + ' PRIMARY KEY (' + fields.join(', ') + ')'; } - else if(unique) { + else if(unique && !where) { ret += '\tCONSTRAINT ' + indexName(constr_no, options.tableName) + ' UNIQUE (' + fields.join(', ') + ')'; } } return ret; } - function createSqlIndexScript(options) + function createSqlIndexScript(index_name, table_name, options) { - var ret = ''; if(fields) { - if(!primary && !unique && !references) { - ret = '(' + fields.join(', ') + ')'; + if (where) { + return 'CREATE ' + + (unique? 'UNIQUE ': ' ') + +'INDEX ' + index_name + + ' ON ' + table_name + ' (' + fields.join(', ') + ')' + + ' WHERE ' + where; + } + else if(!primary && !unique && !references) { + return 'CREATE INDEX ' + index_name + + ' ON ' + table_name + + ' (' + fields.join(', ') + ')'; } } - return ret; + return ''; } } diff --git a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Table.qml b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Table.qml index ab5b750ca..a50ad1766 100644 --- a/quickevent/app/quickevent/plugins/Event/qml/sql/def/Table.qml +++ b/quickevent/app/quickevent/plugins/Event/qml/sql/def/Table.qml @@ -20,8 +20,8 @@ QtObject { var field_defs = []; options.tableName = name; var fieldNames = [] - for(var i=0; i #include #include +#include #include @@ -40,7 +41,6 @@ #include #include #include -#include #include #include @@ -73,23 +73,6 @@ using qff::getPlugin; namespace Event { -class DbEventPayload : public QVariantMap -{ -private: - typedef QVariantMap Super; - - QF_VARIANTMAP_FIELD(QString, e, setE, ventName) - QF_VARIANTMAP_FIELD(QString, d, setD, omain) - QF_VARIANTMAP_FIELD(int, c, setc, onnectionId) - QF_VARIANTMAP_FIELD(QVariant, d, setD, ata) -public: - DbEventPayload(const QVariantMap &data = QVariantMap()) : QVariantMap(data) {} - - static DbEventPayload fromJson(const QByteArray &json); - - QByteArray toJson() const; -}; - DbEventPayload DbEventPayload::fromJson(const QByteArray &json) { QJsonParseError error; diff --git a/quickevent/app/quickevent/plugins/Event/src/eventplugin.h b/quickevent/app/quickevent/plugins/Event/src/eventplugin.h index 50a8e077e..a29badbe4 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventplugin.h +++ b/quickevent/app/quickevent/plugins/Event/src/eventplugin.h @@ -26,6 +26,23 @@ namespace Event { static constexpr auto START_LIST_IOFXML3_FILE = "startlist-iof3.xml"; static constexpr auto RESULTS_IOFXML3_FILE = "results-iof3.xml"; +class DbEventPayload : public QVariantMap +{ +private: + typedef QVariantMap Super; + + QF_VARIANTMAP_FIELD(QString, e, setE, ventName) + QF_VARIANTMAP_FIELD(QString, d, setD, omain) + QF_VARIANTMAP_FIELD(int, c, setc, onnectionId) + QF_VARIANTMAP_FIELD(QVariant, d, setD, ata) +public: + DbEventPayload(const QVariantMap &data = QVariantMap()) : QVariantMap(data) {} + + static DbEventPayload fromJson(const QByteArray &json); + + QByteArray toJson() const; +}; + class EventPlugin : public qf::gui::framework::Plugin { Q_OBJECT @@ -60,7 +77,6 @@ class EventPlugin : public qf::gui::framework::Plugin static constexpr auto DBEVENT_PUNCH_RECEIVED = "punchReceived"; static constexpr auto DBEVENT_REGISTRATIONS_IMPORTED = "registrationsImported"; static constexpr auto DBEVENT_STAGE_START_CHANGED = "stageStartChanged"; - static constexpr auto DBEVENT_QX_CHANGE_RECEIVED = "qxChangeReceived"; static constexpr auto DBEVENT_QX_RECCHNG = "qxRecChng"; Q_INVOKABLE void initEventConfig(); @@ -151,8 +167,7 @@ class EventPlugin : public qf::gui::framework::Plugin bool importEventFromFile(const QString &src_file, const QString &dest_event_name); bool convertSqlEvent(const QString &from_event, const QString &to_event); void deleteEvent(const QString &event_name); - QString copyEventSchema(qf::core::sql::Connection &imp_conn, qf::core::sql::Connection &exp_conn, - const QString &dest_schema_name); + QString copyEventSchema(qf::core::sql::Connection &imp_conn, qf::core::sql::Connection &exp_conn, const QString &dest_schema_name); void onServiceDockVisibleChanged(bool on = true); private: diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp similarity index 63% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp rename to quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp index e2c50edd5..aedcf62bf 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.cpp @@ -1,11 +1,13 @@ -#include "runchangedialog.h" -#include "ui_runchangedialog.h" +#include "lateentrydialog.h" +#include "ui_lateentrydialog.h" #include "qxeventservice.h" #include "runchange.h" #include +#include +#include #include #include @@ -13,15 +15,17 @@ #include #include #include +#include #include +#include namespace Event::services::qx { -RunChangeDialog::RunChangeDialog(int change_id, int run_id, int lock_number, const RunChange &run_change, QWidget *parent) +LateEntryDialog::LateEntryDialog(int change_id, int lock_number, const LateEntry &late_entry, QWidget *parent) : QDialog(parent) - , ui(new Ui::RunChangeDialog) + , ui(new Ui::LateEntryDialog) , m_changeId(change_id) - , m_runId(run_id) + , m_runId(late_entry.run_id) { ui->setupUi(this); @@ -39,26 +43,43 @@ RunChangeDialog::RunChangeDialog(int change_id, int run_id, int lock_number, con resolveChanges(false); }); - ui->edClassName->setText(run_change.class_name.value_or(QString())); - ui->edNote->setText(run_change.note); + ui->edClassName->setText(late_entry.record.class_name.value_or(QString())); + ui->edNote->setText(late_entry.record.note); - ui->edRunId->setValue(run_id); + ui->edRunId->setValue(m_runId); ui->edChangeId->setValue(change_id); ui->edLockNumber->setValue(lock_number); - ui->grpFirstName->setChecked(run_change.first_name.has_value()); - ui->edFirstName->setText(run_change.first_name.has_value()? run_change.first_name.value(): QString()); + auto update_background = [](QWidget *widget) { + bool is_set = false; + if (auto *le = qobject_cast(widget)) { + is_set = !le->text().isEmpty(); + } + else if (auto *spin_box = qobject_cast(widget)) { + is_set = spin_box->value() > 0; + } + if (is_set) { + widget->setStyleSheet("background: honeydew"); + } + }; - ui->grpLastName->setChecked(run_change.last_name.has_value()); - ui->edLastName->setText(run_change.last_name.has_value()? run_change.last_name.value(): QString()); + ui->grpFirstName->setChecked(late_entry.record.first_name.has_value()); + ui->edFirstName->setText(late_entry.record.first_name.has_value()? late_entry.record.first_name.value(): QString()); + update_background(ui->edFirstName); - ui->grpRegistration->setChecked(run_change.registration.has_value()); - ui->edRegistration->setText(run_change.registration.has_value()? run_change.registration.value(): QString()); + ui->grpLastName->setChecked(late_entry.record.last_name.has_value()); + ui->edLastName->setText(late_entry.record.last_name.has_value()? late_entry.record.last_name.value(): QString()); + update_background(ui->edLastName); - ui->grpSiCard->setChecked(run_change.si_id.has_value()); - ui->edSiCard->setValue(run_change.si_id.has_value()? run_change.si_id.value(): 0); + ui->grpRegistration->setChecked(late_entry.record.registration.has_value()); + ui->edRegistration->setText(late_entry.record.registration.has_value()? late_entry.record.registration.value(): QString()); + update_background(ui->edRegistration); - if (run_id > 0) { + ui->grpSiCard->setChecked(late_entry.record.si_id.has_value()); + ui->edSiCard->setValue(late_entry.record.si_id.has_value()? late_entry.record.si_id.value(): 0); + update_background(ui->edSiCard); + + if (m_runId > 0) { loadOrigValues(); } else { @@ -67,19 +88,19 @@ RunChangeDialog::RunChangeDialog(int change_id, int run_id, int lock_number, con lockChange(); } -RunChangeDialog::~RunChangeDialog() +LateEntryDialog::~LateEntryDialog() { delete ui; } -QxEventService *RunChangeDialog::service() +QxEventService *LateEntryDialog::service() { auto *svc = qobject_cast(Service::serviceByName(QxEventService::serviceId())); Q_ASSERT(svc); return svc; } -void RunChangeDialog::setMessage(const QString &msg, bool error) +void LateEntryDialog::setMessage(const QString &msg, bool error) { if (msg.isEmpty()) { ui->lblError->setStyleSheet({}); @@ -93,7 +114,7 @@ void RunChangeDialog::setMessage(const QString &msg, bool error) ui->lblError->setText(msg); } -void RunChangeDialog::loadOrigValues() +void LateEntryDialog::loadOrigValues() { Q_ASSERT(m_runId > 0); @@ -123,7 +144,7 @@ void RunChangeDialog::loadOrigValues() ui->edSiCardOrig->setValue(m_origValues.si_id); } -void RunChangeDialog::loadClassId() +void LateEntryDialog::loadClassId() { auto class_name = ui->edClassName->text(); if (class_name.isEmpty()) { @@ -140,7 +161,7 @@ void RunChangeDialog::loadClassId() } } -void RunChangeDialog::lockChange() +void LateEntryDialog::lockChange() { // NIY // auto *svc = service(); @@ -200,42 +221,7 @@ void RunChangeDialog::lockChange() // }); } -void RunChangeDialog::resolveChanges(bool is_accepted) -{ - if (is_accepted) { - applyLocalChanges(is_accepted); - } - auto *svc = service(); - auto *nm = svc->networkManager(); - QNetworkRequest request; - auto url = svc->shvBrokerUrl(); - // qfInfo() << "url " << url.toString(); - url.setPath("/api/event/current/changes/resolve-change"); - - QUrlQuery query; - query.addQueryItem("change_id", QString::number(m_changeId)); - query.addQueryItem("lock_number", QString::number(m_lockNumber)); - query.addQueryItem("accepted", is_accepted? "true": "false"); - query.addQueryItem("status_message", ui->edStatusMessage->text()); - url.setQuery(query); - - request.setUrl(url); - request.setRawHeader(QxEventService::QX_API_TOKEN, svc->apiToken().toUtf8()); - auto *reply = nm->get(request); - connect(reply, &QNetworkReply::finished, this, [this, reply]() { - if (reply->error() == QNetworkReply::NetworkError::NoError) { - accept(); - } - else { - QMessageBox::warning(this, - QCoreApplication::applicationName(), - tr("Update change error: %1").arg(reply->errorString())); - } - reply->deleteLater(); - }); -} - -void RunChangeDialog::applyLocalChanges(bool is_accepted) +void LateEntryDialog::resolveChanges(bool is_accepted) { using namespace qf::gui::model; @@ -267,33 +253,12 @@ void RunChangeDialog::applyLocalChanges(bool is_accepted) } } doc.save(); - { - qf::core::sql::Query q; - q.execThrow(QStringLiteral("UPDATE qxchanges SET status='%1' WHERE id=%2") - .arg(is_accepted? "Accepted": "Rejected") - .arg(m_changeId) - ); - } - { - auto dc_str = qf::core::Utils::qvariantToJson(m_origValues.toVariantMap()); - QString qs = "UPDATE qxchanges SET orig_data=:orig_data WHERE id=:id"; - qf::core::sql::Query q; - q.prepare(qs, qf::core::Exception::Throw); - q.bindValue(":orig_data", dc_str); - q.bindValue(":id", m_changeId); - q.exec(qf::core::Exception::Throw); - } -} -bool RunChangeDialog::checkHttpError(QNetworkReply *reply) -{ - if (reply->error() != QNetworkReply::NetworkError::NoError) { - setMessage(tr("Http error: %1\n%2") - .arg(reply->request().url().toString()) - .arg(reply->errorString()), true); - return false; - } - return true; + qf::core::sql::Record rec { + {"status", is_accepted? "Accepted": "Rejected"}, + {"orig_data", qf::core::Utils::qvariantToJson(m_origValues.toVariantMap())}, + }; + qf::gui::framework::Application::instance()->qxSql()->updateRecord("qxchanges", m_changeId, rec, this); } } // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h similarity index 55% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h rename to quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h index 15a9bf147..d6be39898 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.h @@ -1,5 +1,5 @@ -#ifndef RUNCHANGEDIALOG_H -#define RUNCHANGEDIALOG_H +#ifndef LATEENTRYDIALOG_H +#define LATEENTRYDIALOG_H #include "runchange.h" @@ -10,19 +10,19 @@ class QNetworkReply; namespace Event::services::qx { namespace Ui { -class RunChangeDialog; +class LateEntryDialog; } -struct RunChange; +struct LateEntryRecord; class QxEventService; -class RunChangeDialog : public QDialog +class LateEntryDialog : public QDialog { Q_OBJECT public: - explicit RunChangeDialog(int change_id, int run_id, int lock_number, const RunChange &run_change, QWidget *parent = nullptr); - ~RunChangeDialog() override; + explicit LateEntryDialog(int change_id, int lock_number, const LateEntry &late_entry, QWidget *parent = nullptr); + ~LateEntryDialog() override; private: QxEventService* service(); @@ -34,11 +34,8 @@ class RunChangeDialog : public QDialog void lockChange(); void resolveChanges(bool is_accepted); - void applyLocalChanges(bool is_accepted); - - bool checkHttpError(QNetworkReply *reply); private: - Ui::RunChangeDialog *ui; + Ui::LateEntryDialog *ui; int m_changeId = 0; int m_runId = 0; int m_competitorId = 0; @@ -51,4 +48,4 @@ class RunChangeDialog : public QDialog } // namespace Event::services::qx -#endif // RUNCHANGEDIALOG_H +#endif // LATEENTRYDIALOG_H diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.ui b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui similarity index 95% rename from quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.ui rename to quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui index c18f157b3..b07ff471c 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchangedialog.ui +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/lateentrydialog.ui @@ -1,7 +1,7 @@ - Event::services::qx::RunChangeDialog - + Event::services::qx::LateEntryDialog + 0 @@ -13,7 +13,7 @@ Dialog - + @@ -308,6 +308,16 @@ + + + + Message + + + + + + @@ -321,20 +331,6 @@ - - - - - - Message - - - - - - - - @@ -402,7 +398,6 @@ edSiCard edSiCardRent edNote - edStatusMessage chkForce btAccept btReject @@ -413,7 +408,7 @@ btCancel clicked() - Event::services::qx::RunChangeDialog + Event::services::qx::LateEntryDialog reject() diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp index 6d43c3b4c..999da24c4 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.cpp @@ -2,7 +2,6 @@ #include "qxeventservicewidget.h" #include "nodes.h" #include "sqlapinode.h" -#include "sqlapi.h" #include "../../eventplugin.h" #include "../../../../Runs/src/runsplugin.h" @@ -103,7 +102,6 @@ void QxEventService::run() { void QxEventService::stop() { m_eventId = 0; - disconnectSSE(); if (m_pollChangesTimer) { m_pollChangesTimer->stop(); } @@ -384,113 +382,6 @@ void QxEventService::httpPostJson(const QString &path, const QString &query, QVa } } -void QxEventService::connectToSSE(int event_id) -{ - Q_UNUSED(event_id); - // auto url = exchangeServerUrl(); - // url.setPath(QStringLiteral("/api/event/%1/run/changes/sse").arg(event_id)); - // QNetworkRequest request(url); - // request.setRawHeader(QByteArray("Accept"), QByteArray("text/event-stream")); - // request.setHeader(QNetworkRequest::UserAgentHeader, "QuickEvent"); - // request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); - // request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); // Events shouldn't be cached - - // qfInfo() << "Connecting to SSE:" << url.toString(); - // m_replySSE = networkManager()->get(request); - // qfInfo() << "Connected"; - // connect(m_replySSE, &QNetworkReply::readyRead, this, [this]() { - // auto data = m_replySSE->readAll(); - // qfInfo() << "DATA:" << data.toStdString(); - // }); - // connect(m_replySSE, &QNetworkReply::finished, this, [this]() { - // qfInfo() << "SSE finished:" << m_replySSE->errorString(); - // }); -} - -void QxEventService::disconnectSSE() -{ - if (m_replySSE) { - qfInfo() << "Disconnecting SSE:" << m_replySSE; - m_replySSE->deleteLater(); - m_replySSE = nullptr; - } -} - -void QxEventService::pollQxChanges() -{ - auto event_plugin = getPlugin(); - if(!getPlugin()->isEventOpen()) { - return; - } - int stage_id = event_plugin->currentStageId(); - try { - int max_change_id = 0; - qf::core::sql::Query q; - q.execThrow("SELECT MAX(change_id) FROM qxchanges WHERE stage_id=" + QString::number(event_plugin->currentStageId())); - if (q.next()) { - max_change_id = q.value(0).toInt(); - } - auto *reply = getQxChangesReply(max_change_id + 1); - connect(reply, &QNetworkReply::finished, this, [reply, stage_id]() { - QString err; - if(reply->error()) { - err = reply->errorString(); - qfWarning() << "Load qxchanges error:" << err; - } - else { - QJsonParseError err; - auto data = reply->readAll(); - auto json = QJsonDocument::fromJson(data, &err); - if (err.error != QJsonParseError::NoError) { - qfWarning() << "Parse qxchanges error:" << err.errorString(); - } - else { - auto records = json.array().toVariantList(); - qf::core::sql::Query q; - q.prepare("INSERT INTO qxchanges (data_type, data, data_id, source, user_id, status, status_message, stage_id, change_id, created)" - " VALUES (:data_type, :data, :data_id, :source, :user_id, :status, :status_message, :stage_id, :change_id, :created)" - " RETURNING id"); - for (const auto &v : records) { - auto rec = v.toMap(); - auto ba = QJsonDocument::fromVariant(rec.value("data")).toJson(QJsonDocument::Compact); - auto data = QString::fromUtf8(ba); - q.bindValue(":data_type", rec.value("data_type")); - q.bindValue(":data", data); - q.bindValue(":data_id", rec.value("data_id")); - q.bindValue(":source", rec.value("source")); - q.bindValue(":user_id", rec.value("user_id")); - q.bindValue(":status", rec.value("status")); - q.bindValue(":status_message", rec.value("status_message")); - q.bindValue(":stage_id", stage_id); - auto change_id = rec.value("id").toInt(); - Q_ASSERT(change_id > 0); - q.bindValue(":change_id", change_id); - auto created = QDateTime::fromString(rec.value("created").toString(), Qt::ISODate); - qfDebug() << "created:" << created.toString(Qt::ISODate); - q.bindValue(":created", created); - // may fail on prikey violation if more clients are inserting simultaneously - if (q.exec()) { - if (q.next()) { - auto id = q.value(0).toInt(); - qfDebug() << "insert ID:" << id; - getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_QX_CHANGE_RECEIVED, id, true); - } - } - else { - qfInfo() << "sql error:" << q.lastErrorText(); - - } - } - } - } - reply->deleteLater(); - }); - } - catch (const qf::core::Exception &e) { - qfWarning() << "Load qxchanges error:" << e.message(); - } -} - EventInfo QxEventService::eventInfo() const { auto *event_plugin = getPlugin(); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h index fc5af4120..7c0eb71c2 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxeventservice.h @@ -79,7 +79,6 @@ class QxEventService : public Service void sendRpcMessage(const shv::chainpack::RpcMessage &rpc_msg); void onBrokerSocketError(const QString &err); void onBrokerLoginError(const shv::chainpack::RpcError &err); - // void sendRecchgShvSignal(const qf::core::sql::QxRecChng &chng); void subscribeChanges(); private: @@ -92,11 +91,6 @@ class QxEventService : public Service void httpPostJson(const QString &path, const QString &query, QVariantMap json, QObject *context = nullptr, const std::function &call_back = nullptr); - void connectToSSE(int event_id); - void disconnectSSE(); - - void pollQxChanges(); - EventInfo eventInfo() const; private: // shv shv::iotqt::rpc::DeviceConnection *m_rpcConnection = nullptr; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp index 1a25ce21b..edaf69723 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.cpp @@ -2,15 +2,18 @@ #include "ui_qxlateentrieswidget.h" #include "qxeventservice.h" -#include "runchangedialog.h" +#include "lateentrydialog.h" #include "runchange.h" #include #include +#include #include #include #include +#include +#include #include #include @@ -23,14 +26,12 @@ using qf::gui::framework::getPlugin; namespace Event::services::qx { constexpr auto COL_ID = "id"; -constexpr auto COL_CHANGE_ID = "change_id"; constexpr auto COL_DATA_TYPE = "data_type"; -constexpr auto COL_DATA_ID = "data_id"; +constexpr auto COL_FOREIGN_ID = "foreign_id"; constexpr auto COL_DATA = "data"; constexpr auto COL_ORIG_DATA = "orig_data"; constexpr auto COL_STATUS = "status"; constexpr auto COL_STATUS_MESSAGE = "status_message"; -constexpr auto COL_SOURCE = "source"; constexpr auto COL_USER_ID = "user_id"; constexpr auto COL_CREATED = "created"; constexpr auto COL_LOCK_NUMBER = "lock_number"; @@ -38,10 +39,6 @@ constexpr auto COL_LOCK_NUMBER = "lock_number"; constexpr auto STATUS_PENDING = "Pending"; constexpr auto STATUS_LOCKED = "Locked"; -constexpr auto DATA_TYPE_RUN_UPDATE_REQUEST = "RunUpdateRequest"; - -constexpr auto SOURCE_WWW = "www"; - QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : QWidget(parent), ui(new Ui::QxLateEntriesWidget) @@ -65,12 +62,10 @@ QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : m_model->addColumn(COL_ID).setReadOnly(true).setAlignment(Qt::AlignLeft); m_model->addColumn(COL_STATUS, tr("Status")); m_model->addColumn(COL_DATA_TYPE, tr("Type")); - m_model->addColumn(COL_DATA_ID, tr("Data ID")).setAlignment(Qt::AlignLeft); - m_model->addColumn(COL_SOURCE, tr("Source")); + m_model->addColumn(COL_FOREIGN_ID, tr("Foreign ID")).setAlignment(Qt::AlignLeft); m_model->addColumn(COL_USER_ID, tr("User")); m_model->addColumn(COL_STATUS_MESSAGE, tr("Status message")); m_model->addColumn(COL_CREATED, tr("Created")); - m_model->addColumn(COL_CHANGE_ID, tr("Change ID")); m_model->addColumn(COL_LOCK_NUMBER, tr("Lock")); m_model->addColumn(COL_DATA, tr("Data"));//.setToolTip(tr("Locked for drawing")); m_model->addColumn(COL_ORIG_DATA, tr("Orig data"));//.setToolTip(tr("Locked for drawing")); @@ -95,8 +90,6 @@ QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : } }); - connect(getPlugin(), &Event::EventPlugin::dbEventNotify, this, &QxLateEntriesWidget::onDbEventNotify, Qt::QueuedConnection); - { auto *lst = ui->lstType; lst->addItem("All"); @@ -131,6 +124,7 @@ QxLateEntriesWidget::QxLateEntriesWidget(QWidget *parent) : reload(); }); + connect(qf::gui::framework::Application::instance()->qxSql(), &qf::core::sql::QxSql::recChng, this, &QxLateEntriesWidget::onQxRecChng, Qt::QueuedConnection); } QxLateEntriesWidget::~QxLateEntriesWidget() @@ -138,15 +132,6 @@ QxLateEntriesWidget::~QxLateEntriesWidget() delete ui; } -void QxLateEntriesWidget::onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload) -{ - Q_UNUSED(connection_id) - if(domain == QLatin1String(Event::EventPlugin::DBEVENT_QX_CHANGE_RECEIVED)) { - int sql_id = payload.toInt(); - addQxChangeRow(sql_id); - } -} - void QxLateEntriesWidget::onVisibleChanged(bool is_visible) { if (is_visible && isEnabled()) { @@ -227,7 +212,7 @@ void QxLateEntriesWidget::reload() m_model->reload(); } -void QxLateEntriesWidget::addQxChangeRow(int sql_id) +void QxLateEntriesWidget::applyQxChange(int sql_id) { qfDebug() << "reloading qxchanges row id:" << sql_id << "col id:" << COL_ID; if(sql_id <= 0) { @@ -252,23 +237,6 @@ void QxLateEntriesWidget::addQxChangeRow(int sql_id) } } - -void QxLateEntriesWidget::applyCurrentChange() -{ - auto row = ui->tableView->currentIndex().row(); - if (row < 0) { - return; - } - auto status = m_model->value(row, COL_STATUS).toString(); - if (status != STATUS_PENDING) { - return; - } - //auto run_id = m_model->value(row, COL_RUN_ID).toInt(); - //int competitor_id = 0; - // int result = getPlugin()->editCompetitor(competitor_id, run_id == 0? Mode::Insert: Mode::Edit); - -} - void QxLateEntriesWidget::onTableCustomContextMenuRequest(const QPoint &pos) { QAction a_neco(tr("Neco"), nullptr); @@ -283,27 +251,24 @@ void QxLateEntriesWidget::onTableCustomContextMenuRequest(const QPoint &pos) void QxLateEntriesWidget::onTableDoubleClicked(const QModelIndex &ix) { auto row = ix.row(); - auto data_type = m_model->value(row, COL_DATA_TYPE).toString(); - if (data_type != DATA_TYPE_RUN_UPDATE_REQUEST) { - return; - } - auto source = m_model->value(row, COL_SOURCE).toString(); - if (source != SOURCE_WWW) { - return; - } - auto change_id = m_model->value(row, COL_CHANGE_ID).toInt(); auto status = m_model->value(row, COL_STATUS).toString(); - auto lock_number = m_model->value(row, COL_LOCK_NUMBER).toInt(); - auto data_id = m_model->value(row, COL_DATA_ID).toInt(); - auto data = m_model->value(row, COL_DATA).toString().toUtf8(); - auto change_rec = QJsonDocument::fromJson(data).toVariant().toMap(); - auto run_change = RunChange::fromVariantMap(change_rec.value(DATA_TYPE_RUN_UPDATE_REQUEST).toMap()); if (status == STATUS_PENDING || status == STATUS_LOCKED) { - RunChangeDialog dlg(change_id, data_id, lock_number, run_change, this); - dlg.exec(); - ui->tableView->reloadRow(row); + auto data = m_model->value(row, COL_DATA).toString(); + auto qxdata = QxChangeData::fromJson(data); + if (qxdata.lateEntry.has_value()) { + auto lock_number = m_model->value(row, COL_LOCK_NUMBER).toInt(); + auto change_id = m_model->value(row, COL_ID).toInt(); + LateEntryDialog dlg(change_id, lock_number, qxdata.lateEntry.value(), this); + dlg.exec(); + ui->tableView->reloadRow(row); + } } } +void QxLateEntriesWidget::onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source) +{ + m_model->applyQxRecChng(recchng, source); +} + } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h index 5ae69ac66..85dd3265e 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/qxlateentrieswidget.h @@ -2,6 +2,7 @@ #include +namespace qf::core::sql { struct QxRecChng; } namespace qf::gui::model { class SqlTableModel; } namespace Event::services::qx { @@ -20,20 +21,19 @@ class QxLateEntriesWidget : public QWidget explicit QxLateEntriesWidget(QWidget *parent = nullptr); ~QxLateEntriesWidget() override; - void onDbEventNotify(const QString &domain, int connection_id, const QVariant &payload); void onVisibleChanged(bool is_visible); private: QxEventService* qxEventService(); void reload(); - void addQxChangeRow(int sql_id); + void applyQxChange(int sql_id); void resizeColumns(); void showMessage(const QString &msg, bool is_error = false); - void applyCurrentChange(); void onTableCustomContextMenuRequest(const QPoint &pos); void onTableDoubleClicked(const QModelIndex &ix); + void onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source); private: Ui::QxLateEntriesWidget *ui; qf::gui::model::SqlTableModel *m_model; diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp index c9a84e23b..3cd7c04d2 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.cpp @@ -11,21 +11,31 @@ namespace Event::services::qx { // return ret; // } -RunChange RunChange::fromVariantMap(const QVariantMap &map) +LateEntryRecord LateEntryRecord::fromVariantMap(const QVariantMap &map) { - RunChange ret; + LateEntryRecord ret; if (auto v = map.value("class_name"); v.isValid()) { ret.class_name = v.toString(); } - if (auto v = map.value("first_name"); v.isValid()) { ret.first_name = v.toString(); } - if (auto v = map.value("last_name"); v.isValid()) { ret.last_name = v.toString(); } + if (auto v = map.value("firstname"); v.isValid()) { ret.first_name = v.toString(); } + if (auto v = map.value("lastname"); v.isValid()) { ret.last_name = v.toString(); } if (auto v = map.value("registration"); v.isValid()) { ret.registration = v.toString(); } - if (auto v = map.value("si_id"); v.isValid()) { ret.si_id = v.toInt(); } + if (auto v = map.value("siid"); v.isValid()) { ret.si_id = v.toInt(); } // if (auto v = map.value("si_id_rent"); v.isValid()) { ret.si_id_rent = v.toBool(); } ret.note = map.value("note").toString(); return ret; } +LateEntry LateEntry::fromVariantMap(const QVariantMap &map) +{ + LateEntry ret; + + ret.record = LateEntryRecord::fromVariantMap(map.value("record").toMap()); + ret.run_id = map.value("run_id").toInt(); + + return ret; +} + QVariantMap OrigRunRecord::toVariantMap() const { QVariantMap ret; @@ -36,6 +46,16 @@ QVariantMap OrigRunRecord::toVariantMap() const return ret; } +QxChangeData QxChangeData::fromJson(const QString &json) +{ + QxChangeData ret; + auto map = QJsonDocument::fromJson(json.toUtf8()).toVariant().toMap(); + if (auto v = map.value(DATA_TYPE_LATE_ENTRY); v.isValid()) { + ret.lateEntry = LateEntry::fromVariantMap(v.toMap()); + } + return ret; +} + } // namespace Event::services::qx diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h index 83ab57062..1577011d8 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/runchange.h @@ -6,7 +6,9 @@ namespace Event::services::qx { -struct RunChange +constexpr auto DATA_TYPE_LATE_ENTRY = "LateEntry"; + +struct LateEntryRecord { std::optional class_name; std::optional first_name; @@ -16,8 +18,24 @@ struct RunChange // std::optional si_id_rent; QString note; - // static RunChange fromJsonString(const QString &json); - static RunChange fromVariantMap(const QVariantMap &map); + // static LateEntryRecord fromJsonString(const QString &json); + static LateEntryRecord fromVariantMap(const QVariantMap &map); +}; + +struct LateEntry +{ + int run_id = 0; + LateEntryRecord record; + + // static LateEntryRecord fromJsonString(const QString &json); + static LateEntry fromVariantMap(const QVariantMap &map); +}; + +struct QxChangeData +{ + std::optional lateEntry; + + static QxChangeData fromJson(const QString &json); }; struct OrigRunRecord diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp index 3e99c1e50..25a68b6fc 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.cpp @@ -96,7 +96,8 @@ RpcValue qxRecChngToRpcValue(const qf::core::sql::QxRecChng &chng) SqlApi::SqlApi(QObject *parent) : QObject{parent} { - // connect(qf::gui::framework::Application::instance(), &qf::gui::framework::Application::qxRecChng, ) + auto *qxsql = qf::gui::framework::Application::instance()->qxSql(); + connect(qxsql, &qf::core::sql::QxSql::recChng, this, &SqlApi::recChng); } // void SqlApi::emitRecChng(const qf::core::sql::QxRecChng &chng) @@ -201,7 +202,7 @@ void SqlApi::transaction(const std::string &query, const shv::chainpack::RpcValu for (const auto &p : params) { qparams << shv::coreqt::rpc::rpcValueToQVariant(p); } - return qxsql->transaction(QString::fromStdString(query), qparams); + qxsql->transaction(QString::fromStdString(query), qparams); } RpcValue::List SqlApi::list(const std::string &table, const std::vector &fields, std::optional ids_above, std::optional limit) diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h index c0d9334b7..4e3294683 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapi.h @@ -38,6 +38,8 @@ class SqlApi : public QObject public: explicit SqlApi(QObject *parent = nullptr); + Q_SIGNAL void recChng(const qf::core::sql::QxRecChng &recchng, QObject *source); + ExecResult exec(const SqlQueryAndParams ¶ms); QueryResult query(const SqlQueryAndParams ¶ms); void transaction(const std::string &query, const shv::chainpack::RpcValue::List ¶ms); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp index a495e24b1..063e5d516 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/qx/sqlapinode.cpp @@ -23,6 +23,16 @@ SqlApiNode::SqlApiNode(shv::iotqt::node::ShvNode *parent) : Super("sql", parent) , m_sqlApi(new SqlApi(this)) { + connect(m_sqlApi, &SqlApi::recChng, this, [this](const qf::core::sql::QxRecChng &recchng, QObject *source) { + Q_UNUSED(source); + RpcSignal sig; + sig.setSignal("recchng"); + sig.setShvPath(shvPath().asString()); + sig.setParams(shv::coreqt::rpc::qVariantToRpcValue(recchng.toVariantMap())); + qfInfo() << "emit recchng:" << recchng.table; + emitSendRpcMessage(sig); + }); + } namespace { diff --git a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp index 83391b3ab..bac275c69 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runstablemodel.cpp @@ -363,7 +363,7 @@ void RunsTableModel::onDataChanged(const QModelIndex &top_left, const QModelInde void RunsTableModel::onQxRecChng(const qf::core::sql::QxRecChng &recchng, QObject *source) { - handleQxRecChng(recchng, source); + applyQxRecChng(recchng, source); } bool RunsTableModel::postRow(int row_no, bool throw_exc)