//#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 #include #include #include #include #include #include #include #include #include #include #include #include #include // eem foreign //#include #include #include "tln_abstract_event_item.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Services/AudioVideoManagement/stream_name_mapping.h" #include #include #include using Tick = LtsTimeline::Tick; using namespace LtsTimeline; namespace Timeline { /* GetOfflineVideosProcess::GetOfflineVideosProcess(const QMap &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(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(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(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(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(m_ppmDtc)) throw "cannot get ppm dtc"; if (!session->getDtc(m_mrkDtc)) throw "cannot get mrk dtc"; if (!session->getDtc(m_ismDtc)) throw "cannot get ism dtc"; if (!session->getForeignDataInterface(m_eemForeign)) throw "cannot get eem foreign"; if (!session->getDtc(m_avmDtc)) throw QStringLiteral("Audio/Video Data not available"); if (!session->getForeignGuiInterface(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 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 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 TimelineModel::findItemIdsForDbId(int dbId) noexcept(false) { QVector itemIds; for (auto it = m_itemCache.cbegin(); it != m_itemCache.cend(); ++it) { EventItem &i = static_cast(*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(*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(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(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 "<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(i).setProperties(properties); backwardsChannelSumEvents(t); } }; try { const QVector 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 ids; for (const std::pair &pair : m_itemCache) ids += pair.first; for (int id : ids) { try { EventItem &ei = static_cast(item(id)); if (ei.type() != EventType::Block) continue; BlockItem &bi = static_cast(ei); bool found = false; for (ItemPtr &itemPtr : bi.items()) { EventItem &ei2 = static_cast(*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(*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(static_cast(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(m_eventFactories, eventType); AbstractEventFilter *eventFilter = constFinder(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 &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(m_view->zoomFactor()); tryUpdate(); } void TimelineModel::onZoomChanged(LtsTimeline::ZoomFactor zoom) { m_modelState.end = m_modelState.start + static_cast(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(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 &itemIds) const { for (int itemId : qAsConst(itemIds)) if (isBlockItem(itemId)) return true; return false; } std::vector TimelineModel::getEventsByFilter(const QVector &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 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(t); return TrackType::Participant; } catch(...) {} try { dynamic_cast(t); return TrackType::Party; } catch(...) {} try { dynamic_cast(t); return TrackType::BuildingsRoot; } catch(...) {} try { dynamic_cast(t); return TrackType::BuildingHits; } catch(...) {} try { dynamic_cast(t); return TrackType::BuildingStream; } catch(...) {} try { dynamic_cast(t); return TrackType::TvcRoot; } catch(...) {} try { dynamic_cast(t); return TrackType::TvcStream; } catch(...) {} try { dynamic_cast(t); return TrackType::CnrRoot; } catch(...) {} try { dynamic_cast(t); return TrackType::CnrStream; } catch(...) {} try { dynamic_cast(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 /* aka. Rows */ TimelineModel::executeQuery(const QString &query, bool ignoreError) const noexcept(false) { return executeQuery(query, m_db, ignoreError); } QVector /* 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 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 itemIds(megaTrack.itemIdsBetween(tickStart, tickStart + m_modelState.tickSizeTime - 1, false, false, true, false)); // QVector 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(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() << 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 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 ("<items()) { EventItem &eventItem = static_cast(*itemPtr.get()); if (eventItem.type() == EventType::Block) { BlockItem &blockItem = static_cast(eventItem); for (const ItemPtr &blockSubItemPtr : blockItem.items()) { try { EventItem &blockSubItem = static_cast(*blockSubItemPtr.get()); insertItem(track, std::move(constFinder(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(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 §orItemPtrNCVRefs) //{ // // 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(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 theItem = (*eventFactoryIt)->createItem(sampleTime, 0, properties, position); theItem->m_dbId = id; try { AbstractParticipantEventItem &participantItem = dynamic_cast(*theItem.get()); participantItem.setParticipantId(participantId); participantItem.setPartyId(partyId); participantItem.setEquipmentNumber(eqId); participantItem.setPosition(position); } catch(...) {} return /*std::move*/(theItem); } std::vector TimelineModel::createItemsFromRows(const Rows &rows) { std::vector 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 TimelineModel::getMarkerItems(const Filters &filters, bool rangeOnly, int limit) noexcept(false) { std::vector ret; // get marker event factory AbstractEventFactory *factory = constFinder(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 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(_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(_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(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(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(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(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(m_buildingStreamsTracks, QPair(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(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(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(_eventItem); const QString &displayStreamName = radioCnrItem.displayStreamName(); try { trackId = constFinder(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 '"<(nullptr, [this](int *) { endInsertRows(); }); (void)_; beginInsertRows(idx, rowCount, rowCount); trackId = addTrack(m_cnrRootTrackId, CnrStreamTrack(QStringLiteral(""), displayStreamName)); // UGH!!! m_cnrTrackIds.insert(displayStreamName, trackId); } } else if (eventType == EventType::RadioTvc) { const RadioTvcItem &radioTvcItem = static_cast(_eventItem); const QString &displayStreamName = radioTvcItem.displayStreamName(); try { trackId = constFinder(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 '"<(nullptr, [this](int *) { endInsertRows(); }); (void)_; beginInsertRows(idx, rowCount, rowCount); trackId = addTrack(m_tvcRootTrackId, TvcStreamTrack(QStringLiteral(""), displayStreamName)); // UGH!!! m_tvcTrackIds.insert(displayStreamName, trackId); } } else if (eventType == EventType::HitBuilding) { const HitBuildingItem &bi = static_cast(_eventItem); int msei = bi.mainStructureElementId(); if (msei <= 0) throw QStringLiteral("msei <= 0"); int buildingTrackId = constFinder(m_modelState.buildingTrackIds, msei); if (buildingTrackId <= 0) throw QStringLiteral("buildingTrackId <= 0"); const BuildingTrack &bt = dynamic_cast(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 ©Item = static_cast(item(copyItemId)); EventItem &eventItem = static_cast(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 blockItemPtr = std::make_unique(/*std::move*/(BlockItem(startTime, blockItemDuration, false))); // BlockItem &blockItem = static_cast(*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(item(itemId)); if (eventItem.type() == EventType::Block) { BlockItem &blockItem = static_cast(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 &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 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 blockItemPtr = std::make_unique(/*std::move*/(BlockItem(startTime, blockItemDuration, false))); BlockItem &blockItem = static_cast(*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(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 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 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(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 ("<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(*iPtr.get()).type() == EventType::Block) { BlockItem &subBlockItem = static_cast(*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(*itemPtr.get()); const AbstractEventFilter *eventFilter = constFinder(m_filter, eventItem.type()); if (!eventFilter->isActive()) return; if (!eventFilter->check(eventItem)) return; try { int trackId = findTrack(static_cast(*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 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 "<getAsyncStreamList(); QMap resultTrackList; connect(streamListResult, &AudioVideoManagement::StreamListResult::emitStreamList, this, [this, streamListResult, resultTrackList](const QMap &streamList) { // liste all // liste only online const QMap streamsDtc = m_avmDtc->streams(); // keine offline && active videos && all const QMap 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 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>::iterator it; AbstractEventFactory *factory = TimelineModel::constFinder(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(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 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 '"<getBuildingDisplayName(msei)); try { int buildingTrackId = addTrack(m_modelState.buildingRootTrackId, BuildingTrack(msei, name)); m_modelState.buildingTrackIds.insert(msei, buildingTrackId); BuildingTrack &buildingTrack = static_cast(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() << " '"<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(m_cnrChannelDisplayNames, channelId); } catch (const QString &msg) { ERROR_LOG(m_logger) << "display name for cnr 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 '"<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(m_tvcChannelDisplayNames, channelId); } catch (const QString &msg) { ERROR_LOG(m_logger) << "display name for tvc 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 '"<(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 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 '"<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 '"< 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(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(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(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(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