UI: Add --control <PATH> argument.

Control running instance's conf by executing scripts.
This commit is contained in:
Nodir Temirkhodjaev 2018-08-24 16:28:26 +05:00
parent 099c3de95f
commit cfddff8a06
14 changed files with 414 additions and 7 deletions

View File

@ -14,6 +14,8 @@ SOURCES += \
conf/addressgroup.cpp \
conf/appgroup.cpp \
conf/firewallconf.cpp \
control/controlmanager.cpp \
control/controlworker.cpp \
db/databasemanager.cpp \
db/databasesql.cpp \
db/quotamanager.cpp \
@ -70,6 +72,8 @@ HEADERS += \
conf/addressgroup.h \
conf/appgroup.h \
conf/firewallconf.h \
control/controlmanager.h \
control/controlworker.h \
db/databasemanager.h \
db/databasesql.h \
db/quotamanager.h \

View File

@ -201,6 +201,15 @@ QQmlListProperty<AddressGroup> FirewallConf::addressGroups()
return QQmlListProperty<AddressGroup>(this, m_addressGroups);
}
AppGroup *FirewallConf::appGroupByName(const QString &name) const
{
foreach (AppGroup *appGroup, appGroupsList()) {
if (appGroup->name() == name)
return appGroup;
}
return nullptr;
}
QQmlListProperty<AppGroup> FirewallConf::appGroups()
{
return QQmlListProperty<AppGroup>(this, m_appGroups);

View File

@ -118,6 +118,8 @@ public:
const QList<AddressGroup *> &addressGroupsList() const { return m_addressGroups; }
QQmlListProperty<AddressGroup> addressGroups();
Q_INVOKABLE AppGroup *appGroupByName(const QString &name) const;
const QList<AppGroup *> &appGroupsList() const { return m_appGroups; }
QQmlListProperty<AppGroup> appGroups();

View File

@ -0,0 +1,135 @@
#include "controlmanager.h"
#include <QJSEngine>
#include <QLoggingCategory>
#include <QThreadPool>
#include "../conf/firewallconf.h"
#include "../util/fileutil.h"
#include "controlworker.h"
#include "fortmanager.h"
Q_DECLARE_LOGGING_CATEGORY(CLOG_CONTROL_MANAGER)
Q_LOGGING_CATEGORY(CLOG_CONTROL_MANAGER, "fort.controlManager")
ControlManager::ControlManager(const QString &globalName,
const QString &scriptPath,
QObject *parent) :
QObject(parent),
m_isClient(!scriptPath.isEmpty()),
m_scriptPath(scriptPath),
m_fortManager(nullptr),
m_worker(nullptr),
m_semaphore(globalName + QLatin1String("_ControlSemaphore"), 0,
isClient() ? QSystemSemaphore::Open : QSystemSemaphore::Create),
m_sharedMemory(globalName + QLatin1String("_ControlSharedMemory"))
{
}
ControlManager::~ControlManager()
{
abort();
}
bool ControlManager::listen(FortManager *fortManager)
{
if (m_sharedMemory.size() > 0)
return true;
if (!m_sharedMemory.create(4096)) {
qWarning(CLOG_CONTROL_MANAGER()) << "Shared Memory create error:"
<< m_sharedMemory.errorString();
return false;
}
m_fortManager = fortManager;
if (!m_worker) {
setupWorker();
}
return true;
}
bool ControlManager::post(const QStringList &args)
{
if (!m_sharedMemory.attach()) {
qWarning(CLOG_CONTROL_MANAGER()) << "Shared Memory attach error:"
<< m_sharedMemory.errorString();
return false;
}
ControlWorker worker(&m_semaphore, &m_sharedMemory);
return worker.post(m_scriptPath, args);
}
void ControlManager::processRequest(const QString &scriptPath,
const QStringList &args)
{
const QString script = FileUtil::readFile(scriptPath);
if (script.isEmpty()) {
qWarning(CLOG_CONTROL_MANAGER()) << "Script is empty:"
<< scriptPath;
return;
}
QJSEngine engine;
engine.installExtensions(QJSEngine::ConsoleExtension);
QJSValue globalObject = engine.globalObject();
// Arguments
QJSValue argsJs = engine.newArray(args.size());
QJSValue argsMapJs = engine.newObject();
for (int i = 0, n = args.size(); i < n; ++i) {
const QString &arg = args.at(i);
argsJs.setProperty(i, arg);
const int sepPos = arg.indexOf('=');
if (sepPos > 0) {
const QString k = arg.left(sepPos);
const QString v = arg.mid(sepPos + 1);
argsMapJs.setProperty(k, v);
}
}
globalObject.setProperty("args", argsJs);
globalObject.setProperty("arg", argsMapJs);
// FirewallConf
QJSValue firewallConfJs = engine.newQObject(
m_fortManager->firewallConf());
globalObject.setProperty("conf", firewallConfJs);
// Run the script
const QJSValue res = engine.evaluate(script, scriptPath);
if (res.isError()) {
qWarning(CLOG_CONTROL_MANAGER()) << "Script error:"
<< scriptPath << "line"
<< res.property("lineNumber").toInt()
<< ":" << res.toString();
return;
}
m_fortManager->saveOriginConf(tr("Control script executed"));
}
void ControlManager::setupWorker()
{
m_worker = new ControlWorker(&m_semaphore, &m_sharedMemory); // autoDelete = true
connect(m_worker, &ControlWorker::requestReady,
this, &ControlManager::processRequest);
QThreadPool::globalInstance()->start(m_worker);
}
void ControlManager::abort()
{
if (!m_worker) return;
m_worker->disconnect(this);
m_worker->abort();
m_worker = nullptr;
}

View File

@ -0,0 +1,51 @@
#ifndef CONTROLMANAGER_H
#define CONTROLMANAGER_H
#include <QObject>
#include <QSharedMemory>
#include <QSystemSemaphore>
QT_FORWARD_DECLARE_CLASS(ControlWorker)
QT_FORWARD_DECLARE_CLASS(FortManager)
class ControlManager : public QObject
{
Q_OBJECT
public:
explicit ControlManager(const QString &globalName,
const QString &scriptPath,
QObject *parent = nullptr);
virtual ~ControlManager();
bool isClient() const { return m_isClient; }
bool listen(FortManager *fortManager);
bool post(const QStringList &args);
signals:
public slots:
private slots:
void processRequest(const QString &scriptPath,
const QStringList &args);
private:
void setupWorker();
void abort();
private:
bool m_isClient;
QString m_scriptPath;
FortManager *m_fortManager;
ControlWorker *m_worker;
QSystemSemaphore m_semaphore;
QSharedMemory m_sharedMemory;
};
#endif // CONTROLMANAGER_H

View File

@ -0,0 +1,114 @@
#include "controlworker.h"
#include <QDataStream>
#include <QSharedMemory>
#include <QSystemSemaphore>
ControlWorker::ControlWorker(QSystemSemaphore *semaphore,
QSharedMemory *sharedMemory,
QObject *parent) :
QObject(parent),
m_aborted(false),
m_semaphore(semaphore),
m_sharedMemory(sharedMemory)
{
}
void ControlWorker::run()
{
while (m_semaphore->acquire() && !m_aborted) {
processRequest();
}
}
void ControlWorker::abort()
{
m_aborted = true;
m_semaphore->release();
}
bool ControlWorker::post(const QString &scriptPath,
const QStringList &args)
{
m_sharedMemory->lock();
const bool res = writeDataStream(scriptPath, args);
m_sharedMemory->unlock();
if (res) {
m_semaphore->release();
}
return res;
}
void ControlWorker::processRequest()
{
QString scriptPath;
QStringList args;
m_sharedMemory->lock();
const bool res = readDataStream(scriptPath, args);
m_sharedMemory->unlock();
if (res) {
emit requestReady(scriptPath, args);
}
}
bool ControlWorker::writeData(const QByteArray &data)
{
const int dataSize = data.size();
if (dataSize < 0 || int(sizeof(int)) + dataSize > m_sharedMemory->size())
return false;
int *p = static_cast<int *>(m_sharedMemory->data());
*p++ = dataSize;
if (dataSize != 0) {
memcpy(p, data.constData(), data.size());
}
return true;
}
QByteArray ControlWorker::readData() const
{
const int *p = static_cast<const int *>(m_sharedMemory->constData());
const int dataSize = *p++;
if (dataSize < 0 || int(sizeof(int)) + dataSize > m_sharedMemory->size())
return QByteArray();
return QByteArray::fromRawData((const char *) p, dataSize);
}
bool ControlWorker::writeDataStream(const QString &scriptPath,
const QStringList &args)
{
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
stream << scriptPath << args;
return writeData(data);
}
bool ControlWorker::readDataStream(QString &scriptPath,
QStringList &args) const
{
const QByteArray data = readData();
if (data.isEmpty())
return false;
QDataStream stream(data);
stream >> scriptPath >> args;
return true;
}

View File

@ -0,0 +1,49 @@
#ifndef CONTROLWORKER_H
#define CONTROLWORKER_H
#include <QObject>
#include <QRunnable>
QT_FORWARD_DECLARE_CLASS(QSharedMemory)
QT_FORWARD_DECLARE_CLASS(QSystemSemaphore)
class ControlWorker : public QObject, public QRunnable
{
Q_OBJECT
public:
explicit ControlWorker(QSystemSemaphore *semaphore,
QSharedMemory *sharedMemory,
QObject *parent = nullptr);
void run() override;
bool post(const QString &scriptPath,
const QStringList &args);
signals:
void requestReady(const QString &scriptPath,
const QStringList &args);
public slots:
void abort();
private:
void processRequest();
bool writeData(const QByteArray &data);
QByteArray readData() const;
bool writeDataStream(const QString &scriptPath,
const QStringList &args);
bool readDataStream(QString &scriptPath,
QStringList &args) const;
private:
volatile bool m_aborted;
QSystemSemaphore *m_semaphore;
QSharedMemory *m_sharedMemory;
};
#endif // CONTROLWORKER_H

View File

@ -12,7 +12,7 @@
#include "sqlite/sqlitestmt.h"
Q_DECLARE_LOGGING_CATEGORY(CLOG_DATABASE_MANAGER)
Q_LOGGING_CATEGORY(CLOG_DATABASE_MANAGER, CLOG_DATABASE_MANAGER_STR)
Q_LOGGING_CATEGORY(CLOG_DATABASE_MANAGER, "fort.databaseManager")
#define INVALID_APP_INDEX qint16(-1)
#define INVALID_APP_ID qint64(-1)

View File

@ -11,8 +11,6 @@ QT_FORWARD_DECLARE_CLASS(QuotaManager)
QT_FORWARD_DECLARE_CLASS(SqliteDb)
QT_FORWARD_DECLARE_CLASS(SqliteStmt)
#define CLOG_DATABASE_MANAGER_STR "fort.databaseManager"
class DatabaseManager : public QObject
{
Q_OBJECT

View File

@ -56,6 +56,11 @@ void FortSettings::processArguments(const QStringList &args)
"Directory to store statistics.", "stat");
parser.addOption(statOption);
const QCommandLineOption controlOption(
QStringList() << "c" << "control",
"Control running instance by executing the JS file.", "control");
parser.addOption(controlOption);
parser.addVersionOption();
parser.addHelpOption();
@ -80,6 +85,15 @@ void FortSettings::processArguments(const QStringList &args)
m_statPath = FileUtil::pathSlash(
FileUtil::absolutePath(m_statPath));
}
// Control QML file path
m_controlPath = parser.value(controlOption);
if (!m_controlPath.isEmpty()) {
m_controlPath = FileUtil::absolutePath(m_controlPath);
}
// Other Arguments
m_args = parser.positionalArguments();
}
void FortSettings::setupIni()

View File

@ -85,6 +85,10 @@ public:
QString statPath() const { return m_statPath; }
QString statFilePath() const;
QString controlPath() const { return m_controlPath; }
QStringList args() const { return m_args; }
QString errorMessage() const { return m_errorMessage; }
QString appUpdatesUrl() const { return APP_UPDATES_URL; }
@ -140,6 +144,8 @@ private:
QString m_profilePath;
QString m_statPath;
QString m_controlPath;
QStringList m_args;
QString m_errorMessage;

Binary file not shown.

View File

@ -39,6 +39,14 @@
<translation>Длина пути приложения должна быть &lt; %1</translation>
</message>
</context>
<context>
<name>ControlManager</name>
<message>
<location filename="../control/controlmanager.cpp" line="114"/>
<source>Control script executed</source>
<translation>Управляющий скрипт выполнен</translation>
</message>
</context>
<context>
<name>FortManager</name>
<message>
@ -80,17 +88,17 @@
<context>
<name>FortSettings</name>
<message>
<location filename="../fortsettings.cpp" line="191"/>
<location filename="../fortsettings.cpp" line="205"/>
<source>Can&apos;t write .ini file</source>
<translation>Не удалось записать .ini файл</translation>
</message>
<message>
<location filename="../fortsettings.cpp" line="206"/>
<location filename="../fortsettings.cpp" line="220"/>
<source>Can&apos;t create .conf file</source>
<translation>Не удалось создать .conf файл</translation>
</message>
<message>
<location filename="../fortsettings.cpp" line="201"/>
<location filename="../fortsettings.cpp" line="215"/>
<source>Can&apos;t create backup .conf file</source>
<translation>Не удалось создать бэкап .conf файла</translation>
</message>

View File

@ -2,6 +2,8 @@
#include <QMessageBox>
#include "../common/version.h"
#include "control/controlmanager.h"
#include "control/controlworker.h"
#include "driver/drivermanager.h"
#include "fortcommon.h"
#include "fortmanager.h"
@ -10,6 +12,7 @@
#define FORT_ERROR_INSTANCE 1
#define FORT_ERROR_DEVICE 2
#define FORT_ERROR_CONTROL 3
int main(int argc, char *argv[])
{
@ -31,7 +34,16 @@ int main(int argc, char *argv[])
return 0;
}
// To check running instance
ControlManager controlManager(app.applicationName(),
fortSettings.controlPath());
// Send control request to running instance
if (controlManager.isClient()) {
return controlManager.post(fortSettings.args())
? 0 : FORT_ERROR_CONTROL;
}
// Check running instance
if (!OsUtil::createGlobalMutex(APP_NAME)) {
QMessageBox::critical(nullptr, QString(),
"Application is already running!");
@ -49,5 +61,10 @@ int main(int argc, char *argv[])
fortManager.showTrayIcon();
// Process control requests from clients
if (!controlManager.listen(&fortManager)) {
return FORT_ERROR_CONTROL;
}
return app.exec();
}