mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
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.
251 lines
7.1 KiB
C++
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]);
|
|
}
|