Files
qt5-legatus-legacy/Lts/Plugins/Timeline/Gui/EventTimeline/tln_timeline_model.cpp
T
2026-08-13 18:33:10 +02:00

3188 lines
109 KiB
C++
Executable File

//#warning TODO: // instead of using raw integers for ids (tracks, hit building tracks, etc. etc.), use typedefs to int! => never mixing up ids again
#include "tln_timeline_model.h"
#include "tln_timeline_delegate.h"
#include "tln_timeline_dialog.h"
#include "logger.h"
#include "tln_av_tracks.h"
#include "tln_marker_track.h"
#include "EventTypes/BiteMessage/tln_bite_message_event_factory.h"
#include "EventTypes/CameraVideo/tln_camera_video_event_factory.h"
#include "EventTypes/CrossBorderline/tln_cross_borderline_event_factory.h"
#include "EventTypes/FireEvent/tln_fire_event_event_factory.h"
#include "EventTypes/Hit/tln_hit_event_factory.h"
#include "EventTypes/HitBuilding/tln_hit_building_event_factory.h"
#include "EventTypes/HitEffectSimulation/tln_hit_effect_simulation_event_factory.h"
#include "EventTypes/Manipulation/tln_manipulation_event_factory.h"
#include "EventTypes/Marker/tln_marker_event_factory.h"
#include "EventTypes/Marker/tln_marker_filter.h"
#include "EventTypes/RadioCnr/tln_radio_cnr_event_factory.h"
#include "EventTypes/RadioTvc/tln_radio_tvc_event_factory.h"
#include "EventTypes/WeaponAssignment/tln_weapon_assignment_event_factory.h"
#include <AudioVideoManagement/stream_name_mapping.h>
#include <InfrastructureManagement/ism_data_plugin_interface.h>
#include <MarkerManagement/mrk_data_plugin_interface.h>
#include <ngldb/dbconnection.h>
#include <nglsettings/nglsettings_connections.h>
#include <nglsettings/nglsettings_settings.h>
#include <ngltools/ngltools_filesystem.h>
#include <LtsSession/lts_session.h>
#include <LtsSession/lts_session_data.h>
#include <LtsTimeline/timeline_view.h>
#include <ParticipantManagement/ppm_data_plugin_interface.h>
#include <ParticipantManagement/ppm_translation.h>
#include <PluginInterfaces/gui_plugin_interface.h>
#include <Timeline/tln_data_plugin_interface.h> // eem foreign
//#include <PluginInterfaces/gui_plugin_interface.h>
#include <LtsWidgets/lts_message_dialog.h>
#include "tln_abstract_event_item.h"
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QSqlRecord>
#include <QSqlField>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QJsonObject>
#include <QJsonArray>
#include <QElapsedTimer>
#include <QTimer>
#include <QDateTime>
#include <QFuture>
#include <QFutureWatcher>
#include <QtConcurrent>
#include <QDebug>
#include "Services/AudioVideoManagement/stream_name_mapping.h"
#include <map>
#include <stdio.h>
#include <deque>
using Tick = LtsTimeline::Tick;
using namespace LtsTimeline;
namespace Timeline
{
/*
GetOfflineVideosProcess::GetOfflineVideosProcess(const QMap<QString, QString> &streams, TimelineModel *model, QObject *parent)
: QObject(parent)
, m_streams(streams)
, m_model(model)
, m_streamsIt(streams.constBegin())
{
//next();
// qCritical() << "\nGetOfflineVideoProcess next\n";
while (m_streamsIt != m_streams.constEnd())
next();
}
void GetOfflineVideosProcess::next()
{
// if (m_streamsIt == m_streams.constEnd())
// {
// emit signalDone();
// return;
// }
QString _internal(m_streamsIt.key());
QString _display(m_streamsIt.value());
// qCritical() << "i:" << _internal << ", d:" << _display;
m_streamsIt++;
AudioVideoManagement::StreamClipsResult *clipsListResult = m_model->m_avmIfc->getAsyncStreamClips(_internal);
m_open++;
if (clipsListResult == nullptr)
{
qCritical() << "getAsyncStreamClips failed - next";
//next();
}
connect(clipsListResult, &AudioVideoManagement::StreamClipsResult::emitClipsList,
this,
[this, clipsListResult, _internal, _display](const AudioVideoManagement::AvaTimeline &clips)
{
m_open--;
auto _ = std::shared_ptr<int>(nullptr, [this](int *)
{
//Emit Signal if fetching offline videos is done
if(m_open == 0)
emit signalDone();
}); (void)_;
const QString &internal = _internal;
const QString &display = _display;
//auto _ = std::shared_ptr<int>(nullptr, [this](int *) { qCritical() << "next!"; next(); }); (void)_;
// get filter
// auto eventFilterIt = std::find_if(m_model->m_filter.constBegin(), m_model->m_filter.constEnd(), [this](const AbstractEventFilter *eventFilter)
// {
// return eventFilter->type() == EventType::CameraVideo;
// });
// if (eventFilterIt == m_model->m_filter.constEnd())
// return;
//AbstractEventFactory *factory = TimelineModel::constFinder<AbstractEventFactory *>(m_model->m_eventFactories, EventType::CameraVideo);
for (auto clip : clips.parts())
{
// qint64 time = clip.first.time_since_epoch().count();
// qint64 duration = clip.second.time_since_epoch().count() - clip.first.time_since_epoch().count();
//#warning rangeOnly
// if ((time < m_model->m_view->startTime() || time > m_model->m_view->endTime()))
// return;
// QJsonObject properties(
// QJsonObject({
// { QStringLiteral("MainStructureElementId"), 0 }
// , { QStringLiteral("duration"), duration }
// , { QStringLiteral("streamName"), internal }
// }));
//qCritical() << "\nHIER WERDEN DIE TRACKS ZUR GEBUFFERTEN LISTE HINZUGEFÜGT : " << clips.parts().length() << "\n";
// NOTE: no collision checks on markers, as all markers have to be visible by spec (don't ask me where to find the spec ;))
//EventItemPtr event = factory->createItem(time, duration, properties, {});
qint64 firstTime = clip.first.time_since_epoch().count() * 10;
qint64 secondTime = clip.second.time_since_epoch().count() * 10;
// NOTE: no collision checks on markers, as all markers have to be visible by spec (don't ask me where to find the spec ;))
//EventItemPtr event = factory->createItem(time, duration, properties, {});
//m_model->m_multiMapClipsForStream.insert(internal, {clip.first.time_since_epoch().count() * 10 , clip.second.time_since_epoch().count() * 10});
m_model->m_multiMapClipsForStream.insert(internal, {firstTime , secondTime});
// QJsonDocument doc(event->properties());
// if (!filter->check(*event.get()))
// return;
// int trackId;
// try
// {
// trackId = TimelineModel::constFinder<int>(m_model->m_offlineVideoTracks, internal);
// }
// catch (...)
// {
// trackId = m_model->addTrack(m_model->m_videosRootTrackId, VideoTrack(display));
// m_model->m_offlineVideoTracks.insert(display, trackId);
// }
// m_model->LtsTimeline::TimelineModel::addItem(trackId, std::move(event), true);
}
// fprintf(stderr, "%d", m_open);
clipsListResult->deleteLater();
});
}*/
struct TimelineTableInfosHandler
{
TimelineTableInfos timelineTableInfos;
};
Q_GLOBAL_STATIC(TimelineTableInfosHandler, s_timelineTableInfos)
TimelineTableInfos *TimelineTableInfos::getInstance()
{
return &s_timelineTableInfos->timelineTableInfos;
}
QString TimelineModel::s_eemEventColumns = QStringLiteral("id, sample_time, event_type_id, properties, participant_id, party_id, equipment_number, position_x, position_y, position_z");
int TimelineModel::s_minThreadCount = 2;
int TimelineModel::s_maxThreadCount = 8;
int TimelineModel::getInt(const Row &row, const QString &column) noexcept(false)
{
auto it = row.constFind(column);
if (it == row.constEnd())
throw QStringLiteral("getInt: column '%1' not found").arg(column);
bool k = false;
const QVariant &value = *it;
int result = value.toInt(&k);
if (!k)
throw QStringLiteral("getInt: value for column %1 ('%2') is not an integer").arg(column, value.toString());
return result;
}
qint64 TimelineModel::getLongLong(const Row &row, const QString &column) noexcept(false)
{
auto it = row.constFind(column);
if (it == row.constEnd())
throw QStringLiteral("getLongLong: column '%1' not found").arg(column);
bool k = false;
const QVariant &value = *it;
qint64 result = value.toLongLong(&k);
if (!k)
throw QStringLiteral("getLongLong: value for column %1 ('%2') is not an integer").arg(column, value.toString());
return result;
}
QJsonObject TimelineModel::getJsonObject(const Row &row, const QString &column, bool ignoreError) noexcept(false)
{
auto it = row.constFind(column);
if (it == row.constEnd())
throw QStringLiteral("getLongLong: column '%1' not found").arg(column);
const QVariant &value = *it;
QJsonParseError err;
QJsonDocument doc(QJsonDocument::fromJson(value.toString().toUtf8(), &err));
if (err.error != QJsonParseError::NoError)
{
if (ignoreError)
return {};
throw QStringLiteral("getJsonObject: json parse error for column '%1': %2 at %3").arg(column, err.errorString()).arg(err.offset);
}
QJsonValue rootVal(doc.object());
if (!rootVal.isObject())
throw QStringLiteral("root not an object");
return rootVal.toObject();
}
double TimelineModel::getJsonDouble(const Row &row, const QString &column) noexcept(false)
{
auto it = row.constFind(column);
if (it == row.constEnd())
throw QStringLiteral("getDouble: column '%1' not found").arg(column);
bool k = false;
const QVariant &value = *it;
double result = value.toDouble(&k);
if (!k)
throw QStringLiteral("getDouble: value for column %1 ('%2') is not an integer").arg(column, value.toString());
return result;
}
TimelineModel::TimelineModel(LtsSession::LtsSession *session, TimelineDialog *dialog, qint64 exerciseStart, qint64 exerciseEnd, QObject *parent) noexcept(false)
: LtsTimeline::TimelineModel(dialog->view(), parent)
, m_session(session)
, m_updateTimer(new QTimer(this))
, m_dialog(dialog)
, m_logger(getLogger("TimelineModel"))
{
m_modelState.publicTableName = QStringLiteral("public.tln_temp_view_%1_%2")
.arg(LtsSessionData::LtsSessionData::getInstance()->getOperatorName().toLower())
.arg(m_modelState.dbUuid);
//m_modelState.dbUuid = QUuid::createUuid().toString(QUuid::Id128);
m_updateTimer->setSingleShot(true);
m_updateTimer->setInterval(1500);
connect(m_updateTimer, &QTimer::timeout,
this, &TimelineModel::onUpdate);
NGL::Settings *s = new NGL::Settings(NGL::findProjectConfigFile());
const QString dbUser(QStringLiteral("ets_exm"));
const QString dbPasswd(QStringLiteral("ets11de"));
const QString hostname(s->databaseHostname());
const QString driver(s->databaseDriver());
const QString dbname(s->databaseName());
m_db = DBConnection::open(m_logger, dbUser, dbPasswd, driver, hostname, dbname);
delete s;
DEBUG_LOG(m_logger) << "s->databaseDriver() " << driver << ", s->databaseName() " << dbname;
DEBUG_LOG(m_logger) << "Trying to open database " << hostname << ":" << dbname;
if (!m_db.isOpen())
{
ERROR_LOG(m_logger) << "Error opening database " << hostname << ":" << dbname << " - " << m_db.lastError().text();
throw "cannot open connection to database";
}
else
NOTICE_LOG(m_logger) << "Opened database connection " << m_db.connectionName();
clearUnusedTempTables(); // needs alive db connection
TimelineTableInfos::getInstance()->insertTable(m_modelState.publicTableName);
if (!session->getDtc<ParticipantManagement::DataInterface>(m_ppmDtc))
throw "cannot get ppm dtc";
if (!session->getDtc<MarkerManagement::DataInterface>(m_mrkDtc))
throw "cannot get mrk dtc";
if (!session->getDtc<InfrastructureManagement::DataInterface>(m_ismDtc))
throw "cannot get ism dtc";
if (!session->getForeignDataInterface<TimeLineManagement::DataInterface>(m_eemForeign))
throw "cannot get eem foreign";
if (!session->getDtc<AudioVideoManagement::DataInterface>(m_avmDtc))
throw QStringLiteral("Audio/Video Data not available");
if (!session->getForeignGuiInterface<AudioVideoManagement::ForeignMenuEditGuiInterface>(m_avmIfc))
throw QStringLiteral("Audio/Video Interface not available");
// TODO: un-/subscribe to live events (aka. only connect the websocket if it's currently needed)
connect(m_eemForeign.get(), &TimeLineManagement::ForeignDataInterface::liveEvent,
this, &TimelineModel::onLiveEvent);
connect(m_mrkDtc.get(), &MarkerManagement::Interface::Dtc::markerChanged,
this, &TimelineModel::onMarkerChanged);
// QTimer *t = new QTimer(this);
// t->setInterval(2000);
// connect(t, &QTimer::timeout, this, [this]()
// {
// onLiveEvent(ExerciseEventManagement::Event(m_fakeId++, QDateTime::currentMSecsSinceEpoch() /*- (1 * 1000 * 60 * 60)*/ /*+ ((qint64)m_fakeId * 1000)*/,
// ProjectDataManagement::EventType::Hit,
// 472, 4, 9999,
// QStringLiteral("{\"FriendlyFire\": false, \"OriginatorId\": 0, \"OriginatorType\": 1, \"VulnerabilityState\": 10, \"OriginatorChargeTypeId\": 3}").toStdString()
// , Core::GeoPosition(0, 0, 0)));
// });
initEventFactories(exerciseStart, exerciseEnd);
beginResetModel();
/**
* @brief getOfflineVideoTracks
* \details Get all OfflineVideoTracks than start Timer if all tracks are there. Than get all
* offline Video Clips and start the Timer if all Offline Videos are there.
*/
/*
getOfflineVideoTracks();
m_offlineVideoTimer = new QTimer(this);
m_offlineVideoTimer->setSingleShot(true);
m_offlineVideoTimer->setInterval(30000);
connect(m_offlineVideoTimer, &QTimer::timeout, this, [this]()
{
if(m_isLive)
{
if(!m_trackFetchedInLiveDone)
{
// getOfflineVideoTracks();
m_trackFetchedInLiveDone = true;
}
else
{
// getOfflineVideos();
}
}
else
{
DEBUG_LOG(m_logger) << "\nHier startet getOfflineVideos\n";
m_trackFetchedInLiveDone = false;
// getOfflineVideos();
}
});
*/
try
{
// get all markers
m_modelState.markerTrackId = addTrack(0, MarkerTrack(tr("Marker")));
getMarkers();
// create all participant tracks
getParticipantTracks();
m_outdoorCameraRootTrackId = addTrack(m_rootTrack.id(), OutdoorCameraRootTrack(tr("Outdoor Cameras")));
getBuildings(); // call before getStreams, as getStreams needs buildings!
getBuildingVideoStreams();
//getAudioStreams();
getRadioTracks();
m_videosRootTrackId = addTrack(m_rootTrack.id(), VideosRootTrack(tr("Videos")));
}
catch (const QString &)
{
endResetModel();
throw; // re-throws original message
}
endResetModel();
emit initializationFinished();
}
TimelineModel::~TimelineModel()
{
TimelineTableInfos::getInstance()->removeTable(m_modelState.publicTableName);
clearUnusedTempTables();
}
void TimelineModel::initEventFactories(qint64 exerciseStart, qint64 exerciseEnd)
{
m_eventFactories.clear();
m_eventFactories.insert(EventType::Marker, new MarkerEventFactory(exerciseStart, exerciseEnd, m_session));
m_eventFactories.insert(EventType::Hit, new HitEventFactory(exerciseStart, exerciseEnd, m_session));
m_eventFactories.insert(EventType::WeaponAssignment, new WeaponAssignmentEventFactory(exerciseStart, exerciseEnd, m_session));
m_eventFactories.insert(EventType::BiteMessage, new BiteMessageEventFactory(exerciseStart, exerciseEnd, m_session));
m_eventFactories.insert(EventType::FireEvent, new FireEventEventFactory(exerciseStart, exerciseEnd, m_session));
m_eventFactories.insert(EventType::HitBuilding, new HitBuildingEventFactory(exerciseStart, exerciseEnd, m_session));
m_eventFactories.insert(EventType::Manipulation, new ManipulationEventFactory(exerciseStart, exerciseEnd, m_session));
m_eventFactories.insert(EventType::CameraVideo, new CameraVideoEventFactory(exerciseStart, exerciseEnd, m_session));
m_eventFactories.insert(EventType::RadioCnr, new RadioCnrEventFactory(exerciseStart, exerciseEnd, m_session));
m_eventFactories.insert(EventType::RadioTvc, new RadioTvcEventFactory(exerciseStart, exerciseEnd, m_session));
m_eventFactories.insert(EventType::HitEffectSimulation, new HitEffectSimulationEventFactory(exerciseStart, exerciseEnd, m_session));
m_eventFactories.insert(EventType::CrossBorderline, new CrossBorderlineEventFactory(exerciseStart, exerciseEnd, m_session));
//#ifndef GUEZ_MODE
// m_eventFactories.insert(EventType::HitMine, new HitMineEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::HitN, new HitNEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::HitC, new HitCEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::EnterDangerArea, new EnterDangerAreaEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::LeaveDangerArea, new LeaveDangerAreaEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::EnterMineArea, new EnterMineAreaEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::LeaveMineArea, new LeaveMineAreaEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::EnterFunctionalArea, new EnterFunctionalAreaEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::LeaveFunctionalArea, new LeaveFunctionalAreaEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::EnterNuclear, new EnterNuclearEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::LeaveNuclear, new LeaveNuclearEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::HitB, new HitBEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::FriendlyFire, new FriendlyFireEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::HitGasPipe, new HitGasPipeEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::MineArea, new MineAreaEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::NContamination, new NContaminationEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::BCContamination, new BCContaminationEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::DangerArea, new DangerAreaEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::FunctionalArea, new FunctionalAreaEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::MineCorridor, new MineCorridorEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::Borderline, new BorderlineEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::TechnicalArea, new TechnicalAreaEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::Sanitation, new SanitationEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::EquipmentErrorReport, new EquipmentErrorReportEventFactory(exerciseStart, exerciseEnd));
// m_eventFactories.insert(EventType::EmergencyCall, new EmergencyCallEventFactory(exerciseStart, exerciseEnd));
// if (projectSettings has "GasPipes") then:
// m_eventFactories.insert(EventType::GasPipe, new GasPipeEventFactory(exerciseStart, exerciseEnd));
//#endif
for (EventType eventType : filterEventTypes())
{
m_filter.insert(eventType, m_eventFactories.value(eventType)->filter());
m_tempFilter.insert(eventType, m_eventFactories.value(eventType)->createFilter());
#ifndef LTS_DEBUG
if (eventType == EventType::Marker)
m_filter.value(eventType)->insertAttributeValue(
MarkerEventFactory::s_operator, LtsSessionData::LtsSessionData::getInstance()->getOperatorId()
);
else
m_filter.value(eventType)->setActive(false);
#endif
connect(m_filter.value(eventType), &AbstractEventFilter::attributeChanged, this, &TimelineModel::onFilterChanged);
connect(m_filter.value(eventType), &AbstractEventFilter::stateChanged, this, &TimelineModel::onFilterChanged);
}
onFilterChanged(); // init filter string
}
void TimelineModel::clearUnusedTempTables()
{
const QStringList &tableNames = TimelineTableInfos::getInstance()->tableNames();
try
{
const Rows rows(executeQuery(QStringLiteral("select tablename from pg_tables where tablename like 'tln_temp_view_%1_%'")
.arg(LtsSessionData::LtsSessionData::getInstance()->getOperatorName().toLower())));
for (const Row &row : rows)
{
const QString tableName(row.first().toString());
if (tableNames.contains(tableName, Qt::CaseInsensitive))
continue;
try
{
if (!tableName.startsWith(QStringLiteral("tln_temp_view_")))
throw QStringLiteral("tried to drop a table that isn't a timeline temp view! '%1'").arg(tableName);
executeQuery(QStringLiteral("DROP TABLE %1").arg(tableName));
} catch(const QString &msg) { ERROR_LOG(m_logger) << "cannot remove stale temp table '" << tableName << "': " << msg; }
}
}
catch(const QString &msg) { ERROR_LOG(m_logger) << "cannot remove stale temp tables: " << msg; }
}
void TimelineModel::addParticipantTrack(Participant &participant, int superiorTrackId)
{
int trackId = addTrack(superiorTrackId, ParticipantTrack(participant.text, participant.participantId, participant.partyId, participant.equipmentNumber, participant.superiorId));
participant.trackId = trackId;
m_modelState.participantTracks.insert(participant.participantId, trackId);
for (int id : qAsConst(participant.inferiorIds))
{
addParticipantTrack(m_modelState.participants[id], trackId);
}
}
void TimelineModel::getParticipantTracks()
{
m_ppmDtc->waitForHistoricalDataParticipant(8000);
m_ppmDtc->waitForHistoricalDataParty(8000);
// create party tracks
for (ParticipantManagement::Party party : m_ppmDtc->partys())
{
DEBUG_LOG(m_logger) << "party id: " << party.id() << ", name: " << party.name();
try
{
m_modelState.partys.insert(party.id(), Party(party.id(), addTrack(rootTrack().id(), PartyTrack(ParticipantManagement::trPartyName(party.name()), party.id()))));
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "getParticipantTracks: cannot add party track: " << msg;
continue;
}
}
// build participant hierarchy, phase 1
QVector<int> superiorIds;
auto cb = [this, &superiorIds](const ParticipantManagement::Participant &participant)
{
Q_ASSERT(participant.id() != 0);
if (participant.isDeleted())
return;
int equipmentNumber = participant.equipmentNumber().is_set() ? participant.equipmentNumber().get() : 0;
if (QString::fromStdString(participant.designator()).contains(QStringLiteral("Ausb")))
DEBUG_LOG(m_logger) << "FOUND Ausb! " << participant.id();
QVector<int> inferiorIds;
std::transform(participant.inferiorIds().cbegin(), participant.inferiorIds().cend(), std::back_inserter(inferiorIds),
[](int32_t id) { return (int)id; });
m_modelState.participants.insert(
participant.id(),
Participant(participant.id(), equipmentNumber, participant.partyId(), QString::fromStdString(participant.designator()),
0, participant.superiorId(), std::move(inferiorIds))
);
if (participant.superiorId() == 0)
{
QStringList infs;
std::transform(participant.inferiorIds().cbegin(), participant.inferiorIds().cend(), std::back_inserter(infs),
[](int32_t id) { return QString::number(id); });
DEBUG_LOG(m_logger) << "sup " << participant.id() << " has infs: " << infs.join(QStringLiteral(", "));
superiorIds += participant.id();
}
};
m_ppmDtc->participants(cb);
for (int superiorId : qAsConst(superiorIds))
{
Participant &superior = m_modelState.participants[superiorId];
auto it = m_modelState.partys.constFind(superior.partyId);
if (it == m_modelState.partys.constEnd())
{
WARNING_LOG(m_logger) << "participant with id " << superior.participantId << " has non-existant party id " << superior.partyId;
continue;
}
addParticipantTrack(superior, it.value().trackId);
}
}
QJsonObject objectFromStdString(const std::string &s) noexcept(false)
{
QJsonParseError err;
QJsonObject obj(QJsonDocument::fromJson(QString::fromStdString(s).toUtf8(), &err).object());
if (err.error != QJsonParseError::NoError && !s.empty()) // empty string should not be illegal json (it's just an empty object)
{
throw QStringLiteral("objectFromStdString: properties json is invalid: '%1' at offset %2").arg(err.errorString()).arg(err.offset);
}
return obj;
}
/*
QModelIndex TimelineModel::megaParentModelIndex(int trackId) noexcept(false)
{
return indexOfTrackSafe(megaParent(trackId));
}
int TimelineModel::megaParent(int trackId) noexcept(false)
{
int lastParent = track(trackId).parent();
Q_ASSERT(lastParent != m_rootTrack.id());
if (lastParent == m_rootTrack.id()) // should not happen, but...
{
ERROR_LOG(m_logger) << "requesting mega parent for a track that has the root track as parent! track was " << trackId;
return trackId;
}
else
{
int emergencyBreak = 0;
while (true)
{
Q_ASSERT(emergencyBreak != s_maxRows + 1);
if (emergencyBreak == s_maxRows + 1)
{
throw QStringLiteral("megaParent: emergency break reached for track %1").arg(trackId);
}
emergencyBreak++;
int parent = track(lastParent).parent();
if (parent == m_rootTrack.id() || parent == -1)
{
// QString txt;
// try { txt = track(lastParent).text(); } catch(...){}
// ERROR_LOG(m_logger) << "megaParent: parent = " << lastParent << " ('" << (txt.isEmpty() ? QStringLiteral("(unknown)") : txt) << "')";
return lastParent;
}
lastParent = parent;
}
}
throw QStringLiteral("megaParent: mega parent for track %1 not found").arg(trackId);
}
*/
QVector<int> TimelineModel::findItemIdsForDbId(int dbId) noexcept(false)
{
QVector<int> itemIds;
for (auto it = m_itemCache.cbegin(); it != m_itemCache.cend(); ++it)
{
EventItem &i = static_cast<EventItem &>(*it->second);
if (i.m_dbId == dbId)
itemIds += i.id();
}
if (itemIds.isEmpty())
throw QStringLiteral("findItemIdsForDbId: no items with db id %1 found").arg(dbId);
return itemIds;
}
EventItem &TimelineModel::itemWithMarkerDbId(int dbId) noexcept(false)
{
for (auto it = m_itemCache.cbegin(); it != m_itemCache.cend(); ++it)
{
EventItem &i = static_cast<EventItem &>(*it->second);
if (i.markerDbId() == dbId)
{
return i;
}
}
throw QStringLiteral("itemWithMarkerDbId: item with db id %1 not found").arg(dbId);
}
void TimelineModel::onMarkerChanged(const MarkerManagement::Marker &marker, DataModification mod)
{
if (m_isCurrentlyUpdating)
return;
qint64 time = marker.timeStamp() - marker.timeStampOffset();
try
{
if (mod == DataModification::eAdded)
{
if (!m_dialog->isLive())
return;
// get marker event factory
AbstractEventFactory *factory = constFinder<AbstractEventFactory *>(m_eventFactories, EventType::Marker);
// get marker filter
auto eventFilterIt = std::find_if(m_filter.constBegin(), m_filter.constEnd(), [this](const AbstractEventFilter *eventFilter)
{
return eventFilter->type() == EventType::Marker;
});
if (eventFilterIt == m_filter.constEnd())
return;
const AbstractEventFilter *filter = (*eventFilterIt);
if (!filter->isActive())
return; // no markers if the whole marker filter is set as inactive
// get markers
QJsonObject properties;
try
{
properties = eventPropertiesFromMarker(marker);
}
catch (const QString &errMsg)
{
ERROR_LOG(m_logger) << "Marker " << marker.id() << " has invalid properties: " << errMsg;
//continue; TODO: just add even though detail data is not available??
}
// NOTE: no collision checks on markers, as all markers have to be visible by spec (don't ask me where to find the spec ;))
EventItemPtr markerEvent = factory->createItem(time, 0, properties, LtsUtils::QPoint3D(marker.position().lon(), marker.position().lat(), marker.position().z())); // okay to not use model's createItem here, as it's just a marker
markerEvent->m_markerDbId = marker.id();
m_modelState.markerTimes += time;
if (!filter->check(*markerEvent.get()))
return;
LtsTimeline::TimelineModel::addItem(m_modelState.markerTrackId, std::move(markerEvent), true);
}
else if (mod == DataModification::eModified)
{
MarkerItem &mi = dynamic_cast<MarkerItem &>(itemWithMarkerDbId(marker.id()));
//DEBUG_LOG(m_logger) << "replacing " << mi.startTime() << " with " << time;
std::replace(m_modelState.markerTimes.begin(), m_modelState.markerTimes.end(), mi.startTime(), time);
mi.setStartTime(marker.timeStamp() - marker.timeStampOffset());
mi.setProperties(eventPropertiesFromMarker(marker));
// NOTE: no need to say dataChanged, as live mode is active - if live mode moves the timeline, items are automatically redrawn
// NOTE: no new collision check is needed, as markers are NOT collision checked, anyways!
}
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "cannot add/modify ("<<(int)mod<<") marker with id "<<marker.id()<<": " << msg;
return;
}
//#warning TODO: // else if (mod == remove) ?? or don't delete it at all?
}
void TimelineModel::onLiveEvent(const ExerciseEventManagement::Event &eemEvent)
{
/* this is the only method that utilizes the model while not threaded
* so there needs to be the isUpdating check
* also, it doesn't make sense to insert live events while the whole model gets re-fetched */
if (m_isCurrentlyUpdating || !m_dialog->isLive())
return;
QJsonObject properties;
try
{
properties = objectFromStdString(eemEvent.properties());
}
catch (const QString &errMsg)
{
ERROR_LOG(m_logger) << "failed to parse properties of live event: " << errMsg << " - adding it anyways...";
}
auto updateItem = [this, &properties, &eemEvent](Item &i, Track &t)
{
int type = (int)eemEvent.eventTypeId().underlying();
if (type == ProjectDataManagement::EventType_def::CameraVideo
|| type == ProjectDataManagement::EventType_def::RadioTvc
|| type == ProjectDataManagement::EventType_def::RadioCnr)
{
int duration = properties[QStringLiteral("duration")].toInt(0);
DEBUG_LOG(m_logger) << "item is vid, tvc or cnr: updating duration to " << duration;
i.setDuration(duration);
static_cast<EventItem &>(i).setProperties(properties);
backwardsChannelSumEvents(t);
}
};
try
{
const QVector<int> itemIds(findItemIdsForDbId(eemEvent.id())); // not throwing? then it already exists
for (int id : itemIds)
{
try
{
Item &i = item(id);
DEBUG_LOG(m_logger) << "item already exists";
updateItem(i, track(i.parentTrackId()));
}
catch (const QString &msg)
{
DEBUG_LOG(m_logger) << "update failed for item with id " << id << "!! " << msg; // FIXME: WHY IS THIS NECESSARY??! Why can some items found by findItemIdsForDbId not exist?? BUG??????
}
}
/*DEBUG*/ERROR_LOG(m_logger) << "done";
return; // stop, job done!
}
catch (const QString &msg)
{
DEBUG_LOG(m_logger) << "!!!!! " << msg;
// might be inside a block item, look into every block item for this event
bool foundAtLeastOne = false;
QVector<int> ids;
for (const std::pair<int, Item *> &pair : m_itemCache)
ids += pair.first;
for (int id : ids)
{
try
{
EventItem &ei = static_cast<EventItem &>(item(id));
if (ei.type() != EventType::Block)
continue;
BlockItem &bi = static_cast<BlockItem &>(ei);
bool found = false;
for (ItemPtr &itemPtr : bi.items())
{
EventItem &ei2 = static_cast<EventItem &>(*itemPtr.get());
if (ei2.dbId() == eemEvent.id())
{
updateItem(ei2, track(bi.parentTrackId()));
DEBUG_LOG(m_logger) << "done2";
found = true;
foundAtLeastOne = true;
break;
}
}
if (!found) // if not found, just add it later (or look somewhere else!)
continue;
// if this block item was modified,
if (bi.duration() != -1)
continue;
bool hasUnfinishedItem = false;
int blockItemDuration = 0;
for (const ItemPtr &itemPtr : bi.items())
{
const EventItem &ei2 = static_cast<const EventItem &>(*itemPtr.get());
if (ei2.duration() == -1)
{
hasUnfinishedItem = true;
break;
}
blockItemDuration = std::max(blockItemDuration, ei2.duration());
}
if (!hasUnfinishedItem)
bi.setDuration(blockItemDuration);
//return; // done
}
catch (...)
{
// may be deleted by updateItem (by moving it into a BLockItem), don't care
DEBUG_LOG(m_logger) << "not found anymore";
}
}
// at this point, the event was neither found in a block item or in the model itself
if (foundAtLeastOne)
return; // no need to add it as it already exists ;)
} // ignore
qint64 start = eemEvent.sampleTime();
DEBUG_LOG(m_logger) << "start = " << start;
// check filter of incoming event
EventType eventType = static_cast<EventType>(static_cast<int>(eemEvent.eventTypeId().underlying()));
if (eemEvent.eventTypeId().underlying() == ProjectDataManagement::EventType_def::CrossGlobalBorderline ||
eemEvent.eventTypeId().underlying() == ProjectDataManagement::EventType_def::CrossLocalBorderline) // these two are one type
eventType = EventType::CrossBorderline;
EventItemPtr eventItem;
try
{
//AbstractEventFactory *factory = constFinder<AbstractEventFactory *>(m_eventFactories, eventType);
AbstractEventFilter *eventFilter = constFinder<AbstractEventFilter *>(m_filter, eventType);
//eventItem = factory->createItem(eemEvent.sampleTime(), getJsonDouble(props, QStringLiteral("duration")), props);
auto const & evt = eemEvent;
eventItem = createItem(evt.id(), evt.sampleTime(), properties, (int)eemEvent.eventTypeId().underlying(), evt.participantId(), evt.partyId(), evt.equipmentNumber(), evt.position().lon(), evt.position().lat(), evt.position().z());
if (!eventFilter->check(*eventItem.get()))
{
ERROR_LOG(m_logger) << "event does not comply with filter";
return;
}
}
catch (const QString &errMsg)
{
ERROR_LOG(m_logger) << "failed to add live event: " << errMsg;
return;
}
try
{
int trackId = findTrack(*eventItem.get());
// ERROR_LOG(m_logger) << "TRY TO ADD TO TRACK ID: " << trackId << "with name " << track(trackId).userData().toString();
if (trackId != -1)
{
insertItem(std::move(eventItem)); // no dataChanged needed, as the live timer progresses every 1 second, which forces a redraw of all events
backwardsChannelSumEvents(track(trackId));
// live event could be added to a currently invisible track -> recursive show of track until root track
// while (trackId != m_rootTrack.id())
// {
// Track &_track = track(trackId);
// const Track &_parentTrack = track(_track.parentId());
// QModelIndex _parentIndex = indexOfTrack(_parentTrack.id());
// _parentIndex = index(_parentIndex.row(), 0, _parentIndex.parent());
// int row = _parentTrack.rowOfTrack(trackId);
// m_view->setRowHidden(row, _parentIndex, false);
// trackId = _track.parentId();
// }
}
}
catch (const QString &errMsg) { ERROR_LOG(m_logger) << LOGFUNCTION << " failed: " << errMsg; }
catch (...) { ERROR_LOG(m_logger) << LOGFUNCTION << " failed: "; }
}
void TimelineModel::onStreamListReceived(const QMap<QString, QString> &streamList)
{
const auto streamsDtc = m_avmDtc->streams();
for(auto value : streamsDtc.values())
{
if(streamList.values().contains(QString::fromStdString(value.name())))
continue;
addTrack(m_videosRootTrackId, VideoTrack(QString::fromStdString(value.name())));
}
}
void TimelineModel::queueUpdate()
{
m_updateQueued = true;
update(); // start it directly (don't worry: only starts if no update currently running) - if update already running, the m_updateQueued will start another update right after the update ended
}
void TimelineModel::tryUpdate()
{
m_updateTimer->start(); // restarts timer if timer already ticking
}
void TimelineModel::onRangeChanged(qint64 start, qint64 end)
{
tryUpdate();
}
void TimelineModel::onCurrentTimeChanged(qint64 time)
{
m_modelState.start = time;
m_modelState.end = m_modelState.start + static_cast<qint64>(m_view->zoomFactor());
tryUpdate();
}
void TimelineModel::onZoomChanged(LtsTimeline::ZoomFactor zoom)
{
m_modelState.end = m_modelState.start + static_cast<qint64>(zoom);
//#warning // TODO: execute collision check again and plop out events that are now visible
m_queueItemDeletion = true;
tryUpdate();
}
bool TimelineModel::isBlockItem(int itemId) const
{
try
{
return static_cast<const AbstractEventItem &>(item(itemId)).type() == EventType::Block;
}
catch (const QString &str)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " " << str;
}
catch (...)
{
ERROR_LOG(m_logger) << LOGFUNCTION << " failed for item with id = " << itemId;
}
return false;
}
bool TimelineModel::hasBlockItems(const QVector<int> &itemIds) const
{
for (int itemId : qAsConst(itemIds))
if (isBlockItem(itemId))
return true;
return false;
}
std::vector<EventItemPtr> TimelineModel::getEventsByFilter(const QVector<AbstractEventFilter *> &filters) noexcept(false)
{
// first get & filter eem events...
QStringList conditionList;
QString filterString;
QStringList eventTypes;
for (const AbstractEventFilter *filter : filters)
{
DEBUG_LOG(m_logger) << "getEventsByFilter: " << filter->type();
eventTypes.append(QString::number(filter->type()));
// if (!filter->isActive())
// continue;
QString conditions = filter->toString();
if (conditions.isEmpty())
conditions = QStringLiteral(" WHEN event_type_id = %1 THEN 1 ").arg(QString::number(filter->type()));
conditionList.append(conditions);
}
// static QString filterStringEventTypes(QStringLiteral(" AND 1 = CASE WHEN event_type_id IN (%1) THEN 1 ELSE 0 END"));
static QString filterStringPrimer(QStringLiteral(" AND 1 = CASE %1 ELSE 0 END"));
if (!eventTypes.isEmpty())
{
filterString = filterStringPrimer.arg(conditionList.join(QString()));
// filterString = filterStringEventTypes.arg(eventTypes.join(QStringLiteral(",")));
}
DEBUG_LOG(m_logger) << "filterString = " << filterString;
QString stmt(QStringLiteral("SELECT %1 FROM ex_%2.eem_event WHERE 1 = 1 %3 LIMIT ") + QString::number(s_maxRows + 1));
const Rows rows(executeQuery(stmt.arg(s_eemEventColumns)
.arg(m_session->exerciseId())
.arg(filterString)
// .arg(m_view->startTime())
// .arg(m_view->endTime())
)); // throws !
std::vector<EventItemPtr> items;
int counter = 0;
for (const Row &row : rows)
{
if (counter > s_maxRows)
break;
try
{
items.push_back(/*std::move*/(createItemFromRow(row)));
}
catch (const QString &errMsg) { ERROR_LOG(m_logger) << "item creation failed: " << errMsg; }
counter++;
}
// markers
if (counter <= s_maxRows)
{
for (EventItemPtr &itemPtr : getMarkerItems(filters) /* throws ! */)
{
if (counter > s_maxRows)
break;
items.push_back(std::move(itemPtr));
counter++;
}
}
return items;
}
TrackType TimelineModel::trackType(const LtsTimeline::Track &t) const // could be made static I guess?
{
try
{
dynamic_cast<const ParticipantTrack &>(t);
return TrackType::Participant;
} catch(...) {}
try
{
dynamic_cast<const PartyTrack &>(t);
return TrackType::Party;
} catch(...) {}
try
{
dynamic_cast<const BuildingsRootTrack&>(t);
return TrackType::BuildingsRoot;
} catch(...) {}
try
{
dynamic_cast<const BuildingHitsTrack&>(t);
return TrackType::BuildingHits;
} catch(...) {}
try
{
dynamic_cast<const BuildingStreamTrack&>(t);
return TrackType::BuildingStream;
} catch(...) {}
try
{
dynamic_cast<const TvcRootTrack &>(t);
return TrackType::TvcRoot;
} catch(...) {}
try
{
dynamic_cast<const TvcStreamTrack &>(t);
return TrackType::TvcStream;
} catch(...) {}
try
{
dynamic_cast<const CnrRootTrack &>(t);
return TrackType::CnrRoot;
} catch(...) {}
try
{
dynamic_cast<const CnrStreamTrack &>(t);
return TrackType::CnrStream;
} catch(...) {}
try
{
dynamic_cast<const MarkerTrack &>(t);
return TrackType::Marker;
} catch(...) {}
return TrackType::Unknown;
}
TrackType TimelineModel::trackType(int trackId) const
{
try
{
return trackType(track(trackId));
}
catch (const QString &str) { ERROR_LOG(m_logger) << LOGFUNCTION << " " << str; }
catch (...) { ERROR_LOG(m_logger) << LOGFUNCTION << " failed for item with id = " << trackId; }
return TrackType::Unknown;
}
QVector<QVariantMap> /* aka. Rows */ TimelineModel::executeQuery(const QString &query, bool ignoreError) const noexcept(false)
{
return executeQuery(query, m_db, ignoreError);
}
QVector<QVariantMap> /* aka. Rows */ TimelineModel::executeQuery(const QString &query, const QSqlDatabase &db, bool ignoreError) const noexcept(false)
{
QSqlQuery q(db);
QElapsedTimer t;
t.start();
bool res = q.exec(query);
DEBUG_LOG(m_logger) << "executeQuery: " << t.elapsed() << "ms\n"
<< ">> rows: " << q.size() << "\n"
<< ">> query: " << query.toStdString().c_str() << "\n"
<< "\n";
int size = q.size();
if (!res)// || size == -1)
{
ERROR_LOG(m_logger) << (ignoreError ? "[ ]" : "[!]") << " query failed! query: '" << q.executedQuery() << "' - error: '" << q.lastError().text() << "'";
if (!ignoreError)
throw QStringLiteral("executeQuery: query failed: %1 - query was: '%2' ('%3')").arg(q.lastError().text(), q.executedQuery(), query);
}
if (size <= 0)
return {};
QVector<QVariantMap> rows;
while (q.next())
{
QVariantMap row;
QSqlRecord record(q.record());
for (int i = 0; i < record.count(); i++)
{
QSqlField field(record.field(i));
// if (isDebug)
// {
// qCritical() << field.name() << ":" << field.value();
// }
row.insert(field.name(), field.value());
}
rows += row;
}
return rows;
}
bool TimelineModel::isCollision(qint64 startTime, qint64 previousStartTime, qint64 previousDuration)
{
qint64 duration = previousDuration;
if (duration < m_zeroDurationItemWidthTime)
duration = m_zeroDurationItemWidthTime;
return (startTime <= (previousStartTime + duration));
}
void TimelineModel::reChannelSumEvents(int trackId, int onlyThisTick) noexcept(false)
{
// /* if for one specific tick only (because onlyThisTick is only set if tick
// * should be re-summed because of a live event) */
// // 1.:
// auto it = m_modelState.ticks.constFind(onlyThisTick);
// if (it == m_modelState.ticks.constEnd())
// {
// ERROR_LOG(m_logger) << "tick " << onlyThisTick << " for re-summing not found";
// return;
// }
// qint64 tickStart = *it;
// Track &megaTrack = track(megaParent(trackId));
// Track &theTrack = track(trackId);
// // all but the last track's block items have to be removed, as the re-channeling will rebuild the upstacking, but there has to be something left to upstack
// QVector<int> itemIds(megaTrack.itemIdsBetween(tickStart, tickStart + m_modelState.tickSizeTime - 1, false, false, true, false));
// QVector<int> keepTheseItemIds(theTrack.itemIdsBetween(tickStart, tickStart + m_modelState.tickSizeTime - 1, false, false, true, false));
// auto itE = std::remove_if(itemIds.begin(), itemIds.end(),
// [&keepTheseItemIds](int id)
// {
// return (keepTheseItemIds.contains(id));
// });
// itemIds.erase(itE, itemIds.end());
// // remove all block items as they are created later and would affect collision checks, etc.
// for (int id : qAsConst(itemIds))
// {
// try
// {
// dynamic_cast<BlockItem &>(item(id));
// // if it hasn't thrown yet, it's a BlockItem => remove it!
// removeItem(id);
// } catch(...){}
// }
// for (int id : megaTrack.copyItemIdsBetween(tickStart, tickStart + m_modelState.tickSizeTime - 1, false, false, true, false))
// megaTrack.removeCopyItem(id);
// for (LtsTimeline::TrackPtr &child : megaTrack.tracks())
// sumEvents(*child, onlyThisTick);
// sumEvents(megaTrack); // and finally itself
}
void TimelineModel::backwardsChannelSumEvents(const Track &t)
{
int lastTrackId = t.id();
while (lastTrackId != m_rootTrack.id())
{
Track &_t = track(lastTrackId);
sumEvents(_t);
lastTrackId = _t.parentId();
}
//#warning dataChanged ?
try
{
if(t.id()!=0 && lastTrackId !=0)
emit dataChanged(indexOfTrackSafe(t.id()), indexOfTrackSafe(lastTrackId), QVector<int>() << Qt::DisplayRole);
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "dataChanged failed: " << msg;
}
}
void TimelineModel::channelSumEvents()
{
for (LtsTimeline::TrackPtr &child : m_rootTrack.tracks())
sumEvents(*child);
}
void TimelineModel::sumEvents(LtsTimeline::Track &track)
{
if (track.tracks().empty())
return; // don't sum on tracks without children, as there is nothing to sum up if no items are located under this track
// first sum the children's tracks (recursive!) (if any) ...
for (LtsTimeline::TrackPtr &child : track.tracks())
sumEvents(*child.get());
// remove items from this track first
const QVector<int> oldIds(track.itemIds());
track.clearItems();
for (int id : oldIds)
{
auto it = m_itemCache.find(id);
if (it == m_itemCache.end())
{
ERROR_LOG(m_logger) << "cannot erase item ("<<id<<") from track ("<<track.id()<<") - not found!!";
continue;
}
m_itemCache.erase(it);
}
// ... then sum our own
// get all events (ids) of all his **direct** children ...
for (LtsTimeline::TrackPtr &child : track.tracks())
{
for (ItemPtr &itemPtr : child->items())
{
EventItem &eventItem = static_cast<EventItem &>(*itemPtr.get());
if (eventItem.type() == EventType::Block)
{
BlockItem &blockItem = static_cast<BlockItem &>(eventItem);
for (const ItemPtr &blockSubItemPtr : blockItem.items())
{
try
{
EventItem &blockSubItem = static_cast<EventItem &>(*blockSubItemPtr.get());
insertItem(track, std::move(constFinder<AbstractEventFactory *>(m_eventFactories, toEventType((int)blockSubItem.type()))->copyItem(blockSubItem)));
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "cannot insert sub item: " << msg;
}
}
continue;
}
try
{
insertItem(track, std::move(constFinder<AbstractEventFactory *>(m_eventFactories, toEventType((int)eventItem.type()))->copyItem(eventItem)));
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "cannot insert item: " << msg;
}
}
}
//#warning // is there a need to sort?
// for (const int itemId : qAsConst(itemIds))
// insertCopyItem(track, itemId);
// NOTE: get item ids and copy item ids from block items
/* ... then re-create the events or
* put them into Block Items if they collide OR if the're already Block Items */
// but first generate ItemInfos from the ItemPtrNCVRefs........ >_>
/* AbstractEventItems are needed for the collision check method... and they
* can't be just moved as they are already in the model... so we need to get
* the reference, static_cast cast it to AbstractEventItems and make
* references from them, again... */
}
//void TimelineModel::Update::sortSectorItems(SectorItemPtrNCVRefs &sectorItemPtrNCVRefs)
//{
// // sort ...
// QElapsedTimer t;
// t.start();
// for (auto it = sectorItemPtrNCVRefs.begin(); it != sectorItemPtrNCVRefs.end(); ++it)
// for (auto itx = it->second.begin(); itx != it->second.end(); ++itx)
// std::sort(itx->second.begin(), itx->second.end(), [](const ItemPtrNCVRef &itemA, const ItemPtrNCVRef &itemB)
// {
// return itemA.get()->startTime() < itemB.get()->startTime();
// });
// DEBUG_LOG(m_logger) << "sort took " << t.elapsed() << "ms";
//}
EventItemPtr TimelineModel::createItem(int id, qint64 sampleTime, QJsonObject properties /* TODO: copy elision? */,
int eventTypeId, int participantId, int partyId, int eqId, double posX = 0, double posY = 0, double posZ = 0) noexcept(false)
{
auto eventFactoryIt = m_eventFactories.find(static_cast<EventType>(toEventType(eventTypeId)));
if (eventFactoryIt == m_eventFactories.end())
{
ERROR_LOG(m_logger) << "event factory for event type id " << eventTypeId << " not found!";
throw QStringLiteral("createItemFromRow: cannot create item because the event factory is missing for type id %1").arg(eventTypeId);
}
LtsUtils::QPoint3D position(posX, posY, posZ);
std::unique_ptr<EventItem> theItem = (*eventFactoryIt)->createItem(sampleTime, 0, properties, position);
theItem->m_dbId = id;
try
{
AbstractParticipantEventItem &participantItem = dynamic_cast<AbstractParticipantEventItem &>(*theItem.get());
participantItem.setParticipantId(participantId);
participantItem.setPartyId(partyId);
participantItem.setEquipmentNumber(eqId);
participantItem.setPosition(position);
}
catch(...) {}
return /*std::move*/(theItem);
}
std::vector<EventItemPtr> TimelineModel::createItemsFromRows(const Rows &rows)
{
std::vector<EventItemPtr> ret;
ret.reserve(rows.count());
for (const Row &row : rows)
{
try
{
//WARNING_LOG(m_logger) << "Pos-X: " << createItemFromRow(row)->position().x();
ret.push_back(createItemFromRow(row));
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "cannot create item: " << msg;
}
}
return ret; // copy elision
}
EventItemPtr TimelineModel::createItemFromRow(const Row &row) noexcept(false)
{
return /*std::move*/(createItem(getInt(row, QStringLiteral("id")),
getLongLong(row, QStringLiteral("sample_time")),
//#warning // TODO: remove ignoreError bool flag:
getJsonObject(row, QStringLiteral("properties"), true),
getInt(row, QStringLiteral("event_type_id")),
getInt(row, QStringLiteral("participant_id")),
getInt(row, QStringLiteral("party_id")),
getInt(row, QStringLiteral("equipment_number")),
getJsonDouble(row, QStringLiteral("position_x")),
getJsonDouble(row, QStringLiteral("position_y")),
getJsonDouble(row, QStringLiteral("position_z"))));
}
bool TimelineModel::knownRange(qint64 &startOut, qint64 &endOut) const
{
qint64 start = m_modelState.knownStart;
qint64 end = m_modelState.knownEnd;
if (start == 0 || end == 0)
return false;
startOut = start;
endOut = end;
return true;
}
QJsonObject TimelineModel::eventPropertiesFromMarker(const MarkerManagement::Marker &marker)
{
bool isSit = false;
bool isGfx = false;
bool isSfx = false;
QStringList streamNames;
for (const MarkerManagement::MarkerDescription &md : marker.descriptions())
{
switch (md.markerType().underlying())
{
case MarkerManagement::MarkerType::Situation:
{
isSit = true;
break;
}
case MarkerManagement::MarkerType::Video:
{
isGfx = true;
//#warning !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
streamNames += QString::fromStdString(md.content());
break;
}
case MarkerManagement::MarkerType::Audio:
{
isSfx = true;
streamNames += QString::fromStdString(md.content());
break;
}
default:
{
WARNING_LOG(m_logger) << "Marker " << marker.id() << "'s description has an invalid marker type: " << (int)md.markerType().underlying();
break;
}
}
}
return QJsonObject({
{ QStringLiteral("MarkerName"), QString::fromStdString(marker.name()) }
, { QStringLiteral("MarkerNote"), QString::fromStdString(marker.note()) }
, { QStringLiteral("Operator"), marker.operatorId() }
, { QStringLiteral("MarkerOffset"), marker.timeStampOffset() }
, { QStringLiteral("MapSettings"), QString::fromStdString(marker.mapSettings()) }
, { QStringLiteral("Filter"), QString::fromStdString(marker.filter()) }
, { QStringLiteral("IsSituationType"), isSit }
, { QStringLiteral("IsVideoType"), isGfx }
, { QStringLiteral("IsAudioType"), isSfx }
, { QStringLiteral("streamName"), streamNames.join(QStringLiteral(",")) }
});
}
std::vector<EventItemPtr> TimelineModel::getMarkerItems(const Filters &filters, bool rangeOnly, int limit) noexcept(false)
{
std::vector<EventItemPtr> ret;
// get marker event factory
AbstractEventFactory *factory = constFinder<AbstractEventFactory *>(m_eventFactories, EventType::Marker);
// get marker filter
auto eventFilterIt = std::find_if(filters.constBegin(), filters.constEnd(), [this](const AbstractEventFilter *eventFilter)
{
return eventFilter->type() == EventType::Marker;
});
if (eventFilterIt == filters.constEnd())
return ret;
const AbstractEventFilter *filter = (*eventFilterIt);
// we need to show the markers even if is not active, per button Show Events next to Markers
//if (!filter->isActive())
// return ret; // no markers if the whole marker filter is set as inactive
// get markers
QVector<MarkerManagement::Marker> markers;
int count = 0;
m_mrkDtc->markers([this, &markers, &count, filter, rangeOnly, limit, factory, &ret](const MarkerManagement::Marker &data) -> void
{
if (limit != -1 && count > limit) // NOTE: no way to exit the cb-loop early ...
return;
qint64 time = data.timeStamp() - data.timeStampOffset();
if (rangeOnly && (time < m_view->startTime() || time > m_view->endTime()))
return;
QJsonObject properties;
try
{
properties = eventPropertiesFromMarker(data);
}
catch (const QString &errMsg)
{
ERROR_LOG(m_logger) << "Marker " << data.id() << " has invalid properties: " << errMsg;
//continue; TODO: just add even though detail data is not available??
}
// NOTE: no collision checks on markers, as all markers have to be visible by spec (don't ask me where to find the spec ;))
EventItemPtr markerEvent = factory->createItem(time, 0, properties, LtsUtils::QPoint3D(data.position().lon(),
data.position().lat(),
data.position().z())); // okay to not use model's createItem here, as it's just a marker
if (filter->check(*markerEvent.get()))
{
markerEvent->m_markerDbId = data.id();
ret.push_back(std::move(markerEvent));
count++;
}
});
return ret;
}
int TimelineModel::findTrack(const AbstractEventItem &_eventItem)
{
try
{
int trackId = -1;
EventType eventType = _eventItem.type();
if (isParticipantEvent(eventType))
{
const AbstractParticipantEventItem &participantItem = dynamic_cast<const AbstractParticipantEventItem &>(_eventItem);
int participantId = participantItem.participantId();
auto it = m_modelState.participantTracks.constFind(participantId);
if (it == m_modelState.participantTracks.constEnd())
throw QStringLiteral("event with id %1 has non-existant participant id %2").arg(_eventItem.dbId()).arg(participantId);
trackId = it.value();
}
else if (eventType == EventType::CameraVideo)
{
const CameraVideoItem &cameraVideoItem = static_cast<const CameraVideoItem &>(_eventItem);
int msei = cameraVideoItem.mainStructureElementId();
const QString &streamName = cameraVideoItem.internalStreamName();
InfrastructureManagement::StructureElement structureElement;
m_ismDtc->getStructureElementById(m_ismDtc->mainStructureElement(msei).structureElementId(), structureElement);
bool isOutdoorCameraEvent = structureElement.mountingPointTypeId() == InfrastructureManagement::MountingPointType_def::OutdoorCamera;
if (!m_mainStructureElements.contains(msei) && msei != 0 /* allow MVS & offline video, etc. */)
throw QStringLiteral("camera video event with msei %1 is not inside msei list (maybe building not mapped into current exercise)").arg(msei);
if (msei == 0 && cameraVideoItem.internalStreamName().contains(QStringLiteral("av_")))
// throw QStringLiteral("ignoring msei 0 with av_%");
// /*DEBUG*/ERROR_LOG(m_logger) << "finding track for camera video event wijth msei " << msei << " and stream name '" << streamName << "' ...";
////#warning // TODO: video teams (or whatever msei 0 is ????)
//// if (msei == 0)
//// {
//// // ...
//// ERROR_LOG(m_logger) << "video teams are not supported";
//// return;
//// }
//// else
// if (isOutdoorCameraEvent)
{
// try
// {
// trackId = constFinder<int>(m_outdoorCameraTracks, streamName);
// }
// catch (...)
// {
// auto idx = indexOfTrack(m_outdoorCameraRootTrackId);
// if (!idx.isValid())
// throw QStringLiteral("cannot find parent for outdoor camera root track - giving up ...");
// Track &_track = track(m_outdoorCameraRootTrackId);
// int rowCount = _track.itemCount();
// auto _ = std::shared_ptr<int>(nullptr, [this](int *) { endInsertRows(); }); (void)_;
// beginInsertRows(idx, rowCount, rowCount);
// trackId = addTrack(m_outdoorCameraRootTrackId, OutdoorCameraTrack(streamName));
// m_outdoorCameraTracks.insert(streamName, trackId);
// /*DEBUG*/ERROR_LOG(m_logger) << "outdoor camera stream track not found, created new track";
// }
}
else
{
DEBUG_LOG(m_logger) << "finding track for camera video event wijth msei " << msei << " and stream name '" << streamName << "' ...";
//#warning // TODO: video teams (or whatever msei 0 is ????)
// if (msei == 0)
// {
// // ...
// ERROR_LOG(m_logger) << "video teams are not supported";
// return;
// }
// else
if (isOutdoorCameraEvent)
{
try
{
trackId = constFinder<int>(m_outdoorCameraTracks, streamName);
}
catch (...)
{
auto idx = indexOfTrack(m_outdoorCameraRootTrackId);
if (!idx.isValid())
throw QStringLiteral("cannot find parent for outdoor camera root track - giving up ...");
Track &_track = track(m_outdoorCameraRootTrackId);
int rowCount = _track.itemCount();
auto _ = std::shared_ptr<int>(nullptr, [this](int *) { endInsertRows(); }); (void)_;
beginInsertRows(idx, rowCount, rowCount);
trackId = addTrack(m_outdoorCameraRootTrackId, OutdoorCameraTrack(streamName));
m_outdoorCameraTracks.insert(streamName, trackId);
/*DEBUG*/ERROR_LOG(m_logger) << "outdoor camera stream track not found, created new track";
}
}
else
{
// get the stream track behind the msei + stream name
try
{
trackId = constFinder<int>(m_buildingStreamsTracks, QPair<int, QString>(msei, streamName));
/*DEBUG*/ERROR_LOG(m_logger) << "track id for camera video event with msei " << msei << " and stream name '" << streamName << "' found by internal name!!";
}
catch (...)
{
// stream not in list, could be a stream given by a DISPLAY name
try
{
trackId = constFinder<int>(m_videoTracks, streamName);
/*DEBUG*/ERROR_LOG(m_logger) << "track id for camera video event wijth msei " << msei << " and stream name '" << streamName << "' found by display name!!";
}
catch (...)
{
// also not found, just adding the stream as a new track in the 'Videos' track
auto idx = indexOfTrack(m_videosRootTrackId);
if (!idx.isValid())
throw QStringLiteral("cannot find parent for video root track - giving up....");
Track &t = track(m_videosRootTrackId);
int rowCount = t.itemCount();
auto _ = std::shared_ptr<int>(nullptr, [this](int *) { endInsertRows(); }); (void)_;
beginInsertRows(idx, rowCount, rowCount);
trackId = addTrack(m_videosRootTrackId, VideoTrack(streamName));
m_videoTracks.insert(streamName, trackId);
/*DEBUG*/ERROR_LOG(m_logger) << "track not found, created new track";
}
}
}
}
/*DEBUG*/ERROR_LOG(m_logger) << "track id for camera video event with msei " << msei << " and stream name '" << streamName << "' ... is " << trackId;
}
else if (eventType == EventType::RadioCnr)
{
const RadioCnrItem &radioCnrItem = static_cast<const RadioCnrItem &>(_eventItem);
const QString &displayStreamName = radioCnrItem.displayStreamName();
try
{
trackId = constFinder<int>(m_cnrTrackIds, displayStreamName);
Track &t = track(m_cnrRootTrackId);
int rowCount = t.trackCount();
if ( rowCount <= 0)
{
trackId = -1;
}
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "cannot find cnr track for stream '"<<displayStreamName<<"': " << msg << " - creating the track....";
// because cnr/tvc events have a streamName which is the DISPLAY NAME - after chaning the cnr/tcv channel display name, events won't get displayed anymore... prevent that by just creating a new track for that...
auto idx = indexOfTrack(m_cnrRootTrackId);
if (!idx.isValid())
throw QStringLiteral("cannot find parent for cnr root track - giving up....");
Track &t = track(m_cnrRootTrackId);
int rowCount = t.trackCount();
auto _ = std::shared_ptr<int>(nullptr, [this](int *) { endInsertRows(); }); (void)_;
beginInsertRows(idx, rowCount, rowCount);
trackId = addTrack(m_cnrRootTrackId, CnrStreamTrack(QStringLiteral("<unknown>"), displayStreamName)); // UGH!!!
m_cnrTrackIds.insert(displayStreamName, trackId);
}
}
else if (eventType == EventType::RadioTvc)
{
const RadioTvcItem &radioTvcItem = static_cast<const RadioTvcItem &>(_eventItem);
const QString &displayStreamName = radioTvcItem.displayStreamName();
try
{
trackId = constFinder<int>(m_tvcTrackIds, displayStreamName);
Track &t = track(m_tvcRootTrackId);
int rowCount = t.trackCount();
if ( rowCount <= 0)
{
trackId = -1;
}
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "cannot find tvc track for stream '"<<displayStreamName<<"': "
<< msg << " - creating the track...."
<< " id=" << _eventItem.id()
<< " m_dbId=" << _eventItem.m_dbId;
auto idx = indexOfTrack(m_tvcRootTrackId);
if (!idx.isValid())
throw QStringLiteral("cannot find parent for tvc root track - giving up....");
// because cnr/tvc events have a streamName which is the DISPLAY NAME - after chaning the cnr/tcv channel display name, events won't get displayed anymore... prevent that by just creating a new track for that...
Track &t = track(m_tvcRootTrackId);
int rowCount = t.trackCount();
auto _ = std::shared_ptr<int>(nullptr, [this](int *) { endInsertRows(); }); (void)_;
beginInsertRows(idx, rowCount, rowCount);
trackId = addTrack(m_tvcRootTrackId, TvcStreamTrack(QStringLiteral("<unknown>"), displayStreamName)); // UGH!!!
m_tvcTrackIds.insert(displayStreamName, trackId);
}
}
else if (eventType == EventType::HitBuilding)
{
const HitBuildingItem &bi = static_cast<const HitBuildingItem &>(_eventItem);
int msei = bi.mainStructureElementId();
if (msei <= 0)
throw QStringLiteral("msei <= 0");
int buildingTrackId = constFinder<int>(m_modelState.buildingTrackIds, msei);
if (buildingTrackId <= 0)
throw QStringLiteral("buildingTrackId <= 0");
const BuildingTrack &bt = dynamic_cast<const BuildingTrack &>(track(buildingTrackId));
int hitsTrackId = bt.hitsTrackId();
if (hitsTrackId <= 0)
throw QStringLiteral("hitsTrackId <= 0");
trackId = hitsTrackId;
}
else
{
// ERROR_LOG(m_logger) << "event with id " << eventItem.dbId() << " has unhandled type " << (int)eventItem.type();
}
return trackId;
}
catch (const QString &errMsg)
{
ERROR_LOG(m_logger) << "cannot find track: " << errMsg;
return -1;
}
}
int TimelineModel::handleCopyCollision(const Track &t,
const int copyItemId /* das neue, hinzuzufuegende Item */,
const int itemId /* ein Item, mit der copyItemId kollidiert */,
const qint64 start, const qint64 duration /* beide von der copyItemId */) noexcept(false)
{
EventItem &copyItem = static_cast<EventItem &>(item(copyItemId));
EventItem &eventItem = static_cast<EventItem &>(item(itemId));
qint64 startTime = std::min(start, eventItem.startTime());
int blockItemDuration;
if (eventItem.duration() == -1 || duration == -1)
blockItemDuration = -1;
else
blockItemDuration = copyItem.duration() == 0 ? 0 : (std::max(start + duration, eventItem.startTime() + eventItem.duration()) - startTime);
std::unique_ptr<BlockItem> blockItemPtr = std::make_unique<BlockItem>(/*std::move*/(BlockItem(startTime, blockItemDuration, false)));
// BlockItem &blockItem = static_cast<BlockItem &>(*blockItemPtr.get());
// blockItem.addCopyItem(copyItemId);
// blockItem.addCopyItem(itemId);
return LtsTimeline::TimelineModel::addItem(t.id(), std::move(blockItemPtr));
}
int TimelineModel::handleCollision(Track &t,
ItemPtr &&itemPtr /* das neue, hinzuzufuegende Item */,
const int itemId /* ein Item, mit dem itemPtr kollidiert */,
const qint64 start, const qint64 duration /* beide vom itemPtr */) noexcept(false)
{
AbstractEventItem &eventItem = static_cast<AbstractEventItem &>(item(itemId));
if (eventItem.type() == EventType::Block)
{
BlockItem &blockItem = static_cast<BlockItem &>(eventItem);
qint64 blockItemStartTime = blockItem.startTime();
//int duration = blockItem.duration();
if (blockItem.startTime() > start)
{
blockItem._setStartTime(start); // using _setStartTime is okay to use, as it is guaranteed that the list stays sorted
//duration += (blockItem.startTime() - start); // add to duration, to match the end time of the block item to the new start time
}
if (blockItem.duration() > 0)
{
if (itemPtr->duration() == -1)
blockItem._setDuration(-1);
else
{
qint64 endTime = std::max(blockItemStartTime + blockItem.duration(), start + itemPtr->duration());
if (endTime != (blockItemStartTime + blockItem.duration()))
{
blockItem._setDuration(endTime - blockItem.startTime());
}
}
}
//ERROR_LOG(m_logger) << "i was here!";
blockItem.addItem(std::move(itemPtr));
// are there events in the block item that would now be outside of the block item? then plop them out.
if (blockItem.duration() != -1) // can't be ploppin' when it's impossible because of indefinite end time
{
std::vector<ItemPtr> &items = blockItem.items();
qint64 newDuration = std::max((qint64)blockItem.duration(), m_zeroDurationItemWidthTime);
qint64 endTime = blockItem.startTime() + newDuration;
items.erase(std::remove_if(items.begin(), items.end(),
[this, endTime, &t, &items](ItemPtr &itemPtr) /*mutable*/
{
///*DEBUG*/ERROR_LOG(m_logger) << "plopping?";
bool ret = (itemPtr->startTime() > endTime);
if (ret)
{
///*DEBUG*/ERROR_LOG(m_logger) << "plopping!!!";
insertItem(t, std::move(itemPtr));
}
return ret;
}), items.end());
// for (std::size_t i = 0; i < items.size(); ++i)
// {
////#warning die items die rausfliegen aus der liste muessen auch wirklich rausfliegen!! aka erase!!
// Item &subItem = *items[i].get();
// if (subItem.startTime() > endTime)
// insertItem(t, std::move(items[i]));
// }
//items.clear();
}
return blockItem.id();
}
else
{
qint64 startTime = std::min(start, eventItem.startTime());
//std::unique_ptr<EventItem> blockItemPtr = (*m_eventFactories.find(EventType::Block))->createItem(startTime, std::max(start + duration, eventItem.startTime() + eventItem.duration()) - startTime, {});
int blockItemDuration;
if (eventItem.duration() == -1 || duration == -1)
blockItemDuration = -1;
else
blockItemDuration = itemPtr->duration() == 0 ? 0 : (std::max(start + duration, eventItem.startTime() + eventItem.duration()) - startTime);
std::unique_ptr<BlockItem> blockItemPtr = std::make_unique<BlockItem>(/*std::move*/(BlockItem(startTime, blockItemDuration, false)));
BlockItem &blockItem = static_cast<BlockItem &>(*blockItemPtr.get());
blockItem.addItem(std::move(itemPtr));
m_itemCache.erase(m_itemCache.find(itemId)); // remove item from cache as it only lives inside the block item now!
try
{
Q_ASSERT(dynamic_cast<EventItem &>(item(itemId)).type() != EventType::Block);
} catch(...) {}
ItemPtr x = std::move(t.takeItem(itemId));
blockItem.addItem(std::move(x));
//#warning // TODO: can itemId be a block item?? if yes, move items out and discard it?
int ret = LtsTimeline::TimelineModel::addItem(t.id(), std::move(blockItemPtr));
return ret;
}
}
/*
void TimelineModel::insertCopyItem(Track &t, const int copyItemId) noexcept(false)
{
const Item &i = item(copyItemId);
qint64 duration = i.duration() == -1 ? -1 : std::max((qint64)i.duration(), m_zeroDurationItemWidthTime);
qint64 start = i.startTime();
if (_isItemsBetween(t, start, start + duration, -1, false, false, false, m_zeroDurationItemWidthTime))
{
// collision!
QVector<int> realIds = _itemIdsBetween(t, start, start + duration, false, false, false, false, m_zeroDurationItemWidthTime);
handleCopyCollision(t, copyItemId, realIds.first(), start, duration);
return;
}
t.addCopyItemId(copyItemId);
}
*/
// insert into ex_92.eem_event (event_type_id, participant_id, party_id, equipment_number, sample_time) VALUES (1, 700, 2, 4420, 1628857645000);
void TimelineModel::insertItem(Track &t, ItemPtr &&itemPtr) noexcept(false) // NOTE: as equipment participant tracks (tracks that don't have sub-tracks) can't have copy items, _isItemsBetween will never act different, as that track can never have copy items - copy items are only available on sum tracks!
{
qint64 duration = itemPtr->duration() == -1 ? -1 : std::max((qint64)itemPtr->duration(), m_zeroDurationItemWidthTime);
qint64 start = itemPtr->startTime();
if (_isItemsBetween(t, start, start + duration, -1, false, false, false, m_zeroDurationItemWidthTime))
{
// collision!
QVector<int> ids = _itemIdsBetween(t, start, start + duration, false, false, false, false, m_zeroDurationItemWidthTime);
if (ids.isEmpty())
{
Q_ASSERT(false);
return;
}
int blockItemId;
int itemDuration = itemPtr->duration();
blockItemId = handleCollision(t, std::move(itemPtr), ids.first(), start, duration);
if (ids.count() == 1)
return;
/* If the new item has no duration, yet collides with two items within the model, it must collide with them at both ends (left and right).
* Since the new item gets merged with the left item into a BlockItem and that item had no collision with the item on the right previously,
* the resulting BlockItem will not collide with the item on the right either. It is therefore safe to return here. */
if (itemDuration == 0)
return;
BlockItem &blockItem = static_cast<BlockItem &>(item(blockItemId));
ids.erase(ids.begin());
for (int id : qAsConst(ids))
{
auto it = m_itemCache.find(id);
if (it == m_itemCache.end())
{
ERROR_LOG(m_logger) << "failed to remove id ("<<id<<") from item cache";
continue;
}
m_itemCache.erase(it);
ItemPtr iPtr = t.takeItem(id);
if (blockItem.duration() != -1)
{
if (iPtr->duration() == -1)
{
blockItem._setDuration(-1);
}
else
{
qint64 endTime = iPtr->startTime() + iPtr->duration();
if (endTime > blockItem.startTime() + blockItem.duration())
blockItem._setDuration(endTime - blockItem.startTime());
}
}
// if the to-be-removed item is a block item, it's sub items need to be copied, too, and that block item can then be deleted
if (static_cast<EventItem &>(*iPtr.get()).type() == EventType::Block)
{
BlockItem &subBlockItem = static_cast<BlockItem &>(*iPtr.get());
for (ItemPtr &subBlockItemItemPtr : subBlockItem.items())
{
blockItem.addItem(std::move(subBlockItemItemPtr));
}
subBlockItem.items().clear();
}
else
{
blockItem.addItem(std::move(iPtr));
}
}
return;
}
LtsTimeline::TimelineModel::addItem(t.id(), std::move(itemPtr)); // no collision
}
void TimelineModel::insertItem(ItemPtr &&itemPtr) // looks for the corresponding track, then calls the discrete insertItem method that actually puts the item on a track
{
EventItem &eventItem = static_cast<EventItem &>(*itemPtr.get());
const AbstractEventFilter *eventFilter = constFinder<const AbstractEventFilter *>(m_filter, eventItem.type());
if (!eventFilter->isActive())
return;
if (!eventFilter->check(eventItem))
return;
try
{
int trackId = findTrack(static_cast<const AbstractEventItem &>(*itemPtr.get()));
if (trackId == -1)
throw QStringLiteral("findTrack returned -1!");
Track &_track = track(trackId);
insertItem(_track, std::move(itemPtr));
}
catch (const QString &errMsg)
{
ERROR_LOG(m_logger) << "cannot add new item: " << errMsg;
}
}
void TimelineModel::insertItems(EventItemPtrs &eventItemPtrs)
{
for (EventItemPtr &eventItemPtr : eventItemPtrs)
insertItem(std::move(eventItemPtr));
eventItemPtrs.clear();
}
void TimelineModel::getMarkers() noexcept(false)
{
m_modelState.markerTimes.clear();
// get markers
m_mrkDtc->waitForHistoricalDataMarker(5000);
std::vector<EventItemPtr> markerItems = getMarkerItems(m_filter.values().toVector(), false, -1);
/*DEBUG*/ERROR_LOG(m_logger) << "markers: " << markerItems.size();
for (EventItemPtr &ptr : markerItems)
{
int markerDbId = ptr->dbId();
try
{
m_modelState.markerTimes += ptr->startTime();
LtsTimeline::TimelineModel::addItem(m_modelState.markerTrackId, std::move(ptr), true);
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "cannot add marker with db id "<<markerDbId<<": " << msg;
}
}
}
void TimelineModel::getOfflineVideoTracks()
{
return;
AudioVideoManagement::StreamListResult *streamListResult = m_avmIfc->getAsyncStreamList();
QMap<int, QString> resultTrackList;
connect(streamListResult, &AudioVideoManagement::StreamListResult::emitStreamList,
this, [this, streamListResult, resultTrackList](const QMap<QString, QString> &streamList)
{
// liste all
// liste only online
const QMap<int, AudioVideoManagement::Stream> streamsDtc = m_avmDtc->streams(); // keine offline && active videos && all
const QMap<int, AudioVideoManagement::VideoTeam> streamsMvs = m_avmDtc->videoTeams();
QStringList streamNames;
QStringList mvsNames;
for(auto s : streamsDtc)
streamNames.append(QString::fromStdString(s.name()));
for(auto s : streamsMvs)
mvsNames.append(QString::fromStdString(s.streamName()));
QMapIterator<QString, QString> it(streamList);
while (it.hasNext())
{
it.next();
const QString internal(it.key());
const QString display (it.value());
if (streamNames.contains(internal.left(internal.length()-3)))
continue;
if (mvsNames.contains(internal.left(internal.length()-3)))
continue;
int trackId = addTrack(m_videosRootTrackId, VideoTrack(display));
m_offlineVideoTracks.insert(display, trackId);
m_offlineVideoDisplayNames.insert(internal/*internal.left(internal.length()-3)*/, display);
}
streamListResult->deleteLater();
// m_offlineVideoTimer->start();
});
}
/*
void TimelineModel::getOfflineVideos() noexcept(false)
{
return;
// now get them
GetOfflineVideosProcess* getOfflineVideoProcess = new GetOfflineVideosProcess(m_offlineVideoDisplayNames, this, this);
connect(getOfflineVideoProcess, &GetOfflineVideosProcess::signalDone, this,[this]()
{
if(m_isLive)
addOfflineVideosToTimeline();
m_offlineVideoTimer->start();
});
}
void TimelineModel::addOfflineVideosToTimeline()
{
return;
/*qWarning() << "XXXXXXXX " << "ADD OFFLINE 1";
if(m_multiMapClipsForStream.isEmpty())
return;
qWarning() << "XXXXXXXX " << "ADD OFFLINE 2";
// first check if videos are even active
const auto filters = m_filter.values().toVector();
auto eventFilterIt = std::find_if(filters.begin(), filters.end(), [this](const AbstractEventFilter *eventFilter)
{
return eventFilter->type() == EventType::CameraVideo;
});
//#warning signalDone on return??!
if (eventFilterIt == filters.end())
return;
qWarning() << "XXXXXXXX " << "ADD OFFLINE 3";
const AbstractEventFilter *filter = (*eventFilterIt);
//#warning signalDone on return??!
if (!filter->isActive())
return;
qWarning() << "XXXXXXXX " << "ADD OFFLINE 4";
QMultiMap<QString, QPair<qint64, qint64>>::iterator it;
AbstractEventFactory *factory = TimelineModel::constFinder<AbstractEventFactory *>(m_eventFactories, EventType::CameraVideo);
for(it = m_multiMapClipsForStream.begin(); it != m_multiMapClipsForStream.end(); it++)
{
qint64 time = it.value().first;
qint64 duration = it.value().second - it.value().first;
if(m_lastStartTimePerStream.value(it.key(), 0) >= it.value().first)
continue;
ERROR_LOG(m_logger) << "\nHZWEIZWEIZWEI \n\n\n\nStartzeit: " << m_view->startTime() << "\nEnde zeit: " << m_view->endTime() << "\n Zeit Clip: " << time;
//#warning rangeOnly
if ((time < m_view->startTime() || time > m_view->endTime()))
continue;
ERROR_LOG(m_logger) << "\DREIDREDREI \n\n\n\n";
QJsonObject properties(
QJsonObject({
{ QStringLiteral("MainStructureElementId"), 0 }
, { QStringLiteral("duration"), duration }
, { QStringLiteral("streamName"), it.key() }
}));
//NOTE: no collision checks on markers, as all markers have to be visible by spec (don't ask me where to find the spec ;))
EventItemPtr event = factory->createItem(time, duration, properties, {});
if (!filter->check(*event.get()))
return;
qWarning() << "XXXXXXXX " << "ADD OFFLINE 4a";
ERROR_LOG(m_logger) << "\VIERVIERVIER \n\n\n\n";
int trackId;
try
{
trackId = TimelineModel::constFinder<int>(m_offlineVideoTracks, it.key());
}
catch (...)
{
trackId = addTrack(m_videosRootTrackId, VideoTrack(m_offlineVideoDisplayNames.value(it.key())));
m_offlineVideoTracks.insert(m_offlineVideoDisplayNames.value(it.key()), trackId);
}
m_lastStartTimePerStream.insert(it.key(), it.value().first);
qWarning() << "XXXXXXXX " << "ADD OFFLINE 5 ADD";
LtsTimeline::TimelineModel::addItem(trackId, std::move(event), true);
}
}
*/
BuildingHitsTrack TimelineModel::createBuildingHitsTrack()
{
return BuildingHitsTrack(tr("Hits"));
}
void TimelineModel::getBuildings()
{
if (!m_mainStructureElements.isEmpty())
return;
m_modelState.buildingRootTrackId = addTrack(m_rootTrack.id(), BuildingsRootTrack(tr("Buildings")));
const QMap<qint32, InfrastructureManagement::MainStructureElement> mainStructureElements(m_ismDtc->mainStructureElements());
m_mainStructureElements.clear();
for (const InfrastructureManagement::MainStructureElement &mse : mainStructureElements)
{
//ERROR_LOG(m_logger) << "!!!! mse => seid: " << mse.structureElementId() << ", id: " << mse.id();
// if (mse.id() >= 17 && mse.id() <= 17)
// ERROR_LOG(m_logger) << "!!!!!!!!!!!!!!FOUND#2!!!!!!!!!!!!!!!!";
// for (const InfrastructureManagement::StructureElement &se : qAsConst(mse.elements()))
// {
// ERROR_LOG(m_logger) << "!!!! se => msei: " << se.mainStructureElementId() << ", id: " << se.id();
// if (se.mainStructureElementId() >= 1871 && se.mainStructureElementId() <= 1877)
// ERROR_LOG(m_logger) << "!!!!!!!!!!!!!!FOUND#3!!!!!!!!!!!!!!!!";
// if (se.id() >= 1871 && se.id() <= 1877)
// ERROR_LOG(m_logger) << "!!!!!!!!!!!!!!FOUND#4!!!!!!!!!!!!!!!!";
// }
if (mse.structureElementTypeId() != InfrastructureManagement::StructureElementType_def::Building
|| !m_ismDtc->isElementAssignedToExercise(mse, m_session->exerciseId())) // is a building AND assigned to this exercise
continue;
// QStringList x;
// for (const InfrastructureManagement::StructureElement &se : qAsConst(mse.elements()))
// {
// if (se.structureElementTypeId() != InfrastructureManagement::StructureElementType_def::MountingPoint
// && se.mountingPointTypeId() != InfrastructureManagement::MountingPointType_def::OutdoorCamera
// && se.mountingPointTypeId() != InfrastructureManagement::MountingPointType_def::IndoorCamera)
// continue;
// x += QStringLiteral("%1 (%2)").arg(QString::fromStdString(se.name())).arg(se.id());
// }
// DEBUG_LOG(m_logger) << ">>> camera ses for '"<<mse.name()<<"': " << x.join(QStringLiteral(", "));
int msei = mse.id();
QString name(m_ismDtc->getBuildingDisplayName(msei));
try
{
int buildingTrackId = addTrack(m_modelState.buildingRootTrackId, BuildingTrack(msei, name));
m_modelState.buildingTrackIds.insert(msei, buildingTrackId);
BuildingTrack &buildingTrack = static_cast<BuildingTrack &>(track(buildingTrackId));
buildingTrack.setHitsTrackId(addTrack(buildingTrackId, createBuildingHitsTrack()));
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "getBuildings: failed to add building tracks: " << msg;
continue;
}
m_mainStructureElements += msei; // finally, after all addTracks etc succeeded, add mse to list
// as streams are bound to mounting points, we need a map that converts a mounting point to main structure elemnt (building)
int mappingCounter = 0;
for (const InfrastructureManagement::StructureElement &se : qAsConst(mse.elements()))
{
if (se.structureElementTypeId() != InfrastructureManagement::StructureElementType_def::MountingPoint
&& se.mountingPointTypeId() != InfrastructureManagement::MountingPointType_def::OutdoorCamera
&& se.mountingPointTypeId() != InfrastructureManagement::MountingPointType_def::IndoorCamera)
continue;
m_mountingPointsToMainStructureElementMapping.insert(se.id(), msei);
mappingCounter++;
}
DEBUG_LOG(m_logger) << "added building " << mse.id() << " '"<<name<<"'" << " with " << mappingCounter << " mpi-to-msei mappings";
}
// connect(m_ismDtc.get(), &InfrastructureManagement::Interface::Dtc::mainStructureElementChanged,
// this, &CameraVideoFilter::onMainStructureElementChanged);
}
void TimelineModel::getRadioTracks()
{
if (m_cnrRootTrackId != -1 || m_tvcRootTrackId != -1)
return;
m_cnrRootTrackId = addTrack(m_rootTrack.id(), CnrRootTrack(tr("CNR")));
m_tvcRootTrackId = addTrack(m_rootTrack.id(), TvcRootTrack(tr("TVC")));
// CNR
{
m_avmDtc->waitForHistoricalDataCnrConfig(8000);
for (const AudioVideoManagement::CnrConfig &config : m_avmDtc->cnrConfigs())
{
if (config.exerciseId() == m_session->exerciseId())
{
m_cnrChannelDisplayNames.insert(config.cnrChannelId(), QString::fromStdString(config.radioCircuit()));
//m_cnrChannelDisplayNamesLookup.insert(QString::fromStdString(config.radioCircuit()), config.cnrChannelId());
DEBUG_LOG(m_logger) << "!!!!!!!!!! config => id: " << config.id() << ", circuit: " << QString::fromStdString(config.radioCircuit())
<< ", channelId: " << config.cnrChannelId();
}
}
m_avmDtc->waitForHistoricalDataCnrChannel(8000);
auto channels = m_avmDtc->cnrChannels();
for (const AudioVideoManagement::CnrChannel &channel : channels)
{
const int channelId = channel.id();
const QString internalChannelName(QString::fromStdString(channel.name()));
QString displayName;
try
{
displayName = constFinder<QString>(m_cnrChannelDisplayNames, channelId);
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "display name for cnr channel '"<<internalChannelName<<"' ("<<channelId<<") not found: " << msg;
displayName = internalChannelName;
}
DEBUG_LOG(m_logger) << "!!!!!!!!!! channel => id: " << channelId << ", name: " << internalChannelName << ", display name: " << displayName;
m_cnrStreams.insert(channelId, displayName);
try
{
m_cnrTrackIds.insert(displayName, addTrack(m_cnrRootTrackId, CnrStreamTrack(internalChannelName, displayName)));
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "cannot add cnr channel '"<<msg<<"': " << msg;
}
}
}
// TVC
{
m_avmDtc->waitForHistoricalDataTvcConfig(8000);
for (const AudioVideoManagement::TvcConfig &config : m_avmDtc->tvcConfigs())
{
if (config.exerciseId() == m_session->exerciseId())
{
m_tvcStreams.insert(config.tetraChannelId(), QString::fromStdString(config.radioCircuit()));
m_tvcChannelDisplayNames.insert(config.tetraChannelId(), QString::fromStdString(config.radioCircuit()));
DEBUG_LOG(m_logger) << "!!!!!!!!!! TETRA config => id: " << config.id() << ", circuit: " << QString::fromStdString(config.radioCircuit())
<< ", channelId: " << config.tetraChannelId();
}
}
}
{
m_avmDtc->waitForHistoricalDataTvcChannel(8000);
auto channels = m_avmDtc->tvcChannels();
for (const AudioVideoManagement::TvcChannel &channel : channels)
{
const int channelId = channel.id();
const QString internalChannelName(QString::fromStdString(channel.name()));
QString displayName;
try
{
displayName = constFinder<QString>(m_tvcChannelDisplayNames, channelId);
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "display name for tvc channel '"<<internalChannelName<<"' ("<<channelId<<") not found: " << msg;
displayName = internalChannelName;
}
DEBUG_LOG(m_logger) << "!!!!!!!!!! channel => id: " << channelId << ", name: " << internalChannelName << ", display name: " << displayName;
m_tvcStreams.insert(channelId, displayName);
try
{
m_tvcTrackIds.insert(displayName, addTrack(m_tvcRootTrackId, TvcStreamTrack(internalChannelName, displayName)));
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "cannot add tvc channel '"<<msg<<"': " << msg;
}
}
}
}
void TimelineModel::getBuildingVideoStreams()
{
if (!m_buildingVideoStreams.isEmpty())
return;
AudioVideoManagement::StreamNameMapping *streamNameMapping = AudioVideoManagement::StreamNameMapping::create(m_avmDtc.get());
auto _ = std::shared_ptr<int>(nullptr, [streamNameMapping](int *) { delete streamNameMapping; }); (void)_;
auto streams = m_avmDtc->streams();
for (auto it1 = streams.constBegin(); it1 != streams.constEnd(); ++it1)
{
if (it1->type() != AudioVideoManagement::StreamType_def::Video)
continue;
int32_t mountingPointId = it1->mountingPointId();
QString internalStreamName(QString::fromStdString(it1->name()));
QString streamDisplayName;
if (mountingPointId >= 0)
{
streamDisplayName = streamNameMapping->displayName(internalStreamName);
}
else
{
const std::function<bool(const AudioVideoManagement::VideoTeam &data)> cb =
[&streamDisplayName, mountingPointId](const AudioVideoManagement::VideoTeam &data) -> bool
{
//#warning // TODO: ist ein video team wirklich einfach die mp id als negative zahl?
if (data.id() == -mountingPointId)
{
streamDisplayName = QString::fromStdString(data.streamName());
return true;
}
return false;
};
m_avmDtc->videoTeams(cb);
}
// now add stream to coresponding building
// first, get the msei behind the mpi
auto it2 = m_mountingPointsToMainStructureElementMapping.constFind(mountingPointId);
if (it2 == m_mountingPointsToMainStructureElementMapping.constEnd())
{
ERROR_LOG(m_logger) << "getStreams: cannot add stream '"<<streamDisplayName<<"' ('"<<internalStreamName<<"') because mounting point with id " << mountingPointId << " cannot be mapped to an msei";
continue;
}
int msei = it2.value();
InfrastructureManagement::StructureElement structureElement;
m_ismDtc->getStructureElementById(m_ismDtc->mainStructureElement(msei).structureElementId(), structureElement);
bool isOutdoorCameraStream = structureElement.mountingPointTypeId() == InfrastructureManagement::MountingPointType_def::OutdoorCamera;
if (isOutdoorCameraStream)
{
m_outdoorCameraStreams.insert(mountingPointId, internalStreamName);
int trackId;
try
{
trackId = addTrack(m_outdoorCameraRootTrackId, OutdoorCameraTrack(streamDisplayName));
}
catch (const QString &errMsg)
{
ERROR_LOG(m_logger) << "getStreams: failed to add track for stream '" << streamDisplayName << "' in building with id " << msei << ": " << errMsg;
return;
}
m_outdoorCameraTracks.insert(internalStreamName, trackId);
}
else
{
m_buildingVideoStreams += mountingPointId;
// now get the track behind the msei
auto it3 = m_modelState.buildingTrackIds.constFind(msei);
if (it3 == m_modelState.buildingTrackIds.constEnd())
{
ERROR_LOG(m_logger) << "getStreams: cannot add stream '"<<streamDisplayName<<"' ('"<<internalStreamName<<"') because msei " << msei << " is missing in building track map";
continue;
}
int buildingTrackId = it3.value();
int trackId;
try
{
trackId = addTrack(buildingTrackId, BuildingStreamTrack(internalStreamName, streamDisplayName));
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "getStreams: failed to add track for stream '"<<streamDisplayName<<"' in building with id " << msei << ": " << msg;
return;
}
m_buildingStreamsTracks.insert({ msei, internalStreamName }, trackId);
m_buildingStreamsTracks.insert({ msei, streamDisplayName }, trackId); // ffs!!!!
}
}
}
void TimelineModel::clearAlmostAllItems()
{
// for (std::pair<int , Track *> p : m_trackCache)
// {
// if (p.second->id() == m_modelState.markerTrackId)
// {
// continue;
// }
// p.second->clearItems();
// }
// m_itemCache.clear();
}
void TimelineModel::update(bool fake)
{
if (m_updateMutex)
return;
m_updateMutex = true;
DEBUG_LOG(m_logger) << ">>>>>>>>>> open sql connections: " << QSqlDatabase::connectionNames().count() << ": " << QSqlDatabase::connectionNames().join(QStringLiteral(", "));
emit updateStatusChanged(true);
// Step 1.2: update the displayed delta in time for items with no duration
int x = m_view->header()->sectionPosition(LtsTimeline::TimelineHeaderView::Section::eItems)
+ TimelineDelegate::s_zeroDurationItemWidth;
m_zeroDurationItemWidthTime = m_view->toTime(x) - m_view->currentTime();
DEBUG_LOG(m_logger) << "m_zeroDurationItemWidthTime = " << m_zeroDurationItemWidthTime;
//#warning // TODO: reactivate ... some time ;)
//if (m_queueItemDeletion)
clearItems();
m_queueItemDeletion = false;
//get All Offline Videos
//addOfflineVideosToTimeline();
//new VideoEvents
// create new temporary view
// insertAudioEvents();
/*
try
{
// const Rows rows(executeQuery(
// QStringLiteral("SELECT camera_id, time_start_msec, stream_name, time_end_msec from avm.avs_camera_events"
// " WHERE context_id = '%3' AND camera_id < 0 AND ((time_start_msec BETWEEN %1 AND %2) "
// " OR (time_start_msec BETWEEN %1 AND %2)) ")
// .arg(m_view->startTime())
// .arg(m_view->endTime())
// .arg(QStringLiteral("ex_")+QString::number(m_session->exerciseId()))
// ));
//
const Rows rows(executeQuery(
QStringLiteral("SELECT camera_id, event_type, time_start_msec, stream_name, time_end_msec from avm.avs_camera_events"
" WHERE context_id = '%3'AND camera_id < 0"
" AND event_type <= 302"
" AND ((time_start_msec BETWEEN %1 AND %2) "
" OR (time_start_msec BETWEEN %1 AND %2)) ")
.arg(m_view->startTime())
.arg(m_view->endTime())
.arg(QStringLiteral("ex_")+QString::number(m_session->exerciseId()))
));
// qCritical() << "PPPPP new QUERRY " ;
AbstractEventFactory *factory = TimelineModel::constFinder<AbstractEventFactory *>(m_eventFactories, EventType::CameraVideo);
auto eventFilterIt = std::find_if(m_filter.constBegin(), m_filter.constEnd(), [this](const AbstractEventFilter *eventFilter)
{
return eventFilter->type() == EventType::CameraVideo;
});
if (eventFilterIt == m_filter.constEnd())
return;
const AbstractEventFilter *filter = (*eventFilterIt);
for (const auto row : rows)
{
// qCritical() << "PPPPPPPP ROW DATA: " << row;
qint64 time = row.value(QStringLiteral("time_start_msec")).toLongLong();
qint64 duration = row.value(QStringLiteral("time_end_msec")).toLongLong() - time;
QString stream_name = row.value(QStringLiteral("stream_name")).toString();
qint64 camera_id = row.value(QStringLiteral("camera_id")).toInt();
int event_type = row.value(QStringLiteral("event_type")).toInt();
int avsStreamId = -1;
int mpi = -1;
if (camera_id > 0 && event_type <= 302)
{
// qCritical() << "PPPPPPPP has CAMERA: " << row;
//we are an video so check for MVS only for now.
if(true)
{
const auto mvsStreams = m_avmDtc->videoTeams();
for (const auto mvsStream : mvsStreams)
{
if (QString::fromStdString(mvsStream.streamName()) == stream_name)
{
avsStreamId = mvsStream.id();
break;
}
}
}
if(avsStreamId < 0)
{
//we area audio and are handled elsewere
continue; //PPPPPPPP
const auto name = m_avmDtc->streams();
for (const auto n : name)
{
if (QString::fromStdString(n.name()) == stream_name)
{
mpi = n.mountingPointId();
break;
}
}
}
//qCritical() << "PPPPPPPP has CAMERA:p2 " << stream_name << " has MPI" << mpi << " with type: " << event_type;
}else if (camera_id > 0 && event_type <= 304)
{
qCritical() << "PPPPPPPP is ADUIO: " << stream_name;
//we area audio and are handled elsewere
//do audio here
// continue;
}
ERROR_LOG(m_logger) << "PPPPP 22222 \n\n\n\n << " << stream_name << " s:" << time << " d:" << duration;
// if(m_lastStartTimePerStream.value(stream_name, 0) >= time)
// continue;
if ((time < m_view->startTime() || time > m_view->endTime()))
continue;
QJsonObject properties(
QJsonObject({
{ QStringLiteral("MainStructureElementId"), camera_id > 0 ? avsStreamId : 0}
, { QStringLiteral("duration"), duration }
, { QStringLiteral("streamName"), stream_name }
}));
//NOTE: no collision checks on markers, as all markers have to be visible by spec (don't ask me where to find the spec ;))
EventItemPtr event = factory->createItem(time, duration, properties, {});
if (!filter->check(*event.get()))
continue;
int trackId;
try
{
// qWarning() << "PPPPP " << "ADD OFFLINE 4b found Stream Name" << stream_name ;
trackId = TimelineModel::constFinder<int>(m_offlineVideoTracks, stream_name);
}
catch (...)
{
// qWarning() << "PPPPP " << "ADD OFFLINE 4c new Stream Track with name " << stream_name;
trackId = addTrack(m_videosRootTrackId, VideoTrack(stream_name));
m_offlineVideoTracks.insert(stream_name, trackId);
}
m_lastStartTimePerStream.insert(stream_name, time);
// qWarning() << "PPPPP " << "ADD OFFLINE 5 ADD " << trackId;
LtsTimeline::TimelineModel::addItem(trackId, std::move(event), true);
}
}
catch (const QString &errMsg) { ERROR_LOG(m_logger) << "create TempRangeTable failed: " << errMsg; return; }
*/
// QEventLoop loop;
// QTimer::singleShot(5000, &loop, &QEventLoop::quit);
// loop.exec();
// get markers
getMarkers();
// rebuild filter table if live false, because when we leave live mode, the timeline has a new highest end time, so new events would otherwise be missing in the filter table
if (m_forceRebuildFilter)
{
m_forceRebuildFilter = false;
updateFilterTable();
}
// get all other events (threaded)
int count;
qint64 start = m_view->currentTime();
qint64 end = start + m_view->zoomFactor();
{
auto _ = std::shared_ptr<int>(nullptr, [this](int *)
{
m_updateMutex = false;
m_isCurrentlyUpdating = false;
m_updateQueued = false;
emit updateStatusChanged(false);
}); // remove mutex on scope-loss
m_isCurrentlyUpdating = true;
// create a new temp range table
// drop old temporary view
try
{
executeQuery(QStringLiteral("DROP TABLE IF EXISTS temp_range_%1").arg(m_modelState.dbUuid), true);
}
catch (const QString &errMsg) { ERROR_LOG(m_logger) << "drop TempRangeTable failed: " << errMsg; return; }
// create new temporary view
//" AND event_type_id != 9 "
try
{
executeQuery(
QStringLiteral("CREATE TEMP TABLE temp_range_%1 AS (SELECT * FROM temp_filter_%1 "
"WHERE (sample_time BETWEEN %2 AND %3) "
"OR (properties->>'endTime' = '-1') "
"OR (coalesce(CAST(properties->>'duration' AS BIGINT), 0) + sample_time) >= %2 AND sample_time <= %3)")
.arg(m_modelState.dbUuid)
.arg(m_view->startTime())
.arg(m_view->endTime())
);
}
catch (const QString &errMsg) { ERROR_LOG(m_logger) << "create TempRangeTable failed: " << errMsg; return; }
m_modelState.tempRangeStart = m_view->startTime();
m_modelState.tempRangeEnd = m_view->endTime();
// create new view db (even if only filters changed, as the view db inherits from filter db)
try
{
executeQuery(QStringLiteral("DROP TABLE IF EXISTS %1").arg(m_modelState.publicTableName), true);
}
catch (const QString &errMsg) { ERROR_LOG(m_logger) << "drop TempViewTable failed: " << errMsg; return; }
//" AND event_type_id != 9 "
try
{
executeQuery(QStringLiteral("CREATE TABLE %1 AS "
"(SELECT * FROM temp_range_%2 "
"WHERE (sample_time BETWEEN %3 AND %4) "
"OR (properties->>'endTime' = '-1' AND sample_time <= %4) "
"OR (coalesce(CAST(properties->>'duration' AS BIGINT), 0) + sample_time) >= %3 AND sample_time <= %4)")
.arg(m_modelState.publicTableName)
.arg(m_modelState.dbUuid)
.arg(m_view->currentTime())
.arg(m_view->currentTime() + m_view->zoomFactor())
);
}
catch (const QString &errMsg) { ERROR_LOG(m_logger) << "create TempViewTable failed: " << errMsg; return; }
try
{
const Rows rows(executeQuery(QStringLiteral("SELECT count(*) FROM %1").arg(m_modelState.publicTableName)));
if (rows.count() != 1)
throw QStringLiteral("count on temp view inplausible!");
count = getInt(rows.first(), QStringLiteral("count"));
}
catch (const QString &errMsg) { ERROR_LOG(m_logger) << "countTempView" << errMsg; return; }
DEBUG_LOG(m_logger) << "number of events in range = " << count;
if (count == 0)
{
WARNING_LOG(m_logger) << "no events in selected range: " << QDateTime::fromMSecsSinceEpoch(m_modelState.tempRangeStart).toString() << " - " << QDateTime::fromMSecsSinceEpoch(m_modelState.tempRangeEnd).toString();
m_modelState.knownStart = start;
m_modelState.knownEnd = end;
return;
}
if (count > 750000)
{
ERROR_LOG(m_logger) << "more than 750.000 events requested - aborted";
emit errorCountExceeded();
return;
}
m_threads = std::min(std::max(QThread::idealThreadCount(), s_minThreadCount), s_maxThreadCount);
}
// scope lost, both set to false, but needed to be set to true again!
emit updateStatusChanged(true);
m_isCurrentlyUpdating = true;
m_updateMutex = true;
m_updateResult = true; // okay until set as failed
if (m_firstUpdate)
{
m_lockyMcLockLock = (int *)malloc(sizeof(int));
m_sharedThing = (Rows **)malloc(sizeof(Rows *) * m_threads);
for (int i = 0; i < m_threads; i++)
m_sharedThing[i] = new Rows;
// NOTE: this is from thread to main-thread!!
connect(this, &TimelineModel::thingy,
this, [this](int i, bool result)
{
m_mutex.lock();
//fprintf(stderr, ">> LOCKED at %x\n", QThread::currentThreadId());
if (result)
{
QElapsedTimer t;
t.start();
//fprintf(stderr, "22222222 %d /ACCESSING SHARED THING!\n", i);
EventItemPtrs eventItemPtrs = createItemsFromRows(*m_sharedThing[i]);
//fprintf(stderr, "22222222 %d \\STOP ACCESSING SHARED THING!\n", i);
DEBUG_LOG(m_logger) << "create took " << t.elapsed() << "ms";
//fprintf(stderr, "33333333 %d /ACCESSING SHARED THING!\n", i);
m_sharedThing[i]->clear();
//fprintf(stderr, "33333333 %d \\STOP ACCESSING SHARED THING!\n", i);
t.start();
insertItems(eventItemPtrs);
DEBUG_LOG(m_logger) << "insert took " << t.elapsed() << "ms";
m_isCurrentlyUpdating = false;
emit dataChanged({}, {});
m_isCurrentlyUpdating = true;
}
else
{
// failed
if (m_updateResult) // first error seen
{
m_updateResult = false;
new LtsWidgets::LtsMessageDialogNew(m_view, tr("Failed to get events. Please try again later."));
}
}
(*m_lockyMcLockLock)--;
Q_ASSERT((*m_lockyMcLockLock) >= 0);
if ((*m_lockyMcLockLock) == 0)
{
if (m_updateResult)
{
m_modelState.knownStart = m_view->currentTime();
m_modelState.knownEnd = m_modelState.knownStart + m_view->zoomFactor();
channelSumEvents();
}
m_isCurrentlyUpdating = false;
m_updateMutex = false;
emit updateStatusChanged(false);
if (m_updateQueued) // start update right after, again
{
m_updateQueued = false;
QMetaObject::invokeMethod(this, &TimelineModel::onUpdate, Qt::ConnectionType::QueuedConnection); // queued to event loop, mutex unlocked at the point of calling the slot
}
}
//fprintf(stderr, ">> UNLOCKING at %x....\n", QThread::currentThreadId());
m_mutex.unlock();
}, Qt::QueuedConnection);
m_firstUpdate = false;
}
int threads = m_threads;
if (count < threads)
threads = 1; // get the few events by one thread... no need to overkill
int limit = std::ceil(double(count) / threads);
(*m_lockyMcLockLock) = threads;
DEBUG_LOG(m_logger) << "threads = " << threads << ", main thread id = " << QThread::currentThreadId();
for (int i = 0; i < threads; ++i)
{
QString stmt(QStringLiteral("SELECT * FROM %1 LIMIT %2 OFFSET %3")
.arg(m_modelState.publicTableName)
.arg(limit)
.arg(i * limit));
//#warning // TODO: wait for threads to finish before allowing the user to quit... otherwise: 'this' doesn't exist anymore
QtConcurrent::run([this, stmt, i]()
{
bool result = false;
auto _ = std::shared_ptr<int>(nullptr, [this, &result, i](int *) { emit thingy(i, result); }); // remove mutex on scope-loss
QElapsedTimer t;
t.start();
QString connectionName(QStringLiteral("hotline_%1").arg((qintptr)QThread::currentThreadId()));
m_mutex.lock();
{
QSqlDatabase db(QSqlDatabase::cloneDatabase(m_db, connectionName));
if (!db.open())
{
ERROR_LOG(m_logger) << "thread " << (qintptr)QThread::currentThreadId() << " cannot connect to db: " << db.lastError().text()
<< " connectionName=" << connectionName;
return;
}
try
{
m_mutex.unlock();
Rows rows(executeQuery(stmt, db, false));
m_mutex.lock();
//fprintf(stderr, "11111111 %d /ACCESSING SHARED THING!\n", i);
Rows *theThing = m_sharedThing[i];
//fprintf(stderr, "11111111 %d \\STOP ACCESSING SHARED THING!\n", i);
theThing->swap(rows);
result = true;
}
catch (const QString &msg)
{
ERROR_LOG(m_logger) << "thread " << QThread::currentThreadId() << " failed: " << msg;
}
db.close();
}
QSqlDatabase::removeDatabase(connectionName);
m_mutex.unlock();
//bool bOk = TimelineModelUpdate(this).run(); // blocking, sync!
DEBUG_LOG(m_logger) << "Update took " << t.elapsed() << "ms in thread " << QThread::currentThreadId();
return;//bOk;
});
}
}
void TimelineModel::updateFilterTable()
{
try
{
executeQuery(QStringLiteral("DROP TABLE IF EXISTS temp_filter_%1").arg(m_modelState.dbUuid), true);
executeQuery(
QStringLiteral(
"CREATE TEMP TABLE temp_filter_%1 AS (SELECT %2 FROM ex_%3.eem_event WHERE %4)"
).arg(m_modelState.dbUuid)
.arg(s_eemEventColumns)
.arg(m_session->exerciseId())
.arg(m_filterString)
);
// m_modelState.knownStart = 0;
// m_modelState.knownEnd = 0;
}
catch (const QString &errMsg) { ERROR_LOG(m_logger) << "updating filter table failed: " << errMsg; }
catch (...) { ERROR_LOG(m_logger) << "updating filter table failed"; }
}
///////////////////////////////////////////////////////////////////////////////
// PUBLIC
bool TimelineModel::canDropItem(const LtsTimeline::Item &item, int trackId, qint64 start) const
{
Q_UNUSED(item);
Q_UNUSED(trackId);
Q_UNUSED(start);
return false;
}
bool TimelineModel::canMoveItem(const LtsTimeline::Item &item, qint64 start) const
{
Q_UNUSED(item);
Q_UNUSED(start);
return false;
}
bool TimelineModel::canResizeItem(const LtsTimeline::Item &item, qint64 start, int duration) const
{
Q_UNUSED(item);
Q_UNUSED(start);
Q_UNUSED(duration);
return false;
}
///////////////////////////////////////////////////////////////////////////////
// PRIVATE SLOTS
void TimelineModel::onFilterChanged()
{
static QString filterStringPrimer(QStringLiteral("1 = CASE %1 ELSE 0 END"));
m_filterString.clear();
QStringList conditionStrings;
for (const AbstractEventFilter *filter : qAsConst(m_filter))
{
if (!filter->isActive())
continue;
QString conditions = filter->toString();
if (conditions.isEmpty())
continue;
conditionStrings.append(conditions);
}
if (!conditionStrings.isEmpty())
m_filterString = filterStringPrimer.arg(conditionStrings.join(QString()));
updateFilterTable();
// refetch all events on update
m_queueItemDeletion = true;
tryUpdate();
}
} // namespace Timeline