146 lines
5.6 KiB
C++
146 lines
5.6 KiB
C++
#include "ScriptRuntime.h"
|
|
|
|
#include <QDebug>
|
|
#include <QFile>
|
|
#include <QJSEngine>
|
|
#include <QJSValue>
|
|
#include <QString>
|
|
#include <QThread>
|
|
|
|
#include "ScriptBridges.h"
|
|
#include "ScriptCache.h"
|
|
#include "ScriptingConfig.h"
|
|
|
|
namespace Scripting {
|
|
|
|
namespace {
|
|
|
|
// Конверсия QJSValue → строка-результат скрипта.
|
|
// null/undefined → пустая строка (успех).
|
|
// "" → пустая строка (успех).
|
|
// строка → текст бизнес-ошибки.
|
|
// число/булево/объект → toString() как diagnostic (рассматриваем как ошибку).
|
|
QString jsValueToResultString(const QJSValue &v) {
|
|
if (v.isUndefined() || v.isNull()) return QString();
|
|
if (v.isString()) return v.toString();
|
|
return v.toString();
|
|
}
|
|
|
|
QString locationOf(const QJSValue &err) {
|
|
const int line = err.property("lineNumber").toInt();
|
|
const QString file = err.property("fileName").toString();
|
|
if (line > 0 || !file.isEmpty()) {
|
|
return QStringLiteral(" at %1:%2").arg(file.isEmpty() ? QStringLiteral("<script>") : file).arg(line);
|
|
}
|
|
return {};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
ScriptRuntime::Result ScriptRuntime::run(const QString &bank,
|
|
const QString &scriptName,
|
|
const QVariantMap ¶ms,
|
|
QString *outResult,
|
|
QObject *signalSink) {
|
|
QString sink;
|
|
if (!outResult) outResult = &sink;
|
|
*outResult = QString();
|
|
|
|
const QString key = bank + "/" + scriptName;
|
|
|
|
if (!ScriptingConfig::isEnabled() || !ScriptingConfig::isJsEnabledFor(key)) {
|
|
*outResult = "JS disabled for " + key;
|
|
return Result::Disabled;
|
|
}
|
|
|
|
QString sourcePath;
|
|
QString loadError;
|
|
if (!ScriptCache::resolve(bank, scriptName, &sourcePath, &loadError)) {
|
|
*outResult = "Script not available: " + loadError;
|
|
return Result::Disabled;
|
|
}
|
|
|
|
QFile f(sourcePath);
|
|
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
*outResult = "Cannot read script file: " + sourcePath;
|
|
return Result::EngineError;
|
|
}
|
|
const QString source = QString::fromUtf8(f.readAll());
|
|
f.close();
|
|
|
|
QJSEngine engine;
|
|
engine.installExtensions(QJSEngine::ConsoleExtension);
|
|
|
|
// Bridges живут на стеке здесь, в worker-потоке. Эти объекты не должны
|
|
// переживать engine — QJSEngine удаляет их при разрушении (мы их newQObject'нули),
|
|
// поэтому передаём nullptr как родителя — engine берёт владение.
|
|
auto *log = new LogBridge(key);
|
|
auto *timer = new TimerBridge();
|
|
auto *adb = new AdbBridge();
|
|
auto *dao = new DaoBridge();
|
|
auto *dump = new DumpBridge();
|
|
auto *ocr = new OcrBridge();
|
|
auto *scriptObj = new ScriptObjectBridge(signalSink);
|
|
|
|
// Bank-specific bridges. Поддержаны: Black (полный набор), Ozon (минимум
|
|
// для get_profile_info — расширяется по мере миграции login_and_check / get_card_info).
|
|
QObject *parserBridge = nullptr;
|
|
QObject *helpersBridge = nullptr;
|
|
if (bank == QLatin1String("black")) {
|
|
auto *pb = new BlackParserBridge();
|
|
parserBridge = pb;
|
|
helpersBridge = new BlackHelpersBridge(pb);
|
|
} else if (bank == QLatin1String("ozon")) {
|
|
auto *pb = new OzonParserBridge();
|
|
parserBridge = pb;
|
|
helpersBridge = new OzonHelpersBridge(pb);
|
|
}
|
|
|
|
QJSValue host = engine.newObject();
|
|
host.setProperty("log", engine.newQObject(log));
|
|
host.setProperty("timer", engine.newQObject(timer));
|
|
host.setProperty("adb", engine.newQObject(adb));
|
|
host.setProperty("dao", engine.newQObject(dao));
|
|
host.setProperty("dump", engine.newQObject(dump));
|
|
host.setProperty("ocr", engine.newQObject(ocr));
|
|
host.setProperty("script", engine.newQObject(scriptObj));
|
|
if (parserBridge) host.setProperty("parser", engine.newQObject(parserBridge));
|
|
if (helpersBridge) host.setProperty("helpers", engine.newQObject(helpersBridge));
|
|
|
|
// params: конвертируем QVariantMap → JS object
|
|
QJSValue jsParams = engine.toScriptValue(params);
|
|
host.setProperty("params", jsParams);
|
|
|
|
engine.globalObject().setProperty("host", host);
|
|
|
|
// Оборачиваем исходник так, чтобы он МОГ просто определять run(host) и не
|
|
// быть IIFE. Сначала evaluate определений, потом достаём `run` из globalObject.
|
|
QJSValue evalResult = engine.evaluate(source, sourcePath);
|
|
if (evalResult.isError()) {
|
|
*outResult = QStringLiteral("JS parse/eval error: %1%2")
|
|
.arg(evalResult.toString(), locationOf(evalResult));
|
|
qWarning() << "[Scripting]" << *outResult;
|
|
return Result::EngineError;
|
|
}
|
|
|
|
QJSValue runFn = engine.globalObject().property("run");
|
|
if (!runFn.isCallable()) {
|
|
*outResult = QStringLiteral("Script %1 did not define function run(host)").arg(key);
|
|
qWarning() << "[Scripting]" << *outResult;
|
|
return Result::EngineError;
|
|
}
|
|
|
|
QJSValue callResult = runFn.call(QJSValueList{} << host);
|
|
if (callResult.isError()) {
|
|
*outResult = QStringLiteral("JS runtime error: %1%2")
|
|
.arg(callResult.toString(), locationOf(callResult));
|
|
qWarning() << "[Scripting]" << *outResult;
|
|
return Result::EngineError;
|
|
}
|
|
|
|
*outResult = jsValueToResultString(callResult);
|
|
return Result::Completed;
|
|
}
|
|
|
|
} // namespace Scripting
|