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 &parameters)
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"