- Add Proof of Concept (PoC) for executing JavaScript scripts fetched remotely. - Integrate `Black::GetProfileInfoScript` logic into a JS version with fallback to C++ on errors. - Extend `ScreenXmlParser` with additional JS-to-C++ bridge methods. - Update `config.ini` for scripting configuration. - Ensure scripts are securely fetched with SHA-256 validation and use an atomic caching mechanism. - Add developer tools for debugging JS scripts and enable easier script updates without app re-deployment.
89 lines
2.8 KiB
C++
89 lines
2.8 KiB
C++
#include "ScriptCache.h"
|
||
|
||
#include <QDir>
|
||
#include <QFile>
|
||
#include <QFileInfo>
|
||
#include <QSaveFile>
|
||
#include <QString>
|
||
|
||
#include "ScriptingConfig.h"
|
||
|
||
namespace Scripting {
|
||
|
||
namespace {
|
||
|
||
QString scriptRelPath(const QString &bank, const QString &scriptName) {
|
||
return bank + "/" + scriptName + ".js";
|
||
}
|
||
|
||
QString versionRelPath(const QString &bank, const QString &scriptName) {
|
||
return bank + "/" + scriptName + ".version";
|
||
}
|
||
|
||
} // namespace
|
||
|
||
bool ScriptCache::resolve(const QString &bank, const QString &scriptName,
|
||
QString *outPath, QString *outError) {
|
||
const QString rel = scriptRelPath(bank, scriptName);
|
||
|
||
const QString cached = QDir(ScriptingConfig::cacheDir()).filePath(rel);
|
||
if (QFileInfo::exists(cached)) {
|
||
if (outPath) *outPath = cached;
|
||
return true;
|
||
}
|
||
|
||
const QString bundled = QDir(ScriptingConfig::bundledDir()).filePath(rel);
|
||
if (QFileInfo::exists(bundled)) {
|
||
if (outPath) *outPath = bundled;
|
||
return true;
|
||
}
|
||
|
||
if (outError) {
|
||
*outError = "not found in cache (" + cached + ") nor bundled (" + bundled + ")";
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool ScriptCache::installFromBytes(const QString &bank, const QString &scriptName,
|
||
const QString &version, const QByteArray &body,
|
||
QString *outError) {
|
||
const QString dirPath = QDir(ScriptingConfig::cacheDir()).filePath(bank);
|
||
QDir().mkpath(dirPath);
|
||
|
||
const QString fullPath = QDir(ScriptingConfig::cacheDir()).filePath(scriptRelPath(bank, scriptName));
|
||
|
||
QSaveFile f(fullPath);
|
||
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||
if (outError) *outError = "cannot open for write: " + fullPath + " — " + f.errorString();
|
||
return false;
|
||
}
|
||
if (f.write(body) != body.size()) {
|
||
if (outError) *outError = "short write to " + fullPath;
|
||
return false;
|
||
}
|
||
if (!f.commit()) {
|
||
if (outError) *outError = "commit failed for " + fullPath + ": " + f.errorString();
|
||
return false;
|
||
}
|
||
|
||
// Sidecar с версией. Не критичен — если упал, скрипт всё равно установлен.
|
||
const QString verPath = QDir(ScriptingConfig::cacheDir()).filePath(versionRelPath(bank, scriptName));
|
||
QFile vf(verPath);
|
||
if (vf.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||
vf.write(version.toUtf8());
|
||
vf.close();
|
||
}
|
||
return true;
|
||
}
|
||
|
||
QString ScriptCache::installedVersion(const QString &bank, const QString &scriptName) {
|
||
const QString verPath = QDir(ScriptingConfig::cacheDir()).filePath(versionRelPath(bank, scriptName));
|
||
QFile vf(verPath);
|
||
if (!vf.open(QIODevice::ReadOnly)) return {};
|
||
const QByteArray b = vf.readAll();
|
||
vf.close();
|
||
return QString::fromUtf8(b).trimmed();
|
||
}
|
||
|
||
} // namespace Scripting
|