#include "lts_app_manager.h" #include "AppChooser/app_chooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef LTS_VERSION_MAJOR #define LTS_VERSION_MAJOR "undefined" #endif #ifndef LTS_VERSION_MINOR #define LTS_VERSION_MINOR "undefined" #endif #ifndef LTS_VERSION_PATCH #define LTS_VERSION_PATCH "undefined" #endif #ifndef JENKINS_BUILD_NUMBER #define JENKINS_BUILD_NUMBER "unknown" #endif static const char ver_major[] = LTS_VERSION_MAJOR; static const char ver_minor[] = LTS_VERSION_MINOR; static const char ver_patch[] = LTS_VERSION_PATCH; static const char ver_jenkins[] = JENKINS_BUILD_NUMBER; namespace LtsAppManager { struct LtsAppManagerHandler { LtsAppManager ltsAppManager; }; Q_GLOBAL_STATIC(LtsAppManagerHandler, s_ltsAppManager) LtsAppManager *LtsAppManager::getInstance() { return &s_ltsAppManager->ltsAppManager; } LtsAppManager::LtsAppManager() : m_login(nullptr) { qRegisterMetaType("LtsAppDescriptor::App"); slotStartDebugServer(); connect(qApp, &QCoreApplication::aboutToQuit, this, [this] { delete m_login; m_login = nullptr; }); } LtsAppManager::~LtsAppManager() { delete m_login; m_login = nullptr; } void LtsAppManager::setAvailableApps(const LtsAppDescriptor::AppDescriptionVector &availableApps) { m_availableApps = availableApps; #ifndef LTS_DEBUG if (LtsSessionData::LtsSessionData::getInstance()->debugFlagValue(QStringLiteral("PluginManager.CheckAppRequirements")) == QVariant(true)) { QMessageBox::information(nullptr, QStringLiteral("LtsAppManager"), QStringLiteral("Checking App Requirements now")); #else { #endif QStringList errors; LtsPluginManager::LtsPluginLoader::getInstance()->checkPlugins(availableApps, errors); if (!errors.isEmpty()) QMessageBox::critical(nullptr, QStringLiteral("LtsAppManager"), QStringLiteral("App Requirements check failed!
- %1").arg(errors.join(QStringLiteral("
- ")))); } if (m_availableApps.isEmpty()) { QMessageBox::critical(nullptr, tr("App Manager"), tr("There are no apps available for this project.\nPlease contact Support.")); abort(); } } void LtsAppManager::slotStartDebugServer() { QTcpServer *debugServer = new QTcpServer(this); if (!debugServer->listen(QHostAddress::Any, 20000)) { debugServer->deleteLater(); const int waitTryAgain = 5; #ifdef LTS_DEBUG #endif QTimer::singleShot(waitTryAgain * 1000, this, &LtsAppManager::slotStartDebugServer); return; } connect(debugServer, &QTcpServer::newConnection, this, [this]() { QTcpServer *server = qobject_cast(sender()); if (server == nullptr) return; QTcpSocket *client = server->nextPendingConnection(); if (client == nullptr) return; connect(client, &QTcpSocket::readyRead, this, [this]() { QTcpSocket *client = qobject_cast(sender()); if (client == nullptr) return; QString commandStr(QString::fromUtf8(client->readLine(100)).remove(QStringLiteral("\r")).remove(QStringLiteral("\n"))); QStringList argList = commandStr.split(QLatin1Char(' '), QString::SplitBehavior::SkipEmptyParts); if (argList.count() < 1) return; const QString &command = argList.at(0); LtsSessionData::LtsSessionData *sessionData = LtsSessionData::LtsSessionData::getInstance(); static QStringList commands; commands << QStringLiteral("showSessions") << QStringLiteral("operator") << QStringLiteral("help") << QStringLiteral("logout") << QStringLiteral("flag") << QStringLiteral("setFlag") << QStringLiteral("start") << QStringLiteral("voiceCommand") << QStringLiteral("ftcmock") ; switch (commands.indexOf(command)) { case 0: { sessionData->setDebugFlagValue(QStringLiteral("DeveloperTools.ShowSessionsOnce"), true); client->write("K"); break; } case 1: { QString operatorName(sessionData->getOperatorName()); if (operatorName.isEmpty()) operatorName = QStringLiteral("(none)"); QString result(QStringLiteral("%1").arg(operatorName)); client->write(result.toUtf8()); break; } case 2: { QString result(QStringLiteral("Available Commands: %1").arg(commands.join(QStringLiteral(", ")))); client->write(result.toUtf8()); break; } case 3: { client->write("..."); slotLogout(); client->write("\r\nK"); break; } case 4: { if (argList.count() != 2) { client->write("Usage: flag "); break; } const QString &debugFlagName = argList.at(1); if (!sessionData->hasDebugFlag(debugFlagName)) { client->write("(unknown)"); break; } client->write(sessionData->debugFlagValue(argList.at(1)).toString().toUtf8()); break; } case 5: { if (argList.count() < 3) { client->write("Usage: setFlag \r\nFlag types: int, string (no spaces allowed yet), boolean, double"); break; } static QStringList flagTypes; flagTypes << QStringLiteral("int") << QStringLiteral("string") << QStringLiteral("boolean") << QStringLiteral("double"); const QString &flagType = argList.at(2); bool k = false; const QString &flagName = argList.at(1); switch (flagTypes.indexOf(flagType)) { case 0: { int value = argList.at(3).toInt(&k); if (k) sessionData->setDebugFlagValue(flagName, value); else client->write("cannot convert value to integer"); break; } case 1: { sessionData->setDebugFlagValue(flagName, argList.at(3)); break; } case 2: { QString value(argList.at(3).toLower().simplified()); if (value == QStringLiteral("true")) sessionData->setDebugFlagValue(flagName, true); else if (value == QStringLiteral("false")) sessionData->setDebugFlagValue(flagName, false); else client->write("cannot convert value to boolean"); break; } case 3: { double value = argList.at(3).toDouble(&k); if (k) sessionData->setDebugFlagValue(flagName, value); else client->write("cannot convert value to double"); break; } default: { client->write("unknown flag type"); break; } } break; } case 6: { if (argList.count() < 3) { client->write("Usage: start "); break; } client->write("not implemented yet"); break; } case 7: { if (argList.count() < 2) { client->write("Usage: voiceCommand "); break; } static QStringList keywords; keywords << QStringLiteral("AddMarker") << QStringLiteral("SetMarker") << QStringLiteral("AddSnapshot") << QStringLiteral("SaveSnapshot") ; const QString &keyword = argList.at(1); switch (keywords.indexOf(keyword)) { case 0: { auto ev = new LtsWidgets::AddMarkerEvent; ev->setAccepted(false); sessionData->setDebugFlagValue(command, keyword); QCoreApplication::postEvent(qApp, ev); break; } case 1: { auto ev = new LtsWidgets::SetMarkerEvent(QString()); if (argList.size() > 2) ev->setDescription(argList.at(2)); ev->setAccepted(false); sessionData->setDebugFlagValue(command, keyword); QCoreApplication::postEvent(qApp, ev); break; } case 2: { auto ev = new LtsWidgets::AddSnapshotEvent; ev->setAccepted(false); sessionData->setDebugFlagValue(command, keyword); QCoreApplication::postEvent(qApp, ev); break; } case 3: { auto ev = new LtsWidgets::SaveSnapshotEvent(QString()); if (argList.size() > 2) ev->setDescription(argList.at(2)); ev->setAccepted(false); sessionData->setDebugFlagValue(command, keyword); QCoreApplication::postEvent(qApp, ev); break; } default: break; } if (!sessionData->hasDebugFlag(command)) { client->write("(unknown)"); break; } client->write(sessionData->debugFlagValue(argList.at(1)).toString().toUtf8()); break; } case 8: { auto usage = [client]() { client->write("Usage: ftcmock start - See 'Session id' in DevDialog " "\nftcmock stop "); }; if (argList.count() < 3) { usage(); break; } bool k = false; switch (QStringList({QStringLiteral("start"), QStringLiteral("stop")}).indexOf(argList.at(1))) { case 0: { if (argList.count() < 6) { usage(); return; } const int min = argList.at(3).toInt(&k); if (!k || min < 0) { client->write("min must be a positive integer!"); return; } const int max = argList.at(4).toInt(&k); if (!k || max < 0) { client->write("max must be a positive integer!"); return; } const int timers = argList.at(5).toInt(&k); if (!k || timers < 1) { client->write("'timer count' must be a positive integer and greater than 0!"); return; } break; } case 1: { break; } default: { usage(); return; } } const int sessionId = argList.at(2).toInt(&k); if (!k || sessionId < 0) { client->write("sessionId must be a positive integer!"); return; } emit signalCommand(client, argList); break; } default: { QString result(QStringLiteral("Unknown command '%1'").arg(command)); client->write(result.toUtf8()); break; } } client->write("\r\n> "); }); QString versionString = QStringLiteral("%1.%2-%3 (rev: %4)") .arg(QString::fromLatin1(ver_major), QString::fromLatin1(ver_minor), QString::fromLatin1(ver_jenkins), QString::fromLatin1(ver_patch)); QString motd(QStringLiteral("\r\nOperatorApplication - Version %1").arg(versionString)); client->write(motd.toUtf8()); client->write("\r\n> "); }); } void LtsAppManager::showLogin() { QTimer::singleShot(0, this, &LtsAppManager::slotShowLogin); } void LtsAppManager::slotShowLogin() { if (m_login == nullptr) { m_login = new LtsLogin::LtsLogin; connect(m_login, &LtsLogin::LtsLogin::signalLoginDone, this, [&] { #ifdef LTS_DEBUG #endif if (m_appList.isEmpty()) if (!startAppOrLetUserChoose(m_app, {})) QTimer::singleShot(100, this, &LtsAppManager::slotLogout); }); connect(m_login, &LtsLogin::LtsLogin::signalLogoffDone, this, &LtsAppManager::signalLogoffDone); connect(m_login, &LtsLogin::LtsLogin::signalLogoffDone, this, &LtsAppManager::slotShowLogin, Qt::QueuedConnection); connect(m_login, &LtsLogin::LtsLogin::signalLogout, this, &LtsAppManager::slotLogout); m_login->setLoginPlugin(m_loginPluginIid); } m_login->login(); } bool LtsAppManager::hasRightForApp(const LtsAppDescriptor::AppDescription &appDescription) const { const QSet &acls = LtsSessionData::LtsSessionData::getInstance()->getOperatorRights(); for (int reqRight : appDescription.requiredAcls) { if (!acls.contains(reqRight)) { QMessageBox::critical(nullptr, tr("App Manager"), tr("The minimal required operator rights are not met. Therefore, the app cannot be started.")); return false; } } return true; } void LtsAppManager::taskSwitch(QObject *_caller) { LtsSession::LtsSession *caller = qobject_cast(_caller); if (caller == nullptr) { Q_ASSERT(false); return; } QVector sessions; for (const LtsPluginManager::LtsPluginManager *app : m_appList) sessions += app->sessions(); if (sessions.isEmpty()) return; int sessionCount = sessions.count(); if (sessionCount < 2) return; auto sessionIt = std::find_if(sessions.begin(), sessions.end(), [caller](const LtsSession::LtsSession *session) { return (session == caller); }); if (sessionIt == sessions.end()) { return; } auto it = ++sessionIt; int x = 0; do { if (x > 100) { return; } x++; if (it == sessions.end()) { if (sessions.begin() == sessionIt) { return; } it = sessions.begin(); } LtsSession::LtsSession *session = *it; QWidget *blw = session->baseLayoutWidget(); if (blw == nullptr || session->isForeverInvisible() || session == caller) { continue; } caller->baseLayoutWidget()->hide(); blw->showFullScreen(); return; } while (++it != sessionIt); } void LtsAppManager::hideAllOtherBLWs(QObject *_caller) { if (!LtsSessionData::LtsSessionData::getInstance()->isTablet()) return; LtsSession::LtsSession *caller = qobject_cast(_caller); QVector sessions; for (const LtsPluginManager::LtsPluginManager *app : m_appList) sessions += app->sessions(); for (LtsSession::LtsSession *session : sessions) { QWidget *blw = session->baseLayoutWidget(); if (blw != nullptr && !session->isForeverInvisible() && session != caller ) { blw->hide(); } } if (caller != nullptr) return; for (LtsSession::LtsSession *session : sessions) { QWidget *blw = session->baseLayoutWidget(); if (blw == nullptr || session->isForeverInvisible()) { continue; } blw->showFullScreen(); return; } } bool LtsAppManager::startAppOrLetUserChoose(LtsAppDescriptor::App appId, const LtsSession::DdsConnectionParams &connParams, bool force) { return startAppOrLetUserChoose(getApp(appId), connParams, force); } bool LtsAppManager::startAppOrLetUserChoose(const LtsAppDescriptor::AppDescription &appDescription, const LtsSession::DdsConnectionParams &connParams, bool force) { if (!appDescription.active && !force) { AppChooser *appChooser = new AppChooser(); appChooser->setAttribute(Qt::WA_DeleteOnClose); connect(appChooser, &AppChooser::signalLogout, this, &LtsAppManager::slotLogout); connect(appChooser, &AppChooser::signalAppSelected, this, [this, connParams](LtsAppDescriptor::App appId) { if (createApp(appId, connParams) == nullptr) QTimer::singleShot(100, this, &LtsAppManager::slotLogout); }, Qt::QueuedConnection); appChooser->show(); return true; } if (createApp(appDescription, connParams, {}, false, force) == nullptr) { QMessageBox::critical(nullptr, tr("App Manager"), tr("Could not create app.\nPlease contact support.")); return false; } return true; } LtsPluginManager::LtsPluginManager *LtsAppManager::createApp(LtsAppDescriptor::App appId, const LtsSession::DdsConnectionParams &connParams, const QString &workspaceId, bool hidden, bool force) { return createApp(getApp(appId), connParams, workspaceId, hidden, force); } LtsPluginManager::LtsPluginManager *LtsAppManager::createApp(const LtsAppDescriptor::AppDescription &appDescription, const LtsSession::DdsConnectionParams &connParams, const QString &workspaceId, bool hidden, bool force) { if (!appDescription.isValid()) { return nullptr; } if (!hasRightForApp(appDescription) && !force) return nullptr; const bool bDesiredAppHasSituationMapInstance = LtsPluginManager::LtsPluginManager::hasSituationMapInstance(appDescription, true); if (bDesiredAppHasSituationMapInstance) { const int curInstances = getNumberOfSituationMapInstances(); if (curInstances >= 3 && !force) { if (QMessageBox::Yes != QMessageBox::question(nullptr, tr("App Manager"), tr("Are you sure you want to launch another resource-intensive app?
" "Please note that you are currently running three apps containing the resource-intensive map componment.
" "It may lead to potential performance issues."))) { return nullptr; } } } auto *pluginManager = new LtsPluginManager::LtsPluginManager(); connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalQuit, this, &LtsAppManager::slotQuit); connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalSoftQuit, this, &LtsAppManager::slotSoftQuit); connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalLogout, this, &LtsAppManager::slotLogout); connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalInitializationFinished, this, &LtsAppManager::signalInitializationFinished); connect(pluginManager, &LtsPluginManager::LtsPluginManager::destroyed, this, &LtsAppManager::slotAppDestroyed); connect(pluginManager, &LtsPluginManager::LtsPluginManager::hideAllOtherBLWs, this, &LtsAppManager::hideAllOtherBLWs); connect(pluginManager, &LtsPluginManager::LtsPluginManager::taskSwitch, this, &LtsAppManager::taskSwitch); #ifdef LTS_DEBUG connect(pluginManager, &LtsPluginManager::LtsPluginManager::signalStillAlive, this, [this]() { auto *app = qobject_cast(sender()); if (!app) return; for (auto i = m_appList.constBegin(); i != m_appList.constEnd(); ++i) { if (*i == app) return; } }); #endif if (!pluginManager->startApp(appDescription, false, connParams, workspaceId, hidden)) { QMessageBox::critical(nullptr, tr("App Manager"), tr("The Plugin Manager failed to start the app due to plugin errors.\nPlease contact support.")); pluginManager->deleteLater(); return nullptr; } m_appList.append(pluginManager); return pluginManager; } bool LtsAppManager::hasApp(LtsAppDescriptor::App appId) const { return (std::find_if(m_availableApps.constBegin(), m_availableApps.constEnd(), [appId](const LtsAppDescriptor::AppDescription &app) { bool ret = (app.id == appId); return ret; }) != m_availableApps.constEnd()); } int LtsAppManager::getNumberOfSituationMapInstances() const { int numberOfSituationMapInstances = 0; for (const LtsPluginManager::LtsPluginManager *app : qAsConst(m_appList)) { if (!app) continue; if (app->hasSituationMapInstance()) ++numberOfSituationMapInstances; } return numberOfSituationMapInstances; } LtsAppDescriptor::AppDescription LtsAppManager::getApp(LtsAppDescriptor::App appId) const { for (const LtsAppDescriptor::AppDescription &app : m_availableApps) { if (app.id == appId) return app; } return LtsAppDescriptor::AppDescription(); } void LtsAppManager::setMainApp(LtsAppDescriptor::App appId) { m_app = getApp(appId); } void LtsAppManager::saveState() { if (LtsSessionData::LtsSessionData::getInstance()->getOperatorId() == 0) return; QStringList unsavedWorkspaces = LtsUtils::OperatorSettings::getInstance()->identifiers(QStringLiteral("UnsavedWorkspace")); for (auto i = unsavedWorkspaces.constBegin(); i != unsavedWorkspaces.constEnd(); ++i) LtsUtils::OperatorSettings::getInstance()->removeValue(QStringLiteral("UnsavedWorkspace"), *i); QStringList unsavedFilterPreset = LtsUtils::OperatorSettings::getInstance()->identifiers(QStringLiteral("UnsavedFilterPreset")); for (auto i = unsavedFilterPreset.constBegin(); i != unsavedFilterPreset.constEnd(); ++i) LtsUtils::OperatorSettings::getInstance()->removeValue(QStringLiteral("UnsavedFilterPreset"), *i); QJsonArray root; for (const LtsPluginManager::LtsPluginManager *app : qAsConst(m_appList)) { const QVector sessions = app->sessions(); const auto appId = app->appDescription().id; if (appId == LtsAppDescriptor::App::Login || sessions.count() < 1) continue; QJsonObject appState; appState[QStringLiteral("id")] = static_cast(appId); QJsonArray sessionStates; for (LtsSession::LtsSession *session : sessions) { if (session->baseLayout() != nullptr && session->baseLayout()->isSecondaryWidget()) continue; QJsonObject sessionState; int screenId = session->screenId(); int screenContainerId = session->screenContainerId(); sessionState[QStringLiteral("screenId")] = screenId; sessionState[QStringLiteral("screenContainerId")] = screenContainerId; sessionState[QStringLiteral("exerciseId")] = session->exerciseId(); sessionState[QStringLiteral("partitionId")] = session->partitionId(); sessionState[QStringLiteral("partitionType")] = int(session->partitionType()); sessionState[QStringLiteral("savegame")] = session->getSavegame(); const QString newId(QUuid::createUuid().toString()); sessionState[QStringLiteral("workspaceId")] = newId; std::shared_ptr ifcOpm; if (session->getForeignGuiInterface(ifcOpm)) { ifcOpm->callWorkspaceSaveUnsaved(newId); } sessionStates.append(sessionState); } if (!sessionStates.isEmpty()) { appState[QStringLiteral("sessions")] = sessionStates; root.append(appState); } } QJsonDocument doc(root); LtsUtils::OperatorSettings::getInstance()->setValue(QStringLiteral("app"), QStringLiteral("LastAppStates"), doc.toJson(QJsonDocument::Compact)); } void LtsAppManager::slotLogout() { for (LtsPluginManager::LtsPluginManager *app : qAsConst(m_appList)) { const QVector sessions = app->sessions(); for (LtsSession::LtsSession *session : sessions) { if (session->notifyUnsavedChanges() == false ) return; } } saveState(); const QVector apps = m_appList; if (apps.isEmpty() && m_login != nullptr) m_login->logoff(); for (LtsPluginManager::LtsPluginManager *app : apps) app->deleteLater(); } void LtsAppManager::slotAppDestroyed() { LtsPluginManager::LtsPluginManager *app = static_cast(sender()); m_appList.removeOne(app); if (m_appList.isEmpty() && m_login != nullptr) { m_login->logoff(); } } void LtsAppManager::slotQuit() { QObject *object = sender(); if (!object) qCritical() << "ERROR: LtsAppManager: slot called without object - so it cannot be deleted from the app assignments!"; LtsPluginManager::LtsPluginManager *app = qobject_cast(object); if (app == nullptr) { qCritical() << "ERROR: LtsAppManager: quit requested for invalid app"; return; } if (m_appList.count() == 1) { m_appList.removeAll(app); saveState(); } app->deleteLater(); } void LtsAppManager::slotSoftQuit() { QObject *object = sender(); LtsPluginManager::LtsPluginManager *app = qobject_cast(object); if (app == nullptr) { qCritical() << "ERROR: LtsAppManager: quit requested for invalid app"; return; } app->_softQuit(); if (m_appList.count() == 1) { m_appList.removeAll(app); saveState(); } } bool LtsAppManager::otherSessionsResponsibility(LtsSession::LtsSession *thisSession, const QVector &requiredAppTypes, const QVector &requiredPartitionTypes, const QString &menuGroup, const QString &identifier, LtsWidgets::LayoutArea area) { const QVector apps = LtsAppManager::LtsAppManager::getInstance()->getAppList(); QVector worthyApps; for (LtsPluginManager::LtsPluginManager *app : apps) { if (!requiredAppTypes.contains(static_cast(app->appDescription().id))) continue; worthyApps.append(app); } for (const LtsPluginManager::LtsPluginManager *app : qAsConst(worthyApps)) { const QVector sessions = app->sessions(); for (const LtsSession::LtsSession *session : sessions) { if (session->widgetExists(menuGroup, identifier, area)) return true; } } LtsMultiMonitor::LtsMultiMonitor *mm = LtsMultiMonitor::LtsMultiMonitor::getInstance(); LtsMultiMonitor::LtsScreenBase *mainScreen = mm->mainScreen(); for (const LtsPluginManager::LtsPluginManager *app : qAsConst(worthyApps)) { const QVector sessions = app->sessions(); for (LtsSession::LtsSession *session : sessions) { if (session != thisSession && mm->widgetsScreen(session->baseLayoutWidget()) == mainScreen && requiredPartitionTypes.contains(session->partitionType())) return true; } } return false; } }