tinymux/mux/modules/engine/vattr.cpp
Stephen Dennis 6b2e1bf753 nls: convert remaining count plurals to MN_() (#1661)
Three player-visible notify sites still computed English morphology in C
and passed it as %s — the #1622 shape:

  %d queue entr%s removed.          y / ies   (cque, two sites)
  %d orphaned attribute name%s purged.  "" / s  (@dbclean)

MN_(singular, plural, n) sends the count to the catalogue.  Regenerates
the pot and fills xx.po; ko.po leaves the new entries untranslated.
2026-07-28 05:12:03 +00:00

251 lines
7.1 KiB
C++

/*! \file vattr.cpp
* \brief Manages the user-defined attributes.
*
*/
#include "copyright.h"
#include "autoconf.h"
#include "config.h"
#include "externs.h"
#include "sqlite_backend.h"
using namespace std;
ATTR *vattr_find_LEN(const UTF8 *pAttrName, size_t nAttrName)
{
string key(reinterpret_cast<const char *>(pAttrName), nAttrName);
auto it = mudstate.vattr_name_map.find(key);
if (it != mudstate.vattr_name_map.end())
{
return static_cast<ATTR *>(anum_table[it->second]);
}
return nullptr;
}
ATTR *vattr_alloc_LEN(const UTF8 *pName, size_t nName, int flags)
{
int number = mudstate.attr_next++;
anum_extend(number);
ATTR *vp = vattr_define_LEN(pName, nName, number, flags);
if (!mudstate.bSQLiteLoading)
{
if (!g_pSQLiteBackend->GetDB().PutMeta("attr_next", mudstate.attr_next))
{
Log.tinyprintf(T("vattr_alloc_LEN: failed to persist attr_next=%d" ENDLINE),
mudstate.attr_next);
}
}
return vp;
}
ATTR *vattr_define_LEN(const UTF8 *pName, size_t nName, int number, int flags)
{
// Validate the attribute number before it reaches anum_extend()/anum_set()
// (#808). User attribute numbers are always in [A_USER_START, A_USER_MAX];
// the read path (atr_num) already validates this range, but this write path
// did not, so a negative number would OOB-write anum_table and a huge one
// would allocate a giant table.
//
if ( number < A_USER_START
|| number > A_USER_MAX)
{
return nullptr;
}
ATTR *vp = vattr_find_LEN(pName, nName);
if (vp)
{
return vp;
}
vp = static_cast<ATTR *>(MEMALLOC(sizeof(ATTR)));
if (vp)
{
string key(reinterpret_cast<const char *>(pName), nName);
auto [it, inserted] = mudstate.vattr_name_map.emplace(std::move(key), number);
if (!inserted)
{
MEMFREE(vp);
return static_cast<ATTR *>(anum_table[it->second]);
}
// ATTR::name points directly into the map key. unordered_map
// guarantees pointer stability for existing elements across
// insertions, so this pointer remains valid until the element
// is erased.
//
vp->name = reinterpret_cast<const UTF8 *>(it->first.c_str());
vp->flags = flags;
vp->number = number;
mudstate.vattr_numbers.insert(number);
anum_extend(vp->number);
anum_set(vp->number, static_cast<ATTR *>(vp));
if ( !mudstate.bSQLiteLoading
&& number >= A_USER_START)
{
if (!g_pSQLiteBackend->GetDB().PutAttrName(number,
reinterpret_cast<const char *>(pName), flags))
{
Log.tinyprintf(T("vattr_define_LEN: failed to persist attrname #%d" ENDLINE),
number);
}
}
}
else
{
STARTLOG(LOG_PROBLEMS, "ATR", "MEM");
log_printf(T("vattr_define_LEN: out of memory allocating vattr."));
ENDLOG;
}
return vp;
}
void do_dbclean(dbref executor, dbref caller, dbref enactor, int eval, int key)
{
UNUSED_PARAMETER(caller);
UNUSED_PARAMETER(enactor);
UNUSED_PARAMETER(eval);
UNUSED_PARAMETER(key);
// Phase 1: Find orphaned vattr names — user-defined attribute
// names (attrnum >= 256) not referenced by any object.
//
std::vector<int> orphans = g_pSQLiteBackend->GetDB().FindOrphanedAttrNames();
// Phase 2: Purge from SQLite first. If this fails, bail out
// without touching in-memory state so the process and database
// stay consistent.
//
int purged = g_pSQLiteBackend->GetDB().PurgeOrphanedAttrNames();
if (purged < 0)
{
notify(executor, M_("@dbclean: SQLite error during orphan purge."));
return;
}
// Phase 3: SQLite succeeded — now remove orphans from in-memory maps.
//
for (int anum : orphans)
{
ATTR *vp = static_cast<ATTR *>(anum_table[anum]);
if (vp)
{
if (vp->name)
{
std::string name(reinterpret_cast<const char *>(vp->name));
mudstate.vattr_name_map.erase(name);
}
mudstate.vattr_numbers.erase(anum);
anum_set(anum, nullptr);
MEMFREE(vp);
}
}
g_pSQLiteBackend->GetDB().Analyze();
// #1661 / #1622: count goes to the catalogue, not an English "s".
//
notify(executor, tprintf(MN_("@dbclean: %d orphaned attribute name purged.",
"@dbclean: %d orphaned attribute names purged.", purged), purged));
}
void vattr_delete_LEN(UTF8 *pName, size_t nName)
{
string key(reinterpret_cast<const char *>(pName), nName);
auto it = mudstate.vattr_name_map.find(key);
if (it != mudstate.vattr_name_map.end())
{
int anum = it->second;
ATTR *vp = static_cast<ATTR *>(anum_table[anum]);
anum_set(anum, nullptr);
mudstate.vattr_name_map.erase(it);
mudstate.vattr_numbers.erase(anum);
if ( !mudstate.bSQLiteLoading
&& anum >= A_USER_START)
{
if (!g_pSQLiteBackend->GetDB().DelAttrName(anum))
{
Log.tinyprintf(T("vattr_delete_LEN: failed to delete attrname #%d" ENDLINE),
anum);
}
}
MEMFREE(vp);
}
}
ATTR *vattr_rename_LEN(UTF8 *pOldName, size_t nOldName, UTF8 *pNewName, size_t nNewName)
{
string oldkey(reinterpret_cast<const char *>(pOldName), nOldName);
auto it = mudstate.vattr_name_map.find(oldkey);
if (it == mudstate.vattr_name_map.end())
{
return nullptr;
}
int anum = it->second;
ATTR *vp = static_cast<ATTR *>(anum_table[anum]);
string newkey(reinterpret_cast<const char *>(pNewName), nNewName);
auto existing = mudstate.vattr_name_map.find(newkey);
if ( existing != mudstate.vattr_name_map.end()
&& existing->second != anum)
{
return nullptr;
}
if (newkey == oldkey)
{
return vp;
}
mudstate.vattr_name_map.erase(it);
auto [newit, inserted] = mudstate.vattr_name_map.emplace(std::move(newkey), anum);
if (!inserted)
{
mudstate.vattr_name_map.emplace(oldkey, anum);
return nullptr;
}
vp->name = reinterpret_cast<const UTF8 *>(newit->first.c_str());
if ( !mudstate.bSQLiteLoading
&& anum >= A_USER_START)
{
if (!g_pSQLiteBackend->GetDB().PutAttrName(anum,
reinterpret_cast<const char *>(pNewName), vp->flags))
{
Log.tinyprintf(T("vattr_rename_LEN: failed to rename attrname #%d" ENDLINE),
anum);
}
}
return vp;
}
ATTR *vattr_first(void)
{
if (mudstate.vattr_numbers.empty())
{
return nullptr;
}
return static_cast<ATTR *>(anum_table[*mudstate.vattr_numbers.begin()]);
}
ATTR *vattr_next(ATTR *vp)
{
if (vp == nullptr)
{
return vattr_first();
}
auto it = mudstate.vattr_numbers.upper_bound(vp->number);
if (it == mudstate.vattr_numbers.end())
{
return nullptr;
}
return static_cast<ATTR *>(anum_table[*it]);
}