diff options
Diffstat (limited to 'indra/llmessage/llcachename.cpp')
-rw-r--r-- | indra/llmessage/llcachename.cpp | 2042 |
1 files changed, 1021 insertions, 1021 deletions
diff --git a/indra/llmessage/llcachename.cpp b/indra/llmessage/llcachename.cpp index cb654f5a30..6fb21957e0 100644 --- a/indra/llmessage/llcachename.cpp +++ b/indra/llmessage/llcachename.cpp @@ -1,1021 +1,1021 @@ -/**
- * @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 "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;
-
-// 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(); }
-};
-
-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 std::set<LLUUID> 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())
-{
- 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)
-{
- 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
-// 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);
-
- if (lastname != "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"
- std::string clean_name = cleanFullName(full_name);
- LLStringUtil::toLower(clean_name);
- return clean_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())
- {
- 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));
-}
-
-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);
-}
+/** + * @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 "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; + +// 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(); } +}; + +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 std::set<LLUUID> 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()) +{ + 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) +{ + 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 +// 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); + + if (lastname != "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" + std::string clean_name = cleanFullName(full_name); + LLStringUtil::toLower(clean_name); + return clean_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()) + { + 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)); +} + +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); +} |