diff --git a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp index cd4908e6b..09a3481ce 100644 --- a/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/eventplugin.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -363,6 +364,9 @@ void EventPlugin::onInstalled() } fwk->menuBar()->actionForPath("view/toolbar")->addActionInto(tb->toggleViewAction()); + services::OResultsClient *oresults_client = new services::OResultsClient(this); + services::Service::addService(oresults_client); + services::EmmaClient *emma_client = new services::EmmaClient(this); services::Service::addService(emma_client); diff --git a/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.cpp b/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.cpp index 1d3697fa6..1432bafdc 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.cpp +++ b/quickevent/app/quickevent/plugins/Event/src/services/emmaclient.cpp @@ -190,14 +190,14 @@ void EmmaClient::exportResultsIofXml3() QString file_name = export_dir + '/' + ss.fileNameBase() + ".results.xml"; int current_stage = getPlugin()->currentStageId(); bool is_relays = getPlugin()->eventConfig()->isRelays(); - if (is_relays) { - QFile f(file_name); - if(f.open(QFile::WriteOnly)) { - f.write(getPlugin()->resultsIofXml30().toUtf8()); - } - } - else { - getPlugin()->exportResultsIofXml30Stage(current_stage, file_name); + + QString str = is_relays + ? getPlugin()->resultsIofXml30() + : getPlugin()->resultsIofXml30Stage(current_stage); + + QFile f(file_name); + if(f.open(QFile::WriteOnly)) { + f.write(str.toUtf8()); } } @@ -210,14 +210,14 @@ void EmmaClient::exportStartListIofXml3() QString file_name = export_dir + '/' + ss.fileNameBase() + ".startlist.xml"; int current_stage = getPlugin()->currentStageId(); bool is_relays = getPlugin()->eventConfig()->isRelays(); - if (is_relays) { - QFile f(file_name); - if(f.open(QFile::WriteOnly)) { - f.write(getPlugin()->startListIofXml30().toUtf8()); - } - } - else { - getPlugin()->exportStartListStageIofXml30(current_stage, file_name); + + QString str = is_relays + ? getPlugin()->startListIofXml30() + : getPlugin()->startListStageIofXml30(current_stage); + + QFile f(file_name); + if(f.open(QFile::WriteOnly)) { + f.write(str.toUtf8()); } } diff --git a/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp new file mode 100644 index 000000000..fd7962425 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.cpp @@ -0,0 +1,145 @@ +#include "oresultsclient.h" +#include "oresultsclientwidget.h" + +#include "../eventplugin.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace qfc = qf::core; +namespace qfw = qf::qmlwidgets; +namespace qfd = qf::qmlwidgets::dialogs; +namespace qfs = qf::core::sql; +using qf::qmlwidgets::framework::getPlugin; +using Event::EventPlugin; +using Relays::RelaysPlugin; +using Runs::RunsPlugin; + +namespace Event { +namespace services { + +OResultsClient::OResultsClient(QObject *parent) + : Super(OResultsClient::serviceName(), parent) +{ + m_networkManager = new QNetworkAccessManager(this); + m_exportTimer = new QTimer(this); + connect(m_exportTimer, &QTimer::timeout, this, &OResultsClient::onExportTimerTimeOut); + connect(this, &OResultsClient::statusChanged, [this](Status status) { + if(status == Status::Running) { + onExportTimerTimeOut(); + m_exportTimer->start(); + } + else { + m_exportTimer->stop(); + } + }); + connect(this, &OResultsClient::settingsChanged, this, &OResultsClient::init, Qt::QueuedConnection); + +} + +QString OResultsClient::serviceName() +{ + return QStringLiteral("OResults"); +} + +void OResultsClient::exportResultsIofXml3() +{ + int current_stage = getPlugin()->currentStageId(); + bool is_relays = getPlugin()->eventConfig()->isRelays(); + + QString str = is_relays + ? getPlugin()->resultsIofXml30() + : getPlugin()->resultsIofXml30Stage(current_stage); + + sendFile("results upload", "/results", str); +} + +void OResultsClient::exportStartListIofXml3() +{ + + int current_stage = getPlugin()->currentStageId(); + bool is_relays = getPlugin()->eventConfig()->isRelays(); + + QString str = is_relays + ? getPlugin()->startListIofXml30() + : getPlugin()->startListStageIofXml30(current_stage); + + sendFile("start list upload", "/start-lists?format=xml", str); +} + +qf::qmlwidgets::framework::DialogWidget *OResultsClient::createDetailWidget() +{ + auto *w = new OResultsClientWidget(); + return w; +} + +void OResultsClient::init() +{ + OResultsClientSettings ss = settings(); + m_exportTimer->setInterval(ss.exportIntervalSec() * 1000); +} + +void OResultsClient::onExportTimerTimeOut() +{ + if(status() != Status::Running) + return; + + exportResultsIofXml3(); +} + +void OResultsClient::loadSettings() +{ + Super::loadSettings(); + init(); +} + +void OResultsClient::sendFile(QString name, QString request_path, QString file) { + + QHttpMultiPart *multi_part = new QHttpMultiPart(QHttpMultiPart::FormDataType); + + QHttpPart api_key_part; + auto api_key = settings().apiKey(); + api_key_part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"apiKey\"")); + api_key_part.setBody(api_key.toUtf8()); + + QHttpPart file_part; + file_part.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/xml")); + file_part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"")); + file_part.setBody(file.toUtf8()); + + multi_part->append(api_key_part); + multi_part->append(file_part); + + QUrl url(API_URL + request_path); + QNetworkRequest request(url); + QNetworkReply *reply = m_networkManager->post(request, multi_part); + + connect(reply, &QNetworkReply::finished, [reply, name]() + { + if(reply->error()) + { + qfError() << "OReuslts.eu [" + name + "]: " + QString(reply->readAll()); + } + else + { + qfInfo() << "OReuslts.eu [" + name + "]: success"; + } + reply->deleteLater(); + }); +} + +}} diff --git a/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.h b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.h new file mode 100644 index 000000000..00c4eb22b --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclient.h @@ -0,0 +1,53 @@ +#ifndef ORESULTSCLIENT_H +#define ORESULTSCLIENT_H + +#pragma once + +#include "service.h" + +class QTimer; +class QNetworkAccessManager; + +namespace Event { +namespace services { + +class OResultsClientSettings : public ServiceSettings +{ + using Super = ServiceSettings; + + QF_VARIANTMAP_FIELD(QString, a, setA, piKey) + QF_VARIANTMAP_FIELD2(int, e, setE, xportIntervalSec, 15) +public: + OResultsClientSettings(const QVariantMap &o = QVariantMap()) : Super(o) {} +}; + +class OResultsClient : public Service +{ + Q_OBJECT + + using Super = Service; +public: + OResultsClient(QObject *parent); + + //void run() override; + //void stop() override; + OResultsClientSettings settings() const {return OResultsClientSettings(m_settings);} + + static QString serviceName(); + + void exportResultsIofXml3(); + void exportStartListIofXml3(); + void loadSettings() override; +private: + qf::qmlwidgets::framework::DialogWidget *createDetailWidget() override; + void onExportTimerTimeOut(); + void init(); + QTimer *m_exportTimer = nullptr; + QNetworkAccessManager *m_networkManager = nullptr; + void sendFile(QString name, QString request_path, QString file); + const QString API_URL = "https://api.oresults.eu"; +}; + +}} + +#endif // ORESULTSCLIENT_H diff --git a/quickevent/app/quickevent/plugins/Event/src/services/oresultsclientwidget.cpp b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclientwidget.cpp new file mode 100644 index 000000000..3fc4713ec --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclientwidget.cpp @@ -0,0 +1,88 @@ +#include "oresultsclientwidget.h" +#include "ui_oresultsclientwidget.h" +#include "oresultsclient.h" +#include "../eventplugin.h" +#include + +#include + +#include + +#include +using qf::qmlwidgets::framework::getPlugin; + +namespace Event { +namespace services { + +OResultsClientWidget::OResultsClientWidget(QWidget *parent) + : Super(parent) + , ui(new Ui::OResultsClientWidget) +{ + setPersistentSettingsId("OResultsClientWidget"); + ui->setupUi(this); + + OResultsClient *svc = service(); + if(svc) { + OResultsClientSettings ss = svc->settings(); + ui->edExportInterval->setValue(ss.exportIntervalSec()); + ui->edApiKey->setText(ss.apiKey()); + } + + connect(ui->btExportResultsXml30, &QPushButton::clicked, this, &OResultsClientWidget::onBtExportResultsXml30Clicked); + connect(ui->btExportStartListXml30, &QPushButton::clicked, this, &OResultsClientWidget::onBtExportStartListXml30Clicked); +} + +OResultsClientWidget::~OResultsClientWidget() +{ + delete ui; +} + +bool OResultsClientWidget::acceptDialogDone(int result) +{ + if(result == QDialog::Accepted) { + if(!saveSettings()) { + return false; + } + } + return true; +} + +OResultsClient *OResultsClientWidget::service() +{ + OResultsClient *svc = qobject_cast(Service::serviceByName(OResultsClient::serviceName())); + QF_ASSERT(svc, OResultsClient::serviceName() + " doesn't exist", return nullptr); + return svc; +} + +bool OResultsClientWidget::saveSettings() +{ + OResultsClient *svc = service(); + if(svc) { + OResultsClientSettings ss = svc->settings(); + ss.setExportIntervalSec(ui->edExportInterval->value()); + ss.setApiKey(ui->edApiKey->text().trimmed()); + + svc->setSettings(ss); + } + return true; +} + +void OResultsClientWidget::onBtExportResultsXml30Clicked() +{ + OResultsClient *svc = service(); + if(svc) { + saveSettings(); + svc->exportResultsIofXml3(); + } +} + +void OResultsClientWidget::onBtExportStartListXml30Clicked() +{ + OResultsClient *svc = service(); + if(svc) { + saveSettings(); + svc->exportStartListIofXml3(); + } +} +}} + diff --git a/quickevent/app/quickevent/plugins/Event/src/services/oresultsclientwidget.h b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclientwidget.h new file mode 100644 index 000000000..5f14dc3a5 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclientwidget.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +namespace Event { +namespace services { + +namespace Ui { +class OResultsClientWidget; +} + +class OResultsClient; + +class OResultsClientWidget : public qf::qmlwidgets::framework::DialogWidget +{ + Q_OBJECT + + using Super = qf::qmlwidgets::framework::DialogWidget; +public: + explicit OResultsClientWidget(QWidget *parent = nullptr); + ~OResultsClientWidget(); +private: + void onBtExportResultsXml30Clicked(); + void onBtExportStartListXml30Clicked(); + OResultsClient* service(); + bool saveSettings(); +private: + Ui::OResultsClientWidget *ui; + bool acceptDialogDone(int result); +}; + +}} + diff --git a/quickevent/app/quickevent/plugins/Event/src/services/oresultsclientwidget.ui b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclientwidget.ui new file mode 100644 index 000000000..0213f4acc --- /dev/null +++ b/quickevent/app/quickevent/plugins/Event/src/services/oresultsclientwidget.ui @@ -0,0 +1,122 @@ + + + Event::services::OResultsClientWidget + + + + 0 + 0 + 287 + 240 + + + + Results upload service for OResults.eu + + + + + + + + Export interval + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + sec + + + 5 + + + 600 + + + 5 + + + 15 + + + + + + + API key + + + + + + + + + + + + + + + + + + Results are exported at given interval and usually includes all neccesarry data. +Nonetheless, both Results and Start list can be exported manualy using the buttons bellow. +In case of unexpected errors, contact support@oresults.eu + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Export start list + + + + + + + Export results + + + + + + + + + + + + edExportInterval + edApiKey + btExportStartListXml30 + btExportResultsXml30 + + + + diff --git a/quickevent/app/quickevent/plugins/Event/src/services/services.pri b/quickevent/app/quickevent/plugins/Event/src/services/services.pri index d78c24a38..e94815f6e 100644 --- a/quickevent/app/quickevent/plugins/Event/src/services/services.pri +++ b/quickevent/app/quickevent/plugins/Event/src/services/services.pri @@ -1,4 +1,6 @@ HEADERS += \ + $$PWD/oresultsclient.h \ + $$PWD/oresultsclientwidget.h \ $$PWD/service.h \ $$PWD/emmaclient.h \ $$PWD/serviceswidget.h \ @@ -6,6 +8,8 @@ HEADERS += \ $$PWD/emmaclientwidget.h \ SOURCES += \ + $$PWD/oresultsclient.cpp \ + $$PWD/oresultsclientwidget.cpp \ $$PWD/service.cpp \ $$PWD/emmaclient.cpp \ $$PWD/serviceswidget.cpp \ @@ -13,5 +17,6 @@ SOURCES += \ $$PWD/emmaclientwidget.cpp \ FORMS += \ + $$PWD/oresultsclientwidget.ui \ $$PWD/servicewidget.ui \ $$PWD/emmaclientwidget.ui \ diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp index b1c28e0d8..5013832cd 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.cpp @@ -773,14 +773,8 @@ QVariantMap RunsPlugin::printAwardsOptionsWithDialog(const QVariantMap &opts) return ret; } -bool RunsPlugin::exportResultsIofXml30Stage(int stage_id, const QString &file_name) +QString RunsPlugin::resultsIofXml30Stage(int stage_id) { - QFile f(file_name); - if(!f.open(QIODevice::WriteOnly)) { - qfError() << "Cannot open file" << f.fileName() << "for writing."; - return false; - } - QDateTime stage_start_date_time = getPlugin()->stageStartDateTime(stage_id);//.toTimeSpec(Qt::OffsetFromUTC); //qfInfo() << stage_start_date_time << datetime_to_string(stage_start_date_time); qf::core::utils::TreeTable tt1 = stageResultsTable(stage_id, QString(), 0, false, true); @@ -944,7 +938,17 @@ bool RunsPlugin::exportResultsIofXml30Stage(int stage_id, const QString &file_na qf::core::utils::HtmlUtils::FromXmlListOptions opts; opts.setDocumentTitle(tr("E%1 IOF XML stage results").arg(tt1.value("stageId").toString())); - QString str = qf::core::utils::HtmlUtils::fromXmlList(result_list, opts); + return qf::core::utils::HtmlUtils::fromXmlList(result_list, opts); +} + +bool RunsPlugin::exportResultsIofXml30Stage(int stage_id, const QString &file_name) +{ + QFile f(file_name); + if(!f.open(QIODevice::WriteOnly)) { + qfError() << "Cannot open file" << f.fileName() << "for writing."; + return false; + } + QString str = resultsIofXml30Stage(stage_id); f.write(str.toUtf8()); qfInfo() << "exported:" << file_name; return true; @@ -2258,7 +2262,7 @@ void RunsPlugin::exportResultsHtmlStageWithLaps(const QString &laps_file_name, c } } -bool RunsPlugin::exportStartListStageIofXml30(int stage_id, const QString &file_name) +QString RunsPlugin::startListStageIofXml30(int stage_id) { QDateTime start00_datetime = getPlugin()->stageStartDateTime(stage_id); //console.info("start00_datetime:", start00_datetime, typeof start00_datetime) @@ -2349,14 +2353,20 @@ bool RunsPlugin::exportStartListStageIofXml30(int stage_id, const QString &file_ } qf::core::utils::HtmlUtils::FromXmlListOptions opts; opts.setDocumentTitle(tr("E%1 IOF XML stage results").arg(tt1.value("stageId").toString())); - QString str = qf::core::utils::HtmlUtils::fromXmlList(xml_root, opts); + return qf::core::utils::HtmlUtils::fromXmlList(xml_root, opts); +} + +bool RunsPlugin::exportStartListStageIofXml30(int stage_id, const QString &file_name) +{ QFile f(file_name); - if(f.open(QFile::WriteOnly)) { - f.write(str.toUtf8()); - qfInfo() << "exported:" << file_name; - return true; + if(!f.open(QIODevice::WriteOnly)) { + qfError() << "Cannot open file" << f.fileName() << "for writing."; + return false; } - return false; + QString str = startListStageIofXml30(stage_id); + f.write(str.toUtf8()); + qfInfo() << "exported:" << file_name; + return true; } void RunsPlugin::addStartTimeTextToClass(qf::core::utils::TreeTable &tt2, const qint64 start00_epoch_sec, const quickevent::gui::ReportOptionsDialog::StartTimeFormat start_time_format) diff --git a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.h b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.h index c0f6db0b4..4f7cbaa51 100644 --- a/quickevent/app/quickevent/plugins/Runs/src/runsplugin.h +++ b/quickevent/app/quickevent/plugins/Runs/src/runsplugin.h @@ -108,6 +108,8 @@ class RunsPlugin : public qf::qmlwidgets::framework::Plugin QString export_resultsHtmlStage(bool with_laps = false); void export_resultsHtmlStageWithLaps(); void export_resultsHtmlNStages(); + QString startListStageIofXml30(int stage_id); + QString resultsIofXml30Stage(int stage_id); private: Q_SLOT void onInstalled();