mirror of
https://github.com/tnodir/fort
synced 2024-11-15 07:45:22 +00:00
UI: ConfUtil: Prepare writeRules()
This commit is contained in:
parent
7a821c2bcc
commit
f3248ac627
@ -148,7 +148,7 @@ typedef struct fort_conf_rule
|
|||||||
|
|
||||||
typedef struct fort_conf_rules
|
typedef struct fort_conf_rules
|
||||||
{
|
{
|
||||||
UINT32 rule_off[FORT_CONF_RULE_MAX];
|
UINT16 max_rule_id;
|
||||||
|
|
||||||
char data[4];
|
char data[4];
|
||||||
} FORT_CONF_RULES, *PFORT_CONF_RULES;
|
} FORT_CONF_RULES, *PFORT_CONF_RULES;
|
||||||
@ -159,6 +159,7 @@ typedef struct fort_conf_rule_flag
|
|||||||
UCHAR enabled;
|
UCHAR enabled;
|
||||||
} FORT_CONF_RULE_FLAG, *PFORT_CONF_RULE_FLAG;
|
} FORT_CONF_RULE_FLAG, *PFORT_CONF_RULE_FLAG;
|
||||||
|
|
||||||
|
#define FORT_CONF_RULES_DATA_OFF offsetof(FORT_CONF_RULES, data)
|
||||||
#define FORT_CONF_RULE_SIZE(rule) \
|
#define FORT_CONF_RULE_SIZE(rule) \
|
||||||
(sizeof(FORT_CONF_RULE) + ((rule)->has_zones ? sizeof(FORT_CONF_RULE_ZONES) : 0) \
|
(sizeof(FORT_CONF_RULE) + ((rule)->has_zones ? sizeof(FORT_CONF_RULE_ZONES) : 0) \
|
||||||
+ (rule)->set_count * sizeof(UINT16))
|
+ (rule)->set_count * sizeof(UINT16))
|
||||||
|
@ -397,6 +397,7 @@ HEADERS += \
|
|||||||
util/conf/addressrange.h \
|
util/conf/addressrange.h \
|
||||||
util/conf/appparseoptions.h \
|
util/conf/appparseoptions.h \
|
||||||
util/conf/confappswalker.h \
|
util/conf/confappswalker.h \
|
||||||
|
util/conf/confruleswalker.h \
|
||||||
util/conf/confutil.h \
|
util/conf/confutil.h \
|
||||||
util/dateutil.h \
|
util/dateutil.h \
|
||||||
util/device.h \
|
util/device.h \
|
||||||
|
@ -62,7 +62,8 @@ const char *const sqlSelectAppById = "SELECT" SELECT_APP_FIELDS " FROM app t"
|
|||||||
|
|
||||||
const char *const sqlSelectApps = "SELECT" SELECT_APP_FIELDS " FROM app t"
|
const char *const sqlSelectApps = "SELECT" SELECT_APP_FIELDS " FROM app t"
|
||||||
" JOIN app_group g ON g.app_group_id = t.app_group_id"
|
" JOIN app_group g ON g.app_group_id = t.app_group_id"
|
||||||
" LEFT JOIN app_alert alert ON alert.app_id = t.app_id;";
|
" LEFT JOIN app_alert alert ON alert.app_id = t.app_id"
|
||||||
|
" ORDER BY t.path;";
|
||||||
|
|
||||||
const char *const sqlSelectAppsToPurge = "SELECT app_id, path FROM app"
|
const char *const sqlSelectAppsToPurge = "SELECT app_id, path FROM app"
|
||||||
" WHERE is_wildcard = 0 AND parked = 0;";
|
" WHERE is_wildcard = 0 AND parked = 0;";
|
||||||
@ -491,7 +492,7 @@ QVector<qint64> ConfAppManager::collectObsoleteApps(quint32 driveMask)
|
|||||||
return appIdList;
|
return appIdList;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConfAppManager::walkApps(const std::function<walkAppsCallback> &func)
|
bool ConfAppManager::walkApps(const std::function<walkAppsCallback> &func) const
|
||||||
{
|
{
|
||||||
SqliteStmt stmt;
|
SqliteStmt stmt;
|
||||||
if (!DbQuery(sqliteDb()).sql(sqlSelectApps).prepare(stmt))
|
if (!DbQuery(sqliteDb()).sql(sqlSelectApps).prepare(stmt))
|
||||||
|
@ -43,7 +43,7 @@ public:
|
|||||||
virtual bool updateAppsBlocked(
|
virtual bool updateAppsBlocked(
|
||||||
const QVector<qint64> &appIdList, bool blocked, bool killProcess);
|
const QVector<qint64> &appIdList, bool blocked, bool killProcess);
|
||||||
|
|
||||||
bool walkApps(const std::function<walkAppsCallback> &func) override;
|
bool walkApps(const std::function<walkAppsCallback> &func) const override;
|
||||||
|
|
||||||
bool saveAppBlocked(const App &app);
|
bool saveAppBlocked(const App &app);
|
||||||
void updateAppEndTimes();
|
void updateAppEndTimes();
|
||||||
|
@ -18,6 +18,25 @@ namespace {
|
|||||||
|
|
||||||
const QLoggingCategory LC("confRule");
|
const QLoggingCategory LC("confRule");
|
||||||
|
|
||||||
|
#define SELECT_RULE_FIELDS \
|
||||||
|
" t.rule_id," \
|
||||||
|
" t.enabled," \
|
||||||
|
" t.blocked," \
|
||||||
|
" t.exclusive," \
|
||||||
|
" t.rule_text," \
|
||||||
|
" t.rule_type," \
|
||||||
|
" t.accept_zones," \
|
||||||
|
" t.reject_zones"
|
||||||
|
|
||||||
|
const char *const sqlSelectRules = "SELECT" SELECT_RULE_FIELDS " FROM rule t"
|
||||||
|
" ORDER BY t.rule_id;";
|
||||||
|
|
||||||
|
const char *const sqlSelectRuleSets = "SELECT t.rule_id, t.sub_rule_id"
|
||||||
|
" FROM rule_set t"
|
||||||
|
" ORDER BY t.rule_id, t.order_index;";
|
||||||
|
|
||||||
|
const char *const sqlSelectMaxRuleId = "SELECT MAX(rule_id) FROM rule;";
|
||||||
|
|
||||||
const char *const sqlInsertRule = "INSERT INTO rule(rule_id, enabled, blocked, exclusive,"
|
const char *const sqlInsertRule = "INSERT INTO rule(rule_id, enabled, blocked, exclusive,"
|
||||||
" name, notes, rule_text, rule_type,"
|
" name, notes, rule_text, rule_type,"
|
||||||
" accept_zones, reject_zones, mod_time)"
|
" accept_zones, reject_zones, mod_time)"
|
||||||
@ -191,8 +210,6 @@ bool ConfRuleManager::addOrUpdateRule(Rule &rule)
|
|||||||
.sql(sqlSelectRuleIds)
|
.sql(sqlSelectRuleIds)
|
||||||
.vars({ ConfUtil::ruleMaxCount() })
|
.vars({ ConfUtil::ruleMaxCount() })
|
||||||
.getFreeId(/*maxId=*/ConfUtil::ruleMaxCount() - 1);
|
.getFreeId(/*maxId=*/ConfUtil::ruleMaxCount() - 1);
|
||||||
} else {
|
|
||||||
updateDriverRuleFlag(rule.ruleId, rule.enabled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const QVariantList vars = {
|
const QVariantList vars = {
|
||||||
@ -220,6 +237,8 @@ bool ConfRuleManager::addOrUpdateRule(Rule &rule)
|
|||||||
if (!ok)
|
if (!ok)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
updateDriverRules();
|
||||||
|
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
emit ruleAdded();
|
emit ruleAdded();
|
||||||
} else {
|
} else {
|
||||||
@ -251,6 +270,8 @@ bool ConfRuleManager::deleteRule(int ruleId)
|
|||||||
|
|
||||||
if (ok) {
|
if (ok) {
|
||||||
emit ruleRemoved(ruleId);
|
emit ruleRemoved(ruleId);
|
||||||
|
|
||||||
|
updateDriverRules();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok;
|
return ok;
|
||||||
@ -296,16 +317,97 @@ bool ConfRuleManager::updateRuleEnabled(int ruleId, bool enabled)
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfRuleManager::updateDriverRules(quint32 rulesMask, quint32 enabledMask, quint32 dataSize,
|
bool ConfRuleManager::walkRules(ruleset_map_t &ruleSetMap, ruleid_arr_t &ruleIds, int &maxRuleId,
|
||||||
const QList<QByteArray> &rulesData)
|
const std::function<walkRulesCallback> &func) const
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
|
||||||
|
sqliteDb()->beginTransaction();
|
||||||
|
|
||||||
|
maxRuleId = DbQuery(sqliteDb()).sql(sqlSelectMaxRuleId).execute().toInt();
|
||||||
|
|
||||||
|
walkRulesMap(ruleSetMap, ruleIds);
|
||||||
|
|
||||||
|
ok = walkRulesLoop(func);
|
||||||
|
|
||||||
|
sqliteDb()->commitTransaction();
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfRuleManager::walkRulesMap(ruleset_map_t &ruleSetMap, ruleid_arr_t &ruleIds) const
|
||||||
|
{
|
||||||
|
SqliteStmt stmt;
|
||||||
|
if (!DbQuery(sqliteDb()).sql(sqlSelectRuleSets).prepare(stmt))
|
||||||
|
return;
|
||||||
|
|
||||||
|
int prevRuleId = 0;
|
||||||
|
int prevIndex = 0;
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
for (;;) {
|
||||||
|
const bool isStepRow = (stmt.step() == SqliteStmt::StepRow);
|
||||||
|
|
||||||
|
const int ruleId = stmt.columnInt(0);
|
||||||
|
const int subRuleId = stmt.columnInt(1);
|
||||||
|
|
||||||
|
if (prevRuleId != ruleId) {
|
||||||
|
const RuleSetIndex ruleSetIndex = {
|
||||||
|
.index = quint32(prevIndex),
|
||||||
|
.count = quint8(index - prevIndex),
|
||||||
|
};
|
||||||
|
|
||||||
|
ruleSetMap.insert(prevRuleId, ruleSetIndex);
|
||||||
|
|
||||||
|
prevRuleId = ruleId;
|
||||||
|
prevIndex = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
ruleIds.append(subRuleId);
|
||||||
|
|
||||||
|
++index;
|
||||||
|
|
||||||
|
if (!isStepRow)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConfRuleManager::walkRulesLoop(const std::function<walkRulesCallback> &func) const
|
||||||
|
{
|
||||||
|
SqliteStmt stmt;
|
||||||
|
if (!DbQuery(sqliteDb()).sql(sqlSelectRules).prepare(stmt))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
while (stmt.step() == SqliteStmt::StepRow) {
|
||||||
|
Rule rule;
|
||||||
|
fillRule(rule, stmt);
|
||||||
|
|
||||||
|
if (!func(rule))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfRuleManager::fillRule(Rule &rule, const SqliteStmt &stmt)
|
||||||
|
{
|
||||||
|
rule.ruleId = stmt.columnInt(0);
|
||||||
|
rule.enabled = stmt.columnBool(1);
|
||||||
|
rule.blocked = stmt.columnBool(2);
|
||||||
|
rule.exclusive = stmt.columnBool(3);
|
||||||
|
rule.ruleText = stmt.columnText(4);
|
||||||
|
rule.ruleType = Rule::RuleType(stmt.columnInt(5));
|
||||||
|
rule.acceptZones = stmt.columnUInt64(6);
|
||||||
|
rule.rejectZones = stmt.columnUInt64(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfRuleManager::updateDriverRules()
|
||||||
{
|
{
|
||||||
ConfUtil confUtil;
|
ConfUtil confUtil;
|
||||||
|
|
||||||
#if 0
|
confUtil.writeRules(*this);
|
||||||
const int entrySize = confUtil.writeRules(rulesMask, enabledMask, dataSize, rulesData);
|
|
||||||
|
|
||||||
driverWriteRules(confUtil, confUtil.buffer(), entrySize);
|
// driverWriteRules(confUtil, confUtil.buffer(), entrySize);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ConfRuleManager::updateDriverRuleFlag(int ruleId, bool enabled)
|
bool ConfRuleManager::updateDriverRuleFlag(int ruleId, bool enabled)
|
||||||
@ -313,9 +415,9 @@ bool ConfRuleManager::updateDriverRuleFlag(int ruleId, bool enabled)
|
|||||||
ConfUtil confUtil;
|
ConfUtil confUtil;
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
const int entrySize = confUtil.writeRuleFlag(ruleId, enabled);
|
confUtil.writeRuleFlag(ruleId, enabled);
|
||||||
|
|
||||||
return driverWriteRules(confUtil, confUtil.buffer(), entrySize, /*onlyFlags=*/true);
|
return driverWriteRules(confUtil, confUtil.buffer(), /*onlyFlags=*/true);
|
||||||
#endif
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,12 @@
|
|||||||
|
|
||||||
#include <conf/rule.h>
|
#include <conf/rule.h>
|
||||||
#include <util/classhelpers.h>
|
#include <util/classhelpers.h>
|
||||||
|
#include <util/conf/confruleswalker.h>
|
||||||
#include <util/ioc/iocservice.h>
|
#include <util/ioc/iocservice.h>
|
||||||
|
|
||||||
class ConfManager;
|
class ConfManager;
|
||||||
|
|
||||||
class ConfRuleManager : public QObject, public IocService
|
class ConfRuleManager : public QObject, public ConfRulesWalker, public IocService
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@ -35,8 +36,10 @@ public:
|
|||||||
virtual bool updateRuleName(int ruleId, const QString &ruleName);
|
virtual bool updateRuleName(int ruleId, const QString &ruleName);
|
||||||
virtual bool updateRuleEnabled(int ruleId, bool enabled);
|
virtual bool updateRuleEnabled(int ruleId, bool enabled);
|
||||||
|
|
||||||
void updateDriverRules(quint32 rulesMask, quint32 enabledMask, quint32 dataSize,
|
bool walkRules(ruleset_map_t &ruleSetMap, ruleid_arr_t &ruleIds, int &maxRuleId,
|
||||||
const QList<QByteArray> &rulesData);
|
const std::function<walkRulesCallback> &func) const override;
|
||||||
|
|
||||||
|
void updateDriverRules();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void ruleAdded();
|
void ruleAdded();
|
||||||
@ -44,6 +47,11 @@ signals:
|
|||||||
void ruleUpdated();
|
void ruleUpdated();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void walkRulesMap(ruleset_map_t &ruleSetMap, ruleid_arr_t &ruleIds) const;
|
||||||
|
bool walkRulesLoop(const std::function<walkRulesCallback> &func) const;
|
||||||
|
|
||||||
|
static void fillRule(Rule &rule, const SqliteStmt &stmt);
|
||||||
|
|
||||||
bool updateDriverRuleFlag(int ruleId, bool enabled);
|
bool updateDriverRuleFlag(int ruleId, bool enabled);
|
||||||
|
|
||||||
bool beginTransaction();
|
bool beginTransaction();
|
||||||
|
@ -12,7 +12,7 @@ using walkAppsCallback = bool(App &app);
|
|||||||
class ConfAppsWalker
|
class ConfAppsWalker
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual bool walkApps(const std::function<walkAppsCallback> &func) = 0;
|
virtual bool walkApps(const std::function<walkAppsCallback> &func) const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONFAPPSWALKER_H
|
#endif // CONFAPPSWALKER_H
|
||||||
|
30
src/ui/util/conf/confruleswalker.h
Normal file
30
src/ui/util/conf/confruleswalker.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#ifndef CONFRULESWALKER_H
|
||||||
|
#define CONFRULESWALKER_H
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <conf/rule.h>
|
||||||
|
|
||||||
|
struct RuleSetIndex
|
||||||
|
{
|
||||||
|
quint32 index : 24;
|
||||||
|
quint32 count : 8;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ruleset_map_t = QHash<quint16, RuleSetIndex>;
|
||||||
|
using ruleid_arr_t = QVector<quint16>;
|
||||||
|
|
||||||
|
using walkRulesCallback = bool(Rule &rule);
|
||||||
|
|
||||||
|
class ConfRulesWalker
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual bool walkRules(ruleset_map_t &ruleSetMap, ruleid_arr_t &ruleIds, int &maxRuleId,
|
||||||
|
const std::function<walkRulesCallback> &func) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CONFRULESWALKER_H
|
@ -15,6 +15,7 @@
|
|||||||
#include <util/stringutil.h>
|
#include <util/stringutil.h>
|
||||||
|
|
||||||
#include "confappswalker.h"
|
#include "confappswalker.h"
|
||||||
|
#include "confruleswalker.h"
|
||||||
|
|
||||||
#define APP_GROUP_MAX FORT_CONF_GROUP_MAX
|
#define APP_GROUP_MAX FORT_CONF_GROUP_MAX
|
||||||
#define APP_GROUP_NAME_MAX 128
|
#define APP_GROUP_NAME_MAX 128
|
||||||
@ -218,7 +219,7 @@ void ConfUtil::writeServices(const QVector<ServiceInfo> &services, int runningSe
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ConfUtil::write(
|
bool ConfUtil::write(
|
||||||
const FirewallConf &conf, ConfAppsWalker *confAppsWalker, EnvManager &envManager)
|
const FirewallConf &conf, const ConfAppsWalker *confAppsWalker, EnvManager &envManager)
|
||||||
{
|
{
|
||||||
WriteConfArgs wca = { .conf = conf,
|
WriteConfArgs wca = { .conf = conf,
|
||||||
.ad = { .addressRanges = addrranges_arr_t(conf.addressGroups().size()) } };
|
.ad = { .addressRanges = addrranges_arr_t(conf.addressGroups().size()) } };
|
||||||
@ -287,6 +288,22 @@ bool ConfUtil::writeAppEntry(const App &app, bool isNew)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ConfUtil::writeRules(const ConfRulesWalker &confRulesWalker)
|
||||||
|
{
|
||||||
|
ruleset_map_t ruleSetMap;
|
||||||
|
ruleid_arr_t ruleIds;
|
||||||
|
int maxRuleId;
|
||||||
|
|
||||||
|
return confRulesWalker.walkRules(ruleSetMap, ruleIds, maxRuleId, [&](Rule &rule) -> bool {
|
||||||
|
if (buffer().isEmpty()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ruleSetIndex = ruleSetMap[rule.ruleId];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void ConfUtil::writeZone(const IpRange &ipRange)
|
void ConfUtil::writeZone(const IpRange &ipRange)
|
||||||
{
|
{
|
||||||
const int addrSize = FORT_CONF_ADDR_LIST_SIZE(
|
const int addrSize = FORT_CONF_ADDR_LIST_SIZE(
|
||||||
@ -467,7 +484,7 @@ bool ConfUtil::parseAppGroups(EnvManager &envManager, const QList<AppGroup *> &a
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ConfUtil::parseExeApps(
|
bool ConfUtil::parseExeApps(
|
||||||
EnvManager &envManager, ConfAppsWalker *confAppsWalker, AppParseOptions &opt)
|
EnvManager &envManager, const ConfAppsWalker *confAppsWalker, AppParseOptions &opt)
|
||||||
{
|
{
|
||||||
if (Q_UNLIKELY(!confAppsWalker))
|
if (Q_UNLIKELY(!confAppsWalker))
|
||||||
return true;
|
return true;
|
||||||
|
@ -14,6 +14,7 @@ class AddressGroup;
|
|||||||
class App;
|
class App;
|
||||||
class AppGroup;
|
class AppGroup;
|
||||||
class ConfAppsWalker;
|
class ConfAppsWalker;
|
||||||
|
class ConfRulesWalker;
|
||||||
class EnvManager;
|
class EnvManager;
|
||||||
class FirewallConf;
|
class FirewallConf;
|
||||||
|
|
||||||
@ -51,10 +52,13 @@ public slots:
|
|||||||
void writeVersion();
|
void writeVersion();
|
||||||
void writeServices(const QVector<ServiceInfo> &services, int runningServicesCount);
|
void writeServices(const QVector<ServiceInfo> &services, int runningServicesCount);
|
||||||
|
|
||||||
bool write(const FirewallConf &conf, ConfAppsWalker *confAppsWalker, EnvManager &envManager);
|
bool write(
|
||||||
|
const FirewallConf &conf, const ConfAppsWalker *confAppsWalker, EnvManager &envManager);
|
||||||
void writeFlags(const FirewallConf &conf);
|
void writeFlags(const FirewallConf &conf);
|
||||||
bool writeAppEntry(const App &app, bool isNew = false);
|
bool writeAppEntry(const App &app, bool isNew = false);
|
||||||
|
|
||||||
|
bool writeRules(const ConfRulesWalker &confRulesWalker);
|
||||||
|
|
||||||
void writeZone(const IpRange &ipRange);
|
void writeZone(const IpRange &ipRange);
|
||||||
void writeZones(quint32 zonesMask, quint32 enabledMask, quint32 dataSize,
|
void writeZones(quint32 zonesMask, quint32 enabledMask, quint32 dataSize,
|
||||||
const QList<QByteArray> &zonesData);
|
const QList<QByteArray> &zonesData);
|
||||||
@ -92,7 +96,8 @@ private:
|
|||||||
bool parseAppGroups(EnvManager &envManager, const QList<AppGroup *> &appGroups,
|
bool parseAppGroups(EnvManager &envManager, const QList<AppGroup *> &appGroups,
|
||||||
ParseAppGroupsArgs &gr, AppParseOptions &opt);
|
ParseAppGroupsArgs &gr, AppParseOptions &opt);
|
||||||
|
|
||||||
bool parseExeApps(EnvManager &envManager, ConfAppsWalker *confAppsWalker, AppParseOptions &opt);
|
bool parseExeApps(
|
||||||
|
EnvManager &envManager, const ConfAppsWalker *confAppsWalker, AppParseOptions &opt);
|
||||||
|
|
||||||
bool parseAppsText(EnvManager &envManager, App &app, AppParseOptions &opt);
|
bool parseAppsText(EnvManager &envManager, App &app, AppParseOptions &opt);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user