1043 lines
25 KiB
C++
1043 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;
|
|
}
|
|
|
|
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);
|
|
}
|