From 3023b0152f325f6ebc692ed90af141e231f45f4e Mon Sep 17 00:00:00 2001 From: Klaus Basan Date: Sun, 24 Feb 2019 03:16:07 +0100 Subject: [PATCH] Ref T554, auto-publish class for data --- src/blackmisc/simulation/autopublishdata.cpp | 284 +++++++++++++++++++ src/blackmisc/simulation/autopublishdata.h | 98 +++++++ 2 files changed, 382 insertions(+) create mode 100644 src/blackmisc/simulation/autopublishdata.cpp create mode 100644 src/blackmisc/simulation/autopublishdata.h diff --git a/src/blackmisc/simulation/autopublishdata.cpp b/src/blackmisc/simulation/autopublishdata.cpp new file mode 100644 index 000000000..9020484e8 --- /dev/null +++ b/src/blackmisc/simulation/autopublishdata.cpp @@ -0,0 +1,284 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +#include "autopublishdata.h" +#include "blackmisc/fileutils.h" +#include "blackmisc/json.h" +#include "blackmisc/logcategorylist.h" + +#include +#include +#include +#include +#include + +using namespace BlackMisc::PhysicalQuantities; + +namespace BlackMisc +{ + namespace Simulation + { + void CAutoPublishData::insert(const QString &modelString, const PhysicalQuantities::CLength &cg) + { + if (cg.isNull() || modelString.isEmpty()) { return; } + m_modelStringVsCG.insert(modelString.toUpper(), cg); + } + + void CAutoPublishData::insert(const QString &modelString, const CSimulatorInfo &simulator) + { + if (!simulator.isSingleSimulator() || modelString.isEmpty()) { return; } + m_modelStringVsSimulatorInfo.insert(modelString.toUpper(), simulator); + } + + void CAutoPublishData::clear() + { + m_modelStringVsCG.clear(); + m_modelStringVsSimulatorInfo.clear(); + } + + bool CAutoPublishData::isEmpty() const + { + return m_modelStringVsCG.isEmpty() && m_modelStringVsSimulatorInfo.isEmpty(); + } + + QString CAutoPublishData::toDatabaseJson() const + { + // used simple string JSON generation as it is faster + QString json; + + for (const auto pair : makePairsRange(m_modelStringVsCG)) + { + json += QStringLiteral("{ \"type\": \"cg\", \"model\": \"%1\", \"cgft\": %2 },\n").arg(pair.first, pair.second.valueRoundedAsString(CLengthUnit::ft(), 1)); + } + + for (const auto pair : makePairsRange(m_modelStringVsSimulatorInfo)) + { + const QString sim = pair.second.toQString(false); + json += QStringLiteral("{ \"type\": \"simulator\", \"model\": \"%1\", \"simulator\": \"%2\" },\n").arg(pair.first, sim); + } + + if (json.isEmpty()) { return {}; } + json.chop(2); + return u"[\n" % json % u"\n]\n"; + } + + int CAutoPublishData::fromDatabaseJson(const QString &jsonData, bool clear) + { + if (clear) { this->clear(); } + if (jsonData.isEmpty()) { return 0; } + const QJsonArray array = Json::jsonArrayFromString(jsonData); + if (array.isEmpty()) { return 0; } + + for (const QJsonValue &value : array) + { + const QJsonObject obj = value.toObject(); + const QString t = obj["type"].toString(); + const QString m = obj["model"].toString(); + if (m.isEmpty()) { continue; } + + if (t.startsWith("simulator", Qt::CaseInsensitive)) + { + const QString simulator = obj["simulator"].toString(); + const CSimulatorInfo si(simulator); + if (si.isSingleSimulator()) + { + this->insert(m, si); + } + } + else if (t.startsWith("cg", Qt::CaseInsensitive)) + { + const double cgFt = obj["cgft"].toDouble(-1); + if (cgFt < 0) { continue; } + this->insert(m, CLength(cgFt, CLengthUnit::ft())); + } + } + + return m_modelStringVsCG.size() + m_modelStringVsSimulatorInfo.size(); + } + + bool CAutoPublishData::writeJsonToFile() const + { + if (this->isEmpty()) { return false; } + const QString fn = fileBaseName() % u'_' % QDateTime::currentDateTimeUtc().toString("yyyyMMddHHmmss") % fileAppendix(); + return this->writeJsonToFile(CFileUtils::appendFilePaths(CDirectoryUtils::logDirectory(), fn)); + } + + bool CAutoPublishData::writeJsonToFile(const QString &pathAndFile) const + { + if (this->isEmpty()) { return false; } + const QString json = this->toDatabaseJson(); + return CFileUtils::writeStringToFile(json, pathAndFile); + } + + bool CAutoPublishData::readFromJsonFile(const QString &fileAndPath, bool clear) + { + const QString json = CFileUtils::readFileToString(fileAndPath); + if (json.isEmpty()) { return false; } + this->fromDatabaseJson(json, clear); + return true; + } + + int CAutoPublishData::readFromJsonFiles(const QString &dirPath) + { + const QStringList fileList = publishFiles(dirPath); + if (fileList.isEmpty()) { return 0; } + this->clear(); + + int c = 0; + for (const QString &file : fileList) + { + // read from file + if (this->readFromJsonFile(CFileUtils::appendFilePaths(dirPath, file), false)) { c++; } + } + return c; + } + + CStatusMessageList CAutoPublishData::analyzeAgainstDBData(const CAircraftModelList &dbModels) + { + static const CLogCategoryList cats({ CLogCategory::mapping(), CLogCategory::webservice() }); + if (dbModels.isEmpty()) { return CStatusMessage(this).validationError(u"No DB data"); } + if (this->isEmpty()) { return CStatusMessage(this).validationWarning(u"No data"); } + + CStatusMessageList msgs; + msgs.push_back(CStatusMessage(cats).validationInfo(u"DB models: %1") << dbModels.size()); + + QSet newModelStrings; // not in DB yet + QSet unchangedCG; + + for (const QString &modelString : m_modelStringVsCG.keys()) + { + const CAircraftModel dbModel = dbModels.findFirstByModelStringOrDefault(modelString); + if (dbModel.hasValidDbKey()) + { + if (dbModel.getCG() == m_modelStringVsCG[modelString]) + { + unchangedCG.insert(modelString); + } + } + else + { + // not in DB + newModelStrings << modelString; + } + } + + QSet unchangedSim; + for (const QString &modelString : m_modelStringVsSimulatorInfo.keys()) + { + const CAircraftModel dbModel = dbModels.findFirstByModelStringOrDefault(modelString); + if (dbModel.hasValidDbKey()) + { + if (dbModel.getSimulator().matchesAny(m_modelStringVsSimulatorInfo[modelString])) + { + unchangedSim.insert(modelString); + } + } + else + { + newModelStrings << modelString; + } + } + + // remove + if (!unchangedCG.isEmpty()) + { + msgs.push_back(CStatusMessage(cats).validationInfo(u"Removing unchanged CGs: %1") << unchangedCG.size()); + for (const QString &m : unchangedCG) { m_modelStringVsCG.remove(m); } + } + + if (!unchangedSim.isEmpty()) + { + msgs.push_back(CStatusMessage(cats).validationInfo(u"Removing unchanged simulators: %1") << unchangedSim.size()); + for (const QString &m : unchangedSim) { m_modelStringVsSimulatorInfo.remove(m); } + } + + msgs.push_back(CStatusMessage(this).validationInfo(this->getSummary())); + return msgs; + } + + QString CAutoPublishData::getSummary() const + { + return QStringLiteral("CGs: %1 | sim.entries: %2").arg(m_modelStringVsCG.size()).arg(m_modelStringVsSimulatorInfo.size()); + } + + QSet CAutoPublishData::allModelStrings() const + { + QSet allStrings(m_modelStringVsCG.keys().toSet()); + allStrings.unite(m_modelStringVsSimulatorInfo.keys().toSet()); + return allStrings; + } + + void CAutoPublishData::testData() + { + this->clear(); + + const CLength cg1(10, CLengthUnit::ft()); + const CLength cg2(40, CLengthUnit::ft()); + const CLength cg3(30, CLengthUnit::ft()); + + this->insert("testModelString1", cg1); + this->insert("testModelString2", cg2); + this->insert("testModelString3", cg3); + + this->insert("testModelString1", CSimulatorInfo::fs9()); + this->insert("testModelString2", CSimulatorInfo::xplane()); + this->insert("testModelString3", CSimulatorInfo::fg()); + this->insert("testModelString4", CSimulatorInfo::p3d()); + this->insert("testModelString5", CSimulatorInfo::fsx()); + this->insert("testModelString6", CSimulatorInfo::fsx()); + } + + const QString &CAutoPublishData::fileBaseName() + { + static const QString fn("autopublish"); + return fn; + } + + const QString &CAutoPublishData::fileAppendix() + { + static const QString a(".json"); + return a; + } + + bool CAutoPublishData::existAutoPublishFiles(const QString &dirPath) + { + return publishFiles(dirPath).size() > 0; + } + + int CAutoPublishData::deleteAutoPublishFiles(const QString &dirPath) + { + const QStringList fileList = publishFiles(dirPath); + if (fileList.isEmpty()) { return 0; } + + int c = 0; + for (const QString &file : fileList) + { + const QString fn = CFileUtils::appendFilePaths(dirPath, file); + QFile f(fn); + if (!f.exists()) { continue; } + if (f.remove()) { c++; } + } + return c; + + } + + QStringList CAutoPublishData::publishFiles(const QString &dirPath) + { + QDir dir(dirPath); + if (!dir.exists()) { return {}; } + + const QString filter = fileBaseName() % u'*' % fileAppendix(); + const QStringList filters({ filter }); + dir.setNameFilters(filters); + dir.setFilter(QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks); + const QStringList fileList = dir.entryList(); + return fileList; + } + + } // namespace +} // namespace diff --git a/src/blackmisc/simulation/autopublishdata.h b/src/blackmisc/simulation/autopublishdata.h new file mode 100644 index 000000000..abfa7f48f --- /dev/null +++ b/src/blackmisc/simulation/autopublishdata.h @@ -0,0 +1,98 @@ +/* Copyright (C) 2019 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated, + * or distributed except according to the terms contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKMISC_SIMULATION_AUTOPUBLISH_H +#define BLACKMISC_SIMULATION_AUTOPUBLISH_H + +#include "aircraftmodellist.h" +#include "simulatorinfo.h" +#include "blackmisc/pq/length.h" +#include "blackmisc/statusmessagelist.h" +#include "blackmisc/directoryutils.h" +#include "blackmisc/blackmiscexport.h" + +#include +#include + +namespace BlackMisc +{ + namespace Simulation + { + //! Objects that can be use for auto-publishing. + //! Auto publishing means we sent those data to the DB. + class BLACKMISC_EXPORT CAutoPublishData + { + public: + //! Insert values we might want to update in the DB @{ + void insert(const QString &modelString, const PhysicalQuantities::CLength &cg); + void insert(const QString &modelString, const CSimulatorInfo &simulator); + //! @} + + //! Clear all + void clear(); + + //! Any data? + bool isEmpty() const; + + //! Simple database JSON + QString toDatabaseJson() const; + + //! Read from database JSON + int fromDatabaseJson(const QString &jsonData, bool clear = true); + + //! Write to file @{ + bool writeJsonToFile() const; + bool writeJsonToFile(const QString &pathAndFile) const; + //! @} + + //! Read from JSON file + bool readFromJsonFile(const QString &fileAndPath, bool clear = true); + + //! Read all JSON files matching the base name + int readFromJsonFiles(const QString &dirPath = CDirectoryUtils::logDirectory()); + + //! Analyze against DB data + CStatusMessageList analyzeAgainstDBData(const CAircraftModelList &dbModels); + + //! Summary + QString getSummary() const; + + //! All affected model strings + QSet allModelStrings() const; + + //! File base name + static const QString &fileBaseName(); + + //! File appendix + static const QString &fileAppendix(); + + //! Do any auto pubish files exist? + static bool existAutoPublishFiles(const QString &dirPath = CDirectoryUtils::logDirectory()); + + //! Delete any existing auto publish files + static int deleteAutoPublishFiles(const QString &dirPath = CDirectoryUtils::logDirectory()); + + // ----------------- testing only --------------- + + //! Add some test data + //! \private testing only + void testData(); + + private: + //! All files matching the pattern + static QStringList publishFiles(const QString &dirPath); + + QMap m_modelStringVsCG; + QMap m_modelStringVsSimulatorInfo; + }; + } // namespace +} // namespace + +#endif // guard