#include "PrtCnvDbThread.hpp" #include "PrtCnvThreadStarter.hpp" #include "PrtCnvCentralThread.hpp" #include "PrtCnvMember.hpp" #include "PrtCnvRadioThread.hpp" #include "PrtCnvStoredCentralTelegram.hpp" #include "PrtCnvStoredRadioTelegram.hpp" #include #include #include #include #include #include #include #include #include #include namespace PrtCnv { PrtCnvDbThread::PrtCnvDbThread(const QString &dbDriver, const QString &dbHostName, const QString &dbDatabaseName, const QString &schemaName, const QString &dbUserName, const QString &dbPassword, PrtCnv::PrtCnvThreadStarter *a_parent) : PrtCnvThread(a_parent, "PrtCnvDbThread") , m_BattleFieldOpStateMutex(QMutex::Recursive) , m_MemberListByIdMutex(QMutex::Recursive) , m_CentralListMutex(QMutex::Recursive) , m_OldCentralAckCounter(0) , m_RadioListMutex(QMutex::Recursive) , m_SqlCmdListMutex(QMutex::Recursive) , m_BroadcastMapMutex(QMutex::Recursive) , m_DoClean(false) , m_DoWaitUntilSqlWritten(false) , m_DoStorePermanent(false) , m_ResetCounter(0) , m_BattleFieldOperatingState(0) , m_EnvironmentTelegram(nullptr) , m_DoProceedCleanup(false) , m_OldCentralCounter(0) , m_StorePermanentPending(0) , m_NbrOfAttempts(5) , m_dbDriver(dbDriver.isEmpty() ? QStringLiteral("QSQLITE") : dbDriver) , m_dbHostName(dbHostName) , m_dbDatabaseName(dbDatabaseName) , m_dbSchemaName(schemaName) , m_dbUserName(dbUserName) , m_dbPassword(dbPassword) { m_Name = QStringLiteral("Db"); } PrtCnvDbThread::~PrtCnvDbThread() { closeDb(); qDeleteAll(m_BroadcastList); qDeleteAll(m_RadioList); qDeleteAll(m_CentralList); for (const auto &p : m_MemberListById) delete p.second; } static const int s_iCurrentDatabaseSchemeVersion = 1; void PrtCnvDbThread::reinitDatabase() { DBTransaction trans(m_db); NSqlQuery q(m_db); if (m_db.driverName() == QStringLiteral("QSQLITE")) { q.exec(QStringLiteral("DROP TABLE IF EXISTS %1;").arg(versionTableName())); q.exec(QStringLiteral("CREATE TABLE %1 (" "version integer," "date text," "product text," "change text)").arg(versionTableName())); q.exec(QStringLiteral("INSERT INTO %1 (version, date, product, change) VALUES " "(1, '2012-11-20', 'TetraInterface', 'Initial version table');").arg(versionTableName())); q.exec(QStringLiteral("DROP TABLE IF EXISTS %1;").arg(toCentralTableName())); q.exec(QStringLiteral("CREATE TABLE %1 (" "id INTEGER PRIMARY KEY AUTOINCREMENT," "tel_id INTEGER," "counter INTEGER DEFAULT 0," "data BLOB);").arg(toCentralTableName())); q.exec(QStringLiteral("CREATE INDEX counterIndex ON %1(counter);").arg(toCentralTableName())); q.exec(QStringLiteral("DROP TABLE IF EXISTS %1;").arg(toRadioTableName())); q.exec(QStringLiteral("CREATE TABLE %1 (" "id INTEGER PRIMARY KEY AUTOINCREMENT," "tel_id INTEGER," "counter INTEGER DEFAULT 0," "broadcast_index INTEGER DEFAULT 0," "reset_counter INTEGER DEFAULT 0," "data BLOB);").arg(toRadioTableName())); q.exec(QStringLiteral("CREATE INDEX bcindex ON %1(reset_counter, broadcast_index);").arg(toRadioTableName())); q.exec(QStringLiteral("DROP TABLE IF EXISTS %1;").arg(memberTableName())); q.exec(QStringLiteral("CREATE TABLE %1 (" "id INTEGER PRIMARY KEY," "issi INTEGER," "licensed INTEGER DEFAULT 0," "claims INTEGER DEFAULT 0," "exercise_id INTEGER" ");").arg(memberTableName())); q.exec(QStringLiteral("DROP TABLE IF EXISTS %1;").arg(statusTableName())); q.exec(QStringLiteral("CREATE TABLE %1 (" "id INTEGER PRIMARY KEY," "reset_counter INTEGER," "ctas_operating_state INTEGER," "machine_state INTEGER" ");").arg(statusTableName())); } q.exec(QStringLiteral("DELETE FROM %1 WHERE id = 1;").arg(statusTableName())); q.exec(QStringLiteral("INSERT INTO %1 " "(id, reset_counter, ctas_operating_state, machine_state) " "VALUES (1, %2, %3, 0);") .arg(statusTableName()).arg(int(m_ResetCounter)).arg(int(m_BattleFieldOperatingState))); q.exec(QStringLiteral("DELETE FROM %1 WHERE id = 0;").arg(memberTableName())); trans.commit(); } bool PrtCnvDbThread::createDatabase() { closeDb(); m_db = QSqlDatabase::addDatabase(m_dbDriver, QStringLiteral("PrtCnv")); if (m_dbDriver == QLatin1String("QSQLITE")) { QString path = m_Config->getString(getName(), QStringLiteral("DatabaseFileName"), QStringLiteral("telegrams.db")); if (!QDir::isAbsolutePath(path)) { path = QCoreApplication::applicationDirPath() + QLatin1String("/../") + path; QFileInfo fi(path); QDir().mkpath(fi.absolutePath()); } m_db.setDatabaseName(path); INFO_LOG(m_logger) << "Use db-file: " << path; std::cout << "Use db-file: " << qPrintable(path) << std::endl; } else { m_db.setDatabaseName(m_dbDatabaseName); m_db.setHostName(m_dbHostName); m_db.setUserName(m_dbUserName); m_db.setPassword(m_dbPassword); } DEBUG_LOG(m_logger) << "Connection name: " << m_db.connectionName(); if (!m_db.open()) { FATAL_LOG(m_logger) << "Error while opening database: " << m_db.lastError().text(); return false; } if (m_DoCleanDb) { NOTICE_LOG(m_logger) << "Permanent data will be erased..."; reinitDatabase(); } else { NOTICE_LOG(m_logger) << "Check database..."; NSqlQuery q(m_db); if (m_db.driverName() == QStringLiteral("QSQLITE")) { if (!q.execAndFetch(QStringLiteral("SELECT MAX(version) FROM %1;").arg(versionTableName()))) { ERROR_LOG(m_logger) << "Could not read version table - reinitialize database"; reinitDatabase(); } else { int iVersion = q.value(0).toInt(); if (iVersion != s_iCurrentDatabaseSchemeVersion) { ERROR_LOG(m_logger) << "Database version does not match (is: " << iVersion << " expected: " << s_iCurrentDatabaseSchemeVersion << " - reinitialize database"; reinitDatabase(); } } } if (!q.exec(QStringLiteral("SELECT * from %1 LIMIT 1;").arg(statusTableName()))) { ERROR_LOG(m_logger) << "Failure while checking DB-table Status"; std::cout << "Failure while checking DB-table Status. Start PrtCnv with --cleandb" << std::endl; return false; } if (!q.exec(QStringLiteral("SELECT * from %1 LIMIT 1;").arg(toCentralTableName()))) { ERROR_LOG(m_logger) << "Failure while checking DB-table ToCentral"; std::cout << "Failure while checking DB-table ToCentral. Start PrtCnv with --cleandb" << std::endl; return false; } if (!q.exec(QStringLiteral("SELECT * from %1 LIMIT 1;").arg(toRadioTableName()))) { ERROR_LOG(m_logger) << "Failure while checking DB-table ToRadio"; std::cout << "Failure while checking DB-table ToRadio. Start PrtCnv with --cleandb" << std::endl; return false; } if (!q.exec(QStringLiteral("SELECT issi FROM %1 WHERE id=0;").arg(memberTableName()))) { ERROR_LOG(m_logger) << "Failure while checking DB-table Member"; std::cout << "Failure while checking DB-table Member. Start PrtCnv with --cleandb" << std::endl; return false; } const int broadcastIssi = m_Config->getInteger(QStringLiteral("Radio"), QStringLiteral("BroadcastISSI"), 5000); if (q.next()) { const int dbBroadcastIssi = q.value(0).toInt(); if (broadcastIssi != dbBroadcastIssi) { WARNING_LOG(m_logger) << "Broadcast-ISSI mismatch (DB: " << dbBroadcastIssi << "; Configfile: " << broadcastIssi << ")"; q.exec(QStringLiteral("DELETE FROM %1 WHERE id = 0;").arg(memberTableName())); licenseMember(addMember(0, broadcastIssi)); } } else { licenseMember(addMember(0, broadcastIssi)); } INFO_LOG(m_logger) << "Restore permanent data (after crash)..."; m_DoStorePermanent = false; INFO_LOG(m_logger) << "restoreStatus"; if (!q.execAndFetch(QStringLiteral("SELECT reset_counter, ctas_operating_state, machine_state FROM %1;").arg(statusTableName()))) { ERROR_LOG(m_logger) << "Error reading Status from DB"; std::cout << "Error reading Status from DB. Start PrtCnv with --cleandb" << std::endl; return false; } m_ResetCounter = q.value(0).toInt(); INFO_LOG(m_logger) << "Restore resetCounter=" << (int)m_ResetCounter; if (1 == q.value(2).toInt()) { INFO_LOG(m_logger) << "Crash happenend while cleaning-DB"; m_DoStorePermanent = true; m_DoProceedCleanup = true; return true; } m_BattleFieldOperatingState = q.value(1).toInt(); INFO_LOG(m_logger) << "Restore battleFieldOperatingState=" << (int)m_BattleFieldOperatingState; if (!q.exec(QStringLiteral("SELECT id, issi, licensed, claims, exercise_id FROM %1;").arg(memberTableName()))) { ERROR_LOG(m_logger) << "Error reading Member from DB"; std::cout << "Error reading Member from DB. Start PrtCnv with --cleandb" << std::endl; return false; } while (q.next()) { int id = q.value(0).toInt(); int issi = q.value(1).toInt(); bool licensed = q.value(2).toBool(); int claims = q.value(3).toInt(); int exerciseId = q.value(4).toInt(); INFO_LOG(m_logger) << "Restore member: id=" << id << " issi=" << issi << " licensed=" << licensed << " claims=" << claims << " exerciseId " << exerciseId; PrtCnvMember *member = addMember(id, issi); if (member) { member->m_ClaimCount = claims; member->m_loggedInExerciseId = exerciseId; if (licensed) { bool ok = licenseMember(member); if (ok) { emit playerLicencedChanged(member->m_Id, N_CoPrtCnv::E_LicensingKind::Success); } } } } if (!q.exec(QStringLiteral("SELECT id, counter, broadcast_index, data " "FROM %1 ORDER BY reset_counter, broadcast_index, id;").arg(toRadioTableName()))) { ERROR_LOG(m_logger) << "Error reading from ToRadio table"; std::cout << "Error reading from ToRadio table. Start PrtCnv with --cleandb" << std::endl; return false; } while (q.next()) { const qint64 id = q.value(0).toLongLong(); const int counter = q.value(1).toInt(); const int bcIndex = q.value(2).toInt(); const QByteArray telData(q.value(3).toByteArray()); auto radioTelegram(std::make_shared(telData)); PrtCnvStoredRadioTelegram *storedTelegram = new PrtCnvStoredRadioTelegram(radioTelegram); storedTelegram->m_Id = id; if (0 == counter) { m_RadioList.push_back(storedTelegram); INFO_LOG(m_logger) << "Restore unsent radiotelegram: id=" << storedTelegram->m_Id << " telId=" << storedTelegram->m_RadioTelegram->getTelID() << " BC=" << storedTelegram->m_RadioTelegram->getBroadcastIndex() << " counter=" << counter; } else { if (bcIndex == 0) { if (radioTelegram->getTelID() == GEF_UMGEBUNGS_MELDUNG2) m_RadioList.push_back(storedTelegram); else delete storedTelegram; } else { radioTelegram->setBroadcastIndex(bcIndex); bool dummy; radioTelegram->mustBeSentAsBroadcast(0xFFFFFFFF, dummy); radioTelegram->mustBeSentAsBroadcast(0xFFFFFFFF, dummy); radioTelegram->mustBeSentAsBroadcast(0xFFFFFFFF, dummy); m_BroadcastList.push_back(storedTelegram); INFO_LOG(m_logger) << "Restore broadcast-radiotelegram: id=" << storedTelegram->m_Id << " telId=" << storedTelegram->m_RadioTelegram->getTelID() << " BC=" << storedTelegram->m_RadioTelegram->getBroadcastIndex() << " counter=" << counter; } } } q.exec(QStringLiteral("UPDATE %1 SET counter=0;").arg(toCentralTableName())); if (!q.exec(QStringLiteral("SELECT id, data FROM %1 ORDER BY id;").arg(toCentralTableName()))) { ERROR_LOG(m_logger) << "Error reading from ToCentral table"; std::cout << "Error reading from ToCentral table. Start PrtCnv with --cleandb" << std::endl; return false; } while (q.next()) { const qint64 id = q.value(0).toLongLong(); const QByteArray telData(q.value(1).toByteArray()); N_CoPrtCnv::CoPrtCnvRadioTelegram radioTelegram(telData); std::shared_ptr centralTelegram(N_CoPrtCnv::CoPrtCnvTelegramConverter::convertToCentral(&radioTelegram)); if (centralTelegram) { PrtCnvStoredCentralTelegram *storedTelegram = new PrtCnvStoredCentralTelegram(centralTelegram, nullptr); storedTelegram->m_Id = id; m_CentralList.push_back(storedTelegram); INFO_LOG(m_logger) << "Restore unsent or unacked centraltelegram: id=" << storedTelegram->m_Id << " telId=" << storedTelegram->m_CentralTelegram->telegramId() << " counter=" << storedTelegram->m_WriteCounter << " size=" << telData.size(); } else ERROR_LOG(m_logger) << "Internal error: conversion of telegram with telId=" << radioTelegram.getTelID() << " from radio to central failed"; } if (m_db.driverName() == QLatin1String("QSQLITE")) { q.exec(QStringLiteral("VACUUM;")); q.exec(QStringLiteral("PRAGMA journal_mode=WAL;")); q.exec(QStringLiteral("PRAGMA synchronous=NORMAL;")); } } m_DoStorePermanent = true; return true; } int32_t PrtCnvDbThread::create(NGL::Settings *settings) { INFO_LOG(m_logger) << "create"; PrtCnvThread::create(settings); m_NbrOfAttempts = m_Config->getInteger(QStringLiteral("Central"), QStringLiteral("NbrOfAttempts"), 5); if (!createDatabase()) { ERROR_LOG(m_logger) << "Trying to create clean database after error"; m_DoCleanDb = true; if (!createDatabase()) exit(-2); } connect(this, &PrtCnvDbThread::doStorePermanent, this, &PrtCnvDbThread::storePermanent, Qt::QueuedConnection); connect(this, &PrtCnvDbThread::doClean, this, &PrtCnvDbThread::clean, Qt::QueuedConnection); PrtCnvCentralThread *centralThread = qobject_cast(m_Parent->getOtherThread(PrtCnvApplication::e_Central)); connect(this, &PrtCnvDbThread::permanentStored, centralThread, &PrtCnvCentralThread::permanentStored, Qt::QueuedConnection); return 0; } int32_t PrtCnvDbThread::init() { INFO_LOG(m_logger) << "init"; PrtCnvThread::init(); m_CentralAckCounter = 0; m_OldCentralAckCounter = 0; m_OldCentralCounter = 0; m_DoClean = false; m_DoWaitUntilSqlWritten = false; return 0; } int32_t PrtCnvDbThread::start() { INFO_LOG(m_logger) << "start"; PrtCnvThread::start(); return 0; } int32_t PrtCnvDbThread::stop() { PrtCnvThread::stop(); m_db.close(); m_db = QSqlDatabase(); return 0; } void PrtCnvDbThread::storeCentralTelegram(const std::shared_ptr &a_centralTelegram, const N_CoPrtCnv::CoPrtCnvRadioTelegram &a_radioTelegram) { PrtCnvStoredCentralTelegram *storedTelegram = new PrtCnvStoredCentralTelegram(a_centralTelegram, std::make_shared(a_radioTelegram)); QMutexLocker l(&m_CentralListMutex); m_CentralList.push_back(storedTelegram); sendStorePermanentSignal(); } N_CoPrtCnv::CoPrtCnvCentralTelegram *PrtCnvDbThread::getNextCentralTelegram(PrtCnvStoredCentralTelegram *&a_handle) { QMutexLocker l(&m_CentralListMutex); std::vector::const_iterator iter = m_CentralList.begin(); std::vector::const_iterator end = m_CentralList.end(); for (; iter != end; ++iter) { PrtCnvStoredCentralTelegram *storedTelegram = *iter; if (!storedTelegram || !storedTelegram->m_CentralTelegram) continue; if (0 == storedTelegram->m_WriteCounter || storedTelegram->m_DoResend) { const auto &telegram = storedTelegram->m_CentralTelegram; storedTelegram->m_DoResend = false; a_handle = storedTelegram; DEBUG_LOG(m_logger) << "Next telegram to send to central: id=" << storedTelegram->m_Id << " a_telId=" << telegram->telegramId(); return new N_CoPrtCnv::CoPrtCnvCentralTelegram(*telegram); } } return nullptr; } void PrtCnvDbThread::centralTelegramSent(const int32_t a_counter, PrtCnvStoredCentralTelegram *a_handle) { QMutexLocker l(&m_CentralListMutex); a_handle->m_WriteCounter = a_counter; a_handle->m_WriteCounterChanged = true; sendStorePermanentSignal(); } void PrtCnvDbThread::ackCentralTelegrams(int32_t a_counter) { m_CentralAckCounter = a_counter; sendStorePermanentSignal(); } void PrtCnvDbThread::centralTelegramCouldNotBeSent(PrtCnvStoredCentralTelegram *a_handle) { if (a_handle) { QMutexLocker l(&m_CentralListMutex); if (++(a_handle->m_Attempts) >= 5) { WARNING_LOG(m_logger) << "Delete unsendable Central-Telegram with id=" << a_handle->m_Id << " telId=" << a_handle->m_CentralTelegram->telegramId() << " rc=" << a_handle->m_CentralTelegram->counter() << " wc=" << a_handle->m_WriteCounter; a_handle->m_DoDelete = true; } l.unlock(); waitUntilDbWritten(); } } void PrtCnvDbThread::storeRadioTelegram(const std::shared_ptr &a_telegram, const int32_t a_readCounter) { PrtCnvStoredRadioTelegram *storedTelegram = new PrtCnvStoredRadioTelegram(a_telegram); storedTelegram->m_ReadCounter = a_readCounter; { QMutexLocker l(&m_RadioListMutex); m_RadioList.push_back(storedTelegram); } if (a_telegram) DEBUG_LOG(m_logger) << "Store Radio-Telegram with telId=" << a_telegram->getTelID() << " rc=" << a_readCounter << " wc=" << int(storedTelegram->m_WriteCounter); if (m_DoStorePermanent) sendStorePermanentSignal(); } void PrtCnvDbThread::centralCounterReadyForAck(const int32_t a_centralCounter) { storeRadioTelegram(nullptr, a_centralCounter); DEBUG_LOG(m_logger) << "No Radio-Telegram to store, but rc=" << a_centralCounter << " must be acked"; } std::shared_ptr PrtCnvDbThread::getNextBroadcastTelegram() { QMutexLocker l(&m_BroadcastMapMutex); if (m_BroadcastList.empty()) return nullptr; PrtCnvStoredRadioTelegram *storedRadioTelegram = m_BroadcastList.front(); std::shared_ptr radioTelegram; if (storedRadioTelegram->m_Id != 0 && !storedRadioTelegram->m_WriteCounterChanged) { radioTelegram = storedRadioTelegram->m_RadioTelegram; m_BroadcastList.pop_front(); delete storedRadioTelegram; } return radioTelegram; } std::shared_ptr PrtCnvDbThread::getNextRadioTelegram(PrtCnvStoredRadioTelegram *&a_handle) { std::shared_ptr telegram; uint8_t priority = 255; QMutexLocker l(&m_RadioListMutex); for (PrtCnvStoredRadioTelegram *storedTelegram: m_RadioList) { const auto &radioTelegram = storedTelegram->m_RadioTelegram; if (radioTelegram) { if (!storedTelegram->m_IsSent) { uint8_t newPriority = radioTelegram->getPriority(); if (newPriority < priority) { priority = newPriority; telegram = radioTelegram; a_handle = storedTelegram; } } } } return telegram; } void PrtCnvDbThread::radioTelegramSent(const uint32_t a_sendCounter, PrtCnvStoredRadioTelegram *a_handle, const uint32_t a_broadcastIndex) { QMutexLocker l(&m_RadioListMutex); if (a_broadcastIndex) { DEBUG_LOG(m_logger) << "request update radio telegramm in db - sc: " << a_sendCounter << " telId: " << a_handle->m_RadioTelegram->getTelID() << " BC: " << a_broadcastIndex; } a_handle->m_WriteCounter = a_sendCounter; a_handle->m_WriteCounterChanged = true; a_handle->m_IsSent = true; if (a_broadcastIndex != 0) a_handle->m_BroadcastIndex = a_broadcastIndex; if (a_handle->m_BroadcastIndex != 0) { bool dummy; a_handle->m_RadioTelegram->mustBeSentAsBroadcast(0xFFFFFFFF, dummy); DEBUG_LOG(m_logger) << "Move Radio-Telegram with telId=" << a_handle->m_RadioTelegram->getTelID() << " and BC=" << a_handle->m_RadioTelegram->getBroadcastIndex() << " to BC-List of DB"; QMutexLocker l2(&m_BroadcastMapMutex); m_BroadcastList.push_back(a_handle); } else { if (a_handle->m_RadioTelegram->isNormalTelegram() && (a_handle->m_RadioTelegram->getTelID() == GEF_UMGEBUNGS_MELDUNG2)) { if (m_EnvironmentTelegram) m_EnvironmentTelegram->m_DoDelete = true; m_EnvironmentTelegram = a_handle; } else a_handle->m_DoDelete = true; } sendStorePermanentSignal(); } void PrtCnvDbThread::deleteTelegramsFromDbById(const QString &table) { if (m_telegramsToDelete.empty()) return; QStringList sl; sl.reserve(m_telegramsToDelete.size()); for (const int64_t telId : m_telegramsToDelete) sl += QString::number(telId); NSqlQuery q(m_db); q.exec(QStringLiteral("DELETE FROM %1 WHERE id IN (%2);").arg(table, sl.join(QLatin1Char(',')))); m_telegramsToDelete.clear(); } void PrtCnvDbThread::updateTelegramsBroadcast(const QString &table) { if (m_telegramsToUpdateBC.empty()) return; NSqlQuery q(m_db); q.prepare(QStringLiteral("UPDATE %1 SET broadcast_index = :broadcast_index WHERE id = :id").arg(table)); for (const auto &p : m_telegramsToUpdateBC) { const qint64 telId = p.first; const qint32 bcIndex = p.second; q.bindValue(QStringLiteral(":broadcast_index"), bcIndex); q.bindValue(QStringLiteral(":id"), telId); q.exec(); DEBUG_LOG(m_logger) << "Update broadcastindex for id: " << telId << " to BC: " << bcIndex; } m_telegramsToUpdateBC.clear(); } void PrtCnvDbThread::updateTelegramsWriteCounter(const QString &table) { if (m_telegramsToUpdateCounter.empty()) return; NSqlQuery q(m_db); q.prepare(QStringLiteral("UPDATE %1 SET counter = :counter WHERE id = :id").arg(table)); for (const auto &p : m_telegramsToUpdateCounter) { const qint64 telId = p.first; const qint32 writeCounter = p.second; q.bindValue(QStringLiteral(":counter"), writeCounter); q.bindValue(QStringLiteral(":id"), telId); q.exec(); DEBUG_LOG(m_logger) << "Update WriteCounter for id: " << telId << " to wc: " << writeCounter; } m_telegramsToUpdateCounter.clear(); } void PrtCnvDbThread::storeCentralTelegrams() { QMutexLocker l(&m_CentralListMutex); if (m_CentralList.empty()) return; int iEmptyCount = 0; for (auto &storedCentralTelegram : m_CentralList) { if (!storedCentralTelegram) { ++iEmptyCount; continue; } const auto centralTelegram = storedCentralTelegram->m_CentralTelegram; if (0 == storedCentralTelegram->m_Id) { m_telegramsToInsert.emplace_back(std::make_pair(storedCentralTelegram, 0)); DEBUG_LOG(m_logger) << "Permanent Central-Telegram with id=" << storedCentralTelegram->m_Id << " data=" << centralTelegram->data() << " wc=" << storedCentralTelegram->m_WriteCounter; } else { const int centralAckCnt = m_CentralAckCounter; if (storedCentralTelegram->m_WriteCounterChanged) { m_telegramsToUpdateCounter.emplace_back(std::make_pair(storedCentralTelegram->m_Id, storedCentralTelegram->m_WriteCounter)); storedCentralTelegram->m_WriteCounterChanged = false; DEBUG_LOG(m_logger) << "Update Central-Telegram with id=" << storedCentralTelegram->m_Id << " data=" << centralTelegram->data() << " wc=" << storedCentralTelegram->m_WriteCounter; } else if ((storedCentralTelegram->m_WriteCounter > 0 && storedCentralTelegram->m_WriteCounter <= centralAckCnt) || storedCentralTelegram->m_DoDelete) { DEBUG_LOG(m_logger) << "Delete Central-Telegram with id=" << storedCentralTelegram->m_Id << " data=" << storedCentralTelegram->m_CentralTelegram->data() << " wc=" << storedCentralTelegram->m_WriteCounter; if (storedCentralTelegram->m_WriteCounter > centralAckCnt) m_telegramsToDelete.push_back(storedCentralTelegram->m_Id); delete storedCentralTelegram; storedCentralTelegram = nullptr; } } } if (iEmptyCount > 50) { const auto isNull = [](const PrtCnvStoredCentralTelegram *t) { return t == nullptr; }; m_CentralList.erase(std::remove_if(m_CentralList.begin(), m_CentralList.end(), isNull), m_CentralList.end()); } l.unlock(); if (!m_telegramsToInsert.empty()) { l.relock(); NSqlQuery q(m_db); q.prepare(QStringLiteral("INSERT INTO %1 (tel_id, counter, data)" "VALUES(:tel_id, :counter, :data);").arg(toCentralTableName())); for (const auto &p : m_telegramsToInsert) { PrtCnvStoredCentralTelegram *storedCentralTelegram = p.first; storedCentralTelegram->m_WriteCounterChanged = false; q.bindValue(QStringLiteral(":tel_id"), storedCentralTelegram->m_CentralTelegram->telegramId()); q.bindValue(QStringLiteral(":counter"), storedCentralTelegram->m_WriteCounter); q.bindValue(QStringLiteral(":data"), storedCentralTelegram->m_RadioTelegram->data()); q.exec(); storedCentralTelegram->m_Id = q.lastInsertId().toInt(); } l.unlock(); m_telegramsToInsert.clear(); } updateTelegramsWriteCounter(toCentralTableName()); const unsigned int centralAckCnt = static_cast(m_CentralAckCounter); if (centralAckCnt > m_OldCentralAckCounter) { m_OldCentralAckCounter = centralAckCnt; DEBUG_LOG(m_logger) << "Central delete all counter <= " << m_OldCentralAckCounter; NSqlQuery q(m_db); q.exec(QStringLiteral("DELETE FROM %1 WHERE counter > 0 AND counter <= %2;").arg(toCentralTableName()).arg(m_OldCentralAckCounter)); } deleteTelegramsFromDbById(toCentralTableName()); } int32_t PrtCnvDbThread::storeRadioTelegrams() { QMutexLocker l(&m_RadioListMutex); if (m_RadioList.empty()) return 0; int32_t centralCounter = 0; NSqlQuery q(m_db); q.prepare(QStringLiteral("INSERT INTO %1 (tel_id, counter, broadcast_index, reset_counter, data) " "VALUES (:tel_id, :counter, :broadcast_index, :reset_counter, :data);").arg(toRadioTableName())); std::list::iterator radioIter = m_RadioList.begin(); while (radioIter != m_RadioList.end()) { bool doDeleteThisItem = false; PrtCnvStoredRadioTelegram *storedRadioTelegram = *radioIter; const auto &radioTelegram = storedRadioTelegram->m_RadioTelegram; if (radioTelegram) { DEBUG_LOG(m_logger) << "Handling Radio-Telegram with id=" << storedRadioTelegram->m_Id << " TelID: " << radioTelegram->getTelID() << " rc: " << int(radioTelegram->zaehler()) << " BC: " << storedRadioTelegram->m_BroadcastIndex << " wc: " << int(storedRadioTelegram->m_WriteCounter); if (0 == storedRadioTelegram->m_Id) { if (storedRadioTelegram->m_ReadCounter > centralCounter) centralCounter = storedRadioTelegram->m_ReadCounter; storedRadioTelegram->m_WriteCounterChanged = false; q.bindValue(QStringLiteral(":tel_id"), radioTelegram->getTelID()); q.bindValue(QStringLiteral(":counter"), storedRadioTelegram->m_WriteCounter); q.bindValue(QStringLiteral(":broadcast_index"), storedRadioTelegram->m_BroadcastIndex); q.bindValue(QStringLiteral(":reset_counter"), int(m_ResetCounter)); q.bindValue(QStringLiteral(":data"), radioTelegram->data()); q.exec(); storedRadioTelegram->m_Id = q.lastInsertId().toInt(); DEBUG_LOG(m_logger) << "Stored Telegramm in DB, id: " << storedRadioTelegram->m_Id; if (storedRadioTelegram->m_RadioTelegram->isMovedToBroadcastList()) { dumpTelegram(m_logger, radioTelegram->getBroadcastCounter(), storedRadioTelegram->m_RadioTelegram.get()); DEBUG_LOG(m_logger) << "Removed Telegramm from radioList (place 1), id: " << storedRadioTelegram->m_Id; doDeleteThisItem = true; } } else { if (storedRadioTelegram->m_DoDelete) { DEBUG_LOG(m_logger) << "Delete Telegramm from DB, id: " << storedRadioTelegram->m_Id; m_telegramsToDelete.push_back(storedRadioTelegram->m_Id); delete storedRadioTelegram; doDeleteThisItem = true; } else if (storedRadioTelegram->m_WriteCounterChanged) { m_telegramsToUpdateCounter.emplace_back(std::make_pair(storedRadioTelegram->m_Id, storedRadioTelegram->m_WriteCounter)); if (storedRadioTelegram->m_BroadcastIndex) m_telegramsToUpdateBC.emplace_back(std::make_pair(storedRadioTelegram->m_Id, storedRadioTelegram->m_BroadcastIndex)); storedRadioTelegram->m_WriteCounterChanged = false; DEBUG_LOG(m_logger) << "Update Write counter in DB, id: " << storedRadioTelegram->m_Id << " New wc: " << int(storedRadioTelegram->m_WriteCounter) << " new bc: " << storedRadioTelegram->m_BroadcastIndex; if (storedRadioTelegram->m_RadioTelegram->isMovedToBroadcastList()) { dumpTelegram(m_logger, storedRadioTelegram->m_BroadcastIndex, storedRadioTelegram->m_RadioTelegram.get()); DEBUG_LOG(m_logger) << "Removed Telegramm from radioList (place 2), id: " << storedRadioTelegram->m_Id; doDeleteThisItem = true; } } } } else { if (storedRadioTelegram->m_ReadCounter > centralCounter) centralCounter = storedRadioTelegram->m_ReadCounter; delete storedRadioTelegram; doDeleteThisItem = true; } if (doDeleteThisItem) radioIter = m_RadioList.erase(radioIter); else ++radioIter; } l.unlock(); static const QString &table = toRadioTableName(); updateTelegramsWriteCounter(table); updateTelegramsBroadcast(table); deleteTelegramsFromDbById(table); return centralCounter; } void PrtCnvDbThread::handleSqlCommandList() { std::vector sqlCommandList; { QMutexLocker l(&m_SqlCmdListMutex); std::swap(m_SqlCommandList, sqlCommandList); } if (sqlCommandList.empty()) return; NSqlQuery q(m_db); for (const QString &sql : sqlCommandList) { DEBUG_LOG(m_logger) << "execute sql=" << sql; q.exec(sql); } } void PrtCnvDbThread::storePermanent() { m_StorePermanentPending = 0; TRACE_LOG(m_logger) << "storePermanent m_DoClean=" << m_DoClean << " m_DoWaitUntilSqlWritten=" << m_DoWaitUntilSqlWritten; if (m_DoClean) { m_DoWaitUntilSqlWritten = false; return; } DBTransaction trans(m_db); storeCentralTelegrams(); const int32_t centralCounter = storeRadioTelegrams(); handleSqlCommandList(); trans.commit(); if (0 != centralCounter && centralCounter != m_OldCentralCounter) { m_OldCentralCounter = centralCounter; emit permanentStored(centralCounter); } m_DoWaitUntilSqlWritten = false; } void PrtCnvDbThread::addSqlCommand(const QString &sql) { QMutexLocker l(&m_SqlCmdListMutex); m_SqlCommandList.push_back(sql); sendStorePermanentSignal(); } void PrtCnvDbThread::cleanAndInit() { m_DoClean = true; emit doClean(); while (m_DoClean) QThread::sleep(1); m_DoProceedCleanup = false; } void PrtCnvDbThread::clean() { NSqlQuery q(m_db); INFO_LOG(m_logger) << "Delete CentralList"; { QMutexLocker l(&m_CentralListMutex); for (PrtCnvStoredCentralTelegram *t : m_CentralList) delete t; m_CentralList.clear(); q.exec(QStringLiteral("DELETE FROM %1;").arg(toCentralTableName())); } m_OldCentralCounter = 0; INFO_LOG(m_logger) << "Delete RadioList"; m_EnvironmentTelegram = nullptr; { QMutexLocker l(&m_RadioListMutex); for (PrtCnvStoredRadioTelegram *t : m_RadioList) delete t; m_RadioList.clear(); q.exec(QStringLiteral("DELETE FROM %1;").arg(toRadioTableName())); } INFO_LOG(m_logger) << "Delete BroadcastList"; { QMutexLocker l(&m_BroadcastMapMutex); for (PrtCnvStoredRadioTelegram *t : m_BroadcastList) delete t; m_BroadcastList.clear(); } { QMutexLocker l(&m_SqlCmdListMutex); m_MemberListByISSI.clear(); { QMutexLocker l2(&m_MemberListByIdMutex); for (const auto &p : m_MemberListById) delete p.second; m_MemberListById.clear(); } INFO_LOG(m_logger) << "Delete SqlList"; m_SqlCommandList.clear(); q.exec(QStringLiteral("DELETE FROM %1;").arg(memberTableName())); } licenseMember(addMember(0, m_Config->getInteger(QStringLiteral("Radio"), QStringLiteral("BroadcastISSI"), 5000))); m_DoClean = false; } bool PrtCnvDbThread::resendCentralTelegrams(const int32_t a_lastSendCounter) { const int centralAckCnt = m_CentralAckCounter; if (a_lastSendCounter > centralAckCnt) { bool warnOnce = true; QMutexLocker l(&m_CentralListMutex); int iCount = 0; std::vector::const_iterator iter = m_CentralList.begin(); std::vector::const_iterator end = m_CentralList.end(); for (; iter != end && iCount < 50; ++iter) { PrtCnvStoredCentralTelegram *storedTelegram = *iter; if (!storedTelegram) continue; ++iCount; if (storedTelegram->m_WriteCounter > centralAckCnt) { if (warnOnce) { WARNING_LOG(m_logger) << "Resend central-telegrams from wc=" << a_lastSendCounter << " centralAckCounter=" << centralAckCnt; warnOnce = false; } INFO_LOG(m_logger) << "Resend central-telegram with telId=" << storedTelegram->m_CentralTelegram->telegramId() << " wc=" << storedTelegram->m_WriteCounter; storedTelegram->m_DoResend = true; } } return true; } return false; } void PrtCnvDbThread::prepareCentralTelegramsForReconnection() { INFO_LOG(m_logger) << "m_CentralAckCounter=" << m_CentralAckCounter; waitUntilDbWritten(); INFO_LOG(m_logger) << "start"; m_CentralAckCounter = 0; m_OldCentralAckCounter = 0; m_OldCentralCounter = 0; QMutexLocker l(&m_CentralListMutex); for (PrtCnvStoredCentralTelegram *storedTelegram : m_CentralList) { if (!storedTelegram) continue; if (storedTelegram->m_WriteCounter != 0) { INFO_LOG(m_logger) << "***Prepare central-telegram with id=" << storedTelegram->m_Id << " telId=" << storedTelegram->m_CentralTelegram->telegramId() << " wc=" << storedTelegram->m_WriteCounter << " wcChg=" << storedTelegram->m_WriteCounterChanged << " for resend after reconnection"; storedTelegram->m_WriteCounter = 0; storedTelegram->m_WriteCounterChanged = false; storedTelegram->m_DoResend = false; } } INFO_LOG(m_logger) << "Ready for reconnection"; } void PrtCnvDbThread::waitUntilDbWritten() { m_DoWaitUntilSqlWritten = true; emit doStorePermanent(); while (m_DoWaitUntilSqlWritten) QThread::usleep(10000); m_DoWaitUntilSqlWritten = true; emit doStorePermanent(); while (m_DoWaitUntilSqlWritten) QThread::usleep(10000); } PrtCnv::PrtCnvMember *PrtCnvDbThread::addMember(const int a_identity, const int a_ISSI) { PrtCnvMember *newMember = new PrtCnvMember(a_identity, a_ISSI); { QMutexLocker lSql(&m_SqlCmdListMutex); QMutexLocker lId(&m_MemberListByIdMutex); m_MemberListById.insert(std::make_pair(a_identity, newMember)); m_MemberListByISSI.insert(std::make_pair(a_ISSI, newMember)); } DEBUG_LOG(m_logger) << "Adding member with eqId " << a_identity << " and issi " << a_ISSI << ", store in db: " << m_DoStorePermanent; if (m_DoStorePermanent) addSqlCommand(QStringLiteral("INSERT INTO %1 (id, issi) VALUES(%2, %3)") .arg(memberTableName()).arg(newMember->m_Id).arg(newMember->m_ISSI)); return newMember; } void PrtCnvDbThread::removeMember(PrtCnv::PrtCnvMember *a_member) { { QMutexLocker lSql(&m_SqlCmdListMutex); QMutexLocker lId(&m_MemberListByIdMutex); auto iterId = m_MemberListById.find(a_member->m_Id); if (iterId != m_MemberListById.end()) m_MemberListById.erase(iterId); else ERROR_LOG(m_logger) << "Internal error: did not find the member by ID"; auto iterIssi = m_MemberListByISSI.find(a_member->m_ISSI); if (iterIssi != m_MemberListByISSI.end()) m_MemberListByISSI.erase(iterIssi); else ERROR_LOG(m_logger) << "Internal error: did not find the member by ISSI"; } DEBUG_LOG(m_logger) << "Removing member with eqId " << a_member->m_Id << " and issi " << a_member->m_ISSI << ", store in db: " << m_DoStorePermanent; if (m_DoStorePermanent) addSqlCommand(QStringLiteral("DELETE FROM %1 WHERE id = %2 OR issi = %3") .arg(memberTableName()).arg(a_member->m_Id).arg(a_member->m_ISSI)); delete a_member; } bool PrtCnvDbThread::licenseMember(PrtCnvMember *a_member, const bool a_doLicense) { if (!a_member) return false; a_member->m_Licensed = a_doLicense; if (m_DoStorePermanent) addSqlCommand(QStringLiteral("UPDATE %1 SET licensed = %2 WHERE id = %3") .arg(memberTableName()).arg(a_doLicense ? 1 : 0).arg(a_member->m_Id)); return true; } void PrtCnvDbThread::updateExerciseIdForMember(PrtCnvMember *a_member, int newExerciseId) { if (!a_member) return; a_member->m_loggedInExerciseId = newExerciseId; DEBUG_LOG(m_logger) << "Updating exerciseId member with eqId " << a_member->m_Id << " and issi " << a_member->m_ISSI << " to " << newExerciseId << ", store in db: " << m_DoStorePermanent; if (m_DoStorePermanent) addSqlCommand(QStringLiteral("UPDATE %1 SET exercise_id = %2 WHERE id = %3") .arg(memberTableName()).arg(newExerciseId).arg(a_member->m_Id)); } PrtCnv::PrtCnvMember *PrtCnvDbThread::getMemberById(const uint16_t a_id) { QMutexLocker l(&m_MemberListByIdMutex); auto iter = m_MemberListById.find(a_id); if (iter != m_MemberListById.end()) return iter->second; return nullptr; } PrtCnv::PrtCnvMember *PrtCnvDbThread::getMemberByISSI(const uint32_t a_ISSI) const { auto iter = m_MemberListByISSI.find(a_ISSI); if (iter != m_MemberListByISSI.end()) return iter->second; return nullptr; } uint8_t PrtCnvDbThread::getResetCounter() const { return m_ResetCounter; } void PrtCnvDbThread::setResetCounter(uint8_t newVal) { m_ResetCounter = newVal; if (m_DoStorePermanent) { NSqlQuery q(m_db); q.exec(QStringLiteral("UPDATE Status SET reset_counter = %1 WHERE id = 1") .arg(int(newVal))); } INFO_LOG(m_logger) << "Update resetCounter=" << (int)m_ResetCounter << " doStorePermanent=" << m_DoStorePermanent; } uint8_t PrtCnvDbThread::getBattleFieldOperatingState() const { QMutexLocker l(&m_BattleFieldOpStateMutex); return m_BattleFieldOperatingState; } void PrtCnvDbThread::setPause(const bool a_doSet) { QMutexLocker l(&m_BattleFieldOpStateMutex); if (a_doSet) m_BattleFieldOperatingState = m_BattleFieldOperatingState | 0x80; else m_BattleFieldOperatingState = m_BattleFieldOperatingState & 0x7F; if (m_DoStorePermanent) addSqlCommand(QStringLiteral("UPDATE %1 SET ctas_operating_state = %2 WHERE id = 1") .arg(statusTableName()).arg(int(m_BattleFieldOperatingState))); INFO_LOG(m_logger) << "Update battleFieldOperatingState=" << (int)m_BattleFieldOperatingState << " doStorePermanent=" << m_DoStorePermanent; } void PrtCnvDbThread::setPositionSendTime(unsigned int sendTime) { QMutexLocker l(&m_BattleFieldOpStateMutex); if (sendTime > 31) sendTime = 31; if (sendTime < 1) sendTime = 1; m_BattleFieldOperatingState &= 0x83; m_BattleFieldOperatingState |= (sendTime << 2); } void PrtCnvDbThread::setAirLoad(PrtCnvRadioThread::E_TrafficLoad airLoad) { QMutexLocker l(&m_BattleFieldOpStateMutex); m_BattleFieldOperatingState &= 0xFC; m_BattleFieldOperatingState |= (static_cast(airLoad) & 0x03); } bool PrtCnvDbThread::isEnvironmentResetCounterOld() const { QMutexLocker l(&m_RadioListMutex); bool isOld = false; if (m_EnvironmentTelegram) isOld = getResetCounter() != m_EnvironmentTelegram->m_RadioTelegram->getPayloadDataUInt8(7); return isOld; } void PrtCnvDbThread::setResetCounterOnEnvironment() { QMutexLocker l(&m_RadioListMutex); if (m_EnvironmentTelegram) m_EnvironmentTelegram->m_RadioTelegram->setPayloadDataWithIndex(7, getResetCounter(), 1); } std::shared_ptr PrtCnvDbThread::getEnvironmentTelegram() { QMutexLocker l(&m_RadioListMutex); if (m_EnvironmentTelegram) return std::make_shared(m_EnvironmentTelegram->m_RadioTelegram->data()); return {}; } std::map PrtCnvDbThread::getClaimStatistics() const { std::map ret; QMutexLocker l(&m_MemberListByIdMutex); for (const auto &p : m_MemberListById) { if (p.first != 0) ret.insert(std::make_pair(p.first, p.second->m_ClaimCount)); } return ret; } void PrtCnvDbThread::incClaimCount(int a_identity) { QMutexLocker l(&m_MemberListByIdMutex); auto iter = m_MemberListById.find(a_identity); if (iter != m_MemberListById.end()) { iter->second->m_ClaimCount++; if (m_DoStorePermanent) addSqlCommand(QStringLiteral("UPDATE %1 SET claims = claims + 1 WHERE id = %2") .arg(memberTableName()).arg(iter->second->m_Id)); } else ERROR_LOG(m_logger) << "Internal error: did not find TNE with id=" << a_identity; } void PrtCnvDbThread::setMachineState(const int a_newMachineState) { NSqlQuery q(m_db); q.exec(QStringLiteral("UPDATE %1 SET machine_state = %2 WHERE id = 1") .arg(statusTableName()).arg(a_newMachineState)); } bool PrtCnvDbThread::mustProceedCleanup() const { return m_DoProceedCleanup; } void PrtCnvDbThread::sendStorePermanentSignal() { if (m_StorePermanentPending.testAndSetOrdered(0, 1)) emit doStorePermanent(); } void PrtCnvDbThread::closeDb() { if (m_db.isOpen()) { m_db.close(); m_db = QSqlDatabase(); QSqlDatabase::removeDatabase(QStringLiteral("PrtCnv")); } } }