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 5b4f9aded7..cb654f5a30 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);
+}
|