Shiny new name cache.

This commit is contained in:
Shyotl
2011-07-15 00:21:38 -05:00
parent ce064f5af2
commit 7e0ee6bb71
52 changed files with 932 additions and 742 deletions

View File

@@ -25,10 +25,6 @@
* $/LicenseInfo$
*/
/*
Ported to Phoenix by Wolfspirit Magic.
*/
#include "linden_common.h"
#include "llavatarnamecache.h"
@@ -91,8 +87,11 @@ namespace LLAvatarNameCache
// only need per-frame timing resolution
LLFrameTimer sRequestTimer;
// Periodically clean out expired entries from the cache
//LLFrameTimer sEraseExpiredTimer;
/// Maximum time an unrefreshed cache entry is allowed
const F64 MAX_UNREFRESHED_TIME = 20.0 * 60.0;
/// Time when unrefreshed cached names were checked last
static F64 sLastExpireCheck;
//-----------------------------------------------------------------------
// Internal methods
@@ -107,10 +106,11 @@ namespace LLAvatarNameCache
void requestNamesViaCapability();
// agent_id/group_id, first_name, last_name, is_group, user_data
// Legacy name system callback
void legacyNameCallback(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* data);
void legacyNameCallback(const LLUUID& agent_id,
const std::string& full_name,
bool is_group
);
void requestNamesViaLegacy();
@@ -124,9 +124,10 @@ namespace LLAvatarNameCache
const LLAvatarName& av_name);
// Is a request in-flight over the network?
bool isRequestPending(const LLUUID& agent_id);
// Erase expired names from cache
void eraseExpired();
void eraseUnrefreshed();
bool expirationFromCacheControl(LLSD headers, F64 *expires);
}
@@ -196,6 +197,7 @@ public:
{
// Pull expiration out of headers if available
F64 expires = LLAvatarNameCache::nameExpirationFromHeaders(mHeaders);
F64 now = LLFrameTimer::getTotalSeconds();
LLSD agents = content["agents"];
LLSD::array_const_iterator it = agents.beginArray();
@@ -216,118 +218,91 @@ public:
av_name.mDisplayName = av_name.mUsername;
}
LL_DEBUGS("AvNameCache") << "LLAvatarNameResponder::result for " << agent_id << " "
<< "user '" << av_name.mUsername << "' "
<< "display '" << av_name.mDisplayName << "' "
<< "expires in " << expires - now << " seconds"
<< LL_ENDL;
// cache it and fire signals
LLAvatarNameCache::processName(agent_id, av_name, true);
}
// Same logic as error response case
LLSD unresolved_agents = content["bad_ids"];
if (unresolved_agents.size() > 0)
S32 num_unresolved = unresolved_agents.size();
if (num_unresolved > 0)
{
const std::string DUMMY_NAME("\?\?\?");
LLAvatarName av_name;
av_name.mUsername = DUMMY_NAME;
av_name.mDisplayName = DUMMY_NAME;
av_name.mIsDisplayNameDefault = false;
av_name.mIsDummy = true;
av_name.mExpires = expires;
LL_WARNS("AvNameCache") << "LLAvatarNameResponder::result " << num_unresolved << " unresolved ids; "
<< "expires in " << expires - now << " seconds"
<< LL_ENDL;
it = unresolved_agents.beginArray();
for ( ; it != unresolved_agents.endArray(); ++it)
{
const LLUUID& agent_id = *it;
// cache it and fire signals
// Wolfspirit: Do not use ??? as username. Try to get a username out of the old legacy name
std::string oldname;
gCacheName->getFullName(agent_id, oldname);
LLStringUtil::toLower(oldname);
LLStringUtil::replaceString(oldname," ",".");
LLStringUtil::replaceString(oldname,".resident","");
av_name.mUsername = oldname;
LL_WARNS("AvNameCache") << "LLAvatarNameResponder::result "
<< "failed id " << agent_id
<< LL_ENDL;
LLAvatarNameCache::processName(agent_id, av_name, true);
LLAvatarNameCache::handleAgentError(agent_id);
}
}
}
LL_DEBUGS("AvNameCache") << "LLAvatarNameResponder::result "
<< LLAvatarNameCache::sCache.size() << " cached names"
<< LL_ENDL;
}
/*virtual*/ void error(U32 status, const std::string& reason)
{
// We're going to construct a dummy record and cache it for a while,
// either briefly for a 503 Service Unavailable, or longer for other
// errors.
F64 retry_timestamp = errorRetryTimestamp(status);
// If there's an error, it might be caused by PeopleApi,
// or when loading textures on startup and using a very slow
// network, this query may time out.
// What we should do depends on whether or not we have a cached name
LL_WARNS("AvNameCache") << "LLAvatarNameResponder::error " << status << " " << reason
<< LL_ENDL;
// *NOTE: "??" starts trigraphs in C/C++, escape the question marks.
const std::string DUMMY_NAME("\?\?\?");
LLAvatarName av_name;
av_name.mUsername = DUMMY_NAME;
av_name.mDisplayName = DUMMY_NAME;
av_name.mIsDisplayNameDefault = false;
av_name.mIsDummy = true;
av_name.mExpires = retry_timestamp;
// Add dummy records for all agent IDs in this request
// Add dummy records for any agent IDs in this request that we do not have cached already
std::vector<LLUUID>::const_iterator it = mAgentIDs.begin();
for ( ; it != mAgentIDs.end(); ++it)
{
const LLUUID& agent_id = *it;
// cache it and fire signals
// Wolfspirit: Do not use ??? as username. Try to get a username out of the old legacy name
std::string oldname;
gCacheName->getFullName(agent_id, oldname);
LLStringUtil::toLower(oldname);
LLStringUtil::replaceString(oldname," ",".");
LLStringUtil::replaceString(oldname,".resident","");
av_name.mUsername = oldname;
LLAvatarNameCache::processName(agent_id, av_name, true);
}
}
// Return time to retry a request that generated an error, based on
// error type and headers. Return value is seconds-since-epoch.
F64 errorRetryTimestamp(S32 status)
{
F64 now = LLFrameTimer::getTotalSeconds();
// Retry-After takes priority
LLSD retry_after = mHeaders["retry-after"];
if (retry_after.isDefined())
{
// We only support the delta-seconds type
S32 delta_seconds = retry_after.asInteger();
if (delta_seconds > 0)
{
// ...valid delta-seconds
return now + F64(delta_seconds);
}
}
// If no Retry-After, look for Cache-Control max-age
F64 expires = 0.0;
if (LLAvatarNameCache::expirationFromCacheControl(mHeaders, &expires))
{
return expires;
}
// No information in header, make a guess
if (status == 503)
{
// ...service unavailable, retry soon
const F64 SERVICE_UNAVAILABLE_DELAY = 600.0; // 10 min
return now + SERVICE_UNAVAILABLE_DELAY;
}
else
{
// ...other unexpected error
const F64 DEFAULT_DELAY = 3600.0; // 1 hour
return now + DEFAULT_DELAY;
LLAvatarNameCache::handleAgentError(agent_id);
}
}
};
// Provide some fallback for agents that return errors
void LLAvatarNameCache::handleAgentError(const LLUUID& agent_id)
{
std::map<LLUUID,LLAvatarName>::iterator existing = sCache.find(agent_id);
if (existing == sCache.end())
{
// there is no existing cache entry, so make a temporary name from legacy
LL_WARNS("AvNameCache") << "LLAvatarNameCache get legacy for agent "
<< agent_id << LL_ENDL;
gCacheName->get(agent_id, false, // legacy compatibility
boost::bind(&LLAvatarNameCache::legacyNameCallback,
_1, _2, _3));
}
else
{
// we have a chached (but probably expired) entry - since that would have
// been returned by the get method, there is no need to signal anyone
// Clear this agent from the pending list
LLAvatarNameCache::sPendingQueue.erase(agent_id);
const LLAvatarName& av_name = existing->second;
LL_DEBUGS("AvNameCache") << "LLAvatarNameCache use cache for agent "
<< agent_id
<< "user '" << av_name.mUsername << "' "
<< "display '" << av_name.mDisplayName << "' "
<< "expires in " << av_name.mExpires - LLFrameTimer::getTotalSeconds() << " seconds"
<< LL_ENDL;
}
}
void LLAvatarNameCache::processName(const LLUUID& agent_id,
const LLAvatarName& av_name,
bool add_to_cache)
@@ -336,12 +311,12 @@ void LLAvatarNameCache::processName(const LLUUID& agent_id,
{
// sCache[agent_id] = av_name;
// [SL:KB] - Patch: Agent-DisplayNames | Checked: 2010-12-28 (Catznip-2.4.0h) | Added: Catznip-2.4.0h
// Don't replace existing entries with dummies
cache_t::iterator itName = (av_name.mIsDummy) ? sCache.find(agent_id) : sCache.end();
if (sCache.end() != itName)
itName->second.mExpires = av_name.mExpires;
else
sCache[agent_id] = av_name;
// Don't replace existing entries with dummies
cache_t::iterator itName = (av_name.mIsTemporaryName) ? sCache.find(agent_id) : sCache.end();
if (sCache.end() != itName)
itName->second.mExpires = av_name.mExpires;
else
sCache[agent_id] = av_name;
// [/SL:KB]
}
@@ -377,6 +352,7 @@ void LLAvatarNameCache::requestNamesViaCapability()
std::vector<LLUUID> agent_ids;
agent_ids.reserve(128);
U32 ids = 0;
ask_queue_t::const_iterator it = sAskQueue.begin();
for ( ; it != sAskQueue.end(); ++it)
{
@@ -387,11 +363,13 @@ void LLAvatarNameCache::requestNamesViaCapability()
// ...starting new request
url += sNameLookupURL;
url += "?ids=";
ids = 1;
}
else
{
// ...continuing existing request
url += "&ids=";
ids++;
}
url += agent_id.asString();
agent_ids.push_back(agent_id);
@@ -401,7 +379,9 @@ void LLAvatarNameCache::requestNamesViaCapability()
if (url.size() > NAME_URL_SEND_THRESHOLD)
{
//llinfos << "requestNames " << url << llendl;
LL_DEBUGS("AvNameCache") << "LLAvatarNameCache::requestNamesViaCapability first "
<< ids << " ids"
<< LL_ENDL;
LLHTTPClient::get(url, new LLAvatarNameResponder(agent_ids));
url.clear();
agent_ids.clear();
@@ -410,7 +390,9 @@ void LLAvatarNameCache::requestNamesViaCapability()
if (!url.empty())
{
//llinfos << "requestNames " << url << llendl;
LL_DEBUGS("AvNameCache") << "LLAvatarNameCache::requestNamesViaCapability all "
<< ids << " ids"
<< LL_ENDL;
LLHTTPClient::get(url, new LLAvatarNameResponder(agent_ids));
url.clear();
agent_ids.clear();
@@ -419,18 +401,25 @@ void LLAvatarNameCache::requestNamesViaCapability()
// We've moved all asks to the pending request queue
sAskQueue.clear();
}
void LLAvatarNameCache::legacyNameCallback(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group, void* data)
void LLAvatarNameCache::legacyNameCallback(const LLUUID& agent_id,
const std::string& full_name,
bool is_group)
{
std::string full_name = first + " " +last;
// Construct a dummy record for this name. By convention, SLID is blank
// Never expires, but not written to disk, so lasts until end of session.
LLAvatarName av_name;
LL_DEBUGS("AvNameCache") << "LLAvatarNameCache::legacyNameCallback "
<< "agent " << agent_id << " "
<< "full name '" << full_name << "'"
<< ( is_group ? " [group]" : "" )
<< LL_ENDL;
buildLegacyName(full_name, &av_name);
// Don't add to cache, the data already exists in the legacy name system
// cache and we don't want or need duplicate storage, because keeping the
// two copies in sync is complex.
processName(id, av_name, false);
processName(agent_id, av_name, false);
}
void LLAvatarNameCache::requestNamesViaLegacy()
@@ -446,7 +435,11 @@ void LLAvatarNameCache::requestNamesViaLegacy()
// invoked below. This should never happen in practice.
sPendingQueue[agent_id] = now;
gCacheName->get(agent_id, false, legacyNameCallback);
LL_DEBUGS("AvNameCache") << "LLAvatarNameCache::requestNamesViaLegacy agent " << agent_id << LL_ENDL;
gCacheName->get(agent_id, false, // legacy compatibility
boost::bind(&LLAvatarNameCache::legacyNameCallback,
_1, _2, _3));
}
// We've either answered immediately or moved all asks to the
@@ -482,21 +475,24 @@ void LLAvatarNameCache::importFile(std::istream& istr)
av_name.fromLLSD( it->second );
sCache[agent_id] = av_name;
}
// entries may have expired since we last ran the viewer, just
// clean them out now
eraseExpired();
llinfos << "loaded " << sCache.size() << llendl;
LL_INFOS("AvNameCache") << "loaded " << sCache.size() << LL_ENDL;
// Some entries may have expired since the cache was stored,
// but they will be flushed in the first call to eraseUnrefreshed
// from LLAvatarNameResponder::idle
}
void LLAvatarNameCache::exportFile(std::ostream& ostr)
{
LLSD agents;
F64 max_unrefreshed = LLFrameTimer::getTotalSeconds() - MAX_UNREFRESHED_TIME;
cache_t::const_iterator it = sCache.begin();
for ( ; it != sCache.end(); ++it)
{
const LLUUID& agent_id = it->first;
const LLAvatarName& av_name = it->second;
if (!av_name.mIsDummy)
// Do not write temporary or expired entries to the stored cache
if (!av_name.mIsTemporaryName && av_name.mExpires >= max_unrefreshed)
{
// key must be a string
agents[agent_id.asString()] = av_name.asLLSD();
@@ -531,36 +527,26 @@ void LLAvatarNameCache::idle()
// return;
//}
// Must be large relative to above
// No longer deleting expired entries, just re-requesting in the get
// this way first synchronous get call on an expired entry won't return
// legacy name. LF
//const F32 ERASE_EXPIRED_TIMEOUT = 60.f; // seconds
//if (sEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT))
//{
// eraseExpired();
//}
if (sAskQueue.empty())
if (!sAskQueue.empty())
{
return;
if (useDisplayNames())
{
requestNamesViaCapability();
}
else
{
// ...fall back to legacy name cache system
requestNamesViaLegacy();
}
}
if (useDisplayNames())
{
requestNamesViaCapability();
}
else
{
// ...fall back to legacy name cache system
requestNamesViaLegacy();
}
// erase anything that has not been refreshed for more than MAX_UNREFRESHED_TIME
eraseUnrefreshed();
}
bool LLAvatarNameCache::isRequestPending(const LLUUID& agent_id)
{
bool isPending = false;
const F64 PENDING_TIMEOUT_SECS = 5.0 * 60.0;
pending_queue_t::const_iterator it = sPendingQueue.find(agent_id);
@@ -568,26 +554,38 @@ bool LLAvatarNameCache::isRequestPending(const LLUUID& agent_id)
{
// in the list of requests in flight, retry if too old
F64 expire_time = LLFrameTimer::getTotalSeconds() - PENDING_TIMEOUT_SECS;
return it->second > expire_time;
isPending = (it->second > expire_time);
}
return false;
return isPending;
}
void LLAvatarNameCache::eraseExpired()
void LLAvatarNameCache::eraseUnrefreshed()
{
F64 now = LLFrameTimer::getTotalSeconds();
cache_t::iterator it = sCache.begin();
for (cache_t::iterator it = sCache.begin(); it != sCache.end();)
{
const LLAvatarName& av_name = it->second;
if (av_name.mExpires < now)
{
sCache.erase(it++);
}
else
{
++it;
}
F64 max_unrefreshed = now - MAX_UNREFRESHED_TIME;
if (!sLastExpireCheck || sLastExpireCheck < max_unrefreshed)
{
sLastExpireCheck = now;
for (cache_t::iterator it = sCache.begin(); it != sCache.end();)
{
const LLAvatarName& av_name = it->second;
if (av_name.mExpires < max_unrefreshed)
{
const LLUUID& agent_id = it->first;
LL_DEBUGS("AvNameCache") << agent_id
<< " user '" << av_name.mUsername << "' "
<< "expired " << now - av_name.mExpires << " secs ago"
<< LL_ENDL;
sCache.erase(it++);
}
else
{
++it;
}
}
LL_INFOS("AvNameCache") << sCache.size() << " cached avatar names" << LL_ENDL;
}
}
@@ -598,8 +596,11 @@ void LLAvatarNameCache::buildLegacyName(const std::string& full_name,
av_name->mUsername = "";
av_name->mDisplayName = full_name;
av_name->mIsDisplayNameDefault = true;
av_name->mIsDummy = true;
av_name->mExpires = F64_MAX;
av_name->mIsTemporaryName = true;
av_name->mExpires = F64_MAX; // not used because these are not cached
LL_DEBUGS("AvNameCache") << "LLAvatarNameCache::buildLegacyName "
<< full_name
<< LL_ENDL;
// [Ansariel/Henri]
// Why ain't those set? In case of disabled display names
@@ -632,6 +633,9 @@ bool LLAvatarNameCache::get(const LLUUID& agent_id, LLAvatarName *av_name)
{
if (!isRequestPending(agent_id))
{
LL_DEBUGS("AvNameCache") << "LLAvatarNameCache::get "
<< "refresh agent " << agent_id
<< LL_ENDL;
sAskQueue.insert(agent_id);
}
}
@@ -641,7 +645,6 @@ bool LLAvatarNameCache::get(const LLUUID& agent_id, LLAvatarName *av_name)
}
else
{
// ...use legacy names cache
std::string full_name;
if (gCacheName->getFullName(agent_id, full_name))
@@ -654,6 +657,9 @@ bool LLAvatarNameCache::get(const LLUUID& agent_id, LLAvatarName *av_name)
if (!isRequestPending(agent_id))
{
LL_DEBUGS("AvNameCache") << "LLAvatarNameCache::get "
<< "queue request for agent " << agent_id
<< LL_ENDL;
sAskQueue.insert(agent_id);
}
@@ -800,6 +806,8 @@ F64 LLAvatarNameCache::nameExpirationFromHeaders(LLSD headers)
bool LLAvatarNameCache::expirationFromCacheControl(LLSD headers, F64 *expires)
{
bool fromCacheControl = false;
F64 now = LLFrameTimer::getTotalSeconds();
// Allow the header to override the default
LLSD cache_control_header = headers["cache-control"];
if (cache_control_header.isDefined())
@@ -808,12 +816,16 @@ bool LLAvatarNameCache::expirationFromCacheControl(LLSD headers, F64 *expires)
std::string cache_control = cache_control_header.asString();
if (max_age_from_cache_control(cache_control, &max_age))
{
F64 now = LLFrameTimer::getTotalSeconds();
*expires = now + (F64)max_age;
return true;
fromCacheControl = true;
}
}
return false;
LL_DEBUGS("AvNameCache")
<< ( fromCacheControl ? "expires based on cache control " : "default expiration " )
<< "in " << *expires - now << " seconds"
<< LL_ENDL;
return fromCacheControl;
}

View File

@@ -85,10 +85,11 @@ namespace LLAvatarNameCache
void setForceDisplayNames(bool force);
// [/RLVa:KB]
bool isRequestPending(const LLUUID& agent_id);
void erase(const LLUUID& agent_id);
/// Provide some fallback for agents that return errors
void handleAgentError(const LLUUID& agent_id);
// Force a re-fetch of the most recent data, but keep the current
// data in cache
void fetch(const LLUUID& agent_id);

View File

@@ -43,10 +43,7 @@
#include "lluuid.h"
#include "message.h"
// Constants
static const std::string CN_WAITING("(Loading...)"); // *TODO: translate
static const std::string CN_NOBODY("(nobody)"); // *TODO: translate
static const std::string CN_NONE("(none)"); // *TODO: translate
#include <boost/regex.hpp>
// llsd serialization constants
static const std::string AGENTS("agents");
@@ -65,6 +62,7 @@ const S32 CN_FILE_VERSION = 2;
// Globals
LLCacheName* gCacheName = NULL;
std::map<std::string, std::string> LLCacheName::sCacheName;
/// ---------------------------------------------------------------------------
/// class LLCacheNameEntry
@@ -84,6 +82,8 @@ public:
};
LLCacheNameEntry::LLCacheNameEntry()
: mIsGroup(false),
mCreateTime(0)
{
}
@@ -92,17 +92,19 @@ class PendingReply
{
public:
LLUUID mID;
LLCacheNameCallback mCallback;
LLCacheNameSignal mSignal;
LLHost mHost;
void* mData;
PendingReply(const LLUUID& id, LLCacheNameCallback callback, void* data = NULL)
: mID(id), mCallback(callback), mData(data)
{ }
PendingReply(const LLUUID& id, const LLHost& host)
: mID(id), mCallback(0), mHost(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; }
};
@@ -126,7 +128,7 @@ private:
};
ReplySender::ReplySender(LLMessageSystem* msg)
: mMsg(msg), mPending(false)
: mMsg(msg), mPending(false), mCurrIsGroup(false)
{ }
ReplySender::~ReplySender()
@@ -187,10 +189,10 @@ void ReplySender::flush()
typedef std::set<LLUUID> AskQueue;
typedef std::vector<PendingReply> ReplyQueue;
typedef std::list<PendingReply*> ReplyQueue;
typedef std::map<LLUUID,U32> PendingQueue;
typedef std::map<LLUUID, LLCacheNameEntry*> Cache;
typedef std::vector<LLCacheNameCallback> Observers;
typedef std::map<std::string, LLUUID> ReverseCache;
class LLCacheName::Impl
{
@@ -200,7 +202,9 @@ public:
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
@@ -211,13 +215,18 @@ public:
ReplyQueue mReplyQueue;
// requests awaiting replies from us
Observers mObservers;
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);
@@ -231,8 +240,6 @@ public:
static void handleUUIDNameReply(LLMessageSystem* msg, void** userdata);
static void handleUUIDGroupNameRequest(LLMessageSystem* msg, void** userdata);
static void handleUUIDGroupNameReply(LLMessageSystem* msg, void** userdata);
void notifyObservers(const LLUUID& id, const std::string& first, const std::string& last, BOOL group);
};
@@ -247,6 +254,9 @@ LLCacheName::LLCacheName(LLMessageSystem* 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);
}
@@ -272,54 +282,34 @@ LLCacheName::Impl::Impl(LLMessageSystem* msg)
LLCacheName::Impl::~Impl()
{
for_each(mCache.begin(), mCache.end(), DeletePairedPointer());
for_each(mReplyQueue.begin(), mReplyQueue.end(), DeletePointer());
}
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;
}
void LLCacheName::addObserver(LLCacheNameCallback callback)
boost::signals2::connection LLCacheName::addObserver(const LLCacheNameCallback& callback)
{
impl.mObservers.push_back(callback);
}
void LLCacheName::removeObserver(LLCacheNameCallback callback)
{
Observers::iterator it = impl.mObservers.begin();
Observers::iterator end = impl.mObservers.end();
for ( ; it != end; ++it)
{
const LLCacheNameCallback& cb = (*it);
if (cb == callback)
{
impl.mObservers.erase(it);
return;
}
}
}
void LLCacheName::cancelCallback(const LLUUID& id, LLCacheNameCallback callback, void* user_data)
{
ReplyQueue::iterator it = impl.mReplyQueue.begin();
ReplyQueue::iterator end = impl.mReplyQueue.end();
for(; it != end; ++it)
{
const PendingReply& reply = (*it);
if ((callback == reply.mCallback)
&& (id == reply.mID)
&& (user_data == reply.mData) )
{
impl.mReplyQueue.erase(it);
return;
}
}
return impl.mSignal.connect(callback);
}
#if 0
void LLCacheName::importFile(LLFILE* fp)
{
S32 count = 0;
@@ -396,11 +386,12 @@ void LLCacheName::importFile(LLFILE* fp)
llinfos << "LLCacheName loaded " << count << " names" << llendl;
}
#endif
bool LLCacheName::importFile(std::istream& istr)
{
LLSD data;
if(LLSDSerialize::fromXML(data, istr) < 1)
if(LLSDSerialize::fromXMLDocument(data, istr) < 1)
return false;
// We'll expire entries more than a week old
@@ -426,6 +417,9 @@ bool LLCacheName::importFile(std::istream& istr)
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;
}
llinfos << "LLCacheName loaded " << count << " agent names" << llendl;
@@ -446,6 +440,7 @@ bool LLCacheName::importFile(std::istream& istr)
entry->mCreateTime = ctime;
entry->mGroupName = group[NAME].asString();
impl.mCache[id] = entry;
impl.mReverseCache[entry->mGroupName] = id;
++count;
}
llinfos << "LLCacheName loaded " << count << " group names" << llendl;
@@ -488,16 +483,16 @@ void LLCacheName::exportFile(std::ostream& ostr)
}
BOOL LLCacheName::getName(const LLUUID& id, std::string& first, std::string& last)
BOOL LLCacheName::Impl::getName(const LLUUID& id, std::string& first, std::string& last)
{
if(id.isNull())
{
first = CN_NOBODY;
first = sCacheName["nobody"];
last.clear();
return FALSE;
return TRUE;
}
LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache, id );
LLCacheNameEntry* entry = get_ptr_in_map(mCache, id );
if (entry)
{
first = entry->mFirstName;
@@ -506,32 +501,42 @@ BOOL LLCacheName::getName(const LLUUID& id, std::string& first, std::string& las
}
else
{
first = CN_WAITING;
first = sCacheName["waiting"];
last.clear();
if (!impl.isRequestPending(id))
if (!isRequestPending(id))
{
impl.mAskNameQueue.insert(id);
mAskNameQueue.insert(id);
}
return FALSE;
}
}
// static
void LLCacheName::localizeCacheName(std::string key, std::string value)
{
if (key!="" && value!= "" )
sCacheName[key]=value;
else
llwarns<< " Error localizing cache key " << key << " To "<< value<<llendl;
}
BOOL LLCacheName::getFullName(const LLUUID& id, std::string& fullname)
{
std::string first_name, last_name;
BOOL res = getName(id, first_name, last_name);
if(res)
fullname = 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 = CN_NONE;
return FALSE;
group = sCacheName["none"];
return TRUE;
}
LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache,id);
@@ -551,7 +556,7 @@ BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group)
}
else
{
group = CN_WAITING;
group = sCacheName["waiting"];
if (!impl.isRequestPending(id))
{
impl.mAskGroupQueue.insert(id);
@@ -560,27 +565,151 @@ BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group)
}
}
// TODO: Make the cache name callback take a SINGLE std::string,
// not a separate first and last name.
void LLCacheName::get(const LLUUID& id, BOOL is_group, LLCacheNameCallback callback, void* user_data)
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)
{
std::string fullname = first;
if (!last.empty()
&& last != "Resident")
{
fullname += ' ';
fullname += last;
}
return fullname;
}
//static
std::string LLCacheName::cleanFullName(const std::string& full_name)
{
return full_name.substr(0, full_name.find(" Resident"));
}
//static
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);
if (lastname != "Resident")
{
username = username + "." + lastname;
}
LLStringUtil::toLower(username);
return username;
}
// if the input wasn't a correctly formatted legacy name just return it unchanged
return 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
S32 open_paren = complete_name.rfind(" (");
S32 close_paren = complete_name.rfind(')');
if (open_paren != std::string::npos &&
close_paren == complete_name.length()-1)
{
S32 length = close_paren - open_paren - 2;
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);
S32 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())
{
callback(id, CN_NOBODY, "", is_group, user_data);
return;
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)
{
callback(id, entry->mGroupName, "", entry->mIsGroup, user_data);
signal(id, entry->mGroupName, entry->mIsGroup);
}
else
{
callback(id, entry->mFirstName, entry->mLastName, entry->mIsGroup, user_data);
std::string fullname =
buildFullName(entry->mFirstName, entry->mLastName);
signal(id, fullname, entry->mIsGroup);
}
}
else
@@ -597,16 +726,29 @@ void LLCacheName::get(const LLUUID& id, BOOL is_group, LLCacheNameCallback callb
impl.mAskNameQueue.insert(id);
}
}
impl.mReplyQueue.push_back(PendingReply(id, callback, user_data));
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)
bool LLCacheName::getIfThere(const LLUUID& id, std::string& fullname, BOOL& is_group)
{
if(id.isNull())
{
fullname = "";
return FALSE;
return false;
}
LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache, id );
@@ -621,10 +763,10 @@ BOOL LLCacheName::getIfThere(const LLUUID& id, std::string& fullname, BOOL& is_g
fullname = entry->mFirstName + " " + entry->mLastName;
}
is_group = entry->mIsGroup;
return TRUE;
return true;
}
fullname = "";
return FALSE;
return false;
}
// </edit>
void LLCacheName::processPending()
@@ -695,7 +837,7 @@ void LLCacheName::dump()
{
llinfos
<< iter->first << " = "
<< entry->mFirstName << " " << entry->mLastName
<< buildFullName(entry->mFirstName, entry->mLastName)
<< " @ " << entry->mCreateTime
<< llendl;
}
@@ -710,16 +852,27 @@ void LLCacheName::dumpStats()
<< " AskGroup=" << impl.mAskGroupQueue.size()
<< " Pending=" << impl.mPendingQueue.size()
<< " Reply=" << impl.mReplyQueue.size()
<< " Observers=" << impl.mObservers.size()
// << " Observers=" << impl.mSignal.size()
<< llendl;
}
void LLCacheName::clear()
{
for_each(impl.mCache.begin(), impl.mCache.end(), DeletePairedPointer());
impl.mCache.clear();
}
//static
std::string LLCacheName::getDefaultName()
{
return CN_WAITING;
return sCacheName["waiting"];
}
//static
std::string LLCacheName::getDefaultLastName()
{
return "Resident";
}
void LLCacheName::Impl::processPendingAsks()
{
sendRequest(_PREHASH_UUIDNameRequest, mAskNameQueue);
@@ -730,50 +883,51 @@ void LLCacheName::Impl::processPendingAsks()
void LLCacheName::Impl::processPendingReplies()
{
ReplyQueue::iterator it = mReplyQueue.begin();
ReplyQueue::iterator end = mReplyQueue.end();
// First call all the callbacks, because they might send messages.
for(; it != end; ++it)
for(ReplyQueue::iterator it = mReplyQueue.begin(); it != mReplyQueue.end(); ++it)
{
LLCacheNameEntry* entry = get_ptr_in_map(mCache, it->mID);
PendingReply* reply = *it;
LLCacheNameEntry* entry = get_ptr_in_map(mCache, reply->mID);
if(!entry) continue;
if (it->mCallback)
if (!entry->mIsGroup)
{
if (!entry->mIsGroup)
{
(it->mCallback)(it->mID,
entry->mFirstName, entry->mLastName,
FALSE, it->mData);
}
else {
(it->mCallback)(it->mID,
entry->mGroupName, "",
TRUE, it->mData);
}
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 (it = mReplyQueue.begin(); it != end; ++it)
for(ReplyQueue::iterator it = mReplyQueue.begin(); it != mReplyQueue.end(); ++it)
{
LLCacheNameEntry* entry = get_ptr_in_map(mCache, it->mID);
PendingReply* reply = *it;
LLCacheNameEntry* entry = get_ptr_in_map(mCache, reply->mID);
if(!entry) continue;
if (it->mHost.isOk())
if (reply->mHost.isOk())
{
sender.send(it->mID, *entry, it->mHost);
sender.send(reply->mID, *entry, reply->mHost);
}
it->done();
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);
}
}
mReplyQueue.erase(
remove_if(mReplyQueue.begin(), mReplyQueue.end(),
std::mem_fun_ref(&PendingReply::isDone)),
mReplyQueue.end());
}
@@ -811,18 +965,6 @@ void LLCacheName::Impl::sendRequest(
}
}
void LLCacheName::Impl::notifyObservers(const LLUUID& id,
const std::string& first, const std::string& last, BOOL is_group)
{
for (Observers::const_iterator i = mObservers.begin(),
end = mObservers.end();
i != end;
++i)
{
(**i)(id, first, last, is_group, NULL);
}
}
bool LLCacheName::Impl::isRequestPending(const LLUUID& id)
{
U32 now = (U32)time(NULL);
@@ -889,7 +1031,7 @@ void LLCacheName::Impl::processUUIDRequest(LLMessageSystem* msg, bool isGroup)
}
}
mReplyQueue.push_back(PendingReply(id, fromHost));
addPending(id, fromHost);
}
}
}
@@ -927,11 +1069,32 @@ void LLCacheName::Impl::processUUIDReply(LLMessageSystem* msg, bool isGroup)
if (!isGroup)
{
notifyObservers(id, entry->mFirstName, entry->mLastName, FALSE);
// 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
{
notifyObservers(id, entry->mGroupName, "", TRUE);
mSignal(id, entry->mGroupName, true);
mReverseCache[entry->mGroupName] = id;
}
}
}

View File

@@ -33,12 +33,21 @@
#ifndef LL_LLCACHENAME_H
#define LL_LLCACHENAME_H
#include <boost/bind.hpp>
#include <boost/signals2.hpp>
class LLMessageSystem;
class LLHost;
class LLUUID;
// agent_id/group_id, first_name, last_name, is_group, user_data
typedef void (*LLCacheNameCallback)(const LLUUID&, const std::string&, const std::string&, BOOL, void*);
typedef boost::signals2::signal<void (const LLUUID& id,
const std::string& name,
bool is_group)> LLCacheNameSignal;
typedef LLCacheNameSignal::slot_type LLCacheNameCallback;
// Old callback with user data for compatability
typedef void (*old_callback_t)(const LLUUID&, const std::string&, bool, void*);
// Here's the theory:
// If you request a name that isn't in the cache, it returns "waiting"
@@ -59,25 +68,44 @@ public:
// for simulators, this is the data server
void setUpstream(const LLHost& upstream_host);
void addObserver(LLCacheNameCallback callback);
void removeObserver(LLCacheNameCallback callback);
void cancelCallback(const LLUUID& id, LLCacheNameCallback callback, void* user_data = NULL);
boost::signals2::connection addObserver(const LLCacheNameCallback& callback);
// janky old format. Remove after a while. Phoenix. 2008-01-30
#if 0
void importFile(LLFILE* fp);
#endif
// storing cache on disk; for viewer, in name.cache
bool importFile(std::istream& istr);
void exportFile(std::ostream& ostr);
// If available, copies the first and last name into the strings provided.
// first must be at least DB_FIRST_NAME_BUF_SIZE characters.
// last must be at least DB_LAST_NAME_BUF_SIZE characters.
// If available, copies name ("bobsmith123" or "James Linden") into string
// If not available, copies the string "waiting".
// Returns TRUE iff available.
BOOL getName(const LLUUID& id, std::string& first, std::string& last);
BOOL getFullName(const LLUUID& id, std::string& fullname);
BOOL getFullName(const LLUUID& id, std::string& full_name);
// Reverse lookup of UUID from name
BOOL getUUID(const std::string& first, const std::string& last, LLUUID& id);
BOOL getUUID(const std::string& fullname, LLUUID& id);
// IDEVO Temporary code
// Clean up new-style "bobsmith123 Resident" names to "bobsmith123" for display
static std::string buildFullName(const std::string& first, const std::string& last);
// Clean up legacy "bobsmith123 Resident" to "bobsmith123"
// If name does not contain "Resident" returns it unchanged.
static std::string cleanFullName(const std::string& full_name);
// Converts a standard legacy name to a username
// "bobsmith123 Resident" -> "bobsmith"
// "Random Linden" -> "random.linden"
static std::string buildUsername(const std::string& name);
// Converts a complete display name to a legacy name
// if possible, otherwise returns the input
// "Alias (random.linden)" -> "Random Linden"
// "Something random" -> "Something random"
static std::string buildLegacyName(const std::string& name);
// If available, this method copies the group name into the string
// provided. The caller must allocate at least
@@ -89,16 +117,19 @@ public:
// If the data is currently available, may call the callback immediatly
// otherwise, will request the data, and will call the callback when
// available. There is no garuntee the callback will ever be called.
void get(const LLUUID& id, BOOL is_group, LLCacheNameCallback callback, void* user_data = NULL);
boost::signals2::connection get(const LLUUID& id, bool is_group, const LLCacheNameCallback& callback);
// <edit>
BOOL getIfThere(const LLUUID& id, std::string& fullname, BOOL& is_group);
// </edit>
//<edit>
bool getIfThere(const LLUUID& id, std::string& fullname, BOOL& is_group);
//</edit>
// Convenience method for looking up a group name, so you can
// tell the difference between avatar lookup and group lookup
// in global searches
boost::signals2::connection getGroup(const LLUUID& group_id, const LLCacheNameCallback& callback);
// LEGACY
void getName(const LLUUID& id, LLCacheNameCallback callback, void* user_data = NULL)
{ get(id, FALSE, callback, user_data); }
boost::signals2::connection get(const LLUUID& id, bool is_group, old_callback_t callback, void* user_data);
// This method needs to be called from time to time to send out
// requests.
void processPending();
@@ -109,9 +140,16 @@ public:
// Debugging
void dump(); // Dumps the contents of the cache
void dumpStats(); // Dumps the sizes of the cache and associated queues.
void clear(); // Deletes all entries from the cache
static std::string getDefaultName();
// Returns "Resident", the default last name for SLID-based accounts
// that have no last name.
static std::string getDefaultLastName();
static void localizeCacheName(std::string key, std::string value);
static std::map<std::string, std::string> sCacheName;
private:
class Impl;

View File

@@ -61,7 +61,7 @@ public:
LLMeanCollisionData(LLMeanCollisionData *mcd)
: mVictim(mcd->mVictim), mPerp(mcd->mPerp), mTime(mcd->mTime), mType(mcd->mType), mMag(mcd->mMag),
mFirstName(mcd->mFirstName), mLastName(mcd->mLastName)
mFullName(mcd->mFullName)
{
}
@@ -95,8 +95,7 @@ public:
time_t mTime;
EMeanCollisionType mType;
F32 mMag;
std::string mFirstName;
std::string mLastName;
std::string mFullName;
};