Update old style callbacks for chatFromLogFile functions to lambdas Keep track of people's log's name using their IDs, but don't rename. Fallback on ID if name is empty (if the grid is a meanie) Also track group names this way, sometimes those change. Prefer std::array, since we're in the area.
1045 lines
25 KiB
C++
1045 lines
25 KiB
C++
/**
|
|
* @file llcachename.cpp
|
|
* @brief A hierarchical cache of first and last names queried based on UUID.
|
|
*
|
|
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
|
|
#include "llcachename.h"
|
|
|
|
// linden library includes
|
|
#include "llcontrol.h" // For LLCachedControl
|
|
#include "lldbstrings.h"
|
|
#include "llframetimer.h"
|
|
#include "llhost.h"
|
|
#include "llrand.h"
|
|
#include "llsdserialize.h"
|
|
#include "lluuid.h"
|
|
#include "message.h"
|
|
|
|
#include <boost/regex.hpp>
|
|
|
|
// llsd serialization constants
|
|
static const std::string AGENTS("agents");
|
|
static const std::string GROUPS("groups");
|
|
static const std::string CTIME("ctime");
|
|
static const std::string FIRST("first");
|
|
static const std::string LAST("last");
|
|
static const std::string NAME("name");
|
|
|
|
// We track name requests in flight for up to this long.
|
|
// We won't re-request a name during this time
|
|
const U32 PENDING_TIMEOUT_SECS = 5 * 60;
|
|
|
|
// File version number
|
|
const S32 CN_FILE_VERSION = 2;
|
|
|
|
// Globals
|
|
LLCacheName* gCacheName = NULL;
|
|
std::map<std::string, std::string> LLCacheName::sCacheName;
|
|
|
|
/// ---------------------------------------------------------------------------
|
|
/// class LLCacheNameEntry
|
|
/// ---------------------------------------------------------------------------
|
|
|
|
class LLCacheNameEntry
|
|
{
|
|
public:
|
|
LLCacheNameEntry();
|
|
|
|
public:
|
|
bool mIsGroup;
|
|
U32 mCreateTime; // unix time_t
|
|
// IDEVO TODO collapse names to one field, which will eliminate
|
|
// many string compares on "Resident"
|
|
std::string mFirstName;
|
|
std::string mLastName;
|
|
std::string mGroupName;
|
|
};
|
|
|
|
LLCacheNameEntry::LLCacheNameEntry()
|
|
: mIsGroup(false),
|
|
mCreateTime(0)
|
|
{
|
|
}
|
|
|
|
|
|
class PendingReply
|
|
{
|
|
public:
|
|
LLUUID mID;
|
|
LLCacheNameSignal mSignal;
|
|
LLHost mHost;
|
|
|
|
PendingReply(const LLUUID& id, const LLHost& host)
|
|
: mID(id), mHost(host)
|
|
{
|
|
}
|
|
|
|
boost::signals2::connection setCallback(const LLCacheNameCallback& cb)
|
|
{
|
|
return mSignal.connect(cb);
|
|
}
|
|
|
|
void done() { mID.setNull(); }
|
|
bool isDone() const { return mID.isNull() != FALSE; }
|
|
};
|
|
|
|
class ReplySender
|
|
{
|
|
public:
|
|
ReplySender(LLMessageSystem* msg);
|
|
~ReplySender();
|
|
|
|
void send(const LLUUID& id,
|
|
const LLCacheNameEntry& entry, const LLHost& host);
|
|
|
|
private:
|
|
void flush();
|
|
|
|
LLMessageSystem* mMsg;
|
|
bool mPending;
|
|
bool mCurrIsGroup;
|
|
LLHost mCurrHost;
|
|
};
|
|
|
|
ReplySender::ReplySender(LLMessageSystem* msg)
|
|
: mMsg(msg), mPending(false), mCurrIsGroup(false)
|
|
{ }
|
|
|
|
ReplySender::~ReplySender()
|
|
{
|
|
flush();
|
|
}
|
|
|
|
void ReplySender::send(const LLUUID& id,
|
|
const LLCacheNameEntry& entry, const LLHost& host)
|
|
{
|
|
if (mPending)
|
|
{
|
|
if (mCurrIsGroup != entry.mIsGroup
|
|
|| mCurrHost != host)
|
|
{
|
|
flush();
|
|
}
|
|
}
|
|
|
|
if (!mPending)
|
|
{
|
|
mPending = true;
|
|
mCurrIsGroup = entry.mIsGroup;
|
|
mCurrHost = host;
|
|
|
|
if(mCurrIsGroup)
|
|
mMsg->newMessageFast(_PREHASH_UUIDGroupNameReply);
|
|
else
|
|
mMsg->newMessageFast(_PREHASH_UUIDNameReply);
|
|
}
|
|
|
|
mMsg->nextBlockFast(_PREHASH_UUIDNameBlock);
|
|
mMsg->addUUIDFast(_PREHASH_ID, id);
|
|
if(mCurrIsGroup)
|
|
{
|
|
mMsg->addStringFast(_PREHASH_GroupName, entry.mGroupName);
|
|
}
|
|
else
|
|
{
|
|
mMsg->addStringFast(_PREHASH_FirstName, entry.mFirstName);
|
|
mMsg->addStringFast(_PREHASH_LastName, entry.mLastName);
|
|
}
|
|
|
|
if(mMsg->isSendFullFast(_PREHASH_UUIDNameBlock))
|
|
{
|
|
flush();
|
|
}
|
|
}
|
|
|
|
void ReplySender::flush()
|
|
{
|
|
if (mPending)
|
|
{
|
|
mMsg->sendReliable(mCurrHost);
|
|
mPending = false;
|
|
}
|
|
}
|
|
|
|
|
|
typedef uuid_set_t AskQueue;
|
|
typedef std::list<PendingReply*> ReplyQueue;
|
|
typedef std::map<LLUUID,U32> PendingQueue;
|
|
typedef std::map<LLUUID, LLCacheNameEntry*> Cache;
|
|
typedef std::map<std::string, LLUUID> ReverseCache;
|
|
|
|
class LLCacheName::Impl
|
|
{
|
|
public:
|
|
LLMessageSystem* mMsg;
|
|
LLHost mUpstreamHost;
|
|
|
|
Cache mCache;
|
|
// the map of UUIDs to names
|
|
ReverseCache mReverseCache;
|
|
// map of names to UUIDs
|
|
|
|
AskQueue mAskNameQueue;
|
|
AskQueue mAskGroupQueue;
|
|
// UUIDs to ask our upstream host about
|
|
|
|
PendingQueue mPendingQueue;
|
|
// UUIDs that have been requested but are not in cache yet.
|
|
|
|
ReplyQueue mReplyQueue;
|
|
// requests awaiting replies from us
|
|
|
|
LLCacheNameSignal mSignal;
|
|
|
|
LLFrameTimer mProcessTimer;
|
|
|
|
Impl(LLMessageSystem* msg);
|
|
~Impl();
|
|
|
|
BOOL getName(const LLUUID& id, std::string& first, std::string& last);
|
|
|
|
boost::signals2::connection addPending(const LLUUID& id, const LLCacheNameCallback& callback);
|
|
void addPending(const LLUUID& id, const LLHost& host);
|
|
|
|
void processPendingAsks();
|
|
void processPendingReplies();
|
|
void sendRequest(const char* msg_name, const AskQueue& queue);
|
|
bool isRequestPending(const LLUUID& id);
|
|
|
|
// Message system callbacks.
|
|
void processUUIDRequest(LLMessageSystem* msg, bool isGroup);
|
|
void processUUIDReply(LLMessageSystem* msg, bool isGroup);
|
|
|
|
static void handleUUIDNameRequest(LLMessageSystem* msg, void** userdata);
|
|
static void handleUUIDNameReply(LLMessageSystem* msg, void** userdata);
|
|
static void handleUUIDGroupNameRequest(LLMessageSystem* msg, void** userdata);
|
|
static void handleUUIDGroupNameReply(LLMessageSystem* msg, void** userdata);
|
|
};
|
|
|
|
|
|
/// --------------------------------------------------------------------------
|
|
/// class LLCacheName
|
|
/// ---------------------------------------------------------------------------
|
|
|
|
LLCacheName::LLCacheName(LLMessageSystem* msg)
|
|
: impl(* new Impl(msg))
|
|
{ }
|
|
|
|
LLCacheName::LLCacheName(LLMessageSystem* msg, const LLHost& upstream_host)
|
|
: impl(* new Impl(msg))
|
|
{
|
|
sCacheName["waiting"] = "(Loading...)";
|
|
sCacheName["nobody"] = "(nobody)";
|
|
sCacheName["none"] = "(none)";
|
|
setUpstream(upstream_host);
|
|
}
|
|
|
|
LLCacheName::~LLCacheName()
|
|
{
|
|
delete &impl;
|
|
}
|
|
|
|
const ReverseCache& LLCacheName::getReverseMap() const { return impl.mReverseCache; }
|
|
|
|
LLCacheName::Impl::Impl(LLMessageSystem* msg)
|
|
: mMsg(msg), mUpstreamHost(LLHost::invalid)
|
|
{
|
|
mMsg->setHandlerFuncFast(
|
|
_PREHASH_UUIDNameRequest, handleUUIDNameRequest, (void**)this);
|
|
mMsg->setHandlerFuncFast(
|
|
_PREHASH_UUIDNameReply, handleUUIDNameReply, (void**)this);
|
|
mMsg->setHandlerFuncFast(
|
|
_PREHASH_UUIDGroupNameRequest, handleUUIDGroupNameRequest, (void**)this);
|
|
mMsg->setHandlerFuncFast(
|
|
_PREHASH_UUIDGroupNameReply, handleUUIDGroupNameReply, (void**)this);
|
|
}
|
|
|
|
|
|
LLCacheName::Impl::~Impl()
|
|
{
|
|
for_each(mCache.begin(), mCache.end(), DeletePairedPointer());
|
|
mCache.clear();
|
|
for_each(mReplyQueue.begin(), mReplyQueue.end(), DeletePointer());
|
|
mReplyQueue.clear();
|
|
}
|
|
|
|
boost::signals2::connection LLCacheName::Impl::addPending(const LLUUID& id, const LLCacheNameCallback& callback)
|
|
{
|
|
PendingReply* reply = new PendingReply(id, LLHost());
|
|
boost::signals2::connection res = reply->setCallback(callback);
|
|
mReplyQueue.push_back(reply);
|
|
return res;
|
|
}
|
|
|
|
void LLCacheName::Impl::addPending(const LLUUID& id, const LLHost& host)
|
|
{
|
|
PendingReply* reply = new PendingReply(id, host);
|
|
mReplyQueue.push_back(reply);
|
|
}
|
|
|
|
void LLCacheName::setUpstream(const LLHost& upstream_host)
|
|
{
|
|
impl.mUpstreamHost = upstream_host;
|
|
}
|
|
|
|
boost::signals2::connection LLCacheName::addObserver(const LLCacheNameCallback& callback)
|
|
{
|
|
return impl.mSignal.connect(callback);
|
|
}
|
|
|
|
bool LLCacheName::importFile(std::istream& istr)
|
|
{
|
|
LLSD data;
|
|
if(LLSDParser::PARSE_FAILURE == LLSDSerialize::fromXMLDocument(data, istr))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// We'll expire entries more than a week old
|
|
U32 now = (U32)time(NULL);
|
|
const U32 SECS_PER_DAY = 60 * 60 * 24;
|
|
U32 delete_before_time = now - (7 * SECS_PER_DAY);
|
|
|
|
// iterate over the agents
|
|
S32 count = 0;
|
|
LLSD agents = data[AGENTS];
|
|
LLSD::map_iterator iter = agents.beginMap();
|
|
LLSD::map_iterator end = agents.endMap();
|
|
for( ; iter != end; ++iter)
|
|
{
|
|
LLUUID id((*iter).first);
|
|
LLSD agent = (*iter).second;
|
|
U32 ctime = (U32)agent[CTIME].asInteger();
|
|
if(ctime < delete_before_time) continue;
|
|
|
|
LLCacheNameEntry* entry = new LLCacheNameEntry();
|
|
entry->mIsGroup = false;
|
|
entry->mCreateTime = ctime;
|
|
entry->mFirstName = agent[FIRST].asString();
|
|
entry->mLastName = agent[LAST].asString();
|
|
impl.mCache[id] = entry;
|
|
std::string fullname = buildFullName(entry->mFirstName, entry->mLastName);
|
|
impl.mReverseCache[fullname] = id;
|
|
|
|
++count;
|
|
}
|
|
LL_INFOS() << "LLCacheName loaded " << count << " agent names" << LL_ENDL;
|
|
|
|
count = 0;
|
|
LLSD groups = data[GROUPS];
|
|
iter = groups.beginMap();
|
|
end = groups.endMap();
|
|
for( ; iter != end; ++iter)
|
|
{
|
|
LLUUID id((*iter).first);
|
|
LLSD group = (*iter).second;
|
|
U32 ctime = (U32)group[CTIME].asInteger();
|
|
if(ctime < delete_before_time) continue;
|
|
|
|
LLCacheNameEntry* entry = new LLCacheNameEntry();
|
|
entry->mIsGroup = true;
|
|
entry->mCreateTime = ctime;
|
|
entry->mGroupName = group[NAME].asString();
|
|
impl.mCache[id] = entry;
|
|
impl.mReverseCache[entry->mGroupName] = id;
|
|
++count;
|
|
}
|
|
LL_INFOS() << "LLCacheName loaded " << count << " group names" << LL_ENDL;
|
|
return true;
|
|
}
|
|
|
|
void LLCacheName::exportFile(std::ostream& ostr)
|
|
{
|
|
LLSD data;
|
|
Cache::iterator iter = impl.mCache.begin();
|
|
Cache::iterator end = impl.mCache.end();
|
|
for( ; iter != end; ++iter)
|
|
{
|
|
// Only write entries for which we have valid data.
|
|
LLCacheNameEntry* entry = iter->second;
|
|
if(!entry
|
|
|| (std::string::npos != entry->mFirstName.find('?'))
|
|
|| (std::string::npos != entry->mGroupName.find('?')))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// store it
|
|
LLUUID id = iter->first;
|
|
std::string id_str = id.asString();
|
|
// IDEVO TODO: Should we store SLIDs with last name "Resident" or not?
|
|
if(!entry->mFirstName.empty() && !entry->mLastName.empty())
|
|
{
|
|
data[AGENTS][id_str][FIRST] = entry->mFirstName;
|
|
data[AGENTS][id_str][LAST] = entry->mLastName;
|
|
data[AGENTS][id_str][CTIME] = (S32)entry->mCreateTime;
|
|
}
|
|
else if(entry->mIsGroup && !entry->mGroupName.empty())
|
|
{
|
|
data[GROUPS][id_str][NAME] = entry->mGroupName;
|
|
data[GROUPS][id_str][CTIME] = (S32)entry->mCreateTime;
|
|
}
|
|
}
|
|
|
|
LLSDSerialize::toPrettyXML(data, ostr);
|
|
}
|
|
|
|
|
|
BOOL LLCacheName::Impl::getName(const LLUUID& id, std::string& first, std::string& last)
|
|
{
|
|
if(id.isNull())
|
|
{
|
|
first = sCacheName["nobody"];
|
|
last.clear();
|
|
return TRUE;
|
|
}
|
|
|
|
LLCacheNameEntry* entry = get_ptr_in_map(mCache, id );
|
|
if (entry)
|
|
{
|
|
first = entry->mFirstName;
|
|
last = entry->mLastName;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
first = sCacheName["waiting"];
|
|
last.clear();
|
|
if (!isRequestPending(id))
|
|
{
|
|
mAskNameQueue.insert(id);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
}
|
|
|
|
// static
|
|
void LLCacheName::localizeCacheName(std::string key, std::string value)
|
|
{
|
|
if (key!="" && value!= "" )
|
|
sCacheName[key]=value;
|
|
else
|
|
LL_WARNS()<< " Error localizing cache key " << key << " To "<< value<<LL_ENDL;
|
|
}
|
|
|
|
BOOL LLCacheName::getFullName(const LLUUID& id, std::string& fullname)
|
|
{
|
|
std::string first_name, last_name;
|
|
BOOL res = impl.getName(id, first_name, last_name);
|
|
fullname = buildFullName(first_name, last_name);
|
|
return res;
|
|
}
|
|
|
|
|
|
|
|
BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group)
|
|
{
|
|
if(id.isNull())
|
|
{
|
|
group = sCacheName["none"];
|
|
return TRUE;
|
|
}
|
|
|
|
LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache,id);
|
|
if (entry && entry->mGroupName.empty())
|
|
{
|
|
// COUNTER-HACK to combat James' HACK in exportFile()...
|
|
// this group name was loaded from a name cache that did not
|
|
// bother to save the group name ==> we must ask for it
|
|
LL_DEBUGS() << "LLCacheName queuing HACK group request: " << id << LL_ENDL;
|
|
entry = NULL;
|
|
}
|
|
|
|
if (entry)
|
|
{
|
|
group = entry->mGroupName;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
group = sCacheName["waiting"];
|
|
if (!impl.isRequestPending(id))
|
|
{
|
|
impl.mAskGroupQueue.insert(id);
|
|
}
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
BOOL LLCacheName::getUUID(const std::string& first, const std::string& last, LLUUID& id)
|
|
{
|
|
std::string full_name = buildFullName(first, last);
|
|
return getUUID(full_name, id);
|
|
}
|
|
|
|
BOOL LLCacheName::getUUID(const std::string& full_name, LLUUID& id)
|
|
{
|
|
ReverseCache::iterator iter = impl.mReverseCache.find(full_name);
|
|
if (iter != impl.mReverseCache.end())
|
|
{
|
|
id = iter->second;
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//static
|
|
std::string LLCacheName::buildFullName(const std::string& first, const std::string& last)
|
|
{
|
|
static const LLCachedControl<bool> show_resident("LiruShowLastNameResident", false);
|
|
std::string fullname = first;
|
|
if (!last.empty()
|
|
&& (show_resident || last != "Resident"))
|
|
{
|
|
fullname += ' ';
|
|
fullname += last;
|
|
}
|
|
return fullname;
|
|
}
|
|
|
|
//static
|
|
std::string LLCacheName::cleanFullName(const std::string& full_name)
|
|
{
|
|
static const LLCachedControl<bool> show_resident("LiruShowLastNameResident", false);
|
|
if (show_resident) return full_name;
|
|
return full_name.substr(0, full_name.find(" Resident"));
|
|
}
|
|
|
|
//static
|
|
// Transform hard-coded name provided by server to a more legible username
|
|
std::string LLCacheName::buildUsername(const std::string& full_name)
|
|
{
|
|
// rare, but handle hard-coded error names returned from server
|
|
if (full_name == "(\?\?\?) (\?\?\?)")
|
|
{
|
|
return "(\?\?\?)";
|
|
}
|
|
|
|
std::string::size_type index = full_name.find(' ');
|
|
|
|
if (index != std::string::npos)
|
|
{
|
|
std::string username;
|
|
username = full_name.substr(0, index);
|
|
std::string lastname = full_name.substr(index+1);
|
|
|
|
static const LLCachedControl<bool> show_resident("LiruShowLastNameResident", false);
|
|
if (lastname != "Resident" || show_resident)
|
|
{
|
|
username = username + "." + lastname;
|
|
}
|
|
|
|
LLStringUtil::toLower(username);
|
|
return username;
|
|
}
|
|
|
|
// if the input wasn't a correctly formatted legacy name, just return it
|
|
// cleaned up from a potential terminal "Resident"
|
|
return cleanFullName(full_name);
|
|
}
|
|
|
|
//static
|
|
std::string LLCacheName::buildLegacyName(const std::string& complete_name)
|
|
{
|
|
//boost::regexp was showing up in the crashreporter, so doing
|
|
//painfully manual parsing using substr. LF
|
|
size_t open_paren = complete_name.rfind(" (");
|
|
size_t close_paren = complete_name.rfind(')');
|
|
|
|
if (open_paren != std::string::npos &&
|
|
close_paren == complete_name.length()-1)
|
|
{
|
|
size_t length = llmax(close_paren - open_paren - 2, (size_t)0);
|
|
std::string legacy_name = complete_name.substr(open_paren+2, length);
|
|
|
|
if (legacy_name.length() > 0)
|
|
{
|
|
std::string cap_letter = legacy_name.substr(0, 1);
|
|
LLStringUtil::toUpper(cap_letter);
|
|
legacy_name = cap_letter + legacy_name.substr(1);
|
|
|
|
size_t separator = legacy_name.find('.');
|
|
|
|
if (separator != std::string::npos)
|
|
{
|
|
std::string last_name = legacy_name.substr(separator+1);
|
|
legacy_name = legacy_name.substr(0, separator);
|
|
|
|
if (last_name.length() > 0)
|
|
{
|
|
cap_letter = last_name.substr(0, 1);
|
|
LLStringUtil::toUpper(cap_letter);
|
|
legacy_name = legacy_name + " " + cap_letter + last_name.substr(1);
|
|
}
|
|
}
|
|
|
|
return legacy_name;
|
|
}
|
|
}
|
|
|
|
return complete_name;
|
|
}
|
|
|
|
// This is a little bit kludgy. LLCacheNameCallback is a slot instead of a function pointer.
|
|
// The reason it is a slot is so that the legacy get() function below can bind an old callback
|
|
// and pass it as a slot. The reason it isn't a boost::function is so that trackable behavior
|
|
// doesn't get lost. As a result, we have to bind the slot to a signal to call it, even when
|
|
// we call it immediately. -Steve
|
|
// NOTE: Even though passing first and last name is a bit of extra overhead, it eliminates the
|
|
// potential need for any parsing should any code need to handle first and last name independently.
|
|
boost::signals2::connection LLCacheName::get(const LLUUID& id, bool is_group, const LLCacheNameCallback& callback)
|
|
{
|
|
boost::signals2::connection res;
|
|
|
|
if(id.isNull())
|
|
{
|
|
LLCacheNameSignal signal;
|
|
signal.connect(callback);
|
|
signal(id, sCacheName["nobody"], is_group);
|
|
return res;
|
|
}
|
|
|
|
LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache, id );
|
|
if (entry)
|
|
{
|
|
LLCacheNameSignal signal;
|
|
signal.connect(callback);
|
|
// id found in map therefore we can call the callback immediately.
|
|
if (entry->mIsGroup)
|
|
{
|
|
signal(id, entry->mGroupName, entry->mIsGroup);
|
|
}
|
|
else
|
|
{
|
|
std::string fullname =
|
|
buildFullName(entry->mFirstName, entry->mLastName);
|
|
signal(id, fullname, entry->mIsGroup);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// id not found in map so we must queue the callback call until available.
|
|
if (!impl.isRequestPending(id))
|
|
{
|
|
if (is_group)
|
|
{
|
|
impl.mAskGroupQueue.insert(id);
|
|
}
|
|
else
|
|
{
|
|
impl.mAskNameQueue.insert(id);
|
|
}
|
|
}
|
|
res = impl.addPending(id, callback);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
boost::signals2::connection LLCacheName::getGroup(const LLUUID& group_id,
|
|
const LLCacheNameCallback& callback)
|
|
{
|
|
return get(group_id, true, callback);
|
|
}
|
|
|
|
boost::signals2::connection LLCacheName::get(const LLUUID& id, bool is_group, old_callback_t callback, void* user_data)
|
|
{
|
|
return get(id, is_group, boost::bind(callback, _1, _2, _3, user_data));
|
|
}
|
|
|
|
// <edit>
|
|
bool LLCacheName::getIfThere(const LLUUID& id, std::string& fullname, BOOL& is_group)
|
|
{
|
|
if (id.notNull())
|
|
if (LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache, id))
|
|
{
|
|
fullname = (is_group = entry->mIsGroup) ? entry->mGroupName : entry->mFirstName + " " + entry->mLastName;
|
|
return true;
|
|
}
|
|
|
|
fullname = "";
|
|
return false;
|
|
}
|
|
// </edit>
|
|
|
|
void LLCacheName::processPending()
|
|
{
|
|
const F32 SECS_BETWEEN_PROCESS = 0.1f;
|
|
if(!impl.mProcessTimer.checkExpirationAndReset(SECS_BETWEEN_PROCESS))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!impl.mUpstreamHost.isOk())
|
|
{
|
|
LL_DEBUGS() << "LLCacheName::processPending() - bad upstream host."
|
|
<< LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
impl.processPendingAsks();
|
|
impl.processPendingReplies();
|
|
}
|
|
|
|
void LLCacheName::deleteEntriesOlderThan(S32 secs)
|
|
{
|
|
U32 now = (U32)time(NULL);
|
|
U32 expire_time = now - secs;
|
|
for(Cache::iterator iter = impl.mCache.begin(); iter != impl.mCache.end(); )
|
|
{
|
|
Cache::iterator curiter = iter++;
|
|
LLCacheNameEntry* entry = curiter->second;
|
|
if (entry->mCreateTime < expire_time)
|
|
{
|
|
delete entry;
|
|
impl.mCache.erase(curiter);
|
|
}
|
|
}
|
|
|
|
// These are pending requests that we never heard back from.
|
|
U32 pending_expire_time = now - PENDING_TIMEOUT_SECS;
|
|
for(PendingQueue::iterator p_iter = impl.mPendingQueue.begin();
|
|
p_iter != impl.mPendingQueue.end(); )
|
|
{
|
|
PendingQueue::iterator p_curitor = p_iter++;
|
|
|
|
if (p_curitor->second < pending_expire_time)
|
|
{
|
|
impl.mPendingQueue.erase(p_curitor);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void LLCacheName::dump()
|
|
{
|
|
for (Cache::iterator iter = impl.mCache.begin(),
|
|
end = impl.mCache.end();
|
|
iter != end; iter++)
|
|
{
|
|
LLCacheNameEntry* entry = iter->second;
|
|
if (entry->mIsGroup)
|
|
{
|
|
LL_INFOS()
|
|
<< iter->first << " = (group) "
|
|
<< entry->mGroupName
|
|
<< " @ " << entry->mCreateTime
|
|
<< LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_INFOS()
|
|
<< iter->first << " = "
|
|
<< buildFullName(entry->mFirstName, entry->mLastName)
|
|
<< " @ " << entry->mCreateTime
|
|
<< LL_ENDL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLCacheName::dumpStats()
|
|
{
|
|
LL_INFOS() << "Queue sizes: "
|
|
<< " Cache=" << impl.mCache.size()
|
|
<< " AskName=" << impl.mAskNameQueue.size()
|
|
<< " AskGroup=" << impl.mAskGroupQueue.size()
|
|
<< " Pending=" << impl.mPendingQueue.size()
|
|
<< " Reply=" << impl.mReplyQueue.size()
|
|
// << " Observers=" << impl.mSignal.size()
|
|
<< LL_ENDL;
|
|
}
|
|
|
|
void LLCacheName::clear()
|
|
{
|
|
for_each(impl.mCache.begin(), impl.mCache.end(), DeletePairedPointer());
|
|
impl.mCache.clear();
|
|
}
|
|
|
|
//static
|
|
std::string LLCacheName::getDefaultName()
|
|
{
|
|
return sCacheName["waiting"];
|
|
}
|
|
|
|
//static
|
|
std::string LLCacheName::getDefaultLastName()
|
|
{
|
|
return "Resident";
|
|
}
|
|
|
|
void LLCacheName::Impl::processPendingAsks()
|
|
{
|
|
sendRequest(_PREHASH_UUIDNameRequest, mAskNameQueue);
|
|
sendRequest(_PREHASH_UUIDGroupNameRequest, mAskGroupQueue);
|
|
mAskNameQueue.clear();
|
|
mAskGroupQueue.clear();
|
|
}
|
|
|
|
void LLCacheName::Impl::processPendingReplies()
|
|
{
|
|
// First call all the callbacks, because they might send messages.
|
|
for(ReplyQueue::iterator it = mReplyQueue.begin(); it != mReplyQueue.end(); ++it)
|
|
{
|
|
PendingReply* reply = *it;
|
|
LLCacheNameEntry* entry = get_ptr_in_map(mCache, reply->mID);
|
|
if(!entry) continue;
|
|
|
|
if (!entry->mIsGroup)
|
|
{
|
|
std::string fullname =
|
|
LLCacheName::buildFullName(entry->mFirstName, entry->mLastName);
|
|
(reply->mSignal)(reply->mID, fullname, false);
|
|
}
|
|
else
|
|
{
|
|
(reply->mSignal)(reply->mID, entry->mGroupName, true);
|
|
}
|
|
}
|
|
|
|
// Forward on all replies, if needed.
|
|
ReplySender sender(mMsg);
|
|
for(ReplyQueue::iterator it = mReplyQueue.begin(); it != mReplyQueue.end(); ++it)
|
|
{
|
|
PendingReply* reply = *it;
|
|
LLCacheNameEntry* entry = get_ptr_in_map(mCache, reply->mID);
|
|
if(!entry) continue;
|
|
|
|
if (reply->mHost.isOk())
|
|
{
|
|
sender.send(reply->mID, *entry, reply->mHost);
|
|
}
|
|
|
|
reply->done();
|
|
}
|
|
|
|
for(ReplyQueue::iterator it = mReplyQueue.begin(); it != mReplyQueue.end(); )
|
|
{
|
|
ReplyQueue::iterator curit = it++;
|
|
PendingReply* reply = *curit;
|
|
if (reply->isDone())
|
|
{
|
|
delete reply;
|
|
mReplyQueue.erase(curit);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void LLCacheName::Impl::sendRequest(
|
|
const char* msg_name,
|
|
const AskQueue& queue)
|
|
{
|
|
if(queue.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool start_new_message = true;
|
|
AskQueue::const_iterator it = queue.begin();
|
|
AskQueue::const_iterator end = queue.end();
|
|
for(; it != end; ++it)
|
|
{
|
|
if(start_new_message)
|
|
{
|
|
start_new_message = false;
|
|
mMsg->newMessageFast(msg_name);
|
|
}
|
|
mMsg->nextBlockFast(_PREHASH_UUIDNameBlock);
|
|
mMsg->addUUIDFast(_PREHASH_ID, (*it));
|
|
|
|
if(mMsg->isSendFullFast(_PREHASH_UUIDNameBlock))
|
|
{
|
|
start_new_message = true;
|
|
mMsg->sendReliable(mUpstreamHost);
|
|
}
|
|
}
|
|
if(!start_new_message)
|
|
{
|
|
mMsg->sendReliable(mUpstreamHost);
|
|
}
|
|
}
|
|
|
|
bool LLCacheName::Impl::isRequestPending(const LLUUID& id)
|
|
{
|
|
U32 now = (U32)time(NULL);
|
|
U32 expire_time = now - PENDING_TIMEOUT_SECS;
|
|
|
|
PendingQueue::iterator iter = mPendingQueue.find(id);
|
|
|
|
if (iter == mPendingQueue.end()
|
|
|| (iter->second < expire_time) )
|
|
{
|
|
mPendingQueue[id] = now;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void LLCacheName::Impl::processUUIDRequest(LLMessageSystem* msg, bool isGroup)
|
|
{
|
|
// You should only get this message if the cache is at the simulator
|
|
// level, hence having an upstream provider.
|
|
if (!mUpstreamHost.isOk())
|
|
{
|
|
LL_WARNS() << "LLCacheName - got UUID name/group request, but no upstream provider!" << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
LLHost fromHost = msg->getSender();
|
|
ReplySender sender(msg);
|
|
|
|
S32 count = msg->getNumberOfBlocksFast(_PREHASH_UUIDNameBlock);
|
|
for(S32 i = 0; i < count; ++i)
|
|
{
|
|
LLUUID id;
|
|
msg->getUUIDFast(_PREHASH_UUIDNameBlock, _PREHASH_ID, id, i);
|
|
LLCacheNameEntry* entry = get_ptr_in_map(mCache, id);
|
|
if(entry)
|
|
{
|
|
if (isGroup != entry->mIsGroup)
|
|
{
|
|
LL_WARNS() << "LLCacheName - Asked for "
|
|
<< (isGroup ? "group" : "user") << " name, "
|
|
<< "but found "
|
|
<< (entry->mIsGroup ? "group" : "user")
|
|
<< ": " << id << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
// ...it's in the cache, so send it as the reply
|
|
sender.send(id, *entry, fromHost);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!isRequestPending(id))
|
|
{
|
|
if (isGroup)
|
|
{
|
|
mAskGroupQueue.insert(id);
|
|
}
|
|
else
|
|
{
|
|
mAskNameQueue.insert(id);
|
|
}
|
|
}
|
|
|
|
addPending(id, fromHost);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void LLCacheName::Impl::processUUIDReply(LLMessageSystem* msg, bool isGroup)
|
|
{
|
|
S32 count = msg->getNumberOfBlocksFast(_PREHASH_UUIDNameBlock);
|
|
for(S32 i = 0; i < count; ++i)
|
|
{
|
|
LLUUID id;
|
|
msg->getUUIDFast(_PREHASH_UUIDNameBlock, _PREHASH_ID, id, i);
|
|
LLCacheNameEntry* entry = get_ptr_in_map(mCache, id);
|
|
if (!entry)
|
|
{
|
|
entry = new LLCacheNameEntry;
|
|
mCache[id] = entry;
|
|
}
|
|
|
|
mPendingQueue.erase(id);
|
|
|
|
entry->mIsGroup = isGroup;
|
|
entry->mCreateTime = (U32)time(NULL);
|
|
if (!isGroup)
|
|
{
|
|
msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_FirstName, entry->mFirstName, i);
|
|
msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_LastName, entry->mLastName, i);
|
|
}
|
|
else
|
|
{ // is group
|
|
msg->getStringFast(_PREHASH_UUIDNameBlock, _PREHASH_GroupName, entry->mGroupName, i);
|
|
LLStringFn::replace_ascii_controlchars(entry->mGroupName, LL_UNKNOWN_CHAR);
|
|
}
|
|
|
|
if (!isGroup)
|
|
{
|
|
// NOTE: Very occasionally the server sends down a full name
|
|
// in the first name field with an empty last name, for example,
|
|
// first = "Ladanie1 Resident", last = "".
|
|
// I cannot reproduce this, nor can I find a bug in the server code.
|
|
// Ensure "Resident" does not appear via cleanFullName, because
|
|
// buildFullName only checks last name. JC
|
|
std::string full_name;
|
|
if (entry->mLastName.empty())
|
|
{
|
|
full_name = cleanFullName(entry->mFirstName);
|
|
|
|
//fix what we are putting in the cache
|
|
entry->mFirstName = full_name;
|
|
entry->mLastName = "Resident";
|
|
}
|
|
else
|
|
{
|
|
full_name = LLCacheName::buildFullName(entry->mFirstName, entry->mLastName);
|
|
}
|
|
mSignal(id, full_name, false);
|
|
mReverseCache[full_name] = id;
|
|
}
|
|
else
|
|
{
|
|
mSignal(id, entry->mGroupName, true);
|
|
mReverseCache[entry->mGroupName] = id;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// static call back functions
|
|
|
|
void LLCacheName::Impl::handleUUIDNameReply(LLMessageSystem* msg, void** userData)
|
|
{
|
|
((LLCacheName::Impl*)userData)->processUUIDReply(msg, false);
|
|
}
|
|
|
|
void LLCacheName::Impl::handleUUIDNameRequest(LLMessageSystem* msg, void** userData)
|
|
{
|
|
((LLCacheName::Impl*)userData)->processUUIDRequest(msg, false);
|
|
}
|
|
|
|
void LLCacheName::Impl::handleUUIDGroupNameRequest(LLMessageSystem* msg, void** userData)
|
|
{
|
|
((LLCacheName::Impl*)userData)->processUUIDRequest(msg, true);
|
|
}
|
|
|
|
void LLCacheName::Impl::handleUUIDGroupNameReply(LLMessageSystem* msg, void** userData)
|
|
{
|
|
((LLCacheName::Impl*)userData)->processUUIDReply(msg, true);
|
|
}
|