/** * Block/unblock topics * $Id$ * $HeadURL$ * $Author$ * Created by Christian Ehrlicher, SLSE, 2018-01-15 */ #include "handler_locked_topics.h" #include "main_handler.h" #include "handler_operator.h" #include #include #include #include namespace OperatorManagement { HandlerLockedTopics::HandlerLockedTopics(MainHandler &mainHandler, QObject *parent) : QObject(parent) , m_mainHandler(mainHandler) , m_logger(getLogger("HandlerLockedTopics")) {} bool HandlerLockedTopics::init() { m_db = m_mainHandler.db(); auto ddsConnector = m_mainHandler.dds()->ddsConnector(); if (!m_db || !ddsConnector) return false; ddsConnector->initSubscriber(); { m_ftsTryLockEntity.reset(ddsConnector->createFTS()); const auto cb = [this](const rti::core::SampleIdentity & cookie, const Core::TryLockEntityRequest & request) { this->onTryLockEntityRequest(cookie, request); }; m_ftsTryLockEntity->setSubscriberCallback(cb); } { m_ftsTryUnlockEntity.reset(ddsConnector->createFTS()); const auto cb = [this](const rti::core::SampleIdentity & cookie, const Core::TryUnlockEntityRequest & request) { this->onTryUnlockEntityRequest(cookie, request); }; m_ftsTryUnlockEntity->setSubscriberCallback(cb); } auto cbDatabase = [this](const Database::OpmLockedTopics & data) { DEBUG_LOG(m_logger) << "Locked topic from db: " << data.exerciseId.toInt() << ", " << data.workstationId << ", " << data.operatorId << data.entityTypeName; m_lockedTopics.insert(Core::key(data.entityId, data.exerciseId.isNull() ? 0 : data.exerciseId.toInt(), data.entityTypeName), data); }; m_db->selectByLocked(cbDatabase, true); m_dtsLockedEntity.reset(ddsConnector->createDTS()); return true; } static bool needsUnlock(int operatorId, int workstationId, const Database::OpmLockedTopics &data) { // same operator -> check if other workstation if (operatorId == data.operatorId) { return true; // NT, 18.09.20 remove. From former (crashed) session, independent from workstation // if (workstationId == 0) // logout // return true; // if (workstationId != data.workstationId) // logged in on another workstation // return true; // return false; } // other operator on a workstation previously locked by another operator -> unlock if (workstationId != 0 && workstationId == data.workstationId) return true; return false; } void HandlerLockedTopics::operatorLoggedInOut(int operatorId, int workstationId) { // check if there are stale locks (e.g. by crashing client) - then remove all of them - if there are any DEBUG_LOG(m_logger) << "operatorLoggedInOut: " << workstationId << ", " << operatorId; // Ueberall wurden - ohne erase wohlgemerkt - mutable Iteratoren verwendet. Nur hier - wo erase verwendet wurde - nicht! :') QMutableHashIterator it(m_lockedTopics); while (it.hasNext()) { const auto lockedTopic = it.next().value(); DEBUG_LOG(m_logger) << "check if unlock: id=" << lockedTopic.id << ", exId=" << lockedTopic.exerciseId.toInt() << ", wsId=" << lockedTopic.workstationId << ", opId=" << lockedTopic.operatorId; if (!needsUnlock(operatorId, workstationId, lockedTopic)) { continue; } DEBUG_LOG(m_logger) << "Unlocking topic: id=" << lockedTopic.id << ", exId=" << lockedTopic.exerciseId.toInt() << ", wsId=" << lockedTopic.workstationId << ", opId=" << lockedTopic.operatorId; unlockInDb(lockedTopic); removeLockedEntity(lockedTopic.id); it.remove(); } } void HandlerLockedTopics::onTryLockEntityRequest(const rti::core::SampleIdentity &cookie, const Core::TryLockEntityRequest &request) { if (m_logger.isDebugEnabled()) dumpLockedTopics(__LINE__); auto ret = tryLock(request); if (!ret.errorString().empty()) { WARNING_LOG(m_logger) << Core::FunctionReplyObject(ret.FunctionReply::id(), ret.errorString(), ret.errorParams()).translated() << " - " << request; } else if (ret.alreadyLocked()) { WARNING_LOG(m_logger) << "onTryLockEntityRequest failed because the topic was already locked by ws " << ret.alreadyLocked().get().workstationName() << " and op " << ret.alreadyLocked().get().operatorName() << " - " << request; } m_ftsTryLockEntity->sendReply(cookie, ret); if (m_logger.isDebugEnabled()) dumpLockedTopics(__LINE__); } Core::TryLockEntityReply lockErrorReply(const long topicId, const std::string &msg) { return Core::TryLockEntityReply(-1, msg, { std::to_string(topicId) }, {}, {}); } Core::TryLockEntityReply lockAlreadyLockedReply(const std::string &ws, const QString &op) { return Core::TryLockEntityReply(-1, {}, {},{}, Core::LockEntityError(ws, op.toStdString())); } Core::TryLockEntityReply lockSuccessReply(const Database::OpmLockedTopics &dbStruct) { return Core::TryLockEntityReply(dbStruct.id, {}, {}, {}, {}); } Core::TryLockEntityReply HandlerLockedTopics::tryLock(const Core::TryLockEntityRequest &request) { DEBUG_LOG(m_logger) << "tryLock : " << request; const auto topicId = request.entityId(); const Core::key k(request.entityId(), request.exerciseId(), QString::fromStdString(request.entityTypeName())); Database::OpmLockedTopics dbStruct(toDb(request)); auto it = m_lockedTopics.constFind(k); if (it == m_lockedTopics.constEnd()) { // noch nicht vorhanden -> add dbStruct.timeStampLock = QDateTime::currentMSecsSinceEpoch(); if (!m_db->insert(dbStruct)) { return lockErrorReply(topicId, tr_lts("dds", "Failed to lock element with id %1 in database")); } m_lockedTopics.insert(k, dbStruct); Core::LockedEntity lckEntity(request.operatorId(), request.workstationId(), request.exerciseId(), request.entityId(), request.entityTypeName(), dbStruct.id); publishLockedEntity(lckEntity); return lockSuccessReply(dbStruct); } // NOTE: don't check if there is already a lock for that exact same key (same op, ws, topic, ...): Reason: Szenario: in 2 sitmaps "Exercise Purpose" aufschalten... 2. Dlg muss gelockt werden !!! // already locked by another user const Database::OpmLockedTopics &data = *it; const auto opName = m_mainHandler.getHandlerOperator()->operatorNameById(data.operatorId); const auto wsName = m_mainHandler.dds()->dtcAsm()->asset(data.workstationId).name(); return lockAlreadyLockedReply(wsName, opName); } //Core::TryLockEntityReply HandlerLockedTopics::tryLockModify(const Core::TryLockEntityRequest &request) //{ // DEBUG_LOG(m_logger) << "tryLockModify: " << request; // const auto lockEntityId = request.id(); // // requestedKey == new key // const Core::key requestedKey(request.entityId(), request.exerciseId(), QString::fromStdString(request.entityTypeName())); // // is requested topic already locked? // { // auto it = m_lockedTopics.constFind(requestedKey); // if (it != m_lockedTopics.constEnd()) // { // // vorhanden -> locked by another user/dlg // // or be me??? (locked by this dialog instance / topicId ? // const Database::OpmLockedTopics &data = it.value(); // if (lockEntityId != data.id) // { // // vorhanden and locked by another user/dlg: // // "release" my *former* lock: // removeReleaseOutdatedLock(lockEntityId); // const auto opName = m_mainHandler->getHandlerOperator()->operatorNameById(data.operatorId); // const auto wsName = m_mainHandler->dds()->dtcAsm()->asset(data.workstationId).name(); // return lockAlreadyLockedReply(wsName, opName); // } // } // } // // not already lock -> 'update' the entry: // // // // 1) DB-Update // // // Database::OpmLockedTopics dbStruct(toDb(request)); // dbStruct.id = lockEntityId; // to enable dbUpdate :-) // dbStruct.timeStampLock = QDateTime::currentMSecsSinceEpoch(); // if (!m_db->update(dbStruct)) // { // return lockErrorReply(lockEntityId, tr_lts("dds", "Failed to update lock entity (%1) in database")); // } // // // // 2) DDS-Update // // // Core::LockedEntity lckEntity(request.operatorId(), request.workstationId(), // request.exerciseId(), request.entityId(), // request.entityTypeName(), dbStruct.id); // publishLockedEntity(lckEntity); // // // // 3) QHash-Update // // // // update both: key and value!!! by removing the key-value-pair with topicId ... // { // auto lockedTopicIt = std::find_if(m_lockedTopics.constBegin(), m_lockedTopics.constEnd(), // [&lockEntityId](const Database::OpmLockedTopics &lockedTopic) // { // return (lockEntityId == lockedTopic.id); // }); // if (lockedTopicIt == m_lockedTopics.constEnd()) // { // return lockErrorReply(lockEntityId, tr_lts("dds", "Failed to update lock entity (%1) on the heap")); // } // const Database::OpmLockedTopics &lockedTopic = *lockedTopicIt; // DEBUG_LOG(m_logger) << LOGFUNCTION << ": locked topic id " << lockedTopic.id; // // ... and then reinserting the updated lock entity (as the id has now changed) - while the key (op, ws, exc, topic id) stays the same! // m_lockedTopics[requestedKey] = dbStruct; // } // return lockSuccessReply(dbStruct); // success //} //bool HandlerLockedTopics::removeReleaseOutdatedLock(const int lockEntityId) //{ // DEBUG_LOG(m_logger) << "removeReleaseOutdatedLock: lockEntityId = " << lockEntityId; // auto lockedTopicIt = std::find_if(m_lockedTopics.constBegin(), m_lockedTopics.constEnd(), // [&lockEntityId](const Database::OpmLockedTopics &lockedTopic) // { // return (lockEntityId == lockedTopic.id); // }); // if (lockedTopicIt == m_lockedTopics.constEnd()) // { // ERROR_LOG(m_logger) << LOGFUNCTION << ": Failed to release outdated lock as lock entity id ("<sendReply(cookie, ret); if (m_logger.isDebugEnabled()) dumpLockedTopics(__LINE__); } Core::FunctionReply HandlerLockedTopics::tryUnlock(const Core::TryUnlockEntityRequest &request) { DEBUG_LOG(m_logger) << "tryUnlock: " << request; const auto lockEntityId = request.id(); auto lockedTopicIt = std::find_if(m_lockedTopics.constBegin(), m_lockedTopics.constEnd(), [lockEntityId](const Database::OpmLockedTopics &lockedTopic) { return (lockEntityId == lockedTopic.id); }); if (lockedTopicIt == m_lockedTopics.constEnd()) { // nicht vorhanden => unlock nicht möglich return Core::FunctionReply(-1, tr_lts("dds", "Failed to unlock unknown lock with id %1."), {}, { std::to_string(lockEntityId) }); } // found => unlock const Database::OpmLockedTopics &lockedTopic = *lockedTopicIt; unlockInDb(lockedTopic); removeLockedEntity(lockedTopic.id); m_lockedTopics.erase(lockedTopicIt); return Core::FunctionReply(); // success! } void HandlerLockedTopics::unlockInDb(const Database::OpmLockedTopics &data) { Database::OpmLockedTopics dbStruct(data); dbStruct.locked = false; dbStruct.timeStampUnlock = QDateTime::currentMSecsSinceEpoch(); m_db->update(dbStruct); } Database::OpmLockedTopics HandlerLockedTopics::toDb(const Core::LockedEntityBase &request) { Database::OpmLockedTopics ret; ret.locked = true; ret.entityId = request.entityId(); ret.entityTypeName = QString::fromStdString(request.entityTypeName()); ret.operatorId = request.operatorId(); ret.workstationId = request.workstationId(); ret.exerciseId = request.exerciseId() ? QVariant(request.exerciseId()) : QVariant(); return ret; } bool HandlerLockedTopics::publishLockedEntity(const Core::LockedEntity &entity) { if (m_dtsLockedEntity) { m_dtsLockedEntity->write(entity); return true; } ERROR_LOG(m_logger) << "Publishing LockedEntity " << entity.id() << " failed: DTS not available!"; return false; } bool HandlerLockedTopics::removeLockedEntity(int64_t id) { Core::LockedEntity entity; entity.id(id); if (m_dtsLockedEntity) { m_dtsLockedEntity->remove(entity); return true; } ERROR_LOG(m_logger) << "Removing LockedEntity " << entity.id() << " failed: DTS not available!"; return false; } //Core::key HandlerLockedTopics::topicIdToKey(long id) //{ // QHashIterator it (m_lockedTopics); // while (it.hasNext()) // { // it.next(); // const Database::OpmLockedTopics &topicRef = it.value(); // if (topicRef.id == id) // { // return it.key(); // } // } // while (it.hasNext()) // return {}; //} void HandlerLockedTopics::dumpLockedTopics(int line) { QHashIterator it (m_lockedTopics); INFO_LOG(m_logger) << " dump m_lockedTopics, START size= " << m_lockedTopics.size() << " (callerline=" << line << ")"; while (it.hasNext()) { it.next(); const Core::key &keyRef = it.key(); const Database::OpmLockedTopics &valRef = it.value(); INFO_LOG(m_logger) << " entityTypeName=" << keyRef.entityTypeName << "'" << ", entityId=" << keyRef.entityId << ", exerciseId=" << keyRef.exerciseId << " - eTN=" << valRef.entityTypeName << ", entityId=" << valRef.entityId << ", exId=" << valRef.exerciseId.toInt() << ", opId=" << valRef.operatorId; } INFO_LOG(m_logger) << " dump m_lockedTopics, END size= " << m_lockedTopics.size(); } } // namespace