#include "scriptengine.h" #include "devices/devicemanager.h" #include "scriptaction.h" #include "scriptevent.h" #include "scriptstate.h" #include "nymeasettings.h" #include #include #include #include #include #include "loggingcategories.h" namespace nymeaserver { ScriptEngine::ScriptEngine(DeviceManager *deviceManager, QObject *parent) : QObject(parent), m_deviceManager(deviceManager) { qmlRegisterType("nymea", 1, 0, "Event"); qmlRegisterType("nymea", 1, 0, "Action"); qmlRegisterType("nymea", 1, 0, "State"); m_engine = new QQmlApplicationEngine(this); m_engine->setProperty("deviceManager", reinterpret_cast(m_deviceManager)); loadScripts(); } Scripts ScriptEngine::scripts() { Scripts ret; foreach (Script *script, m_scripts) { ret.append(*script); } return ret; } ScriptEngine::AddScriptReply ScriptEngine::addScript(const QString &name, const QByteArray &content) { QUuid id = QUuid::createUuid(); QString fileName = baseName(id) + ".qml"; QString jsonFileName = baseName(id) + ".json"; AddScriptReply reply; QFile jsonFile(jsonFileName); if (!jsonFile.open(QFile::ReadWrite)) { qCWarning(dcScriptEngine()) << "Error opening script metadata" << jsonFileName; reply.scriptError = ScriptErrorHardwareFailure; return reply; } QVariantMap metadata; metadata.insert("name", name); jsonFile.write(QJsonDocument::fromVariant(metadata).toJson()); jsonFile.close(); QFile scriptFile(fileName); if (!scriptFile.open(QFile::WriteOnly)) { qCWarning(dcScriptEngine()) << "Error opening script file:" << fileName; reply.scriptError = ScriptErrorHardwareFailure; return reply; } qint64 len = scriptFile.write(content); if (len != content.length()) { qCWarning(dcScriptEngine()) << "Error writing script content"; reply.scriptError = ScriptErrorHardwareFailure; return reply; } scriptFile.close(); Script *script = new Script(); script->setId(id); script->setName(name); bool loaded = loadScript(script); if (!loaded) { reply.scriptError = ScriptErrorInvalidScript; reply.errors = script->errors; delete script; return reply; } m_scripts.insert(script->id(), script); reply.scriptError = ScriptErrorNoError; reply.script = *m_scripts.value(id); return reply; } ScriptEngine::EditScriptReply ScriptEngine::editScript(const QUuid &id, const QByteArray &content) { QString scriptFileName = baseName(id) + ".qml"; QFile scriptFile(scriptFileName); EditScriptReply reply; if (!m_scripts.contains(id)) { qCWarning(dcScriptEngine()) << "No script with id" << id; reply.scriptError = ScriptErrorScriptNotFound; return reply; } Script *script = m_scripts.value(id); unloadScript(script); // Deleted compiled qml file to make sure we're reloading the new one QString compiledScriptFileName = baseName(id) + ".qmlc"; QFile::remove(compiledScriptFileName); if (!scriptFile.open(QFile::ReadWrite | QFile::Truncate)) { qCWarning(dcScriptEngine()) << "Error opening script" << id; reply.scriptError = ScriptErrorHardwareFailure; return reply; } QByteArray oldContent = scriptFile.readAll(); scriptFile.seek(0); qint64 bytesWritten = scriptFile.write(content); if (bytesWritten != content.length()) { qCWarning(dcScriptEngine()) << "Error writing script content"; reply.scriptError = ScriptErrorHardwareFailure; return reply; } bool loaded = loadScript(script); if (!loaded) { reply.scriptError = ScriptErrorInvalidScript; reply.errors = script->errors; // Restore old content scriptFile.seek(0); scriptFile.write(oldContent); loadScript(script); return reply; } reply.scriptError = ScriptErrorNoError; return reply; } ScriptEngine::ScriptError ScriptEngine::removeScript(const QUuid &id) { Script *script = m_scripts.take(id); if (!script) { return ScriptErrorScriptNotFound; } unloadScript(script); QString jsonFileName = baseName(id) + ".json"; QString scriptFileName = baseName(id) + ".qml"; QString compiledScriptFileName = baseName(id) + ".qmlc"; QFile::remove(scriptFileName); QFile::remove(jsonFileName); QFile::remove(compiledScriptFileName); delete script; return ScriptErrorNoError; } void ScriptEngine::loadScripts() { // QString fileName = "/home/micha/Develop/nymea/tests/script.qml"; // loadScript(fileName); } bool ScriptEngine::loadScript(Script *script) { QString fileName = baseName(script->id()) + ".qml"; QString jsonFileName = baseName(script->id()) + ".json"; QFile jsonFile(jsonFileName); if (!jsonFile.open(QFile::ReadOnly)) { qCWarning(dcScriptEngine()) << "Failed to open script metadata"; return false; } QJsonParseError error; QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonFile.readAll(), &error); jsonFile.close(); if (error.error != QJsonParseError::NoError) { qCWarning(dcScriptEngine()) << "Failed to parse script metadata"; return false; } QString name = jsonDoc.toVariant().toMap().value("name").toString(); qCWarning(dcScriptEngine()) << "Loading script"; script->component = new QQmlComponent(m_engine, QUrl::fromLocalFile(fileName), this); script->context = new QQmlContext(m_engine, this); script->object = script->component->create(script->context); if (!script->object) { qCWarning(dcScriptEngine()) << "Script failed to load:"; foreach (const QQmlError &error, script->component->errors()) { qCWarning(dcScriptEngine()) << error.toString(); script->errors.append(QString("%1:%2: %3").arg(error.line()).arg(error.column()).arg(error.description())); } delete script->context; delete script->component; return false; } return true; } void ScriptEngine::unloadScript(Script *script) { delete script->object; script->object = nullptr; delete script->component; script->component = nullptr; delete script->context; script->context = nullptr; } QString ScriptEngine::baseName(const QUuid &id) { QString path = NymeaSettings::storagePath() + '/'; QString basename = id.toString().remove(QRegExp("[{}]")); return path + basename; } }