From 0dae2f94cb54f73a62a3e8352f2c80f91dfbfdeb 2017-07-10 08:09:56 From: Alexandre Leroux Date: 2017-07-10 08:09:56 Subject: [PATCH] Merge branch 'feature/AmdaProvider' into develop --- diff --git a/app/ui/MainWindow.ui b/app/ui/MainWindow.ui index 30bc87a..44aecb5 100644 --- a/app/ui/MainWindow.ui +++ b/app/ui/MainWindow.ui @@ -11,7 +11,7 @@ - QLop + SciQlop v0.0.1 true @@ -126,7 +126,7 @@ 0 0 800 - 26 + 28 diff --git a/core/include/Data/IDataProvider.h b/core/include/Data/IDataProvider.h index c627123..59799d2 100644 --- a/core/include/Data/IDataProvider.h +++ b/core/include/Data/IDataProvider.h @@ -4,6 +4,7 @@ #include #include +#include #include @@ -26,10 +27,11 @@ class IDataProvider : public QObject { public: virtual ~IDataProvider() noexcept = default; - virtual void requestDataLoading(const QVector &dateTimeList) = 0; + virtual void requestDataLoading(QUuid token, const QVector &dateTimeList) = 0; signals: - void dataProvided(std::shared_ptr dateSerie, const SqpDateTime &dateTime); + void dataProvided(QUuid token, std::shared_ptr dateSerie, + const SqpDateTime &dateTime); }; // Required for using shared_ptr in signals/slots diff --git a/core/src/Variable/VariableController.cpp b/core/src/Variable/VariableController.cpp index 350c766..24bebc2 100644 --- a/core/src/Variable/VariableController.cpp +++ b/core/src/Variable/VariableController.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,7 @@ struct VariableController::VariableControllerPrivate { std::unordered_map, std::shared_ptr > m_VariableToProviderMap; + std::unordered_map, QUuid> m_VariableToToken; }; VariableController::VariableController(QObject *parent) @@ -118,16 +120,21 @@ void VariableController::createVariable(const QString &name, /// in sciqlop auto dateTime = impl->m_TimeController->dateTime(); if (auto newVariable = impl->m_VariableModel->createVariable(name, dateTime)) { + auto token = QUuid::createUuid(); // store the provider impl->m_VariableToProviderMap[newVariable] = provider; + impl->m_VariableToToken[newVariable] = token; auto addDateTimeAcquired = [ this, varW = std::weak_ptr{newVariable} ]( - auto dataSeriesAcquired, auto dateTimeToPutInCache) + QUuid token, auto dataSeriesAcquired, auto dateTimeToPutInCache) { if (auto variable = varW.lock()) { - impl->m_VariableCacheController->addDateTime(variable, dateTimeToPutInCache); - variable->setDataSeries(dataSeriesAcquired); + auto varToken = impl->m_VariableToToken.at(variable); + if (varToken == token) { + impl->m_VariableCacheController->addDateTime(variable, dateTimeToPutInCache); + variable->setDataSeries(dataSeriesAcquired); + } } }; @@ -166,8 +173,9 @@ void VariableController::onRequestDataLoading(std::shared_ptr variable if (!dateTimeListNotInCache.empty()) { // Ask the provider for each data on the dateTimeListNotInCache + auto token = impl->m_VariableToToken.at(variable); impl->m_VariableToProviderMap.at(variable)->requestDataLoading( - std::move(dateTimeListNotInCache)); + token, std::move(dateTimeListNotInCache)); } else { emit variable->updated(); diff --git a/plugins/amda/CMakeLists.txt b/plugins/amda/CMakeLists.txt index bfa7e21..1b66326 100644 --- a/plugins/amda/CMakeLists.txt +++ b/plugins/amda/CMakeLists.txt @@ -13,7 +13,7 @@ INCLUDE_DIRECTORIES(${RESOURCES_DIR}) # # Find Qt modules # -SCIQLOP_FIND_QT(Core Widgets) +SCIQLOP_FIND_QT(Core Widgets Network) # # Find dependent libraries @@ -68,7 +68,7 @@ INSTALL(TARGETS ${SQPAMDA_LIBRARY_NAME} TARGET_LINK_LIBRARIES(${SQPAMDA_LIBRARY_NAME} ${LIBRARIES}) -qt5_use_modules(${SQPAMDA_LIBRARY_NAME} Core Widgets) +qt5_use_modules(${SQPAMDA_LIBRARY_NAME} Core Widgets Network) add_dependencies(${SQPAMDA_LIBRARY_NAME} ${SQPPLUGIN_LIBRARY_NAME} ${SQPGUI_LIBRARY_NAME} ${SQPCORE_LIBRARY_NAME}) diff --git a/plugins/amda/include/AmdaProvider.h b/plugins/amda/include/AmdaProvider.h new file mode 100644 index 0000000..38c5924 --- /dev/null +++ b/plugins/amda/include/AmdaProvider.h @@ -0,0 +1,36 @@ +#ifndef SCIQLOP_AMDAPROVIDER_H +#define SCIQLOP_AMDAPROVIDER_H + +#include "AmdaGlobal.h" + +#include + +#include + +#include + + +Q_DECLARE_LOGGING_CATEGORY(LOG_AmdaProvider) + +/** + * @brief The AmdaProvider class is an example of how a data provider can generate data + */ +class SCIQLOP_AMDA_EXPORT AmdaProvider : public IDataProvider { +public: + explicit AmdaProvider(); + + void requestDataLoading(QUuid token, const QVector &dateTimeList) override; + +private: + void retrieveData(QUuid token, const DataProviderParameters ¶meters) const; + + class AmdaProviderPrivate; + spimpl::unique_impl_ptr impl; + +private slots: + void httpFinished() noexcept; + void httpDownloadFinished() noexcept; + void httpDownloadReadyRead() noexcept; +}; + +#endif // SCIQLOP_AMDAPROVIDER_H diff --git a/plugins/amda/include/AmdaResultParser.h b/plugins/amda/include/AmdaResultParser.h new file mode 100644 index 0000000..8c4bc61 --- /dev/null +++ b/plugins/amda/include/AmdaResultParser.h @@ -0,0 +1,19 @@ +#ifndef SCIQLOP_AMDARESULTPARSER_H +#define SCIQLOP_AMDARESULTPARSER_H + +#include "AmdaGlobal.h" + +#include + +#include + +class IDataSeries; + +Q_DECLARE_LOGGING_CATEGORY(LOG_AmdaResultParser) + +struct SCIQLOP_AMDA_EXPORT AmdaResultParser { + + static std::shared_ptr readTxt(const QString &filePath) noexcept; +}; + +#endif // SCIQLOP_AMDARESULTPARSER_H diff --git a/plugins/amda/src/AmdaPlugin.cpp b/plugins/amda/src/AmdaPlugin.cpp index 555755d..af18f76 100644 --- a/plugins/amda/src/AmdaPlugin.cpp +++ b/plugins/amda/src/AmdaPlugin.cpp @@ -1,8 +1,10 @@ #include "AmdaPlugin.h" #include "AmdaParser.h" +#include "AmdaProvider.h" #include #include +#include #include @@ -16,6 +18,28 @@ const auto DATA_SOURCE_NAME = QStringLiteral("AMDA"); /// Path of the file used to generate the data source item for AMDA const auto JSON_FILE_PATH = QStringLiteral(":/samples/AmdaSample.json"); +void associateActions(DataSourceItem &item, const QUuid &dataSourceUid) +{ + if (item.type() == DataSourceItemType::PRODUCT) { + auto itemName = item.name(); + + item.addAction(std::make_unique( + QObject::tr("Load %1 product").arg(itemName), + [itemName, dataSourceUid](DataSourceItem &item) { + if (auto app = sqpApp) { + app->dataSourceController().loadProductItem(dataSourceUid, item); + } + })); + } + + auto count = item.childCount(); + for (auto i = 0; i < count; ++i) { + if (auto child = item.child(i)) { + associateActions(*child, dataSourceUid); + } + } +} + } // namespace void AmdaPlugin::initialize() @@ -27,11 +51,16 @@ void AmdaPlugin::initialize() // Sets data source tree if (auto dataSourceItem = AmdaParser::readJson(JSON_FILE_PATH)) { + associateActions(*dataSourceItem, dataSourceUid); + dataSourceController.setDataSourceItem(dataSourceUid, std::move(dataSourceItem)); } else { qCCritical(LOG_AmdaPlugin()) << tr("No data source item could be generated for AMDA"); } + + // Sets data provider + dataSourceController.setDataProvider(dataSourceUid, std::make_unique()); } else { qCWarning(LOG_AmdaPlugin()) << tr("Can't access to SciQlop application"); diff --git a/plugins/amda/src/AmdaProvider.cpp b/plugins/amda/src/AmdaProvider.cpp new file mode 100644 index 0000000..88777fc --- /dev/null +++ b/plugins/amda/src/AmdaProvider.cpp @@ -0,0 +1,134 @@ +#include "AmdaProvider.h" +#include "AmdaResultParser.h" + +#include + +#include +#include +#include + +Q_LOGGING_CATEGORY(LOG_AmdaProvider, "AmdaProvider") + +namespace { + +/// URL format for a request on AMDA server. The parameters are as follows: +/// - %1: start date +/// - %2: end date +/// - %3: parameter id +const auto AMDA_URL_FORMAT = QStringLiteral( + "http://amda.irap.omp.eu/php/rest/" + "getParameter.php?startTime=%1&stopTime=%2¶meterID=%3&sampling=60&outputFormat=ASCII&" + "timeFormat=ISO8601&gzip=0"); + +/// Dates format passed in the URL (e.g 2013-09-23T09:00) +const auto AMDA_TIME_FORMAT = QStringLiteral("yyyy-MM-ddThh:ss"); + +/// Formats a time to a date that can be passed in URL +QString dateFormat(double sqpDateTime) noexcept +{ + auto dateTime = QDateTime::fromMSecsSinceEpoch(sqpDateTime * 1000.); + return dateTime.toString(AMDA_TIME_FORMAT); +} + +} // namespace + +struct AmdaProvider::AmdaProviderPrivate { + DataProviderParameters m_Params{}; + std::unique_ptr m_AccessManager{nullptr}; + QNetworkReply *m_Reply{nullptr}; + std::unique_ptr m_File{nullptr}; + QUuid m_Token; +}; + +AmdaProvider::AmdaProvider() : impl{spimpl::make_unique_impl()} +{ +} + +void AmdaProvider::requestDataLoading(QUuid token, const QVector &dateTimeList) +{ + // NOTE: Try to use multithread if possible + for (const auto &dateTime : dateTimeList) { + retrieveData(token, DataProviderParameters{dateTime}); + } +} + +void AmdaProvider::retrieveData(QUuid token, const DataProviderParameters ¶meters) const +{ + // /////////// // + // Creates URL // + // /////////// // + + auto startDate = dateFormat(parameters.m_Time.m_TStart); + auto endDate = dateFormat(parameters.m_Time.m_TEnd); + auto productId = QStringLiteral("imf(0)"); + + auto url = QUrl{QString{AMDA_URL_FORMAT}.arg(startDate, endDate, productId)}; + + // //////////////// // + // Executes request // + // //////////////// // + + impl->m_Token = token; + impl->m_Params = parameters; + impl->m_AccessManager = std::make_unique(); + impl->m_Reply = impl->m_AccessManager->get(QNetworkRequest{url}); + connect(impl->m_Reply, &QNetworkReply::finished, this, &AmdaProvider::httpFinished); +} + +void AmdaProvider::httpFinished() noexcept +{ + // ////////////////////// // + // Gets download file url // + // ////////////////////// // + + auto downloadFileUrl = QUrl{QString{impl->m_Reply->readAll()}}; + + // ///////////////////////////////////// // + // Executes request for downloading file // + // ///////////////////////////////////// // + + // Deletes old reply + impl->m_Reply->deleteLater(); + impl->m_Reply = nullptr; + + // Creates destination file + impl->m_File = std::make_unique(); + if (impl->m_File->open()) { + qCDebug(LOG_AmdaProvider()) << "Temp file: " << impl->m_File->fileName(); + + // Executes request + impl->m_AccessManager = std::make_unique(); + impl->m_Reply = impl->m_AccessManager->get(QNetworkRequest{downloadFileUrl}); + connect(impl->m_Reply, &QNetworkReply::finished, this, + &AmdaProvider::httpDownloadReadyRead); + connect(impl->m_Reply, &QNetworkReply::finished, this, &AmdaProvider::httpDownloadFinished); + } +} + +void AmdaProvider::httpDownloadFinished() noexcept +{ + if (impl->m_File) { + impl->m_File->close(); + + // Parse results file + if (auto dataSeries = AmdaResultParser::readTxt(impl->m_File->fileName())) { + emit dataProvided(impl->m_Token, dataSeries, impl->m_Params.m_Time); + } + else { + /// @todo ALX : debug + } + + impl->m_File = nullptr; + } + + // Deletes reply + impl->m_Reply->deleteLater(); + impl->m_Reply = nullptr; +} + +void AmdaProvider::httpDownloadReadyRead() noexcept +{ + if (impl->m_File) { + impl->m_File->write(impl->m_Reply->readAll()); + } +} diff --git a/plugins/amda/src/AmdaResultParser.cpp b/plugins/amda/src/AmdaResultParser.cpp new file mode 100644 index 0000000..b929c7b --- /dev/null +++ b/plugins/amda/src/AmdaResultParser.cpp @@ -0,0 +1,70 @@ +#include "AmdaResultParser.h" + +#include + +#include +#include + +Q_LOGGING_CATEGORY(LOG_AmdaResultParser, "AmdaResultParser") + +namespace { + +/// Format for dates in result files +const auto DATE_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz"); + +/// @todo ALX +double doubleDate(const QString &stringDate) noexcept +{ + auto dateTime = QDateTime::fromString(stringDate, DATE_FORMAT); + return dateTime.toMSecsSinceEpoch() / 1000.; +} + +} // namespace + +std::shared_ptr AmdaResultParser::readTxt(const QString &filePath) noexcept +{ + QFile file{filePath}; + + if (!file.open(QFile::ReadOnly | QIODevice::Text)) { + qCCritical(LOG_AmdaResultParser()) + << QObject::tr("Can't retrieve AMDA data from file %1: %2") + .arg(filePath, file.errorString()); + return nullptr; + } + + auto xData = QVector{}; + auto valuesData = QVector{}; + + QTextStream stream{&file}; + + // Ignore comment lines (3 lines) + stream.readLine(); + stream.readLine(); + stream.readLine(); + + QString line{}; + auto lineRegex = QRegExp{QStringLiteral("\\s+")}; + while (stream.readLineInto(&line)) { + auto lineData = line.split(lineRegex, QString::SkipEmptyParts); + if (lineData.size() == 2) { + // X : the data is converted from date to double (in secs) + xData.push_back(doubleDate(lineData.at(0))); + + // Value + valuesData.push_back(lineData.at(1).toDouble()); + } + else { + /// @todo ALX : log + } + } + + /// @todo ALX : handle units + auto scalarSeries = std::make_shared(xData.size(), Unit{"nT", true}, Unit{}); + + const auto count = xData.size(); + for (auto i = 0; i < count; ++i) { + scalarSeries->setData(i, xData.at(i), valuesData.at(i)); + } + + return scalarSeries; +} diff --git a/plugins/mockplugin/include/CosinusProvider.h b/plugins/mockplugin/include/CosinusProvider.h index 63c1fd2..41e7db4 100644 --- a/plugins/mockplugin/include/CosinusProvider.h +++ b/plugins/mockplugin/include/CosinusProvider.h @@ -14,7 +14,7 @@ Q_DECLARE_LOGGING_CATEGORY(LOG_CosinusProvider) */ class SCIQLOP_MOCKPLUGIN_EXPORT CosinusProvider : public IDataProvider { public: - void requestDataLoading(const QVector &dateTimeList) override; + void requestDataLoading(QUuid token, const QVector &dateTimeList) override; private: diff --git a/plugins/mockplugin/src/CosinusProvider.cpp b/plugins/mockplugin/src/CosinusProvider.cpp index 8bb61dc..f3fab7d 100644 --- a/plugins/mockplugin/src/CosinusProvider.cpp +++ b/plugins/mockplugin/src/CosinusProvider.cpp @@ -38,13 +38,13 @@ CosinusProvider::retrieveData(const DataProviderParameters ¶meters) const return scalarSeries; } -void CosinusProvider::requestDataLoading(const QVector &dateTimeList) +void CosinusProvider::requestDataLoading(QUuid token, const QVector &dateTimeList) { qCDebug(LOG_CosinusProvider()) << "CosinusProvider::requestDataLoading" << QThread::currentThread()->objectName(); // NOTE: Try to use multithread if possible for (const auto &dateTime : dateTimeList) { auto scalarSeries = this->retrieveData(DataProviderParameters{dateTime}); - emit dataProvided(scalarSeries, dateTime); + emit dataProvided(token, scalarSeries, dateTime); } }