mirror of
https://github.com/MeshEnvy/lobbs.git
synced 2026-10-31 02:11:59 +01:00
191 lines
5.4 KiB
C++
191 lines
5.4 KiB
C++
#if !MESHTASTIC_EXCLUDE_LOBBS
|
|
|
|
#include "YarnDal.h"
|
|
#include "../AppUtil.h"
|
|
#include "YarnRecords.h"
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
#include "LoBBSStackGuard.h"
|
|
|
|
YarnDal::YarnDal(LoDb &lodb) : lodb_(lodb)
|
|
{
|
|
lodb_.registerTable("yarn_current");
|
|
lodb_.registerTable("yarn_seen");
|
|
lodb_.registerTable("yarn_quota");
|
|
}
|
|
|
|
static lodb_uuid_t yarnCurrentRecordUuid()
|
|
{
|
|
return lodb_new_uuid("lobbs_yarn_current_v1", 0);
|
|
}
|
|
|
|
static lodb_uuid_t yarnSeenRecordUuid(uint64_t userUuid)
|
|
{
|
|
char hex[17];
|
|
lodb_uuid_to_hex(userUuid, hex);
|
|
return lodb_new_uuid(hex, 0);
|
|
}
|
|
|
|
void YarnDal::loadCurrent(CurrentState &out)
|
|
{
|
|
out.text[0] = '\0';
|
|
out.total_words_appended = 0;
|
|
LoScalar rec;
|
|
if (lodb_.get("yarn_current", yarnCurrentRecordUuid(), rec) != LODB_OK)
|
|
return;
|
|
std::string text;
|
|
if (rec.getString(LODB_F_DESCRIPTION, text))
|
|
strncpy(out.text, text.c_str(), LOBBS_YARN_BODY_MAX);
|
|
out.text[LOBBS_YARN_BODY_MAX] = '\0';
|
|
rec.getUint32(YarnCurrentField::FIELD_TOTAL_WORDS, out.total_words_appended);
|
|
}
|
|
|
|
LoDbError YarnDal::saveCurrent(CurrentState &cur)
|
|
{
|
|
lodb_uuid_t id = yarnCurrentRecordUuid();
|
|
LoScalar rec;
|
|
rec.setString(LODB_F_DESCRIPTION, cur.text);
|
|
rec.setUint32(YarnCurrentField::FIELD_TOTAL_WORDS, cur.total_words_appended);
|
|
return lodb_.upsert("yarn_current", id, rec);
|
|
}
|
|
|
|
void YarnDal::trimTailToMax(char *text)
|
|
{
|
|
if (!text)
|
|
return;
|
|
while (strlen(text) > LOBBS_YARN_BODY_MAX) {
|
|
char *sp = strchr(text, ' ');
|
|
if (!sp) {
|
|
text[LOBBS_YARN_BODY_MAX] = '\0';
|
|
return;
|
|
}
|
|
memmove(text, sp + 1, strlen(sp + 1) + 1);
|
|
}
|
|
}
|
|
|
|
bool YarnDal::isValidWordToken(const char *word)
|
|
{
|
|
if (!word || !word[0])
|
|
return false;
|
|
for (const char *p = word; *p; p++) {
|
|
unsigned char c = (unsigned char)*p;
|
|
if (c < 0x21 || c > 0x7e)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool YarnDal::formatYarnView(char *out, size_t outCap)
|
|
{
|
|
if (!out || outCap == 0)
|
|
return false;
|
|
CurrentState cur = {};
|
|
loadCurrent(cur);
|
|
if (!cur.text[0]) {
|
|
snprintf(out, outCap, "Yarn empty.");
|
|
return true;
|
|
}
|
|
snprintf(out, outCap, "%s", cur.text);
|
|
return true;
|
|
}
|
|
|
|
uint32_t YarnDal::totalWordsAppended()
|
|
{
|
|
CurrentState cur = {};
|
|
loadCurrent(cur);
|
|
return cur.total_words_appended;
|
|
}
|
|
|
|
uint32_t YarnDal::newWordsForUser(uint64_t userUuid)
|
|
{
|
|
CurrentState cur = {};
|
|
loadCurrent(cur);
|
|
LoScalar seen;
|
|
if (lodb_.get("yarn_seen", yarnSeenRecordUuid(userUuid), seen) != LODB_OK)
|
|
return cur.total_words_appended;
|
|
uint32_t lastSeen = 0;
|
|
seen.getUint32(YarnSeenField::FIELD_LAST_TOTAL_WORDS, lastSeen);
|
|
if (cur.total_words_appended <= lastSeen)
|
|
return 0;
|
|
return cur.total_words_appended - lastSeen;
|
|
}
|
|
|
|
bool YarnDal::markYarnSeen(uint64_t userUuid)
|
|
{
|
|
CurrentState cur = {};
|
|
loadCurrent(cur);
|
|
LoScalar seen;
|
|
seen.setUint64(YarnSeenField::FIELD_USER_UUID, userUuid);
|
|
seen.setUint32(YarnSeenField::FIELD_LAST_TOTAL_WORDS, cur.total_words_appended);
|
|
lodb_uuid_t id = yarnSeenRecordUuid(userUuid);
|
|
return lodb_.upsert("yarn_seen", id, seen) == LODB_OK;
|
|
}
|
|
|
|
const char *YarnDal::appendWords(uint64_t userUuid, bool isSysop, const char *const *words, int wordCount, uint32_t periodSeconds,
|
|
uint32_t maxWords, uint32_t maxChars)
|
|
{
|
|
if (!words || wordCount <= 0)
|
|
return "No words.";
|
|
for (int i = 0; i < wordCount; i++) {
|
|
if (!isValidWordToken(words[i]))
|
|
return "Bad word.";
|
|
if (strlen(words[i]) > LOBBS_YARN_BODY_MAX)
|
|
return "Word too long.";
|
|
}
|
|
CurrentState cur = {};
|
|
loadCurrent(cur);
|
|
|
|
uint32_t charCost = 0;
|
|
bool needSpace = cur.text[0] != '\0';
|
|
for (int i = 0; i < wordCount; i++) {
|
|
if (needSpace)
|
|
charCost += 1;
|
|
charCost += (uint32_t)strlen(words[i]);
|
|
needSpace = true;
|
|
}
|
|
|
|
uint32_t add[2] = {(uint32_t)wordCount, charCost};
|
|
if (!isSysop) {
|
|
if (add[0] > maxWords || add[1] > maxChars)
|
|
return "Too many words.";
|
|
uint32_t used[2];
|
|
lobbsQuotaUsed(lodb_, "yarn_quota", userUuid, periodSeconds, used, 2);
|
|
if (used[0] + add[0] > maxWords)
|
|
return "Quota full.";
|
|
if (used[1] + add[1] > maxChars)
|
|
return "Too many chars.";
|
|
}
|
|
|
|
static constexpr size_t kCandidateCap = LOBBS_YARN_BODY_MAX + 64;
|
|
std::string candidate(cur.text);
|
|
for (int i = 0; i < wordCount; i++) {
|
|
const char *w = words[i];
|
|
size_t wlen = strlen(w);
|
|
if (!candidate.empty()) {
|
|
if (candidate.size() + 1 >= kCandidateCap)
|
|
break;
|
|
candidate += ' ';
|
|
}
|
|
if (candidate.size() + wlen >= kCandidateCap)
|
|
break;
|
|
candidate += w;
|
|
}
|
|
char *text = &candidate[0];
|
|
trimTailToMax(text);
|
|
if (strlen(text) > LOBBS_YARN_BODY_MAX)
|
|
return "Too many chars.";
|
|
strncpy(cur.text, text, LOBBS_YARN_BODY_MAX);
|
|
cur.text[LOBBS_YARN_BODY_MAX] = '\0';
|
|
cur.total_words_appended += (uint32_t)wordCount;
|
|
LoDbError err = saveCurrent(cur);
|
|
if (err != LODB_OK)
|
|
return lobbsDbErrorText(err, "Save failed.");
|
|
if (!isSysop && (err = lobbsQuotaAdd(lodb_, "yarn_quota", userUuid, periodSeconds, add, 2)) != LODB_OK)
|
|
return lobbsDbErrorText(err, "Quota save.");
|
|
return nullptr;
|
|
}
|
|
|
|
#endif
|