427 lines
15 KiB
C++
Executable File
427 lines
15 KiB
C++
Executable File
/**
|
|
* 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 <DDS/FTS/generic_fts_impl.h>
|
|
#include <DDS/DTS/generic_dts_impl.h>
|
|
#include <DDS/Core/function_reply_object.h>
|
|
|
|
#include <QMutableHashIterator>
|
|
|
|
|
|
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<FTS::TryLockEntity>());
|
|
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<FTS::TryUnlockEntity>());
|
|
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<DTS::LockedEntityImpl>());
|
|
|
|
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<Core::key, Database::OpmLockedTopics> 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 ("<<lockedTopicIt<<") was not found!";
|
|
// Q_ASSERT(false);
|
|
// return false;
|
|
// }
|
|
|
|
// const Database::OpmLockedTopics &lockedTopic = *lockedTopicIt;
|
|
// DEBUG_LOG(m_logger) << LOGFUNCTION << ": found lock entity " << lockEntityId;
|
|
|
|
// unlockInDb(lockedTopic);
|
|
// removeLockedEntity(lockEntityId);
|
|
|
|
// m_lockedTopics.erase(lockedTopicIt);
|
|
|
|
// return true;
|
|
//}
|
|
|
|
void HandlerLockedTopics::onTryUnlockEntityRequest(const rti::core::SampleIdentity &cookie,
|
|
const Core::TryUnlockEntityRequest &request)
|
|
{
|
|
if (m_logger.isDebugEnabled())
|
|
dumpLockedTopics(__LINE__);
|
|
|
|
auto ret = tryUnlock(request);
|
|
if (!ret.errorString().empty())
|
|
{
|
|
WARNING_LOG(m_logger) << Core::FunctionReplyObject(ret.FunctionReply::id(), ret.errorString(), ret.errorParams()).translated() << " - " << request;
|
|
}
|
|
m_ftsTryUnlockEntity->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<Core::key, Database::OpmLockedTopics> 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<Core::key, Database::OpmLockedTopics> 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
|