diff --git a/src/ui/FortFirewall.pro b/src/ui/FortFirewall.pro index a6cf365c..70458bc2 100644 --- a/src/ui/FortFirewall.pro +++ b/src/ui/FortFirewall.pro @@ -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 \ diff --git a/src/ui/conf/firewallconf.cpp b/src/ui/conf/firewallconf.cpp index 69f33a94..cdf55d13 100644 --- a/src/ui/conf/firewallconf.cpp +++ b/src/ui/conf/firewallconf.cpp @@ -201,6 +201,15 @@ QQmlListProperty FirewallConf::addressGroups() return QQmlListProperty(this, m_addressGroups); } +AppGroup *FirewallConf::appGroupByName(const QString &name) const +{ + foreach (AppGroup *appGroup, appGroupsList()) { + if (appGroup->name() == name) + return appGroup; + } + return nullptr; +} + QQmlListProperty FirewallConf::appGroups() { return QQmlListProperty(this, m_appGroups); diff --git a/src/ui/conf/firewallconf.h b/src/ui/conf/firewallconf.h index 85783e87..66c6bfca 100644 --- a/src/ui/conf/firewallconf.h +++ b/src/ui/conf/firewallconf.h @@ -118,6 +118,8 @@ public: const QList &addressGroupsList() const { return m_addressGroups; } QQmlListProperty addressGroups(); + Q_INVOKABLE AppGroup *appGroupByName(const QString &name) const; + const QList &appGroupsList() const { return m_appGroups; } QQmlListProperty appGroups(); diff --git a/src/ui/control/controlmanager.cpp b/src/ui/control/controlmanager.cpp new file mode 100644 index 00000000..a51bb4a5 --- /dev/null +++ b/src/ui/control/controlmanager.cpp @@ -0,0 +1,135 @@ +#include "controlmanager.h" + +#include +#include +#include + +#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; +} diff --git a/src/ui/control/controlmanager.h b/src/ui/control/controlmanager.h new file mode 100644 index 00000000..9eea5104 --- /dev/null +++ b/src/ui/control/controlmanager.h @@ -0,0 +1,51 @@ +#ifndef CONTROLMANAGER_H +#define CONTROLMANAGER_H + +#include +#include +#include + +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 diff --git a/src/ui/control/controlworker.cpp b/src/ui/control/controlworker.cpp new file mode 100644 index 00000000..73b97d26 --- /dev/null +++ b/src/ui/control/controlworker.cpp @@ -0,0 +1,114 @@ +#include "controlworker.h" + +#include +#include +#include + +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(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(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; +} diff --git a/src/ui/control/controlworker.h b/src/ui/control/controlworker.h new file mode 100644 index 00000000..bee13ea8 --- /dev/null +++ b/src/ui/control/controlworker.h @@ -0,0 +1,49 @@ +#ifndef CONTROLWORKER_H +#define CONTROLWORKER_H + +#include +#include + +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 diff --git a/src/ui/db/databasemanager.cpp b/src/ui/db/databasemanager.cpp index 5d1c47e0..030b1f0a 100644 --- a/src/ui/db/databasemanager.cpp +++ b/src/ui/db/databasemanager.cpp @@ -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) diff --git a/src/ui/db/databasemanager.h b/src/ui/db/databasemanager.h index 39ce44d5..8a6cc002 100644 --- a/src/ui/db/databasemanager.h +++ b/src/ui/db/databasemanager.h @@ -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 diff --git a/src/ui/fortsettings.cpp b/src/ui/fortsettings.cpp index 81c061d0..bbf2438b 100644 --- a/src/ui/fortsettings.cpp +++ b/src/ui/fortsettings.cpp @@ -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() diff --git a/src/ui/fortsettings.h b/src/ui/fortsettings.h index 278dc330..c8acaf97 100644 --- a/src/ui/fortsettings.h +++ b/src/ui/fortsettings.h @@ -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; diff --git a/src/ui/i18n/i18n_ru.qm b/src/ui/i18n/i18n_ru.qm index 4b354814..da5c7551 100644 Binary files a/src/ui/i18n/i18n_ru.qm and b/src/ui/i18n/i18n_ru.qm differ diff --git a/src/ui/i18n/i18n_ru.ts b/src/ui/i18n/i18n_ru.ts index 651f73de..4b76ea20 100644 --- a/src/ui/i18n/i18n_ru.ts +++ b/src/ui/i18n/i18n_ru.ts @@ -39,6 +39,14 @@ Длина пути приложения должна быть < %1 + + ControlManager + + + Control script executed + Управляющий скрипт выполнен + + FortManager @@ -80,17 +88,17 @@ FortSettings - + Can't write .ini file Не удалось записать .ini файл - + Can't create .conf file Не удалось создать .conf файл - + Can't create backup .conf file Не удалось создать бэкап .conf файла diff --git a/src/ui/main.cpp b/src/ui/main.cpp index c07ae81a..988b2935 100644 --- a/src/ui/main.cpp +++ b/src/ui/main.cpp @@ -2,6 +2,8 @@ #include #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(); }