/* Copyright (C) 2015 * 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 and at http://www.swift-project.org/license.html. 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 "interpolationlogger.h" #include "blackconfig/buildconfig.h" #include "blackmisc/aviation/callsign.h" #include "blackmisc/aviation/heading.h" #include "blackmisc/pq/angle.h" #include "blackmisc/pq/speed.h" #include "blackmisc/pq/units.h" #include "blackmisc/pq/length.h" #include "blackmisc/logmessage.h" #include "blackmisc/worker.h" #include "blackmisc/directoryutils.h" #include #include using namespace BlackConfig; using namespace BlackMisc; using namespace BlackMisc::Aviation; using namespace BlackMisc::Geo; using namespace BlackMisc::Math; using namespace BlackMisc::PhysicalQuantities; using namespace BlackMisc::Simulation; namespace BlackMisc { namespace Simulation { CInterpolationLogger::CInterpolationLogger(QObject *parent) : QObject(parent) { this->setObjectName("CInterpolationLogger"); } const CLogCategoryList &CInterpolationLogger::getLogCategories() { static const CLogCategoryList cats { CLogCategory::interpolator() }; return cats; } CWorker *CInterpolationLogger::writeLogInBackground() { QList situations; QList parts; { QReadLocker l(&m_lockSituations); situations = m_situationLogs; } { QReadLocker l(&m_lockParts); parts = m_partsLogs; } CWorker *worker = CWorker::fromTask(this, "WriteInterpolationLog", [situations, parts]() { const CStatusMessageList msg = CInterpolationLogger::writeLogFile(situations, parts); CLogMessage::preformatted(msg); }); return worker; } QStringList CInterpolationLogger::getLatestLogFiles() { QStringList files({ "", ""}); const QString logDir = CDirectoryUtils::logDirectory(); QDir logs(logDir); if (!logs.exists()) { return files; } logs.setNameFilters(filePatterns()); const QStringList interpolations = logs.entryList(QStringList({filePatternInterpolationLog()}), QDir::NoFilter, QDir::Time); if (!interpolations.isEmpty()) { files[0] = CFileUtils::appendFilePaths(logDir, interpolations.first()); } const QStringList parts = logs.entryList(QStringList({filePatternPartsLog()}), QDir::NoFilter, QDir::Time); if (!parts.isEmpty()) { files[1] = CFileUtils::appendFilePaths(logDir, parts.first()); } return files; } QString CInterpolationLogger::getLogDirectory() { return CDirectoryUtils::logDirectory(); } CStatusMessageList CInterpolationLogger::writeLogFile(const QList &interpolation, const QList &parts) { if (parts.isEmpty() && interpolation.isEmpty()) { return CStatusMessage(static_cast(nullptr)).warning("No data for log"); } static const QString html = QStringLiteral("Entries: %1\n\n%2"); const QString htmlTemplate = CFileUtils::readFileToString(CDirectoryUtils::htmlTemplateFilePath()); CStatusMessageList msgs; const QString ts = QDateTime::currentDateTimeUtc().toString("yyyyMMddhhmmss"); const QString htmlInterpolation = CInterpolationLogger::getHtmlInterpolationLog(interpolation); if (!htmlInterpolation.isEmpty()) { QString file = filePatternInterpolationLog(); file.remove('*'); const QString fn = CFileUtils::appendFilePaths(CDirectoryUtils::logDirectory(), QString("%1 %2").arg(ts, file)); const bool s = CFileUtils::writeStringToFile(htmlTemplate.arg(html.arg(interpolation.size()).arg(htmlInterpolation)), fn); msgs.push_back(CInterpolationLogger::logStatusFileWriting(s, fn)); } const QString htmlParts = CInterpolationLogger::getHtmlPartsLog(parts); if (!htmlParts.isEmpty()) { QString file = filePatternPartsLog(); file.remove('*'); const QString fn = CFileUtils::appendFilePaths(CDirectoryUtils::logDirectory(), QString("%1 %2").arg(ts, file)); const bool s = CFileUtils::writeStringToFile(htmlTemplate.arg(html.arg(parts.size()).arg(htmlParts)), fn); msgs.push_back(CInterpolationLogger::logStatusFileWriting(s, fn)); } return msgs; } CStatusMessage CInterpolationLogger::logStatusFileWriting(bool success, const QString &fileName) { return success ? CStatusMessage(static_cast(nullptr)).info("Written log file '%1'") << fileName : CStatusMessage(static_cast(nullptr)).error("Failed to write log file '%1'") << fileName; } void CInterpolationLogger::logInterpolation(const CInterpolationLogger::SituationLog &log) { QWriteLocker l(&m_lockSituations); m_situationLogs.push_back(log); if (m_situationLogs.size() > m_maxSituations) { m_situationLogs.removeFirst(); } } void CInterpolationLogger::logParts(const CInterpolationLogger::PartsLog &log) { QWriteLocker l(&m_lockParts); m_partsLogs.push_back(log); } void CInterpolationLogger::setMaxSituations(int max) { QReadLocker l(&m_lockSituations); m_maxSituations = max; } QList CInterpolationLogger::getSituationsLog() const { QReadLocker l(&m_lockSituations); return m_situationLogs; } QList CInterpolationLogger::getPartsLog() const { QReadLocker l(&m_lockParts); return m_partsLogs; } QList CInterpolationLogger::getSituationsLog(const CCallsign &cs) const { const QList copy(this->getSituationsLog()); QList logs; for (const SituationLog &log : copy) { if (log.callsign != cs) { continue; } logs.push_back(log); } return logs; } QList CInterpolationLogger::getPartsLog(const CCallsign &cs) const { const QList copy(this->getPartsLog()); QList logs; for (const PartsLog &log : copy) { if (log.callsign != cs) { continue; } logs.push_back(log); } return logs; } CAircraftSituation CInterpolationLogger::getLastSituation() const { QReadLocker l(&m_lockSituations); if (m_situationLogs.isEmpty()) { return CAircraftSituation(); } return m_situationLogs.last().currentSituation; } CAircraftSituation CInterpolationLogger::getLastSituation(const CCallsign &cs) const { const QList copy(this->getSituationsLog(cs)); if (copy.isEmpty()) { return CAircraftSituation(); } return copy.last().currentSituation; } CAircraftParts CInterpolationLogger::getLastParts() const { QReadLocker l(&m_lockParts); if (m_partsLogs.isEmpty()) { return CAircraftParts(); } return m_partsLogs.last().parts; } CAircraftParts CInterpolationLogger::getLastParts(const CCallsign &cs) const { const QList copy(this->getPartsLog(cs)); if (copy.isEmpty()) { return CAircraftParts(); } return copy.last().parts; } const QString &CInterpolationLogger::filePatternInterpolationLog() { static const QString p("*interpolation.html"); return p; } const QString &CInterpolationLogger::filePatternPartsLog() { static const QString p("*parts.html"); return p; } const QStringList &CInterpolationLogger::filePatterns() { static const QStringList l({ filePatternInterpolationLog(), filePatternPartsLog() }); return l; } QString CInterpolationLogger::getHtmlInterpolationLog(const QList &logs) { if (logs.isEmpty()) { return {}; } const QString tableHeader = QLatin1String("") % QLatin1String("cs.Int") % QLatin1String("CSVTOLtimestampsince") % QLatin1String("ts oldts newts cur") % QLatin1String("ΔtΔt fr.fraction") % QLatin1String("lat.oldlat.newlat.cur") % QLatin1String("lng.oldlng.newlng.cur") % QLatin1String("alt.oldalt.newalt.cur") % QLatin1String("elv.oldelv.newelv.cur") % QLatin1String("gnd.factor") % QLatin1String("onGnd.oldonGnd.newonGnd.cur") % QLatin1String("CG") % QLatin1String("partscp.parts details") % QLatin1String("\n"); static const CLengthUnit ft = CLengthUnit::ft(); const SituationLog firstLog = logs.first(); qint64 newPosTs = firstLog.newSituation.getMSecsSinceEpoch(); CAircraftParts lastParts; // default, so shown if parts are different from default QString tableRows("\n"); for (const SituationLog &log : logs) { const bool changedNewPosition = newPosTs != log.newSituation.getMSecsSinceEpoch(); const bool changedParts = lastParts != log.parts; newPosTs = log.newSituation.getMSecsSinceEpoch(); lastParts = log.parts; // concatenating in multiple steps, otherwise C4503 warnings tableRows += QLatin1String("") % (changedNewPosition ? QLatin1String("*") : QLatin1String("")) % QLatin1String("") % log.interpolator % QLatin1String("") % QLatin1String("") % log.callsign.asString() % QLatin1String("") % QLatin1String("") % boolToYesNo(log.vtolAircraft) % QLatin1String("") % QLatin1String("") % msSinceEpochToTime(log.timestamp) % QLatin1String("") % QLatin1String("") % QString::number(log.timestamp - firstLog.timestamp) % QLatin1String("") % QLatin1String("") % msSinceEpochToTime(log.oldSituation.getAdjustedMSecsSinceEpoch()) % QLatin1Char('-') % QString::number(log.oldSituation.getTimeOffsetMs()) % QLatin1String("") % QLatin1String("") % msSinceEpochToTime(log.newSituation.getAdjustedMSecsSinceEpoch()) % QLatin1Char('-') % QString::number(log.newSituation.getTimeOffsetMs()) % QLatin1String("") % QLatin1String("") % msSinceEpochToTime(log.currentSituation.getAdjustedMSecsSinceEpoch()) % QLatin1Char('-') % QString::number(log.currentSituation.getTimeOffsetMs()) % QLatin1String("") % QLatin1String("") % QString::number(log.deltaTimeMs) % QLatin1String("") % QLatin1String("") % QString::number(log.deltaTimeFractionMs) % QLatin1String("") % QLatin1String("") % QString::number(log.simulationTimeFraction) % QLatin1String(""); tableRows += QLatin1String("") % log.oldSituation.latitudeAsString() % QLatin1String("") % QLatin1String("") % log.newSituation.latitudeAsString() % QLatin1String("") % QLatin1String("") % log.currentSituation.latitudeAsString() % QLatin1String("") % QLatin1String("") % log.oldSituation.longitudeAsString() % QLatin1String("") % QLatin1String("") % log.newSituation.longitudeAsString() % QLatin1String("") % QLatin1String("") % log.currentSituation.longitudeAsString() % QLatin1String(""); tableRows += QLatin1String("") % log.oldSituation.getAltitude().valueRoundedWithUnit(ft, 1) % QLatin1String("") % QLatin1String("") % log.newSituation.getAltitude().valueRoundedWithUnit(ft, 1) % QLatin1String("") % QLatin1String("") % log.currentSituation.getAltitude().valueRoundedWithUnit(ft, 1) % QLatin1String("") % QLatin1String("") % log.oldSituation.getGroundElevation().valueRoundedWithUnit(ft, 1) % QLatin1String("") % QLatin1String("") % log.newSituation.getGroundElevation().valueRoundedWithUnit(ft, 1) % QLatin1String("") % QLatin1String("") % log.currentSituation.getGroundElevation().valueRoundedWithUnit(ft, 1) % QLatin1String("") % QLatin1String("") % QString::number(log.groundFactor) % QLatin1String("") % QLatin1String("") % log.oldSituation.getOnGroundInfo() % QLatin1String("") % QLatin1String("") % log.newSituation.getOnGroundInfo() % QLatin1String("") % QLatin1String("") % log.currentSituation.getOnGroundInfo() % QLatin1String(""); tableRows += QLatin1String("") % log.cgAboveGround.valueRoundedWithUnit(CLengthUnit::ft(), 0) % QLatin1String("") % QLatin1String("") % boolToYesNo(log.useParts) % QLatin1String("") % (changedParts ? QLatin1String("*") : QLatin1String("")) % QLatin1String("") % (log.useParts ? log.parts.toQString(true) : QLatin1String("")) % QLatin1String("") % QLatin1String("\n"); } tableRows += QLatin1String("\n"); return QLatin1String("\n") % tableHeader % tableRows % QLatin1String("
\n"); } QString CInterpolationLogger::getHtmlPartsLog(const QList &logs) { if (logs.isEmpty()) { return {}; } const QString tableHeader = QLatin1String("") % QLatin1String("CStimestamp") % QLatin1String("c.") % QLatin1String("parts") % QLatin1String("\n"); CAircraftParts lastParts; // default, so shown if parts are different from default QString tableRows("\n"); for (const PartsLog &log : logs) { const bool changedParts = lastParts != log.parts; lastParts = log.parts; tableRows += QLatin1String("") % QLatin1String("") % log.callsign.asString() % QLatin1String("") % QLatin1String("") % msSinceEpochToTime(log.timestamp) % QLatin1String("") % (changedParts ? QLatin1String("*") : QLatin1String("")) % QLatin1String("") % (log.empty ? QLatin1String("empty") : log.parts.toQString()) % QLatin1String(""); } tableRows += QLatin1String("\n"); return QLatin1String("\n") % tableHeader % tableRows % QLatin1String("
\n"); } void CInterpolationLogger::clearLog() { { QWriteLocker l(&m_lockSituations); m_situationLogs.clear(); } { QWriteLocker l(&m_lockParts); m_partsLogs.clear(); } } QString CInterpolationLogger::msSinceEpochToTime(qint64 ms) { static const QString dateFormat("hh:mm:ss.zzz"); return QDateTime::fromMSecsSinceEpoch(ms).toString(dateFormat); } QString CInterpolationLogger::msSinceEpochToTime(qint64 t1, qint64 t2, qint64 t3) { if (t3 < 0) return QString("%1 %2").arg(msSinceEpochToTime(t1), msSinceEpochToTime(t2)); return QString("%1 %2 %3").arg(msSinceEpochToTime(t1), msSinceEpochToTime(t2), msSinceEpochToTime(t3)); } } // namespace } // namespace