feat(meshtastic): extract LoBBS plugin library

This commit is contained in:
Ben Allfree committed 2026-10-06 23:04:05 -07:00
1 parent 1f7ded8f3f
commit 5bffe8d259
124 files changed
+10907 -39

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
#if !MESHTASTIC_EXCLUDE_LOBBS
#include "Config.h"
#include "LoBBSStackGuard.h"
ConfigApp::ConfigApp(LoDb &lodb) : dal_(lodb) {}
#endif
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#if !MESHTASTIC_EXCLUDE_LOBBS
#include "ConfigDal.h"
#include <lodb/LoDB.h>
class ConfigApp
{
public:
explicit ConfigApp(LoDb &lodb);
ConfigDal &dal() { return dal_; }
private:
ConfigDal dal_;
};
#endif
+200
View File
@@ -0,0 +1,200 @@
#if !MESHTASTIC_EXCLUDE_LOBBS
#include "ConfigCommands.h"
#include "../../LoBBSCommandRegistry.h"
#include "../../LoBBSHooks.h"
#include "../../LoBBSModule.h"
#include "../../LoBBSReplyCache.h"
#include "../../LoBBSResponse.h"
#include "../AppUtil.h"
#include "ConfigCommon.h"
#include "ConfigDal.h"
#include "ConfigRecords.h"
#include <cstdio>
#include <cstring>
#include <strings.h>
#include "LoBBSStackGuard.h"
static uint32_t gLobbsPagerTtlSec = LOBBS_REPLY_CACHE_TTL_SEC;
uint32_t lobbsReplyCacheTtlSec()
{
return gLobbsPagerTtlSec;
}
static void filterConfigKeysPager(LoBBSCommandCtx *ctx, std::vector<LoScalar> &keys, const LoScalar &args)
{
(void)ctx;
(void)args;
lobbsConfigPushKey(keys, "pager.ttl", LOBBS_REPLY_CACHE_TTL_SEC, 60, 3600, "Seconds /pN cache lasts");
}
static void filterConfigValidateRange(LoBBSCommandCtx *ctx, LoScalar &value, const LoScalar &args)
{
(void)ctx;
std::string existing;
if (value.getString(LODB_F_ERROR, existing) && !existing.empty())
return;
uint32_t proposed = 0;
uint32_t minV = 0;
uint32_t maxV = 0;
if (!value.getUint32(ConfigValidateField::FIELD_VALUE, proposed))
return;
if (!args.getUint32(ConfigKeyField::FIELD_MIN, minV) || !args.getUint32(ConfigKeyField::FIELD_MAX, maxV))
return;
if (proposed < minV || proposed > maxV) {
char buf[48];
snprintf(buf, sizeof(buf), "Must be %u-%u.", (unsigned)minV, (unsigned)maxV);
value.setString(LODB_F_ERROR, buf);
}
}
static void actionConfigChangedPager(LoBBSCommandCtx *ctx, const LoScalar &args)
{
(void)ctx;
std::string key;
if (!args.getString(LODB_F_TITLE, key) || key != "pager.ttl")
return;
uint32_t ttl = LOBBS_REPLY_CACHE_TTL_SEC;
args.getUint32(ConfigValidateField::FIELD_VALUE, ttl);
gLobbsPagerTtlSec = ttl;
}
static void handleConfig(LoBBSCommandCtx &ctx)
{
if (!lobbsCommandRequireSysop(ctx))
return;
ConfigDal &cfg = ctx.mod->config().dal();
const char *key = lobbsArgShift(ctx);
if (!key) {
LoBBSResponse resp;
lobbsRecordPush(resp.records, "Use /help config key for details");
for (const LoScalar &def : cfg.keyDefs(ctx)) {
std::string name;
if (!def.getString(LODB_F_TITLE, name))
continue;
uint32_t minV = 0;
uint32_t maxV = 0;
def.getUint32(ConfigKeyField::FIELD_MIN, minV);
def.getUint32(ConfigKeyField::FIELD_MAX, maxV);
uint32_t val = cfg.effectiveValue(ctx, name.c_str());
char line[96];
snprintf(line, sizeof(line), "%s %u (%u-%u)", name.c_str(), (unsigned)val, (unsigned)minV, (unsigned)maxV);
lobbsRecordPush(resp.records, line);
}
lobbsCommandReplyResponse(ctx, resp);
return;
}
const char *next = lobbsArgPeek(ctx);
if (!next) {
cfg.ensureRegistry(ctx);
const LoScalar *def = cfg.findKeyDef(key);
if (!def) {
lobbsCommandReplyError(ctx, "Unknown setting.");
return;
}
uint32_t val = cfg.effectiveValue(ctx, key);
uint32_t defV = 0;
uint32_t minV = 0;
uint32_t maxV = 0;
std::string help;
def->getUint32(ConfigKeyField::FIELD_DEFAULT, defV);
def->getUint32(ConfigKeyField::FIELD_MIN, minV);
def->getUint32(ConfigKeyField::FIELD_MAX, maxV);
def->getString(LODB_F_DESCRIPTION, help);
char line[128];
snprintf(line, sizeof(line), "%s = %u (default %u, %u-%u). %s", key, (unsigned)val, (unsigned)defV, (unsigned)minV,
(unsigned)maxV, help.c_str());
lobbsCommandReply(ctx, line);
return;
}
if (strcasecmp(next, "reset") == 0) {
lobbsArgShift(ctx);
if (lobbsArgHasMore(ctx)) {
lobbsCommandReplyError(ctx, "Usage: /config key reset");
return;
}
if (const char *err = cfg.resetKey(ctx, key)) {
lobbsCommandReplyError(ctx, err);
return;
}
char reply[64];
snprintf(reply, sizeof(reply), "%s reset.", key);
lobbsCommandReply(ctx, reply);
return;
}
uint32_t value = 0;
if (!lobbsArgShiftUint(ctx, value) || lobbsArgHasMore(ctx)) {
lobbsCommandReplyError(ctx, "Usage: /config key value");
return;
}
if (const char *err = cfg.validateAndSet(ctx, key, value)) {
lobbsCommandReplyError(ctx, err);
return;
}
char reply[64];
snprintf(reply, sizeof(reply), "%s = %u.", key, (unsigned)value);
lobbsCommandReply(ctx, reply);
}
static void slashConfig(LoBBSCommandCtx *ctx, const LoScalar &args)
{
if (!ctx || !lobbsSlashVerbIs(args, "config"))
return;
handleConfig(*ctx);
}
static const LoBBSVerb configHelpVerbs[] = {
{"config", nullptr, LOBBS_V_SYSOP, "config — sysop: list/get/set/reset settings (/help config key explains one)"},
};
static void filterConfigHelpTopics(LoBBSCommandCtx *ctx, std::vector<LoScalar> &topics, const LoScalar &args)
{
(void)ctx;
(void)args;
lobbsRecordPush(topics, "config", "sysop settings");
}
static void filterConfigHelpForTopic(LoBBSCommandCtx *ctx, LoScalar &value, const LoScalar &args)
{
lobbsHelpForTable(ctx, value, args, "config", configHelpVerbs, sizeof(configHelpVerbs) / sizeof(configHelpVerbs[0]));
if (!ctx || !ctx->mod || value.has(LODB_F_DESCRIPTION) || !lobbsCtxIsSysop(*ctx))
return;
std::string query;
if (!args.getString(LODB_F_TITLE, query) || strncasecmp(query.c_str(), "config ", 7) != 0)
return;
const char *key = query.c_str() + 7;
ConfigDal &cfg = ctx->mod->config().dal();
cfg.ensureRegistry(*ctx);
const LoScalar *def = cfg.findKeyDef(key);
if (!def)
return;
std::string help;
uint32_t defV = 0;
uint32_t minV = 0;
uint32_t maxV = 0;
def->getString(LODB_F_DESCRIPTION, help);
def->getUint32(ConfigKeyField::FIELD_DEFAULT, defV);
def->getUint32(ConfigKeyField::FIELD_MIN, minV);
def->getUint32(ConfigKeyField::FIELD_MAX, maxV);
char buf[128];
snprintf(buf, sizeof(buf), "%s: %s. Default %u, range %u-%u.", key, help.c_str(), (unsigned)defV, (unsigned)minV,
(unsigned)maxV);
value.setString(LODB_F_DESCRIPTION, buf);
}
void lobbsConfigRegisterCommands()
{
lobbsAddFilter("config_keys", filterConfigKeysPager, LOBBS_HOOK_PRIORITY_HELP);
lobbsAddFilter("config_validate", filterConfigValidateRange, LOBBS_HOOK_PRIORITY_HELP);
lobbsAddAction("config_changed", actionConfigChangedPager, LOBBS_HOOK_PRIORITY_HELP);
lobbsAddAction("slash_cmd", slashConfig, LOBBS_HOOK_PRIORITY_FEATURE);
lobbsAddFilter("help_topics", filterConfigHelpTopics, LOBBS_HOOK_PRIORITY_FEATURE);
lobbsAddFilter("help_for_topic", filterConfigHelpForTopic, LOBBS_HOOK_PRIORITY_FEATURE);
}
#endif
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#if !MESHTASTIC_EXCLUDE_LOBBS
#include <cstdint>
void lobbsConfigRegisterCommands();
uint32_t lobbsReplyCacheTtlSec();
#endif
+31
View File
@@ -0,0 +1,31 @@
#if !MESHTASTIC_EXCLUDE_LOBBS
#include "ConfigCommon.h"
#include "../../LoBBSModule.h"
#include "ConfigDal.h"
#include <cstring>
#include "LoBBSStackGuard.h"
void lobbsConfigPushKey(std::vector<LoScalar> &keys, const char *key, uint32_t def, uint32_t min, uint32_t max, const char *help)
{
if (!key)
return;
LoScalar rec;
rec.setString(LODB_F_TITLE, key);
if (help)
rec.setString(LODB_F_DESCRIPTION, help);
rec.setUint32(ConfigKeyField::FIELD_DEFAULT, def);
rec.setUint32(ConfigKeyField::FIELD_MIN, min);
rec.setUint32(ConfigKeyField::FIELD_MAX, max);
keys.push_back(rec);
}
uint32_t lobbsConfigGet(LoBBSCommandCtx &ctx, const char *key)
{
if (!ctx.mod || !key)
return 0;
return ctx.mod->config().dal().effectiveValue(ctx, key);
}
#endif
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#if !MESHTASTIC_EXCLUDE_LOBBS
#include "../../LoBBSCommandCtx.h"
#include "ConfigRecords.h"
#include <lodb/LoDB.h>
#include <loscalar/LoScalar.h>
#include <vector>
void lobbsConfigPushKey(std::vector<LoScalar> &keys, const char *key, uint32_t def, uint32_t min, uint32_t max, const char *help);
uint32_t lobbsConfigGet(LoBBSCommandCtx &ctx, const char *key);
#endif
+158
View File
@@ -0,0 +1,158 @@
#if !MESHTASTIC_EXCLUDE_LOBBS
#include "ConfigDal.h"
#include "../../LoBBSCommandCtx.h"
#include "../../LoBBSHooks.h"
#include "../../LoBBSModule.h"
#include "../AppUtil.h"
#include "ConfigRecords.h"
#include <cstdio>
#include <cstring>
#include "LoBBSStackGuard.h"
ConfigDal::ConfigDal(LoDb &lodb) : lodb_(lodb)
{
lodb_.registerTable("config");
}
lodb_uuid_t ConfigDal::rowUuid(const char *key) const
{
return lodb_new_uuid(key, 0);
}
void ConfigDal::resetCache()
{
overrides_.clear();
registry_.clear();
registryBuilt_ = false;
overridesLoaded_ = false;
}
void ConfigDal::reloadOverrides()
{
overrides_.clear();
overridesLoaded_ = true;
auto rows = lodb_.select(
"config", [](const LoScalar &) { return true; }, LoDbComparator());
for (const LoScalar &rec : rows) {
std::string key;
if (!rec.getString(LODB_F_TITLE, key) || key.empty())
continue;
uint32_t val = 0;
if (rec.getUint32(ConfigStoreField::FIELD_VALUE, val))
overrides_[key] = val;
}
}
void ConfigDal::ensureRegistry(LoBBSCommandCtx &ctx)
{
if (registryBuilt_)
return;
registryBuilt_ = true;
registry_.clear();
lobbsApplyFilter("config_keys", ctx, registry_, LoScalar());
}
const std::vector<LoScalar> &ConfigDal::keyDefs(LoBBSCommandCtx &ctx)
{
ensureRegistry(ctx);
return registry_;
}
const LoScalar *ConfigDal::findKeyDef(const char *key)
{
if (!key)
return nullptr;
for (const LoScalar &rec : registry_) {
std::string title;
if (rec.getString(LODB_F_TITLE, title) && strcasecmp(title.c_str(), key) == 0)
return &rec;
}
return nullptr;
}
uint32_t ConfigDal::effectiveValue(LoBBSCommandCtx &ctx, const char *key)
{
ensureRegistry(ctx);
const LoScalar *def = findKeyDef(key);
if (!def)
return 0;
uint32_t fallback = 0;
def->getUint32(ConfigKeyField::FIELD_DEFAULT, fallback);
if (!overridesLoaded_)
return fallback;
auto it = overrides_.find(key);
if (it != overrides_.end())
return it->second;
return fallback;
}
void ConfigDal::fireConfigChanged(LoBBSCommandCtx &ctx, const char *key, uint32_t value)
{
LoScalar args;
args.setString(LODB_F_TITLE, key);
args.setUint32(ConfigValidateField::FIELD_VALUE, value);
lobbsDoAction("config_changed", ctx, args);
}
const char *ConfigDal::validateAndSet(LoBBSCommandCtx &ctx, const char *key, uint32_t value)
{
ensureRegistry(ctx);
const LoScalar *def = findKeyDef(key);
if (!def)
return "Unknown setting.";
LoScalar probe;
probe.setString(LODB_F_TITLE, key);
probe.setUint32(ConfigValidateField::FIELD_VALUE, value);
lobbsApplyFilter("config_validate", ctx, probe, *def);
std::string err;
if (probe.getString(LODB_F_ERROR, err) && !err.empty()) {
strncpy(lastValidateError_, err.c_str(), sizeof(lastValidateError_) - 1);
lastValidateError_[sizeof(lastValidateError_) - 1] = '\0';
return lastValidateError_;
}
LoScalar row;
row.setString(LODB_F_TITLE, key);
row.setUint32(ConfigStoreField::FIELD_VALUE, value);
LoDbError dbErr = lodb_.upsert("config", rowUuid(key), row);
if (dbErr != LODB_OK)
return lobbsDbErrorText(dbErr);
overrides_[key] = value;
fireConfigChanged(ctx, key, value);
return nullptr;
}
const char *ConfigDal::resetKey(LoBBSCommandCtx &ctx, const char *key)
{
ensureRegistry(ctx);
const LoScalar *def = findKeyDef(key);
if (!def)
return "Unknown setting.";
uint32_t fallback = 0;
def->getUint32(ConfigKeyField::FIELD_DEFAULT, fallback);
lodb_.deleteRecord("config", rowUuid(key));
overrides_.erase(key);
fireConfigChanged(ctx, key, fallback);
return nullptr;
}
void ConfigDal::notifyDatabaseOpened(LoBBSModule &mod)
{
resetCache();
reloadOverrides();
LoBBSCommandCtx ctx;
ctx.mod = &mod;
ensureRegistry(ctx);
for (const LoScalar &rec : registry_) {
std::string key;
if (!rec.getString(LODB_F_TITLE, key) || key.empty())
continue;
fireConfigChanged(ctx, key.c_str(), effectiveValue(ctx, key.c_str()));
}
}
#endif
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#if !MESHTASTIC_EXCLUDE_LOBBS
#include <lodb/LoDB.h>
#include <loscalar/LoScalar.h>
#include <map>
#include <string>
#include <vector>
class LoBBSModule;
struct LoBBSCommandCtx;
class ConfigDal
{
public:
explicit ConfigDal(LoDb &lodb);
void reloadOverrides();
void resetCache();
void ensureRegistry(LoBBSCommandCtx &ctx);
const LoScalar *findKeyDef(const char *key);
const std::vector<LoScalar> &keyDefs(LoBBSCommandCtx &ctx);
uint32_t effectiveValue(LoBBSCommandCtx &ctx, const char *key);
const char *validateAndSet(LoBBSCommandCtx &ctx, const char *key, uint32_t value);
const char *resetKey(LoBBSCommandCtx &ctx, const char *key);
void notifyDatabaseOpened(LoBBSModule &mod);
private:
lodb_uuid_t rowUuid(const char *key) const;
void fireConfigChanged(LoBBSCommandCtx &ctx, const char *key, uint32_t value);
LoDb &lodb_;
std::map<std::string, uint32_t> overrides_;
std::vector<LoScalar> registry_;
bool registryBuilt_ = false;
bool overridesLoaded_ = false;
char lastValidateError_[128] = {0};
};
#endif
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#if !MESHTASTIC_EXCLUDE_LOBBS
#include <cstdint>
/** Stored override row in table `config`. */
namespace ConfigStoreField
{
inline constexpr uint32_t FIELD_VALUE = 0;
} // namespace ConfigStoreField
/** Key definition rows from `config_keys`. */
namespace ConfigKeyField
{
inline constexpr uint32_t FIELD_DEFAULT = 0;
inline constexpr uint32_t FIELD_MIN = 1;
inline constexpr uint32_t FIELD_MAX = 2;
} // namespace ConfigKeyField
/** Proposed value in `config_validate` value record. */
namespace ConfigValidateField
{
inline constexpr uint32_t FIELD_VALUE = 0;
} // namespace ConfigValidateField
#endif