Initial commit

This commit is contained in:
torsten committed 2026-08-13 18:33:10 +02:00
commit b7d90b9afe
16245 files changed
+3396666

No files matched your search

@@ -0,0 +1,212 @@
/**
* @RDE_HEADER@
* DbReader for RecordingManagement
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, NLSE, 2018-03-14
*/
#include "db_reader.h"
#include <DDS/DDSConnector/dds_connector.h>
#include <LtsPluginManager/lts_plugin_loader.h>
#include <ngldb/nsqlquery.h>
#include <ngldb/dbtransaction.h>
#include <QSqlDriver>
#include <QSqlError>
#include <QSqlField>
#include <QSqlIndex>
#include <QSqlRecord>
#include <QVariant>
namespace TakeHomePackageGenerator
{
DbReader::DbReader(int exerciseId)
: m_exerciseId(exerciseId)
, m_queryFrom(nullptr)
, m_queryTo(nullptr)
, m_bSupportsSchemasFrom(false)
, m_bSupportsSchemasTo(false)
, m_logger(PresentationManagement::getLogger("DbReader"))
{}
DbReader::~DbReader()
{
delete m_queryFrom;
delete m_queryTo;
// Todo: crashes OperatorApplication. Required to unload?
// LtsPluginManager::LtsPluginLoader::getInstance()->unloadAllPlugins();
m_plugins.clear();
}
bool DbReader::loadPlugin(const LtsPluginManager::PluginInstance &pi)
{
RecordReplay::RecordReplayPlugin *recPlugin = qobject_cast<RecordReplay::RecordReplayPlugin*>(pi.plugin);
if (!recPlugin)
{
ERROR_LOG(m_logger) << "Cannot load RecordReplayPlugin " << pi.fileName;
return false;
}
if (!recPlugin->initCopy(m_exerciseId, m_bSupportsSchemasFrom, m_bSupportsSchemasTo))
{
ERROR_LOG(m_logger) << "Initialisation for RecordReplayPlugin " << pi.fileName << " failed ";
return false;
}
INFO_LOG(m_logger) << "Loaded record replay plugin " << recPlugin->pluginName();
m_plugins += recPlugin;
return true;
}
bool DbReader::init(const QSqlDatabase &dbFrom, const QSqlDatabase &dbTo)
{
m_dbFrom = dbFrom;
delete m_queryFrom;
m_queryFrom = new NSqlQuery(dbFrom);
m_bSupportsSchemasFrom = dbFrom.driverName() != QStringLiteral("QSQLITE");
m_dbTo = dbTo;
delete m_queryTo;
m_queryTo = new NSqlQuery(dbTo);
m_bSupportsSchemasTo = dbTo.driverName() != QStringLiteral("QSQLITE");
auto cb = [this](const LtsPluginManager::PluginInstance &pi) { return loadPlugin(pi);};
LtsPluginManager::LtsPluginLoader::getInstance()->loadPlugins(LtsPluginManager::PluginType::SPECIAL,
cb,
QStringList(),
QStringLiteral("recordreplay"));
return true;
}
bool DbReader::createTables()
{
for(RecordReplay::RecordReplayPlugin *recPlugin : qAsConst(m_plugins))
{
const QVector<QString> tableNames = recPlugin->getRecordTableNames();
for (const QString &name : tableNames)
{
const QString fqTableName = DBCommon::getExerciseTableName(name, m_exerciseId);
const QSqlRecord record = m_dbFrom.record(fqTableName);
if (record.isEmpty())
{
ERROR_LOG(m_logger) << "Empty record for " << fqTableName;
return false;
}
const QSqlIndex pk = m_dbFrom.primaryIndex(fqTableName);
createTable(*m_queryTo, name, record, pk, m_logger);
}
}
return true;
}
bool DbReader::copyData(qint64 fromTime, qint64 toTime)
{
INFO_LOG(m_logger) << "IN readData";
if (!m_queryFrom || !m_queryTo)
return false;
// for each plugin
for(RecordReplay::RecordReplayPlugin *recPlugin : qAsConst(m_plugins))
{
if (!recPlugin->copyData(*m_queryFrom, *m_queryTo, fromTime, toTime))
return false;
}
return true;
}
static QString sqliteTypeFromVariant(QVariant::Type t)
{
switch (t)
{
case QVariant::Bool:
return QStringLiteral("boolean");
case QVariant::Int:
return QStringLiteral("integer");
case QVariant::UInt:
case QVariant::LongLong:
case QVariant::ULongLong: // mhhh
return QStringLiteral("bigint");
case QVariant::Double:
return QStringLiteral("double precision");
case QVariant::String:
return QStringLiteral("text");
case QVariant::ByteArray:
return QStringLiteral("blob");
case QVariant::Date:
case QVariant::Time:
case QVariant::DateTime:
return QStringLiteral("text");
default:
Q_ASSERT(false);
}
return QString();
}
bool DbReader::createTable(NSqlQuery &q, const QString &tableName, const QSqlRecord &record, const QSqlIndex &pk, NGL::INGLLogger &logger)
{
QStringList createTableColumns;
createTableColumns.reserve(record.count());
QStringList pks;
pks.reserve(pk.count());
for (int i = 0; i < pk.count(); ++i)
pks += pk.field(i).name();
bool bCreateIdxOnId = false;
bool bCreateIdxOnSampleTime = false;
for (int i = 0; i < record.count(); ++i)
{
const QSqlField f = record.field(i);
if (!bCreateIdxOnId && f.name() == QLatin1String("id"))
bCreateIdxOnId = true;
if (!bCreateIdxOnSampleTime && f.name() == QLatin1String("sample_time"))
bCreateIdxOnSampleTime = true;
QString type = sqliteTypeFromVariant(f.type());
const int idx = pks.indexOf(f.name());
if (idx >= 0)
{
pks.removeAt(i);
type = QStringLiteral("INTEGER PRIMARY KEY");
}
createTableColumns += f.name() + QLatin1Char(' ') + type;
}
if (bCreateIdxOnSampleTime)
bCreateIdxOnId = false;
const QString createTableCommand(QStringLiteral("CREATE TABLE %1 (%2)").arg(tableName, createTableColumns.join(QStringLiteral(",\n"))));
const QString indexIdTableCommand(QStringLiteral("CREATE INDEX IDX_%1_id ON %1 (id)").arg(tableName));
const QString indexStTableCommand(QStringLiteral("CREATE INDEX IDX_%1_sampleTime ON %1 (sample_time)").arg(tableName));
DEBUG_LOG(logger) << createTableCommand;
DEBUG_LOG(logger) << indexIdTableCommand;
DEBUG_LOG(logger) << indexStTableCommand;
q.exec(QStringLiteral("DROP TABLE IF EXISTS %1").arg(tableName));
if (!q.exec(createTableCommand))
{
ERROR_LOG(logger) << "qOut create table failed... " << q.lastError().text();
return false;
}
if (bCreateIdxOnId && !q.exec(indexIdTableCommand))
{
ERROR_LOG(logger) << "qOut create index on table... " << q.lastError().text();
return false;
}
if (bCreateIdxOnSampleTime && !q.exec(indexStTableCommand))
{
ERROR_LOG(logger) << "qOut create index on table... " << q.lastError().text();
return false;
}
return true;
}
} // TakeHomePackageGenerator
@@ -0,0 +1,53 @@
/**
* @RDE_HEADER@
* DbReader for SituationReplay
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, NLSE, 2018-03-14
*/
#pragma once
#include <QVector>
#include <QSqlDatabase>
#include <PluginInterfaces/record_replay_plugin.h>
#include <PresentationManagement/logger.h>
class NSqlQuery;
namespace LtsPluginManager
{
struct PluginInstance;
}
namespace TakeHomePackageGenerator
{
class DbReader final
{
public:
explicit DbReader(int exerciseId);
~DbReader();
bool init(const QSqlDatabase &dbFrom, const QSqlDatabase &dbTo);
bool createTables();
bool copyData(qint64 fromTime, qint64 toTime);
static bool createTable(NSqlQuery &q, const QString &tableName, const QSqlRecord &record, const QSqlIndex &pk, NGL::INGLLogger &logger);
private:
bool loadPlugin(const LtsPluginManager::PluginInstance &pi);
private:
int m_exerciseId;
QSqlDatabase m_dbFrom;
QSqlDatabase m_dbTo;
NSqlQuery *m_queryFrom;
NSqlQuery *m_queryTo;
bool m_bSupportsSchemasFrom;
bool m_bSupportsSchemasTo;
QVector<RecordReplay::RecordReplayPlugin*> m_plugins;
NGL::INGLLogger &m_logger;
};
} // namespace TakeHomePackageGenerator
@@ -0,0 +1,254 @@
/**
* @RDE_HEADER@
* Burn data to dvd
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Last, NLSE, 2018-07-09
*/
#include "dvd_writer.h"
#include <LtsWidgets/lts_message_dialog.h>
#include <QFileInfo>
namespace TakeHomePackageGenerator
{
DVDWriter::DVDWriter(const QString &packageDir, QObject *pParent)
: QObject(pParent)
, m_pProgressDlg(new QProgressDialog(tr("Burning files..."), tr("Abort"), 0, 100, nullptr))
, m_packageDir(packageDir)
, m_logger(PresentationManagement::getLogger("DbReader"))
{
connect(m_pProgressDlg, &QProgressDialog::canceled, this, &DVDWriter::slotCancel);
}
void DVDWriter::slotCancel()
{
cleanupProcess();
m_pProgressDlg->setLabelText(tr("Cancelling burning process..."));
m_pProgressDlg->setCancelButton(nullptr);
m_pProgressDlg->setMaximum(0);
}
DVDWriter::~DVDWriter()
{
if (m_pProgressDlg)
m_pProgressDlg->deleteLater();
}
void DVDWriter::slotWriteDVD()
{
cleanupProcess();
m_dvdBurnProcess = new QProcess(this);
m_pProgressDlg->setMinimumDuration(0);
m_pProgressDlg->setWindowTitle(tr("Burning..."));
m_pProgressDlg->setWindowModality(Qt::WindowModal);
QStringList args;
args << QStringLiteral("-Z") << dvdDevice() << QStringLiteral("-R") << QStringLiteral("-J") << QStringLiteral("-udf") << m_packageDir;
connect(m_dvdBurnProcess.data(), &QProcess::readyRead,
this, &DVDWriter::incomingDataAvailable);
connect(m_dvdBurnProcess.data(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, &DVDWriter::dvdFinished);
m_dvdBurnProcess->setProcessChannelMode(QProcess::MergedChannels);
m_dvdBurnProcess->start(dvdProgram(), args);
}
void DVDWriter::incomingDataAvailable()
{
while (m_dvdBurnProcess && m_dvdBurnProcess->canReadLine())
{
const QByteArray qB = m_dvdBurnProcess->readLine().trimmed();
INFO_LOG(m_logger) << "qb = " << qB.data()
<< " m_packageDir=" << m_packageDir;
if (qB.contains(":-["))
{
ERROR_LOG(m_logger) << "growisofs error: '" << qB << "'";
cleanupProcess();
QString errString = QString::fromUtf8(qB.mid(3));
if (LtsWidgets::LtsMessageDialog::showMessage(qobject_cast<QWidget *>(parent()), tr("No DVD in drive / error burning dvd. Try again?"),
errString,
LtsWidgets::Behaviour::Modal,
LtsWidgets::Button::Yes | LtsWidgets::Button::No) == LtsWidgets::Button::Yes)
slotWriteDVD();
else
{
cleanup(false);
return;
}
}
// Example Text:
if (qB.contains("estimate finish")) // " 6.31% done, estimate finish Thu Sep 1 12:09:05 2011"
m_pProgressDlg->setValue((int) qB.left(qB.indexOf("%")).toDouble() * 0.9); // 90% for processes %age, 10% for lead out & Co.
else if (qB.contains("Total translation table size")) // "Total translation table size: 0"
m_pProgressDlg->setValue(91);
else if (qB.contains("Total rockridge attributes bytes")) // "Total rockridge attributes bytes: 92758"
m_pProgressDlg->setValue(92);
else if (qB.contains("Total directory bytes")) // "Total directory bytes: 165888"
m_pProgressDlg->setValue(93);
else if (qB.contains("Path table size(bytes)")) // "Path table size(bytes): 244"
m_pProgressDlg->setValue(94);
else if (qB.contains("Max brk space used")) // "Max brk space used ea000"
m_pProgressDlg->setValue(95);
else if (qB.contains("extents written")) // "79377 extents written (155 MB)"
m_pProgressDlg->setValue(96);
else if (qB.contains("builtin_dd")) // "builtin_dd: 79392*2KB out @ average 5.6x1352KBps"
m_pProgressDlg->setValue(97);
else if (qB.contains("flushing cache")) // "/dev/dvd: flushing cache"
m_pProgressDlg->setValue(98);
else if (qB.contains("updating RMA")) // "/dev/dvd: updating RMA"
m_pProgressDlg->setValue(99);
else if (qB.contains("closing session")) // "/dev/dvd: closing session"
m_pProgressDlg->setValue(100);
else if (qB.contains("media is not recognized"))
{
ERROR_LOG(m_logger) << "growisofs error: '" << qB << "'";
cleanupProcess();
if (LtsWidgets::LtsMessageDialog::showMessage(qobject_cast<QWidget *>(parent()), tr("No DVD in drive. Try again?"),
tr("Media is not recognized"),
LtsWidgets::Behaviour::Modal,
LtsWidgets::Button::Yes | LtsWidgets::Button::No) == LtsWidgets::Button::Yes)
slotWriteDVD();
else
cleanup(false);
return;
}
else if (qB.contains(":-( unable to open"))
{
ERROR_LOG(m_logger) << "growisofs error: '" << qB << "'";
cleanupProcess();
if (LtsWidgets::LtsMessageDialog::showMessage(qobject_cast<QWidget *>(parent()), tr("Unable to open DVD device. Try again?"),
tr("Unable to open DVD device"),
LtsWidgets::Behaviour::Modal,
LtsWidgets::Button::Yes | LtsWidgets::Button::No) == LtsWidgets::Button::Yes)
slotWriteDVD();
else
cleanup(false);
return;
}
else if (qB.contains(":-("))
{
ERROR_LOG(m_logger) << "growisofs error: '" << qB << "'";
cleanupProcess();
QString strError(tr("Unable to open DVD device"));
QString strErrorReason(QString::fromUtf8(qB));
int index = strErrorReason.indexOf(QLatin1Char(':'), 1); // Skip eyes of the sad smiley (':-(')
if (index > 0)
{
strError += QStringLiteral("<br>Error:");
strError += strErrorReason.remove(0, index + 1);
}
LtsWidgets::LtsMessageDialog::showMessage(qobject_cast<QWidget *>(parent()), strError);
cleanup(false);
}
}
}
void DVDWriter::dvdFinished(int exitCode, QProcess::ExitStatus exitStatus)
{
// TODO: exitCode und exitStatus korrekt auswerten
INFO_LOG(m_logger) << "growisofs ends with extitCode : '" << exitCode << " and ExitStatus '" << exitStatus;
//Closing Down DVD Process
if (!m_dvdBurnProcess)
{
WARNING_LOG(m_logger) << "dvdFinished with error code: " << exitCode << ", status: " << exitStatus
<< " process is already cleaned up";
return;
}
m_dvdBurnProcess->close();
m_dvdBurnProcess->deleteLater();
m_dvdBurnProcess = nullptr;
if (exitCode == 0 && exitStatus == QProcess::NormalExit)
{
m_pProgressDlg->setValue(100);
LtsWidgets::LtsMessageDialog::showMessage(qobject_cast<QWidget *>(parent()), tr("Burning complete"));
cleanup(true);
}
else
{
ERROR_LOG(m_logger) << "dvdFinished with error code: " << exitCode << ", status: " << exitStatus;
LtsWidgets::LtsMessageDialog::showMessage(qobject_cast<QWidget *>(parent()), tr("Burning aborted with an unkown error %1").arg(exitCode));
cleanup(false);
}
}
void DVDWriter::cleanup(bool success)
{
if (m_pProgressDlg)
{
m_pProgressDlg->deleteLater();
m_pProgressDlg = nullptr;
}
emit signalBurningFinished(success);
}
void DVDWriter::cleanupProcess()
{
if (m_dvdBurnProcess)
{
m_dvdBurnProcess->disconnect();
m_dvdBurnProcess->terminate();
if (!m_dvdBurnProcess->waitForFinished(1000))
m_dvdBurnProcess->kill();
m_dvdBurnProcess->deleteLater();
m_dvdBurnProcess = nullptr;
}
}
bool DVDWriter::checkBuringCapabilities()
{
QFileInfo fi1(dvdProgram());
if (!fi1.exists() || !fi1.isFile() || !fi1.isExecutable())
{
LtsWidgets::LtsMessageDialog::showMessage(qobject_cast<QWidget *>(parent()), tr("Unable to locate burning executable (%1)").arg(dvdProgram()));
return false;
}
QFileInfo fi2(dvdDevice());
if (!fi2.exists() || !fi2.isReadable() || !fi2.isWritable())
{
LtsWidgets::LtsMessageDialog::showMessage(qobject_cast<QWidget *>(parent()), tr("Unable to find DVD device (%1)").arg(dvdDevice()));
return false;
}
return true;
}
QString DVDWriter::dvdProgram() const
{
return QStringLiteral("/usr/bin/growisofs");
}
QString DVDWriter::dvdDevice() const
{
#ifdef LTS_DEBUG
return QStringLiteral("/dev/sr0");
#else
return QStringLiteral("/dev/rde/dvd");
#endif
}
}
@@ -0,0 +1,54 @@
/**
* @RDE_HEADER@
* Burn data to dvd
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Last, NLSE, 2018-07-09
*/
#pragma once
#include <PresentationManagement/logger.h>
#include "export.h"
#include <QObject>
#include <QString>
#include <QProcess>
#include <QPointer>
#include <QProgressDialog>
namespace TakeHomePackageGenerator
{
class PREGUISCRIPTCOMMON_EXPORT DVDWriter : public QObject
{
Q_OBJECT
public:
explicit DVDWriter(const QString &packageDir, QObject *pParent = nullptr);
~DVDWriter();
signals:
void signalBurningFinished(bool success);
public slots:
void slotWriteDVD();
private slots:
void slotCancel();
void incomingDataAvailable();
void dvdFinished(int exitCode, QProcess::ExitStatus exitStatus);
private:
void cleanup(bool success);
void cleanupProcess();
bool checkBuringCapabilities();
QString dvdProgram() const;
QString dvdDevice() const;
QProgressDialog *m_pProgressDlg;
QString m_packageDir;
QPointer<QProcess> m_dvdBurnProcess;
NGL::INGLLogger &m_logger;
};
}
@@ -0,0 +1,25 @@
/**
* @RDE_HEADER@
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Last, NLSE, 2018-07-09
*/
#include "export.h"
#include "situation_exporter.h"
#include <DDS/DDSConnector/dds_connector.h>
namespace TakeHomePackageGenerator
{
bool exportSituationMapData(int exerciseId, const QString& strPathToFile, qint64 fromTime, qint64 toTime)
{
SituationExporter se(exerciseId);
if (!se.exportStaticData(strPathToFile))
return false;
if (!se.exportDynamicData(fromTime, toTime))
return false;
return true;
}
} // namespace TakeHomePackageGenerator
@@ -0,0 +1,17 @@
/**
* @RDE_HEADER@
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Last, NLSE, 2018-07-09
*/
#pragma once
#include <QtGlobal>
#include <Plugins/PresentationManagement/Gui/Script/Common/preguiscriptcommon_export.h>
namespace TakeHomePackageGenerator
{
PREGUISCRIPTCOMMON_EXPORT bool exportSituationMapData(int exerciseId, const QString& strPathToFile, qint64 fromTime = 0, qint64 toTime = 1822159491 * 1000LL);
}
@@ -0,0 +1,637 @@
/**
* @RDE_HEADER@
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Last, NLSE, 2018-07-09
*/
#include "export_process.h"
#include "export_step.h"
#include <QDir>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QProgressDialog>
#include <Plugins/ExerciseManagement/exm_data_plugin_interface.h>
#include <Plugins/AssetManagement/asm_data_plugin_interface.h>
#include <ngltools/ngltools_filesystem.h>
namespace TakeHomePackageGenerator
{
//
// ExportedScriptInformation
//
QJsonValue ExportedScriptInformation::toJson() const
{
// THP-Export: In der Übersichts-Website 'Take Home Package werden einige
// Übungsparameter angezeigt, aber auch die Präsenmtationen mit ihrem
// Namen aufgelistet. Dort gab es wohl in Hin und Her zw.
// 'presentations.name' und 'presentations.displayName'.
// Daher schreiben wir nun den ScriptName doppelt, damit es für beiden
// Varianten funktioniert:
return QJsonObject({{QStringLiteral("name"), scriptName},
{QStringLiteral("displayName"), scriptName}, // write {name and displayName} with identical value (not clear which will be used at index_de_DE.html @ thp_viewer)
{QStringLiteral("path"), jsonPath},
{QStringLiteral("scriptId"), scriptId},
{QStringLiteral("exportTime"), exportTime},
{QStringLiteral("exerciseName"), exerciseName}
});
}
ExportProcess::ExportProcess(LtsSession::LtsSession *pSession,
const std::shared_ptr<SituationReplay::ForeignGuiInterface> &replayGuiIfc,
QProgressDialog *pProgressDlg,
const QString &strScriptName,
const QString &strExerciseName,
int scriptId,
const std::vector<PresentationManagement::PresentationContent> &contentList,
const QMap<QString, PresentationManagement::TopicAssignment> &trackEntryAssignments,
QObject *parent)
: QObject(parent)
, m_contentList(contentList)
, m_trackEntryAssignments(trackEntryAssignments)
, m_strScriptName(strScriptName)
, m_strExerciseName(strExerciseName)
, m_scriptId(scriptId)
, m_pSession(pSession)
, m_replayGuiIfc(replayGuiIfc)
, m_pProgressDlg(pProgressDlg)
, m_cntTopicsToExport(0)
, m_logger(PresentationManagement::getLogger("ExportProcess"))
{
QDir().mkpath(contentPath());
}
ExportProcess::~ExportProcess()
{
for (auto e : qAsConst(m_vecExportSteps))
e->deleteLater();
for (auto e : qAsConst(m_vecExportStepsExecuted))
e->deleteLater();
}
QString ExportProcess::exportPath(const int exerciseId)
{
return exportRootPath() + QStringLiteral("Exercise_%1/").arg(exerciseId);
}
QString ExportProcess::exportRootPath()
{
return QDir::tempPath() + QLatin1String("/THP-Export/");
}
static QVector<TopicReplay::Interface::SituationReplayTime> extractUsedSitReplayTimes(
const TopicEditorBase::TopicData &data, const QVector<TopicReplay::Interface::SituationReplayTime> &allSitReplayTimes
)
{
// Add replay information (not necessarily available) to attributes
QVector<TopicReplay::Interface::SituationReplayTime> ret;
ret.reserve(5);
for (const TopicReplay::Interface::SituationReplayTime &sitReplayTime : allSitReplayTimes)
{
bool collides = LtsScriptTimeline::isCollision(data.startTime, data.startTime + data.duration, sitReplayTime.topicStart_s, sitReplayTime.topicEnd_s);
if (collides)
ret.push_back(sitReplayTime);
}
// Order chronologically
std::sort(ret.begin(), ret.end(),
[](const TopicReplay::Interface::SituationReplayTime &a, const TopicReplay::Interface::SituationReplayTime &b)
{
return a.topicStart_s < b.topicStart_s;
});
return ret;
}
void ExportProcess::slotStartExport()
{
/*
* Identify all replay topics, save their start and end times (topic start/end and actual replay start/end time).
* Replay topics are used when exporting, e.g. Building Topics since they may be placed parallel to one or more ReplayTopics
*/
QVector<TopicReplay::Interface::SituationReplayTime> allSitReplayTimes;
for (const PresentationManagement::PresentationContent &content : qAsConst(m_contentList))
{
auto it = m_trackEntryAssignments.constFind(QString::fromStdString(content.presentationTopicType()));
if (it == m_trackEntryAssignments.constEnd())
{
ERROR_LOG(m_logger) << "Invalid content for type (" << content.presentationTopicType() << "): " << QString::fromStdString(content.presentationTopicType());
continue;
}
const PresentationManagement::TopicAssignment &ta = it.value();
if (ta.topicType == TopicEditorBase::TopicType::SituationReplay ||
ta.topicType == TopicEditorBase::TopicType::Situation)
{
// Verify content
if (content.attributes().size() == 0)
continue;
QJsonParseError parseError;
const QJsonDocument doc(QJsonDocument::fromJson(QByteArray(content.attributes().c_str(), content.attributes().size()), &parseError));
if (parseError.error != QJsonParseError::NoError)
continue;
const QJsonObject detailData = doc.object();
TopicReplay::Interface::SituationReplayTime sitReplayTime;
sitReplayTime.id = content.id();
sitReplayTime.topicStart_s = content.startTime();
sitReplayTime.topicEnd_s = content.startTime() + content.duration();
sitReplayTime.replaySpeedFactor = qRound64(detailData.value(QStringLiteral("speed")).toDouble(1.0));
sitReplayTime.replayStart_ms = qRound64(detailData.value(QStringLiteral("start")).toDouble());
sitReplayTime.replayEnd_ms = sitReplayTime.replayStart_ms + (content.duration() * sitReplayTime.replaySpeedFactor * 1000); // do not use 'end' json object
allSitReplayTimes.push_back(std::move(sitReplayTime));
}
}
for (const PresentationManagement::PresentationContent &content : m_contentList)
{
auto it = m_trackEntryAssignments.constFind(QString::fromStdString(content.presentationTopicType()));
if (it == m_trackEntryAssignments.constEnd())
{
ERROR_LOG(m_logger) << "Invalid content for presentation type " << content.presentationTopicType();
continue;
}
// Verify content
if (content.attributes().size() == 0)
continue;
QJsonParseError parseError;
const QJsonDocument doc(QJsonDocument::fromJson(QByteArray(content.attributes().c_str(), content.attributes().size()), &parseError));
if (parseError.error != QJsonParseError::NoError)
continue;
QJsonObject detailData = doc.object();
const PresentationManagement::TopicAssignment &ta = *it;
// Get export interface
TopicReplay::Interface *topicExportIfc = ta.topicExportFactory->createInterface(ta.topicType);
if (!topicExportIfc)
{
//WARNING_LOG(m_logger) << "Warning: Export for topic id " << te->m_topicId << " failed";
continue;
}
// add the filter steps
m_vecExportSteps += parseFilterSteps(content.id(), detailData, allSitReplayTimes);
// TrackEntry is used when all topics have been exported and the html table is created (start time, duration)
// after filter steps to make sure we use the updated detailData
TopicEditorBase::TopicData data(
content.id(), ta.topicType, content.startTime(), content.duration(), QString::fromStdString(content.description()),
QString::fromStdString(content.channel()), detailData
);
QJsonArray unused;
bool keepOriginalTopic = false;
m_vecExportSteps += checkAdditionalExportEntries(content.id(), data, unused, topicExportIfc, allSitReplayTimes, keepOriginalTopic);
if (keepOriginalTopic)
m_vecExportSteps += createExportStep(content.id(), data, topicExportIfc, allSitReplayTimes);
}
connect(m_pProgressDlg, &QProgressDialog::canceled, this, &ExportProcess::slotCancel);
m_cntTopicsToExport = m_vecExportSteps.size();
m_pProgressDlg->setMaximum(m_cntTopicsToExport);
slotRunExportStep();
}
QVector<ExportStep *> ExportProcess::parseFilterSteps(
int parentTopicId, QJsonObject &detailData, const QVector<TopicReplay::Interface::SituationReplayTime> &allSitReplayTimes
)
{
QVector<ExportStep *> ret;
const QJsonArray filters = detailData.value(QStringLiteral("Filters")).toArray();
QJsonArray newFilterLines;
for (const QJsonValue &filterLineValue : qAsConst(filters))
{
TopicEditorBase::TopicData data;
auto temp = filterLineValue.toObject();
data.fromJson(temp);
if (data.topicType != TopicEditorBase::TopicType::TacticalPlan &&
data.topicType != TopicEditorBase::TopicType::SituationMapFilter &&
data.topicType != TopicEditorBase::TopicType::Pdf &&
data.topicType != TopicEditorBase::TopicType::Image &&
data.topicType != TopicEditorBase::TopicType::Text)
continue;
auto topicExportIfc = topicIfcFromType(data.topicType, ExportOrReplay::Export);
if (!topicExportIfc)
continue;
auto topicReplayIfc = topicIfcFromType(data.topicType, ExportOrReplay::Replay);
if (!topicReplayIfc)
{
delete topicExportIfc;
continue;
}
bool keepOriginalTopic = true;
ret += checkAdditionalExportEntries(parentTopicId, data, newFilterLines, topicReplayIfc, allSitReplayTimes, keepOriginalTopic);
if (keepOriginalTopic)
ret += createExportStep(parentTopicId, data, topicExportIfc, allSitReplayTimes);
// delete topicExportIfc; // ownership is transfered to TakeHomePackageGenerator::ExportStep within createExportStep
delete topicReplayIfc;
newFilterLines += temp;
}
detailData[QStringLiteral("Filters")] = newFilterLines;
return ret;
}
QVector<ExportStep *> ExportProcess::checkAdditionalExportEntries(
int realParentId, const TopicEditorBase::TopicData &parentData, QJsonArray &filterLines,
TopicReplay::Interface *parentTopicReplayIfc, const QVector<TopicReplay::Interface::SituationReplayTime> &allSitReplayTimes,
bool &keepOriginalTopic
)
{
QVector<TakeHomePackageGenerator::ExportStep *> ret;
// add possible additional export entries
const auto addTrackEntries = parentTopicReplayIfc->additionalTrackEntries(parentData.properties, keepOriginalTopic);
for (const TopicEditorBase::TopicData &data : addTrackEntries)
{
TopicEditorBase::TopicData addData(data);
addData.description = parentData.description;
addData.startTime = parentData.startTime;
addData.duration = parentData.duration;
addData.id = parentData.id;
auto topicExportIfc = topicIfcFromType(addData.topicType, ExportOrReplay::Export);
DEBUG_LOG(m_logger) << "addEntry type: " << addData.topicType << ", ifc: " << topicExportIfc;
if (!topicExportIfc)
continue;
filterLines += addData.toJson();
ret += createExportStep(realParentId, addData, topicExportIfc, allSitReplayTimes);
m_cntTopicsToExport++;
}
DEBUG_LOG(m_logger) << "topic count: " << m_cntTopicsToExport;
return ret;
}
ExportStep *ExportProcess::createExportStep(
int parentTopicId, const TopicEditorBase::TopicData &data, TopicReplay::Interface *topicExportIfc,
const QVector<TopicReplay::Interface::SituationReplayTime> &allSitReplayTimes
)
{
TakeHomePackageGenerator::ExportStep *pExportStep = new TakeHomePackageGenerator::ExportStep(this, data, topicExportIfc);
pExportStep->setSituationReplayTimes(extractUsedSitReplayTimes(data, allSitReplayTimes));
pExportStep->setParentTopicId(parentTopicId);
pExportStep->setBasePath(exportPath(m_pSession->exerciseId()));
connect(pExportStep, &TakeHomePackageGenerator::ExportStep::signalExportSuccessful,
this, &ExportProcess::slotRunExportStep, Qt::QueuedConnection);
connect(pExportStep, &TakeHomePackageGenerator::ExportStep::signalExportFailed,
this, &ExportProcess::slotRunExportStep, Qt::QueuedConnection);
DEBUG_LOG(m_logger) << "Adding filter step " << data.topicType;
return pExportStep;
}
TopicReplay::Interface *ExportProcess::topicIfcFromType(TopicEditorBase::TopicType type, ExportOrReplay exportOrReplay) const
{
auto it = m_trackEntryAssignments.constFind(TopicEditorBase::topicTypeString(type));
if (it == m_trackEntryAssignments.constEnd())
{
WARNING_LOG(m_logger) << "Invalid content for presentation type " << type;
return nullptr;
}
const PresentationManagement::TopicAssignment &ta = *it;
TopicReplay::Interface *topicIfc = exportOrReplay == ExportOrReplay::Export ? ta.topicExportFactory->createInterface(ta.topicType)
: ta.topicReplayFactory->createInterface(ta.topicType);
if (!topicIfc)
WARNING_LOG(m_logger) << "Could not find replay interface for " << type;
return topicIfc;
}
void ExportProcess::slotCancel()
{
m_pProgressDlg->setLabelText(tr("Export cancelled, waiting for current\ntopic export to finish..."));
m_pProgressDlg->setCancelButton(nullptr);
m_pProgressDlg->setMaximum(0);
m_pProgressDlg->show();
}
void ExportProcess::slotRunExportStep()
{
if (m_logger.isDebugEnabled())
{
DEBUG_LOG(m_logger) << "ExportProcess::slotRunExportStep() CALLED, m_vecExportSteps.size(): " << m_vecExportSteps.size()
<< ", m_vecExportStepsExecuted.size(): " << m_vecExportStepsExecuted.size();
for (const auto &s : qAsConst(m_vecExportSteps))
DEBUG_LOG(m_logger) << "m_vecExportSteps: " << s << " compare: " << (s == sender());
for (const auto &s : qAsConst(m_vecExportStepsExecuted))
DEBUG_LOG(m_logger) << "m_vecExportStepsExecuted: " << s << " compare: " << (s == sender());
}
if (m_pProgressDlg->wasCanceled())
{
m_pProgressDlg->hide();
m_pProgressDlg->deleteLater();
return;
}
m_pProgressDlg->setValue(m_vecExportStepsExecuted.size());
if (m_vecExportSteps.size())
{
TakeHomePackageGenerator::ExportStep *pExportStep = m_vecExportSteps.takeFirst();
m_vecExportStepsExecuted.push_back(pExportStep);
QString strExportLabel(tr("Exporting topic of type \"%1\" (Topic %2 / %3)")
.arg(TopicEditorBase::topicTypeString(pExportStep->m_topicData.topicType))
.arg(m_vecExportStepsExecuted.size())
.arg(m_cntTopicsToExport));
DEBUG_LOG(m_logger) << strExportLabel;
m_pProgressDlg->setLabelText(strExportLabel);
QString strExportPath(contentPath() + QString::number(pExportStep->topicId()) + QLatin1Char('/'));
QDir dir(strExportPath);
dir.mkdir(strExportPath);
pExportStep->startExport(strExportPath);
}
else
exportDone();
}
void ExportProcess::exportDone()
{
createJsonFiles();
createPresentationTable();
copyStaticContent();
QStringList sl;
for (const auto &step : qAsConst(m_vecExportStepsExecuted))
{
if (step->exportFailed())
sl << QStringLiteral("%1 (%2)").arg(step->m_topicData.description, TopicEditorBase::topicTypeTranslation(step->m_topicData.topicType));
}
emit exportSuccessful(exportPath(m_pSession->exerciseId()), sl.join(QStringLiteral(",<br />")));
m_pProgressDlg->deleteLater();
deleteLater();
}
static inline QJsonArray descVecToArray(const std::vector<AssetManagement::MapDescription> &descriptions,
const QString &layerBaseName)
{
QJsonArray arr;
QJsonObject obj;
for (size_t i = 0; i < descriptions.size(); ++i)
{
// must be in sync with SituationMap::BaseUrl
const AssetManagement::MapDescription &desc = descriptions.at(i);
const bool isElevation = desc.mapDescriptionTypeId() == AssetManagement::MapDescriptionType::Elevation;
obj[QStringLiteral("name")] = QString::fromStdString(desc.name());
obj[QStringLiteral("description")] = QString::fromStdString(desc.description());
obj[QStringLiteral("url")] = QString(layerBaseName + QLatin1Char('_') + QString::number(i) + (isElevation ? QStringLiteral(".tiff") : QStringLiteral(".tpk")));
obj[QStringLiteral("minScale")] = desc.minScale();
obj[QStringLiteral("maxScale")] = desc.maxScale();
arr.append(obj);
}
return arr;
}
void ExportProcess::createJsonFiles()
{
std::shared_ptr<ExerciseManagement::Interface::Dtc> exmDtc = nullptr;
std::shared_ptr<AssetManagement::Interface::Dtc> asmDtc = nullptr;
if (!m_pSession->getDtc<AssetManagement::DataInterface>(asmDtc) ||
!m_pSession->getDtc<ExerciseManagement::DataInterface>(exmDtc))
return;
createLayerMappings(exmDtc, asmDtc);
createDataJson(exmDtc, asmDtc);
}
void ExportProcess::createLayerMappings(const std::shared_ptr<ExerciseManagement::Interface::Dtc> &exmDtc,
const std::shared_ptr<AssetManagement::Interface::Dtc> &asmDtc) const
{
QFile file(exportPath(m_pSession->exerciseId()) + QStringLiteral("layerMapping.json"));
if (!file.open(QIODevice::WriteOnly))
return;
const ExerciseManagement::Exercise exercise = exmDtc->exercise(m_pSession->exerciseId());
const AssetManagement::TrainingArea ta = asmDtc->trainingArea(exercise.trainingAreaId());
// must be in sync with MapSettingsJson::fromJsonObject
QJsonObject obj;
obj[QStringLiteral("mapUrls")] = descVecToArray(ta.map2D(), QStringLiteral("map2D"));
obj[QStringLiteral("sceneUrls")] = descVecToArray(ta.scene3D(), QStringLiteral("scene3D"));
obj[QStringLiteral("elevationUrls")] = descVecToArray(ta.elevation(), QStringLiteral("elevation"));
obj[QStringLiteral("sceneObjectUrls")] = descVecToArray(ta.sceneObjects(), QStringLiteral("sceneObjects"));
file.write(QJsonDocument(obj).toJson(QJsonDocument::Indented));
file.close();
}
void ExportProcess::createDataJson(const std::shared_ptr<ExerciseManagement::Interface::Dtc> &exmDtc,
const std::shared_ptr<AssetManagement::Interface::Dtc> &asmDtc) const
{
// Add exercise to ExportRootPath
{
DEBUG_LOG(m_logger) << "Add exercise to ExportRootPath";
QFile file(exportRootPath() + QStringLiteral("exercises.json"));
if (!file.open(QFile::ReadWrite))
{
WARNING_LOG(m_logger) << "Can not open " << file.fileName() << " for read/write: " << file.errorString();
return;
}
QJsonDocument doc(QJsonDocument::fromJson(file.readAll()));
QJsonObject obj = doc.object();
QJsonArray arr = obj[QStringLiteral("Exercises")].toArray();
if (!arr.contains(m_pSession->exerciseId()) )
arr.append(m_pSession->exerciseId());
obj[QStringLiteral("Exercises")] = arr;
file.resize(0);
file.write(QJsonDocument(obj).toJson(QJsonDocument::Indented));
INFO_LOG(m_logger) << " file.write " << QJsonDocument(obj).toJson(QJsonDocument::Indented);
file.close();
}
//
QFile file(exportPath(m_pSession->exerciseId()) + QStringLiteral("data.json"));
INFO_LOG(m_logger) << "ExportProcess::createDataJson " << exportPath(m_pSession->exerciseId()) + QStringLiteral("data.json");
if (!file.open(QFile::ReadWrite))
return;
QJsonDocument doc(QJsonDocument::fromJson(file.readAll()));
QJsonObject obj = doc.object();
const ExerciseManagement::Exercise exercise = exmDtc->exercise(m_pSession->exerciseId());
const AssetManagement::TrainingArea ta = asmDtc->trainingArea(exercise.trainingAreaId());
obj[QStringLiteral("title")] = QString::fromStdString(exercise.name());
obj[QStringLiteral("exerciseName")] = QString::fromStdString(exercise.name());
obj[QStringLiteral("exerciseId")] = exercise.id();
obj[QStringLiteral("exerciseArea")] = QString::fromStdString(ta.name());
obj[QStringLiteral("cycleTime")] = QString(QDateTime::fromMSecsSinceEpoch(exercise.startTime()).toString() +
QStringLiteral(" - ") + QDateTime::fromMSecsSinceEpoch(exercise.stopTime()).toString());
obj[QStringLiteral("exerciseStart")] = exercise.startTime();
obj[QStringLiteral("exerciseEnd")] = exercise.stopTime();
obj[QStringLiteral("exercisingForces")] = QString::fromStdString(exercise.forces());
obj[QStringLiteral("exercisingPurpose")] = QString::fromStdString(exercise.description());
// TODO: administrativeInstructions, exerciseIncidents
QJsonArray presentations = obj[QStringLiteral("presentations")].toArray();
const QString presPath = QString(QString::number(m_scriptId) + QStringLiteral("/presentation.json"));
bool bPresFound = false;
for (const QJsonValue &val : qAsConst(presentations))
{
const QJsonObject arrObj = val.toObject();
if (arrObj[QStringLiteral("path")].toString() == presPath)
{
bPresFound = true;
break;
}
}
if (!bPresFound)
{
ExportedScriptInformation esi;
esi.scriptId = m_scriptId;
esi.scriptName = m_strScriptName;
esi.exerciseName = m_strExerciseName;
esi.jsonPath = presPath;
esi.exportTime = QDateTime::currentDateTimeUtc().toMSecsSinceEpoch();
presentations += esi.toJson();
obj[QStringLiteral("presentations")] = presentations;
}
file.resize(0);
file.write(QJsonDocument(obj).toJson(QJsonDocument::Indented));
INFO_LOG(m_logger) << " file.write " << QJsonDocument(obj).toJson(QJsonDocument::Indented);
file.close();
}
void ExportProcess::createPresentationTable()
{
// Order chronologically
std::sort(m_vecExportStepsExecuted.begin(), m_vecExportStepsExecuted.end(),
[](const TakeHomePackageGenerator::ExportStep * a, const TakeHomePackageGenerator::ExportStep * b) -> bool
{
return a->m_topicData.startTime < b->m_topicData.startTime;
});
QString presentationTableDirName = contentPath();
presentationTableDirName.truncate(presentationTableDirName.size() - 1);
QDir presentationDir(presentationTableDirName);
if (!presentationDir.exists())
{
bool succ = presentationDir.mkdir(presentationTableDirName);
if (!succ)
{
return;
}
}
QFile file(contentPath() + QStringLiteral("presentation.json"));
if (!file.open(QFile::WriteOnly | QFile::Truncate))
return;
QJsonObject obj;
obj[QStringLiteral("title")] = m_strScriptName;
obj[QStringLiteral("exerciseName")] = m_strExerciseName;
QJsonArray topicArray;
int topicId = 1;
for (const TakeHomePackageGenerator::ExportStep *s : qAsConst(m_vecExportStepsExecuted))
{
if (s->exportFailed())
{
WARNING_LOG(m_logger) << "Export failed for " << s->topicId();
continue;
}
if (s->m_topicData.topicType == TopicEditorBase::TopicType::SituationReplay ||
s->m_topicData.topicType == TopicEditorBase::TopicType::TacticalPlan ||
s->m_topicData.topicType == TopicEditorBase::TopicType::Situation ||
s->filename().isEmpty())
{
/* these topics exported their data into an existing topic - e.g.:
* Replay is put into a SituationMap topic, because a Replay topic
* alone doesn't make sense in the THP, as to play a topic, all topic
* information (Map, Replay, Filters, etc.) need to be inside ONE topic */
continue;
}
QJsonObject topicObj;
topicObj[QStringLiteral("topicId")] = topicId++;
topicObj[QStringLiteral("topicStart")] = QTime::fromMSecsSinceStartOfDay(s->m_topicData.startTime * 1000).toString();
topicObj[QStringLiteral("duration")] = QTime::fromMSecsSinceStartOfDay(s->m_topicData.duration * 1000).toString();
topicObj[QStringLiteral("type")] = TopicEditorBase::topicTypeString(s->m_topicData.topicType);
#ifndef EXPORT_BDV_TOPIC_AS_JSON
if (s->m_topicData.topicType == TopicEditorBase::TopicType::Building)
{
QDir dir(s->filename());
const auto listVideos = dir.entryList({QStringLiteral("*.mpg")}, QDir::Files, QDir::Name);
for (const QString &strVideo : listVideos)
{
const QString relPath = QString::number(m_scriptId) + QLatin1Char('/') + dir.dirName() + QLatin1Char('/') + strVideo;
topicObj[QStringLiteral("description")] = QJsonObject({{QStringLiteral("displayName"), strVideo},
{QStringLiteral("href"), relPath}});
topicArray += topicObj;
}
continue;
}
#endif
QString dispName(s->m_topicData.description.isEmpty() ?
TopicEditorBase::topicTypeString(s->m_topicData.topicType) :
s->m_topicData.description);
if (s->m_topicData.topicType == TopicEditorBase::TopicType::Audio || s->m_topicData.topicType == TopicEditorBase::TopicType::Video)
dispName = s->filename().splitRef(QLatin1Char('/')).last().toString();
if (!s->filename().isEmpty())
{
const QString stRelativeLinkToExportedFile(QString::number(m_scriptId) + QLatin1Char('/') +
QString::number(s->topicId()) + QLatin1Char('/') +
s->filename());
const QString stRelativeLinkToLocalExportedFile(QString::number(s->topicId()) + QLatin1Char('/') + s->filename());
topicObj[QStringLiteral("description")] = QJsonObject({{QStringLiteral("displayName"), dispName},
{QStringLiteral("href"), stRelativeLinkToExportedFile},
{QStringLiteral("href_local"), stRelativeLinkToLocalExportedFile}
});
// topicObj[QStringLiteral("href")] = stRelativeLinkToExportedFile;
// topicObj[QStringLiteral("href_local")] = stRelativeLinkToLocalExportedFile;
}else
{
topicObj[QStringLiteral("description")] = QJsonObject({{ QStringLiteral("displayName"), dispName }});
}
topicArray += topicObj;
}
obj[QStringLiteral("content")] = topicArray;
file.write(QJsonDocument(obj).toJson(QJsonDocument::Indented));
file.close();
}
void ExportProcess::copyStaticContent() const
{
QString exportPathStr = exportPath(m_pSession->exerciseId());
exportPathStr.truncate(exportPathStr.size() - 1);
if (!NGL::copyDirContentsToDir(QStringLiteral("thp"), exportPathStr, true))
WARNING_LOG(m_logger) << "Could not find thp static content directory exportPath=" << exportPathStr;
if (!NGL::copyDirContentsToDir(QStringLiteral("thp_standalone"), exportPathStr + QDir::separator() + QString::number(m_scriptId), true))
WARNING_LOG(m_logger) << "Could not find thp_standalone static content directory exportPath=" << exportPathStr + QDir::separator() + QString::number(m_scriptId);
}
}
@@ -0,0 +1,125 @@
/**
* @RDE_HEADER@
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Last, NLSE, 2018-07-09
*/
#pragma once
#include <QObject>
#include <QJsonArray>
#include <Plugins/PresentationManagement/Gui/Script/Common/preguiscriptcommon_export.h>
#include <Plugins/PresentationManagement/Gui/Common/topic_assignment.h>
#include <Plugins/SituationReplay/srp_gui_plugin_interface.h>
#include <PresentationManagement/pre_dtc.h>
#include <PresentationManagement/logger.h>
#include <AssetManagement/asm_dtc.h>
#include <ExerciseManagement/exm_dtc.h>
#include <LtsSession/lts_session.h>
#include <LtsScriptTimeline/track_content_widget.h>
class QProgressDialog;
namespace TakeHomePackageGenerator
{
class ExportStep;
struct ExportedScriptInformation
{
QString scriptName;
QString exerciseName;
QString jsonPath;
qint64 exportTime = 0;
int scriptId = 0;
int id() const { return scriptId; }
QJsonValue toJson() const;
};
class PREGUISCRIPTCOMMON_EXPORT ExportProcess : public QObject
{
Q_OBJECT
public:
ExportProcess(LtsSession::LtsSession *pSession,
const std::shared_ptr<SituationReplay::ForeignGuiInterface> &replayGuiIfc,
QProgressDialog *pProgressDlg,
const QString &strScriptName,
const QString &strExerciseName,
int scriptId,
const std::vector<PresentationManagement::PresentationContent> &contentList,
const QMap<QString, PresentationManagement::TopicAssignment> &trackEntryAssignments,
QObject *parent);
~ExportProcess();
static QString exportPath(const int exerciseId);
static QString exportRootPath();
signals:
void exportSuccessful(const QString &m_strExportPath, const QString &failedTopicsDescription);
public slots:
void slotStartExport();
private slots:
void slotCancel();
void slotRunExportStep();
private:
QVector<ExportStep *> parseFilterSteps(
int parentTopicId, QJsonObject &detailData, const QVector<TopicReplay::Interface::SituationReplayTime> &allSitReplayTimes
);
QVector<ExportStep *> checkAdditionalExportEntries(
int realParentId, const TopicEditorBase::TopicData &parentData, QJsonArray &filterLines,
TopicReplay::Interface *parentTopicReplayIfc, const QVector<TopicReplay::Interface::SituationReplayTime> &allSitReplayTimes,
bool &keepOriginalTopic
);
ExportStep *createExportStep(
int parentTopicId, const TopicEditorBase::TopicData &data, TopicReplay::Interface *topicExportIfc,
const QVector<TopicReplay::Interface::SituationReplayTime> &allSitReplayTimes
);
enum class ExportOrReplay
{
Export,
Replay
};
TopicReplay::Interface *topicIfcFromType(TopicEditorBase::TopicType type, ExportOrReplay exportOrReplay) const;
QString contentPath() const { return exportPath(m_pSession->exerciseId()) + QString::number(m_scriptId) + QLatin1Char('/'); }
void exportDone();
void createJsonFiles();
void createLayerMappings(const std::shared_ptr<ExerciseManagement::Interface::Dtc> &exmDtc,
const std::shared_ptr<AssetManagement::Interface::Dtc> &asmDtc) const;
void createDataJson(const std::shared_ptr<ExerciseManagement::Interface::Dtc> &exmDtc,
const std::shared_ptr<AssetManagement::Interface::Dtc> &asmDtc) const;
void createPresentationTable();
void copyStaticContent() const;
private:
QVector<ExportStep *> m_vecExportSteps;
QVector<ExportStep *> m_vecExportStepsExecuted;
std::vector<PresentationManagement::PresentationContent> m_contentList;
QMap<QString, PresentationManagement::TopicAssignment> m_trackEntryAssignments;
QString m_strScriptName;
QString m_strExerciseName;
int m_scriptId;
LtsSession::LtsSession *m_pSession;
std::shared_ptr<SituationReplay::ForeignGuiInterface> m_replayGuiIfc;
QProgressDialog *m_pProgressDlg;
int m_cntTopicsToExport;
NGL::INGLLogger &m_logger;
};
}
@@ -0,0 +1,70 @@
/**
* @RDE_HEADER@
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Last, NLSE, 2018-07-09
*/
#include "export_step.h"
namespace TakeHomePackageGenerator
{
ExportStep::ExportStep(QObject *parent, const TopicEditorBase::TopicData &data, TopicReplay::Interface *topicReplayIfc)
: QObject(parent)
, m_topicData(data)
, m_notifier(this)
, m_topicReplayIfc(topicReplayIfc)
, m_timer(new QTimer(this))
, m_parentTopicId(-1)
, m_exportFailed(false)
{
m_timer->setSingleShot(true);
m_timer->setInterval(30 * 1000); // 30s timeout
connect(m_timer, &QTimer::timeout, this, &ExportStep::slotExportFailed);
connect(&m_notifier, &TopicEditorBase::ExportNotifier::stillAlive, this, &ExportStep::slotResetTimer);
connect(&m_notifier, &TopicEditorBase::ExportNotifier::exportSuccessful, this, &ExportStep::slotExportSuccessful);
connect(&m_notifier, &TopicEditorBase::ExportNotifier::exportFailed, this, &ExportStep::slotExportFailed);
}
ExportStep::~ExportStep()
{
m_timer->stop();
delete m_topicReplayIfc;
}
void ExportStep::startExport(const QString &strExportPath)
{
m_timer->start();
m_topicReplayIfc->setExportPlaybackTimes(m_topicData.startTime, m_topicData.duration, m_sitReplayTimes);
m_topicReplayIfc->setBasePath(m_basePath);
m_topicReplayIfc->exportTopic(m_topicData.properties, strExportPath, &m_notifier);
}
void ExportStep::slotResetTimer()
{
m_timer->start();
}
void ExportStep::slotExportFailed()
{
m_timer->stop();
m_exportFailed = true;
m_notifier.disconnect(); // Export failed e.g. due to timeout. We do not want to get a "success signal" seconds later. Timeout is timeout, fail is fail, later success does not matter
emit signalExportFailed();
}
void ExportStep::slotExportSuccessful(const QString &strFilename)
{
m_timer->stop();
m_strFileName = strFilename;
m_notifier.disconnect();
emit signalExportSuccessful();
}
}
@@ -0,0 +1,60 @@
/**
* @RDE_HEADER@
* $Id$
* $HeadURL$
* $Author$
* Created by Philipp Last, NLSE, 2018-07-09
*/
#pragma once
#include <Plugins/PresentationManagement/Gui/Script/Common/preguiscriptcommon_export.h>
#include <QObject>
#include <QTimer>
#include <LtsScriptTimeline/track_entry.h>
#include <PluginInterfaces/topic_replay_interface.h>
#include <Common/TopicEditorBase/topic_editor_base.h>
namespace TakeHomePackageGenerator
{
class PREGUISCRIPTCOMMON_EXPORT ExportStep : public QObject
{
Q_OBJECT
public:
ExportStep(QObject *parent, const TopicEditorBase::TopicData &data, TopicReplay::Interface *topicReplayIfc);
~ExportStep();
void startExport(const QString &strExportPath);
void setSituationReplayTimes(const QVector<TopicReplay::Interface::SituationReplayTime> &times) { m_sitReplayTimes = times; }
void setParentTopicId(int parentId) { m_parentTopicId = parentId; }
void setBasePath(const QString &basePath) { m_basePath = basePath; }
int topicId() const { return m_parentTopicId < 0 ? m_topicData.id : m_parentTopicId; }
bool exportFailed() const { return m_exportFailed; }
QString filename() const { return m_strFileName; }
// wtf?
TopicEditorBase::TopicData m_topicData;
signals:
void signalExportSuccessful();
void signalExportFailed();
private slots:
void slotResetTimer();
void slotExportSuccessful(const QString &strFilename);
void slotExportFailed();
private:
QVector<TopicReplay::Interface::SituationReplayTime> m_sitReplayTimes;
QString m_strFileName;
QString m_basePath;
TopicEditorBase::ExportNotifier m_notifier;
TopicReplay::Interface *m_topicReplayIfc;
QTimer *m_timer;
int m_parentTopicId;
bool m_exportFailed;
};
}
@@ -0,0 +1,234 @@
/**
* @RDE_HEADER@
* Export data from postgresql database to sqlite
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, NLSE, 2018-03-14
*/
#include "situation_exporter.h"
#include "db_reader.h"
#include <nglsettings/nglsettings_settings.h>
#include <ngldb/dbconnection.h>
#include <ngldb/dbtransaction.h>
#include <ngldb/nsqlquery.h>
#include <QFile>
#include <QSqlRecord>
#include <QSqlError>
#include <QSqlField>
#include <QSqlIndex>
#include <QSqlDatabase>
#include <QVariant>
namespace TakeHomePackageGenerator
{
SituationExporter::SituationExporter(int exerciseId)
: m_exerciseId(exerciseId)
, m_logger(PresentationManagement::getLogger("SituationExporter"))
{}
SituationExporter::~SituationExporter()
{
const QString dbConInName = m_dbIn.connectionName();
const QString dbConOutName = m_dbOut.connectionName();
m_dbIn.close();
m_dbOut.close();
m_dbIn = QSqlDatabase();
m_dbOut = QSqlDatabase();
if (!dbConInName.isEmpty())
QSqlDatabase::removeDatabase(dbConInName);
if (!dbConOutName.isEmpty())
QSqlDatabase::removeDatabase(dbConOutName);
}
bool SituationExporter::exportStaticData(const QString &strPathToFile)
{
DEBUG_LOG(m_logger) << "Exporting static data to " << strPathToFile;
m_dbIn.close();
m_dbOut.close();
QString str;
NGL::Settings s(str); // FIXME
// open postgres db, read table structure, read static data
const QString dbUser(QStringLiteral("postgres"));
const QString dbPasswd(QStringLiteral("postgres"));
m_dbIn = DBConnection::open(m_logger,
dbUser,
dbPasswd,
s.databaseDriver(),
s.databaseHostname(),
s.databaseName());
if (!m_dbIn.isOpen())
return false;
// open sqlite db, create tables, write static data
QFile::remove(strPathToFile);
m_dbOut = DBConnection::open(m_logger,
QString(),
QString(),
QStringLiteral("QSQLITE"),
QString(),
strPathToFile);
if (!m_dbOut.isOpen())
return false;
DBTransaction transOut(m_dbOut);
//const int fileTypeId = FileManagement::FileType::TacticalPlan;
constexpr int fileTypeTacticalPlan = 6;
constexpr int fileTypeOperatorDefinedSymbol = 8;
const QVector<StaticTableToCopy> staticTables =
{{QStringLiteral("asm"), QStringLiteral("asm_training_area"), QString()},
{QStringLiteral("exm"), QStringLiteral("exercise"), QStringLiteral("id = %1").arg(m_exerciseId)},
{QStringLiteral("exm"), QStringLiteral("exercise_phase"), QStringLiteral("exercise_id = %1").arg(m_exerciseId)},
{QStringLiteral("flm"), QStringLiteral("flm_file_content"), QStringLiteral("(file_type_id = %1 OR file_type_id = %2) AND file_status_id = 0").arg(fileTypeTacticalPlan).arg(fileTypeOperatorDefinedSymbol)},
{QStringLiteral("flm"), QStringLiteral("flm_file_type_info"), QString()},
{QStringLiteral("flm"), QStringLiteral("flm_folder"), QString()},
{QStringLiteral("ism"), QStringLiteral("ism_main_structure_element"), QString()},
{QStringLiteral("ism"), QStringLiteral("ism_structure_element"), QString()},
{QStringLiteral("ism"), QStringLiteral("ism_structure_element_type"), QString()},
{QStringLiteral("ism"), QStringLiteral("ism_mounting_point_type"), QString()},
{QStringLiteral("ism"), QStringLiteral("ism_structure_element_footprint"), QString()},
{QStringLiteral("ppm"), QStringLiteral("ppm_type"), QString()},
{QStringLiteral("pdm"), QStringLiteral("pdm_can_component_string"), QString()},
{QStringLiteral("pdm"), QStringLiteral("pdm_charge_type"), QString()},
{QStringLiteral("pdm"), QStringLiteral("pdm_fire_purpose"), QString()},
{QStringLiteral("pdm"), QStringLiteral("pdm_gear_type"), QString()},
{QStringLiteral("pdm"), QStringLiteral("pdm_gear_type_charge_type"), QString()},
{QStringLiteral("pdm"), QStringLiteral("pdm_language_hmi"), QString()},
{QStringLiteral("pdm"), QStringLiteral("pdm_translation"), QString()},
{QStringLiteral("pdm"), QStringLiteral("pdm_vulnerability_kind"), QString()}
};
for (const auto &t : staticTables)
{
if (!copyTable(t, false))
{
ERROR_LOG(m_logger) << "Copying table " << t.schema << "." << t.table << " failed... ";
return false;
}
}
const QString exerciseSchema = DBCommon::getExerciseSchemaName(m_exerciseId);
const QVector<StaticTableToCopy> srpTables =
{{exerciseSchema, QStringLiteral("flt_aes_filter"), QString()},
{exerciseSchema, QStringLiteral("flt_symbol_appearance"), QString()},
{exerciseSchema, QStringLiteral("srp_situation_replay"), QString()},
{exerciseSchema, QStringLiteral("srp_replay_journal"), QString()},
{exerciseSchema, QStringLiteral("rec_snapshot"), QString()}
};
for (const auto &t : srpTables)
{
if (!copyTable(t, true))
{
ERROR_LOG(m_logger) << "Copying table " << t.schema << "." << t.table << " failed... ";
return false;
}
}
transOut.commit();
return true;
}
bool SituationExporter::exportDynamicData(qint64 fromTime, qint64 toTime)
{
DbReader dbReader(m_exerciseId);
if (!dbReader.init(m_dbIn, m_dbOut))
return false;
DBTransaction trans(m_dbOut);
if (!dbReader.createTables() || !dbReader.copyData(fromTime, toTime)) // todo: !
return false;
QString stmtUpdateExercise = QStringLiteral("UPDATE exercise SET begin_time = :begin_time, end_time = :end_time");
NSqlQuery q(m_dbOut);
if (!q.prepare(stmtUpdateExercise))
return false;
q.bindValue(QStringLiteral("begin_time"), fromTime);
q.bindValue(QStringLiteral("end_time"), toTime);
if (!q.exec())
return false;
trans.commit();
return true;
}
bool SituationExporter::copyTable(const StaticTableToCopy &t, bool bOnlyCreateSchema)
{
NSqlQuery qIn(m_dbIn);
NSqlQuery qOut(m_dbOut);
const QString fqTableName(t.schema.isEmpty() ? t.table : (t.schema + QLatin1Char('.') + t.table));
DEBUG_LOG(m_logger) << "Copying " << fqTableName << " started";
const QSqlRecord record = m_dbIn.record(fqTableName);
if (record.isEmpty())
{
ERROR_LOG(m_logger) << "Empty record for " << fqTableName;
return false;
}
QStringList selectTableColumns;
QStringList insertTablePlaceholder;
selectTableColumns.reserve(record.count());
insertTablePlaceholder.reserve(record.count());
for (int i = 0; i < record.count(); ++i)
{
const QSqlField &f = record.field(i);
selectTableColumns += f.name();
insertTablePlaceholder += QLatin1Char(':') + f.name();
}
const QSqlIndex pk = m_dbIn.primaryIndex(fqTableName);
if (!DbReader::createTable(qOut, t.table, record, pk, m_logger))
return false;
if (bOnlyCreateSchema)
return true;
// do not copy tables without schemaName
if (t.schema.isEmpty())
return true;
const QString whereStmt(t.where.isEmpty() ? QString() : QStringLiteral(" WHERE %1").arg(t.where));
const QString selectTableCommand(QStringLiteral("SELECT %1 FROM %2%3")
.arg(selectTableColumns.join(QStringLiteral(", ")), fqTableName, whereStmt));
const QString insertTableCommand(QStringLiteral("INSERT INTO %1 (%2) VALUES (%3)")
.arg(t.table,
selectTableColumns.join(QStringLiteral(", ")),
insertTablePlaceholder.join(QStringLiteral(", "))));
DEBUG_LOG(m_logger) << selectTableCommand;
DEBUG_LOG(m_logger) << insertTableCommand;
if (!qOut.prepare(insertTableCommand))
{
ERROR_LOG(m_logger) << "qOut prepare failed... " << qOut.lastError().text();
return false;
}
if (!qIn.exec(selectTableCommand))
{
ERROR_LOG(m_logger) << "qIn select failed... " << qIn.lastError().text();
return false;
}
while (qIn.next())
{
for (int i = 0; i < insertTablePlaceholder.size(); ++i)
qOut.bindValue(insertTablePlaceholder.at(i), qIn.value(i));
if (!qOut.exec())
{
ERROR_LOG(m_logger) << "qOut insert failed.. " << qOut.lastError().text();
return false;
}
}
DEBUG_LOG(m_logger) << "Copying " << fqTableName << " finished";
return true;
}
} // namespace TakeHomePackageGenerator
@@ -0,0 +1,41 @@
/**
* @RDE_HEADER@
* Export data from postgresql database to sqlite
* $Id$
* $HeadURL$
* $Author$
* Created by Christian Ehrlicher, NLSE, 2018-03-14
*/
#pragma once
#include <PresentationManagement/logger.h>
#include <QString>
#include <QSqlDatabase>
namespace TakeHomePackageGenerator
{
class SituationExporter
{
public:
explicit SituationExporter(int exerciseId);
~SituationExporter();
bool exportStaticData(const QString& strPathToFile);
bool exportDynamicData(qint64 fromTime, qint64 toTime);
private:
struct StaticTableToCopy
{
QString schema;
QString table;
QString where;
};
bool copyTable(const StaticTableToCopy &t, bool bOnlyCreateSchema);
private:
int m_exerciseId;
QSqlDatabase m_dbIn;
QSqlDatabase m_dbOut;
NGL::INGLLogger &m_logger;
};
} // namespace TakeHomePackageGenerator