File indexing completed on 2024-11-10 05:11:09
0001 /* 0002 * Copyright 2018 by Marco Martin <mart@kde.org> 0003 * Copyright 2018 David Edmundson <davidedmundson@kde.org> 0004 * 0005 * Licensed under the Apache License, Version 2.0 (the "License"); 0006 * you may not use this file except in compliance with the License. 0007 * You may obtain a copy of the License at 0008 * 0009 * http://www.apache.org/licenses/LICENSE-2.0 0010 * 0011 * Unless required by applicable law or agreed to in writing, software 0012 * distributed under the License is distributed on an "AS IS" BASIS, 0013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 0014 * See the License for the specific language governing permissions and 0015 * limitations under the License. 0016 * 0017 */ 0018 0019 #include "abstractskillview.h" 0020 #include "activeskillsmodel.h" 0021 #include "abstractdelegate.h" 0022 #include "sessiondatamap.h" 0023 #include "sessiondatamodel.h" 0024 #include "delegatesmodel.h" 0025 0026 #include <QWebSocket> 0027 #include <QUuid> 0028 #include <QJsonObject> 0029 #include <QJsonArray> 0030 #include <QJsonDocument> 0031 #include <QQmlContext> 0032 #include <QQmlEngine> 0033 #include <QTranslator> 0034 0035 AbstractSkillView::AbstractSkillView(QQuickItem *parent) 0036 : QQuickItem(parent), 0037 m_id(QUuid::createUuid().toString()), 0038 m_controller(MycroftController::instance()) 0039 { 0040 m_activeSkillsModel = new ActiveSkillsModel(this); 0041 0042 m_guiWebSocket = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this); 0043 m_controller->registerView(this); 0044 0045 connect(m_guiWebSocket, &QWebSocket::connected, this, 0046 [this] () { 0047 m_reconnectTimer.stop(); 0048 emit statusChanged(); 0049 }); 0050 0051 connect(m_guiWebSocket, &QWebSocket::disconnected, this, &AbstractSkillView::closed); 0052 0053 connect(m_guiWebSocket, &QWebSocket::disconnected, this, [this]() { 0054 m_activeSkillsModel->removeRows(0, m_activeSkillsModel->rowCount()); 0055 }); 0056 0057 connect(m_guiWebSocket, &QWebSocket::stateChanged, this, 0058 [this] (QAbstractSocket::SocketState state) { 0059 emit statusChanged(); 0060 }); 0061 0062 connect(m_guiWebSocket, &QWebSocket::textMessageReceived, this, &AbstractSkillView::onGuiSocketMessageReceived); 0063 0064 connect(m_guiWebSocket, &QWebSocket::stateChanged, this, 0065 [this](QAbstractSocket::SocketState socketState) { 0066 //TODO: when the connection closes, all session data and guis should be destroyed 0067 //qWarning()<<"GUI SOCKET STATE:"<<socketState; 0068 //Try to reconnect if our connection died but the main server connection is still alive 0069 if (socketState == QAbstractSocket::UnconnectedState && m_url.isValid() && m_controller->status() == MycroftController::Open) { 0070 m_reconnectTimer.start(); 0071 } 0072 }); 0073 0074 connect(m_guiWebSocket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error), this, 0075 [this](QAbstractSocket::SocketError error) { 0076 qWarning() << "Gui socket Connection Error:" << error; 0077 m_reconnectTimer.start(); 0078 }); 0079 0080 0081 connect(m_controller, &MycroftController::socketStatusChanged, this, 0082 [this]() { 0083 if (m_controller->status() != MycroftController::Open) { 0084 m_guiWebSocket->close(); 0085 //don't assume the url will be still valid 0086 m_url = QUrl(); 0087 } 0088 }); 0089 0090 // Reconnect timer 0091 m_reconnectTimer.setInterval(1000); 0092 connect(&m_reconnectTimer, &QTimer::timeout, this, [this]() { 0093 m_guiWebSocket->close(); 0094 m_guiWebSocket->open(m_url); 0095 }); 0096 0097 // Trim components cache timer 0098 m_trimComponentsTimer.setInterval(100); 0099 m_trimComponentsTimer.setSingleShot(true); 0100 connect(&m_trimComponentsTimer, &QTimer::timeout, this, [this]() { 0101 QQmlEngine *engine = qmlEngine(this); 0102 if (engine) { 0103 engine->clearComponentCache(); 0104 } 0105 }); 0106 0107 connect(m_controller, &MycroftController::utteranceManagedBySkill, this, 0108 [this](const QString &skillId) { 0109 m_activeSkillsModel->checkGuiActivation(skillId); 0110 }); 0111 } 0112 0113 AbstractSkillView::~AbstractSkillView() 0114 { 0115 } 0116 0117 0118 QUrl AbstractSkillView::url() const 0119 { 0120 return m_url; 0121 } 0122 0123 void AbstractSkillView::setUrl(const QUrl &url) 0124 { 0125 if (m_url == url) { 0126 return; 0127 } 0128 0129 m_url = url; 0130 0131 //don't connect if the controller is offline 0132 if (m_controller->status() == MycroftController::Open) { 0133 m_guiWebSocket->close(); 0134 m_guiWebSocket->open(url); 0135 } 0136 } 0137 0138 QString AbstractSkillView::id() const 0139 { 0140 return m_id; 0141 } 0142 0143 void AbstractSkillView::triggerEvent(const QString &skillId, const QString &eventName, const QVariantMap ¶meters) 0144 { 0145 if (m_guiWebSocket->state() != QAbstractSocket::ConnectedState) { 0146 qWarning() << "Error: Mycroft gui connection not open!"; 0147 return; 0148 } 0149 QJsonObject root; 0150 0151 root[QStringLiteral("type")] = QStringLiteral("mycroft.events.triggered"); 0152 root[QStringLiteral("namespace")] = skillId; 0153 root[QStringLiteral("event_name")] = eventName; 0154 root[QStringLiteral("parameters")] = QJsonObject::fromVariantMap(parameters); 0155 0156 QJsonDocument doc(root); 0157 m_guiWebSocket->sendTextMessage(QString::fromUtf8(doc.toJson())); 0158 } 0159 0160 void AbstractSkillView::writeProperties(const QString &skillId, const QVariantMap &data) 0161 { 0162 if (m_guiWebSocket->state() != QAbstractSocket::ConnectedState) { 0163 qWarning() << "Error: Mycroft gui connection not open!"; 0164 return; 0165 } 0166 QJsonObject root; 0167 0168 root[QStringLiteral("type")] = QStringLiteral("mycroft.session.set"); 0169 root[QStringLiteral("namespace")] = skillId; 0170 root[QStringLiteral("data")] = QJsonObject::fromVariantMap(data); 0171 0172 QJsonDocument doc(root); 0173 m_guiWebSocket->sendTextMessage(QString::fromUtf8(doc.toJson())); 0174 } 0175 0176 void AbstractSkillView::deleteProperty(const QString &skillId, const QString &property) 0177 { 0178 if (m_guiWebSocket->state() != QAbstractSocket::ConnectedState) { 0179 qWarning() << "Error: Mycroft gui connection not open!"; 0180 return; 0181 } 0182 QJsonObject root; 0183 0184 root[QStringLiteral("type")] = QStringLiteral("mycroft.session.delete"); 0185 root[QStringLiteral("namespace")] = skillId; 0186 root[QStringLiteral("property")] = property; 0187 0188 QJsonDocument doc(root); 0189 m_guiWebSocket->sendTextMessage(QString::fromUtf8(doc.toJson())); 0190 } 0191 0192 MycroftController::Status AbstractSkillView::status() const 0193 { 0194 if (m_reconnectTimer.isActive()) { 0195 return MycroftController::Connecting; 0196 } 0197 0198 switch(m_guiWebSocket->state()) 0199 { 0200 case QAbstractSocket::ConnectingState: 0201 case QAbstractSocket::BoundState: 0202 case QAbstractSocket::HostLookupState: 0203 return MycroftController::Connecting; 0204 case QAbstractSocket::UnconnectedState: 0205 return MycroftController::Closed; 0206 case QAbstractSocket::ConnectedState: 0207 return MycroftController::Open; 0208 case QAbstractSocket::ClosingState: 0209 return MycroftController::Closing; 0210 default: 0211 return MycroftController::Connecting; 0212 } 0213 } 0214 0215 ActiveSkillsModel *AbstractSkillView::activeSkills() const 0216 { 0217 return m_activeSkillsModel; 0218 } 0219 0220 SessionDataMap *AbstractSkillView::sessionDataForSkill(const QString &skillId) 0221 { 0222 SessionDataMap *map = nullptr; 0223 0224 if (m_skillData.contains(skillId)) { 0225 map = m_skillData[skillId]; 0226 } else if (m_activeSkillsModel->skillIndex(skillId).isValid()) { 0227 map = new SessionDataMap(skillId, this); 0228 m_skillData[skillId] = map; 0229 } 0230 0231 return map; 0232 } 0233 0234 QList<QVariantMap> variantListToOrderedMap(const QVariantList &data) 0235 { 0236 QList<QVariantMap> ordMap; 0237 0238 QStringList roleNames; 0239 0240 for (const auto &item : data) { 0241 if (!item.canConvert<QVariantMap>()) { 0242 qWarning() << "Error: Array data structure corrupted: " << data; 0243 return ordMap; 0244 } 0245 const auto &map = item.value<QVariantMap>(); 0246 if (roleNames.isEmpty()) { 0247 roleNames = map.keys(); 0248 } else if (roleNames != map.keys()) { 0249 qWarning() << "WARNING: Item with a wrong set of roles encountered, some roles will be inaccessible from QML, expected: " << roleNames << "Encountered: " << map.keys(); 0250 } 0251 ordMap << map; 0252 } 0253 0254 return ordMap; 0255 } 0256 0257 QStringList jsonModelToStringList(const QString &key, const QJsonValue &data) 0258 { 0259 QStringList items; 0260 0261 if (!data.isArray()) { 0262 qWarning() << "Error: Model data is not an Array" << data; 0263 return items; 0264 } 0265 0266 const auto &array = data.toArray(); 0267 for (const auto &item : array) { 0268 if (!item.isObject()) { 0269 qWarning() << "Error: Array data structure currupted: " << data; 0270 items.clear(); 0271 return items; 0272 } 0273 const auto &obj = item.toObject(); 0274 if (obj.keys().length() != 1 || !obj.contains(key)) { 0275 qWarning() << "Error: Item with a wrong key encountered, expected: " << key << "Encountered: " << obj.keys(); 0276 items.clear(); 0277 return items; 0278 } 0279 const auto &value = obj.value(key); 0280 if (!value.isString()) { 0281 qWarning() << "Error: item in model not a string" << value; 0282 } 0283 items << value.toString(); 0284 } 0285 0286 return items; 0287 } 0288 0289 void AbstractSkillView::onGuiSocketMessageReceived(const QString &message) 0290 { 0291 QJsonParseError parseError; 0292 auto doc = QJsonDocument::fromJson(message.toUtf8(), &parseError); 0293 0294 if (doc.isEmpty()) { 0295 qWarning() << "Empty or invalid JSON message arrived on the gui socket:" << message << "Error:" << parseError.errorString(); 0296 return; 0297 } 0298 0299 auto type = doc[QStringLiteral("type")].toString(); 0300 0301 if (type.isEmpty()) { 0302 qWarning() << "Empty type in the JSON message on the gui socket"; 0303 return; 0304 } 0305 0306 //qDebug() << "gui message type" << type; 0307 0308 //BEGIN SKILLDATA 0309 // The SkillData was updated by the server 0310 if (type == QLatin1String("mycroft.session.set")) { 0311 const QString skillId = doc[QStringLiteral("namespace")].toString(); 0312 const QVariantMap data = doc[QStringLiteral("data")].toVariant().toMap(); 0313 0314 if (skillId.isEmpty()) { 0315 qWarning() << "Empty skill_id in mycroft.session.set"; 0316 return; 0317 } 0318 if (!m_activeSkillsModel->skillIndex(skillId).isValid()) { 0319 qWarning() << "Invalid skill_id in mycroft.session.set:" << skillId; 0320 return; 0321 } 0322 if (data.isEmpty()) { 0323 qWarning() << "Empty data in mycroft.session.set"; 0324 return; 0325 } 0326 0327 //we already checked, assume *map is valid 0328 SessionDataMap *map = sessionDataForSkill(skillId); 0329 if (!map) { 0330 return; 0331 } 0332 QVariantMap::const_iterator i; 0333 for (i = data.constBegin(); i != data.constEnd(); ++i) { 0334 //insert it as a model 0335 //QList<QVariantMap> list = variantListToOrderedMap(i.value().value<QVariantList>()); 0336 0337 QVariantList variantList = i.value().toList(); 0338 QList<QVariantMap> list; 0339 if(i.value().userType() != QMetaType::QString) { 0340 for (const QVariant &variant : variantList) { 0341 QVariantMap map = variant.toMap(); 0342 list.append(map); 0343 } 0344 } 0345 0346 SessionDataModel *dm = map->value(i.key()).value<SessionDataModel *>(); 0347 0348 if (!list.isEmpty()) { 0349 qDebug() << "list is not empty"; 0350 if (!dm) { 0351 dm = new SessionDataModel(map); 0352 map->insertAndNotify(i.key(), QVariant::fromValue(dm)); 0353 } else { 0354 dm->clear(); 0355 } 0356 dm->insertData(0, list); 0357 0358 //insert it as is. 0359 } else { 0360 qDebug() << "inserting as it is"; 0361 if (dm) { 0362 dm->deleteLater(); 0363 } 0364 map->insertAndNotify(i.key(), i.value()); 0365 } 0366 //qDebug() << " " << i.key() << " = " << i.value(); 0367 } 0368 0369 // The SkillData was removed by the server 0370 } else if (type == QLatin1String("mycroft.session.delete")) { 0371 const QString skillId = doc[QStringLiteral("namespace")].toString(); 0372 const QString property = doc[QStringLiteral("property")].toString(); 0373 if (skillId.isEmpty()) { 0374 qWarning() << "No skill_id provided in mycroft.session.delete"; 0375 return; 0376 } 0377 if (!m_activeSkillsModel->skillIndex(skillId).isValid()) { 0378 qWarning() << "Invalid skill_id in mycroft.session.delete:" << skillId; 0379 return; 0380 } 0381 if (property.isEmpty()) { 0382 qWarning() << "No property provided in mycroft.session.delete"; 0383 return; 0384 } 0385 0386 SessionDataMap *map = sessionDataForSkill(skillId); 0387 SessionDataModel *dm = map->value(property).value<SessionDataModel *>(); 0388 map->clearAndNotify(property); 0389 //a model will need to be manually deleted 0390 if (dm) { 0391 dm->deleteLater(); 0392 } 0393 //END SKILLDATA 0394 0395 0396 //BEGIN ACTIVESKILLS 0397 // Insert new active skill 0398 } else if (type == QLatin1String("mycroft.session.list.insert") && doc[QStringLiteral("namespace")].toString() == QLatin1String("mycroft.system.active_skills")) { 0399 const int position = doc[QStringLiteral("position")].toInt(); 0400 0401 if (position < 0 || position > m_activeSkillsModel->rowCount()) { 0402 qWarning() << "Error: Invalid position in mycroft.session.list.insert of mycroft.system.active_skills"; 0403 return; 0404 } 0405 0406 const QStringList skillList = jsonModelToStringList(QStringLiteral("skill_id"), doc[QStringLiteral("data")]); 0407 0408 if (skillList.isEmpty()) { 0409 qWarning() << "Error: no valid skills received in mycroft.session.list.insert of mycroft.system.active_skills"; 0410 return; 0411 } 0412 0413 m_activeSkillsModel->insertSkills(position, skillList); 0414 0415 0416 // Active skill removed 0417 } else if (type == QLatin1String("mycroft.session.list.remove") && doc[QStringLiteral("namespace")].toString() == QLatin1String("mycroft.system.active_skills")) { 0418 const int position = doc[QStringLiteral("position")].toInt(); 0419 const int itemsNumber = doc[QStringLiteral("items_number")].toInt(); 0420 0421 if (position < 0 || position > m_activeSkillsModel->rowCount() - 1) { 0422 qWarning() << "Error: Invalid position in mycroft.session.list.remove of mycroft.system.active_skills"; 0423 return; 0424 } 0425 if (itemsNumber < 0 || itemsNumber > m_activeSkillsModel->rowCount() - position) { 0426 qWarning() << "Error: Invalid items_number in mycroft.session.list.remove of mycroft.system.active_skills"; 0427 return; 0428 } 0429 0430 for (int i = 0; i < itemsNumber; ++i) { 0431 0432 const QString skillId = m_activeSkillsModel->data(m_activeSkillsModel->index(position+i, 0)).toString(); 0433 0434 if (!m_translatorsForSkill.contains(skillId)) { 0435 QTranslator *translator = m_translatorsForSkill[skillId]; 0436 QCoreApplication::removeTranslator(translator); 0437 m_translatorsForSkill.remove(skillId); 0438 delete translator; 0439 } 0440 //TODO: do this after an animation 0441 { 0442 auto i = m_skillData.find(skillId); 0443 if (i != m_skillData.end()) { 0444 i.value()->deleteLater(); 0445 m_skillData.erase(i); 0446 } 0447 } 0448 } 0449 m_activeSkillsModel->removeRows(position, itemsNumber); 0450 0451 // Active skill moved 0452 } else if (type == QLatin1String("mycroft.session.list.move") && doc[QStringLiteral("namespace")].toString() == QLatin1String("mycroft.system.active_skills")) { 0453 const int from = doc[QStringLiteral("from")].toInt(); 0454 const int to = doc[QStringLiteral("to")].toInt(); 0455 const int itemsNumber = doc[QStringLiteral("items_number")].toInt(); 0456 0457 if (from < 0 || from > m_activeSkillsModel->rowCount() - 1) { 0458 qWarning() << "Error: Invalid from position in mycroft.session.list.move of mycroft.system.active_skills"; 0459 return; 0460 } 0461 if (to < 0 || to > m_activeSkillsModel->rowCount() - 1) { 0462 qWarning() << "Error: Invalid to position in mycroft.session.list.move of mycroft.system.active_skills"; 0463 return; 0464 } 0465 if (itemsNumber <= 0 || itemsNumber > m_activeSkillsModel->rowCount() - from) { 0466 qWarning() << "Error: Invalid items_number in mycroft.session.list.move of mycroft.system.active_skills"; 0467 return; 0468 } 0469 0470 m_activeSkillsModel->moveRows(QModelIndex(), from, itemsNumber, QModelIndex(), to); 0471 //END ACTIVESKILLS 0472 0473 0474 //BEGIN GUI MODEL 0475 // Insert new new gui delegates 0476 } else if (type == QLatin1String("mycroft.gui.list.insert")) { 0477 const QString skillId = doc[QStringLiteral("namespace")].toString(); 0478 if (skillId.isEmpty()) { 0479 qWarning() << "No skill_id provided in mycroft.gui.list.insert"; 0480 return; 0481 } 0482 0483 const int position = doc[QStringLiteral("position")].toInt(); 0484 0485 DelegatesModel *delegatesModel = m_activeSkillsModel->delegatesModelForSkill(skillId); 0486 0487 if (!delegatesModel) { 0488 qWarning() << "Error: no delegates model for skill" << skillId; 0489 return; 0490 } 0491 if (position < 0 || position > delegatesModel->rowCount()) { 0492 qWarning() << "Error: Invalid position in mycroft.gui.list.insert"; 0493 return; 0494 } 0495 0496 const QStringList delegateUrls = jsonModelToStringList(QStringLiteral("url"), doc[QStringLiteral("data")]); 0497 0498 if (delegateUrls.isEmpty()) { 0499 qWarning() << "Error: no valid skills received in mycroft.gui.list.insert"; 0500 return; 0501 } 0502 0503 qWarning() << "Arrived mycroft.gui.list.insert, delegateUrls are" << delegateUrls; 0504 0505 QList <DelegateLoader *> delegateLoaders; 0506 for (const auto &urlString : delegateUrls) { 0507 const QUrl delegateUrl = QUrl::fromUserInput(urlString); 0508 0509 if (!delegateUrl.isValid()) { 0510 continue; 0511 } 0512 0513 DelegateLoader *loader = new DelegateLoader(this); 0514 loader->init(skillId, delegateUrl); 0515 0516 qWarning() << "Created a new DelegateLoader" << loader << "which will load" << delegateUrl << "for the skill" << skillId; 0517 0518 if (!m_translatorsForSkill.contains(skillId)) { 0519 QTranslator *translator = new QTranslator(this); 0520 // TODO: download translations if skills are remote 0521 if (translator->load(QLocale(), skillId, QLatin1String("_"), loader->translationsUrl().path())) { 0522 QCoreApplication::installTranslator(translator); 0523 m_translatorsForSkill[skillId] = translator; 0524 } else { 0525 translator->deleteLater(); 0526 } 0527 } 0528 0529 connect(loader, &QObject::destroyed, &m_trimComponentsTimer, QOverload<>::of(&QTimer::start)); 0530 0531 delegateLoaders << loader; 0532 } 0533 0534 if (delegateLoaders.count() > 0) { 0535 delegatesModel->insertDelegateLoaders(position, delegateLoaders); 0536 //give the focus to the first 0537 delegateLoaders.first()->setFocus(true); 0538 } 0539 0540 0541 // Gui delegates removed 0542 } else if (type == QLatin1String("mycroft.gui.list.remove")) { 0543 const QString skillId = doc[QStringLiteral("namespace")].toString(); 0544 if (skillId.isEmpty()) { 0545 qWarning() << "No skill_id provided in mycroft.gui.list.remove"; 0546 return; 0547 } 0548 0549 const int position = doc[QStringLiteral("position")].toInt(); 0550 const int itemsNumber = doc[QStringLiteral("items_number")].toInt(); 0551 0552 //TODO: try with lifecycle managed by the view? 0553 DelegatesModel *delegatesModel = m_activeSkillsModel->delegatesModelForSkill(skillId); 0554 if (!delegatesModel) { 0555 qWarning() << "Error: no delegates model for skill" << skillId; 0556 return; 0557 } 0558 0559 if (position < 0 || position > delegatesModel->rowCount() - 1) { 0560 qWarning() << "Error: Invalid position in mycroft.gui.list.remove"; 0561 return; 0562 } 0563 0564 if (itemsNumber < 0 || itemsNumber > delegatesModel->rowCount()) { 0565 qWarning() << "Error: Invalid items_number in mycroft.gui.list.remove"; 0566 return; 0567 } 0568 0569 delegatesModel->removeRows(position, itemsNumber); 0570 0571 // Gui delegates moved 0572 } else if (type == QLatin1String("mycroft.gui.list.move")) { 0573 0574 const QString skillId = doc[QStringLiteral("namespace")].toString(); 0575 if (skillId.isEmpty()) { 0576 qWarning() << "No skill_id provided in mycroft.gui.list.move"; 0577 return; 0578 } 0579 0580 const int from = doc[QStringLiteral("from")].toInt(); 0581 const int to = doc[QStringLiteral("to")].toInt(); 0582 const int itemsNumber = doc[QStringLiteral("items_number")].toInt(); 0583 0584 DelegatesModel *delegatesModel = m_activeSkillsModel->delegatesModelForSkill(skillId); 0585 0586 if (!delegatesModel) { 0587 qWarning() << "Error: no delegates model for skill" << skillId; 0588 return; 0589 } 0590 0591 if (from < 0 || from > delegatesModel->rowCount() - 1) { 0592 qWarning() << "Error: Invalid from position in mycroft.gui.list.move"; 0593 return; 0594 } 0595 if (to < 0 || to > delegatesModel->rowCount() - 1) { 0596 qWarning() << "Error: Invalid to position in mycroft.gui.list.move"; 0597 return; 0598 } 0599 if (itemsNumber <= 0 || itemsNumber > delegatesModel->rowCount() - from) { 0600 qWarning() << "Error: Invalid items_number in mycroft.gui.list.move"; 0601 return; 0602 } 0603 delegatesModel->moveRows(QModelIndex(), from, itemsNumber, QModelIndex(), to); 0604 //END GUI MODELS 0605 0606 0607 //TODO: manage nested models? 0608 //BEGIN DATA MODELS 0609 // Insert new items in an existing list, or creates one under "property" 0610 } else if (type == QLatin1String("mycroft.session.list.insert")) { 0611 const QString skillId = doc[QStringLiteral("namespace")].toString(); 0612 if (skillId.isEmpty()) { 0613 qWarning() << "No skill_id provided in mycroft.session.list.insert"; 0614 return; 0615 } 0616 const QString &property = doc[QStringLiteral("property")].toString(); 0617 if (property.isEmpty()) { 0618 qWarning() << "Error: Invalid or empty \"property\" in mycroft.session.list.insert"; 0619 return; 0620 } 0621 0622 SessionDataMap *map = sessionDataForSkill(skillId); 0623 SessionDataModel *dm = map->value(property).value<SessionDataModel *>(); 0624 0625 if (!dm) { 0626 dm = new SessionDataModel(map); 0627 map->insertAndNotify(property, QVariant::fromValue(dm)); 0628 } 0629 0630 const int position = doc[QStringLiteral("position")].toInt(); 0631 0632 if (position < 0 || position > dm->rowCount()) { 0633 qWarning() << "Error: Invalid position in mycroft.session.list.insert"; 0634 return; 0635 } 0636 0637 // QList<QVariantMap> list = variantListToOrderedMap(doc[QStringLiteral("data")].toVariant().value<QVariantList>()); 0638 QVariantList variantList = doc[QStringLiteral("data")].toVariant().toList(); 0639 QList<QMap<QString, QVariant>> list; 0640 0641 for (const QVariant &variant : variantList) { 0642 QVariantMap map = variant.toMap(); 0643 list.append(map); 0644 } 0645 0646 if (list.isEmpty()) { 0647 qWarning() << "Error: invalid data in mycroft.session.list.insert:" << doc[QStringLiteral("data")]; 0648 return; 0649 } 0650 0651 dm->insertData(position, list); 0652 0653 // Updates the value of items in an existing list, Error if under "property" no list exists 0654 } else if (type == QLatin1String("mycroft.session.list.update")) { 0655 const QString skillId = doc[QStringLiteral("namespace")].toString(); 0656 if (skillId.isEmpty()) { 0657 qWarning() << "No skill_id provided in mycroft.session.list.update"; 0658 return; 0659 } 0660 const QString &property = doc[QStringLiteral("property")].toString(); 0661 if (property.isEmpty()) { 0662 qWarning() << "Error: Invalid or empty \"property\" in mycroft.session.list.update"; 0663 return; 0664 } 0665 0666 SessionDataMap *map = sessionDataForSkill(skillId); 0667 SessionDataModel *dm = map->value(property).value<SessionDataModel *>(); 0668 0669 if (!dm) { 0670 qWarning() << "Error: no list model existing under property" << property << "in mycroft.session.list.update"; 0671 return; 0672 } 0673 0674 const int position = doc[QStringLiteral("position")].toInt(); 0675 0676 if (position < 0 || position > m_activeSkillsModel->rowCount()) { 0677 qWarning() << "Error: Invalid position in mycroft.session.list.update"; 0678 return; 0679 } 0680 0681 //QList<QVariantMap> list = variantListToOrderedMap(doc[QStringLiteral("data")].toVariant().value<QVariantList>()); 0682 0683 QVariantList variantList = doc[QStringLiteral("data")].toVariant().toList(); 0684 QList<QMap<QString, QVariant>> list; 0685 0686 for (const QVariant &variant : variantList) { 0687 QVariantMap map = variant.toMap(); 0688 list.append(map); 0689 } 0690 0691 if (list.isEmpty()) { 0692 qWarning() << "Error: invalid data in mycroft.session.list.insert:" << doc[QStringLiteral("data")]; 0693 return; 0694 } 0695 0696 dm->updateData(position, list); 0697 0698 // Moves items within an existing list, Error if under "property" no list exists 0699 } else if (type == QLatin1String("mycroft.session.list.move")) { 0700 const QString skillId = doc[QStringLiteral("namespace")].toString(); 0701 if (skillId.isEmpty()) { 0702 qWarning() << "No skill_id provided in mycroft.session.list.update"; 0703 return; 0704 } 0705 const QString &property = doc[QStringLiteral("property")].toString(); 0706 if (property.isEmpty()) { 0707 qWarning() << "Error: Invalid or empty \"property\" in mycroft.session.list.move"; 0708 return; 0709 } 0710 0711 SessionDataMap *map = sessionDataForSkill(skillId); 0712 SessionDataModel *dm = map->value(property).value<SessionDataModel *>(); 0713 0714 if (!dm) { 0715 qWarning() << "Error: no list model existing under property" << property << "in mycroft.session.list.move"; 0716 return; 0717 } 0718 0719 const int from = doc[QStringLiteral("from")].toInt(); 0720 const int to = doc[QStringLiteral("to")].toInt(); 0721 const int itemsNumber = doc[QStringLiteral("items_number")].toInt(); 0722 0723 if (from < 0 || from > dm->rowCount() - 1) { 0724 qWarning() << "Error: Invalid from position in mycroft.session.list.move"; 0725 return; 0726 } 0727 if (to < 0 || to > dm->rowCount()) { 0728 qWarning() << "Error: Invalid to position in mycroft.session.list.move"; 0729 return; 0730 } 0731 if (itemsNumber <= 0 || itemsNumber > dm->rowCount() - from) { 0732 qWarning() << "Error: Invalid items_number in mycroft.session.list.move"; 0733 return; 0734 } 0735 dm->moveRows(QModelIndex(), from, itemsNumber, QModelIndex(), to); 0736 0737 // Removes items from an existing list, Error if under "property" no list exists 0738 } else if (type == QLatin1String("mycroft.session.list.remove")) { 0739 const QString skillId = doc[QStringLiteral("namespace")].toString(); 0740 if (skillId.isEmpty()) { 0741 qWarning() << "No skill_id provided in mycroft.session.list.update"; 0742 return; 0743 } 0744 const QString &property = doc[QStringLiteral("property")].toString(); 0745 if (property.isEmpty()) { 0746 qWarning() << "Error: Invalid or empty \"property\" in mycroft.session.list.move"; 0747 return; 0748 } 0749 0750 SessionDataMap *map = sessionDataForSkill(skillId); 0751 SessionDataModel *dm = map->value(property).value<SessionDataModel *>(); 0752 0753 if (!dm) { 0754 qWarning() << "Error: no list model existing under property" << property << "in mycroft.session.list.move"; 0755 return; 0756 } 0757 0758 const int position = doc[QStringLiteral("position")].toInt(); 0759 const int itemsNumber = doc[QStringLiteral("items_number")].toInt(); 0760 0761 if (position < 0 || position > dm->rowCount() - 1) { 0762 qWarning() << "Error: Invalid position in mycroft.session.list.remove of mycroft.system.active_skills"; 0763 return; 0764 } 0765 if (itemsNumber < 0 || itemsNumber > dm->rowCount() - position) { 0766 qWarning() << "Error: Invalid items_number in mycroft.session.list.remove of mycroft.system.active_skills"; 0767 return; 0768 } 0769 0770 dm->removeRows(position, itemsNumber); 0771 //END DATA MODELS 0772 0773 0774 //BEGIN EVENTS 0775 // Action triggered from the server 0776 } else if (type == QLatin1String("mycroft.events.triggered")) { 0777 const QString skillOrSystem = doc[QStringLiteral("namespace")].toString(); 0778 0779 if (skillOrSystem.isEmpty()) { 0780 qWarning() << "No namespace provided for mycroft.events.triggered"; 0781 return; 0782 } 0783 /*FIXME: do we need to keep this check? we need to also include skills without gui 0784 // If it's a skill it must exist 0785 if (skillOrSystem != QLatin1String("system") && !m_activeSkillsModel->skillIndex(skillOrSystem).isValid()) { 0786 qWarning() << "Invalid skill id passed as namespace for mycroft.events.triggered:" << skillOrSystem; 0787 return; 0788 }*/ 0789 0790 const QString eventName = doc[QStringLiteral("event_name")].toString(); 0791 if (eventName.isEmpty()) { 0792 qWarning() << "No namespace provided for mycroft.events.triggered"; 0793 return; 0794 } 0795 0796 // data can also be empty 0797 const QVariantMap data = doc[QStringLiteral("data")].toVariant().toMap(); 0798 0799 QList<AbstractDelegate *> delegates; 0800 0801 if (skillOrSystem == QLatin1String("system")) { 0802 for (auto *delegatesModel : activeSkills()->delegatesModels()) { 0803 delegates << delegatesModel->delegates(); 0804 } 0805 } else { 0806 DelegatesModel *delegatesModel = activeSkills()->delegatesModelForSkill(skillOrSystem); 0807 if (delegatesModel) { 0808 delegates << delegatesModel->delegates(); 0809 } 0810 } 0811 0812 // page_gained_focus is special: interests only one single delegate 0813 if (eventName == QStringLiteral("page_gained_focus")) { 0814 int pos = data.value(QStringLiteral("number")).toInt(); 0815 if (pos >= 0 && pos < delegates.count()) { 0816 AbstractDelegate *delegate = delegates[pos]; 0817 delegate->forceActiveFocus((Qt::FocusReason)ServerEventFocusReason); 0818 emit delegate->guiEvent(eventName, data); 0819 } 0820 } else if (eventName == QStringLiteral("mycroft.gui.close.screen")) { 0821 emit activeSkillClosed(); 0822 } else { 0823 for (auto *delegate : delegates) { 0824 emit delegate->guiEvent(eventName, data); 0825 } 0826 } 0827 } else { 0828 qWarning() << "Unrecognized operation" << type; 0829 } 0830 //END EVENTS 0831 } 0832 0833 #include "moc_abstractskillview.cpp"