Newer
Older
/**
* @file llavatarnamecache.cpp
* @brief Provides lookup of avatar SLIDs ("bobsmith123") and display names
* ("James Cook") from avatar UUIDs.
*
* $LicenseInfo:firstyear=2010&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 "llavatarnamecache.h"
#include "llcachename.h" // we wrap this system
#include "llsd.h"
#include "llsdserialize.h"
Rider Linden
committed
#include "httpresponse.h"
#include "llhttpsdhandler.h"
#include <boost/tokenizer.hpp>
Rider Linden
committed
#include "httpcommon.h"
#include "httprequest.h"
#include "httpheaders.h"
#include "httpoptions.h"
#include "llcoros.h"
#include "lleventcoro.h"
#include "llcorehttputil.h"
#include "llexception.h"
#include "stringize.h"
Rider Linden
committed
// Time-to-live for a temp cache entry.
const F64 TEMP_CACHE_ENTRY_LIFETIME = 60.0;
// Maximum time an unrefreshed cache entry is allowed.
const F64 MAX_UNREFRESHED_TIME = 20.0 * 60.0;
// Send bulk lookup requests a few times a second at most.
// Only need per-frame timing resolution.
static LLFrameTimer sRequestTimer;
Rider Linden
committed
// static to avoid unnessesary dependencies
LLCore::HttpRequest::ptr_t sHttpRequest;
LLCore::HttpHeaders::ptr_t sHttpHeaders;
LLCore::HttpOptions::ptr_t sHttpOptions;
LLCore::HttpRequest::policy_t sHttpPolicy;
LLCore::HttpRequest::priority_t sHttpPriority;
/* Sample response:
<?xml version="1.0"?>
<llsd>
<map>
<key>agents</key>
<array>
<map>
<key>display_name_next_update</key>
<date>2010-04-16T21:34:02+00:00Z</date>
<key>display_name_expires</key>
<date>2010-04-16T21:32:26.142178+00:00Z</date>
<key>display_name</key>
<string>MickBot390 LLQABot</string>
<key>sl_id</key>
<string>mickbot390.llqabot</string>
<key>id</key>
<string>0012809d-7d2d-4c24-9609-af1230a37715</string>
<key>is_display_name_default</key>
<boolean>false</boolean>
</map>
<map>
<key>display_name_next_update</key>
<date>2010-04-16T21:34:02+00:00Z</date>
<key>display_name_expires</key>
<date>2010-04-16T21:32:26.142178+00:00Z</date>
<key>display_name</key>
<string>Bjork Gudmundsdottir</string>
<key>sl_id</key>
<string>sardonyx.linden</string>
<key>id</key>
<string>3941037e-78ab-45f0-b421-bd6e77c1804d</string>
<key>is_display_name_default</key>
<boolean>true</boolean>
</map>
</array>
</map>
</llsd>
*/
Rider Linden
committed
// Coroutine for sending and processing avatar name cache requests.
// Do not call directly. See documentation in lleventcoro.h and llcoro.h for
// further explanation.
LLAvatarNameCache::LLAvatarNameCache()
{
// Will be set to running later
// For now fail immediate lookups and query async ones.
mRunning = false;
mUsePeopleAPI = true;
sHttpRequest = LLCore::HttpRequest::ptr_t(new LLCore::HttpRequest());
sHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders());
sHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions());
sHttpPolicy = LLCore::HttpRequest::DEFAULT_POLICY_ID;
sHttpPriority = 0;
}
LLAvatarNameCache::~LLAvatarNameCache()
{
sHttpRequest.reset();
sHttpHeaders.reset();
sHttpOptions.reset();
mCache.clear();
}
void LLAvatarNameCache::requestAvatarNameCache_(std::string url, std::vector<LLUUID> agentIds)
LL_DEBUGS("AvNameCache") << "Entering coroutine " << LLCoros::getName()
Rider Linden
committed
<< " with url '" << url << "', requesting " << agentIds.size() << " Agent Ids" << LL_ENDL;
// Check pointer that can be cleaned up by cleanupClass()
if (!sHttpRequest || !sHttpOptions || !sHttpHeaders)
{
LL_WARNS("AvNameCache") << " Trying to request name cache when http pointers are not initialized." << LL_ENDL;
return;
}
LLSD httpResults;
Rider Linden
committed
try
{
bool success = true;
LLCoreHttpUtil::HttpCoroutineAdapter httpAdapter("NameCache", sHttpPolicy);
LLSD results = httpAdapter.getAndSuspend(sHttpRequest, url);
Rider Linden
committed
LL_DEBUGS() << results << LL_ENDL;
Rider Linden
committed
if (!results.isMap())
{
LL_WARNS("AvNameCache") << " Invalid result returned from LLCoreHttpUtil::HttpCoroHandler." << LL_ENDL;
success = false;
}
else
{
httpResults = results["http_result"];
success = httpResults["success"].asBoolean();
if (!success)
{
LL_WARNS("AvNameCache") << "Error result from LLCoreHttpUtil::HttpCoroHandler. Code "
<< httpResults["status"] << ": '" << httpResults["message"] << "'" << LL_ENDL;
}
}
Rider Linden
committed
if (!success)
{ // on any sort of failure add dummy records for any agent IDs
// in this request that we do not have cached already
std::vector<LLUUID>::const_iterator it = agentIds.begin();
for ( ; it != agentIds.end(); ++it)
{
const LLUUID& agent_id = *it;
LLAvatarNameCache::getInstance()->handleAgentError(agent_id);
Rider Linden
committed
}
return;
}
Oz Linden
committed
LLAvatarNameCache::getInstance()->handleAvNameCacheSuccess(results, httpResults);
Oz Linden
committed
}
Rider Linden
committed
catch (...)
{
LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << LLCoros::getName()
<< "('" << url << "', " << agentIds.size()
<< " http result: " << httpResults.asString()
<< " Agent Ids)"));
throw;
Rider Linden
committed
}
}
Rider Linden
committed
void LLAvatarNameCache::handleAvNameCacheSuccess(const LLSD &data, const LLSD &httpResult)
{
LLSD headers = httpResult["headers"];
// Pull expiration out of headers if available
F64 expires = LLAvatarNameCache::nameExpirationFromHeaders(headers);
F64 now = LLFrameTimer::getTotalSeconds();
const LLSD& agents = data["agents"];
for (const LLSD& row : agents.array())
Rider Linden
committed
{
LLUUID agent_id = row["id"].asUUID();
LLAvatarName av_name;
av_name.fromLLSD(row);
// Use expiration time from header
av_name.mExpires = expires;
LL_DEBUGS("AvNameCache") << "LLAvatarNameResponder::result for " << agent_id << LL_ENDL;
av_name.dump();
// cache it and fire signals
LLAvatarNameCache::processName(agent_id, av_name);
}
// Same logic as error response case
const LLSD& unresolved_agents = data["bad_ids"];
S32 num_unresolved = unresolved_agents.size();
if (num_unresolved > 0)
{
LL_WARNS("AvNameCache") << "LLAvatarNameResponder::result " << num_unresolved << " unresolved ids; "
<< "expires in " << expires - now << " seconds"
<< LL_ENDL;
for (const LLSD& llsd_val : unresolved_agents.array())
Rider Linden
committed
{
const LLUUID& agent_id = llsd_val.asUUID();
Rider Linden
committed
LL_WARNS("AvNameCache") << "LLAvatarNameResponder::result "
<< "failed id " << agent_id
<< LL_ENDL;
LLAvatarNameCache::handleAgentError(agent_id);
}
}
LL_DEBUGS("AvNameCache") << "LLAvatarNameResponder::result "
<< LLAvatarNameCache::mCache.size() << " cached names"
Rider Linden
committed
<< LL_ENDL;
}
Oz Linden
committed
// Provide some fallback for agents that return errors
void LLAvatarNameCache::handleAgentError(const LLUUID& agent_id)
{
if (existing == mCache.end())
Oz Linden
committed
{
// there is no existing cache entry, so make a temporary name from legacy
LL_WARNS("AvNameCache") << "LLAvatarNameCache get legacy for agent "
Brad Payne (Vir Linden)
committed
<< agent_id << LL_ENDL;
Oz Linden
committed
gCacheName->get(agent_id, false, // legacy compatibility
boost::bind(&LLAvatarNameCache::legacyNameFetch, _1, _2, _3));
Oz Linden
committed
}
else
{
// we have a cached (but probably expired) entry - since that would have
Oz Linden
committed
// been returned by the get method, there is no need to signal anyone
// Clear this agent from the pending list
LLAvatarNameCache::mPendingQueue.erase(agent_id);
Oz Linden
committed
Brad Payne (Vir Linden)
committed
LLAvatarName& av_name = existing->second;
Merov Linden
committed
LL_DEBUGS("AvNameCache") << "LLAvatarNameCache use cache for agent " << agent_id << LL_ENDL;
av_name.dump();
// Reset expiry time so we don't constantly rerequest.
av_name.setExpires(TEMP_CACHE_ENTRY_LIFETIME);
Oz Linden
committed
}
}
void LLAvatarNameCache::processName(const LLUUID& agent_id, const LLAvatarName& av_name)
if (agent_id.isNull())
{
return;
}
// Add to the cache
mCache[agent_id] = av_name;
// Suppress request from the queue
mPendingQueue.erase(agent_id);
// Signal everyone waiting on this name
signal_map_t::iterator sig_it = mSignalMap.find(agent_id);
if (sig_it != mSignalMap.end())
{
callback_signal_t* signal = sig_it->second;
(*signal)(agent_id, av_name);
mSignalMap.erase(agent_id);
delete signal;
signal = NULL;
}
}
void LLAvatarNameCache::requestNamesViaCapability()
F64 now = LLFrameTimer::getTotalSeconds();
// URL format is like:
// http://pdp60.lindenlab.com:8000/agents/?ids=3941037e-78ab-45f0-b421-bd6e77c1804d&ids=0012809d-7d2d-4c24-9609-af1230a37715&ids=0019aaba-24af-4f0a-aa72-6457953cf7f0
//
// Apache can handle URLs of 4096 chars, but let's be conservative
static const U32 NAME_URL_MAX = 4096;
static const U32 NAME_URL_SEND_THRESHOLD = 3500;
std::string url;
url.reserve(NAME_URL_MAX);
std::vector<LLUUID> agent_ids;
agent_ids.reserve(128);
Oz Linden
committed
U32 ids = 0;
ask_queue_t::const_iterator it;
while(!mAskQueue.empty())
it = mAskQueue.begin();
LLUUID agent_id = *it;
if (url.empty())
{
// ...starting new request
url += mNameLookupURL;
Oz Linden
committed
ids = 1;
}
else
{
// ...continuing existing request
url += "&ids=";
Oz Linden
committed
ids++;
url += agent_id.asString();
agent_ids.push_back(agent_id);
// mark request as pending
mPendingQueue[agent_id] = now;
if (url.size() > NAME_URL_SEND_THRESHOLD)
{
}
}
Rider Linden
committed
if (!url.empty())
{
LL_DEBUGS("AvNameCache") << "requested " << ids << " ids" << LL_ENDL;
Rider Linden
committed
std::string coroname =
LLCoros::instance().launch("LLAvatarNameCache::requestAvatarNameCache_",
boost::bind(&LLAvatarNameCache::requestAvatarNameCache_, url, agent_ids));
Rider Linden
committed
LL_DEBUGS("AvNameCache") << coroname << " with url '" << url << "', agent_ids.size()=" << agent_ids.size() << LL_ENDL;
}
void LLAvatarNameCache::legacyNameCallback(const LLUUID& agent_id,
const std::string& full_name,
bool is_group)
{
// Put the received data in the cache
legacyNameFetch(agent_id, full_name, is_group);
// Retrieve the name and set it to never (or almost never...) expire: when we are using the legacy
// protocol, we do not get an expiration date for each name and there's no reason to ask the
// data again and again so we set the expiration time to the largest value admissible.
auto av_record = LLAvatarNameCache::getInstance()->mCache.find(agent_id);
if (av_record != LLAvatarNameCache::getInstance()->mCache.end())
{
LLAvatarName& av_name = av_record->second;
av_name.setExpires(MAX_UNREFRESHED_TIME);
}
}
void LLAvatarNameCache::legacyNameFetch(const LLUUID& agent_id,
const std::string& full_name,
bool is_group)
{
LL_DEBUGS("AvNameCache") << "LLAvatarNameCache agent " << agent_id << " "
Oz Linden
committed
<< "full name '" << full_name << "'"
<< ( is_group ? " [group]" : "" )
<< LL_ENDL;
// Construct an av_name record from this name.
LLAvatarName av_name;
av_name.fromString(full_name);
// Add to cache: we're still using the new cache even if we're using the old (legacy) protocol.
LLAvatarNameCache::getInstance()->processName(agent_id, av_name);
void LLAvatarNameCache::requestNamesViaLegacy()
{
static const S32 MAX_REQUESTS = 100;
F64 now = LLFrameTimer::getTotalSeconds();
std::string full_name;
ask_queue_t::const_iterator it;
for (S32 requests = 0; !mAskQueue.empty() && requests < MAX_REQUESTS; ++requests)
it = mAskQueue.begin();
LLUUID agent_id = *it;
// Mark as pending first, just in case the callback is immediately
// invoked below. This should never happen in practice.
mPendingQueue[agent_id] = now;
LL_DEBUGS("AvNameCache") << "agent " << agent_id << LL_ENDL;
Oz Linden
committed
gCacheName->get(agent_id, false, // legacy compatibility
boost::bind(&LLAvatarNameCache::legacyNameCallback, _1, _2, _3));
}
bool LLAvatarNameCache::importFile(std::istream& istr)
if (LLSDParser::PARSE_FAILURE == LLSDSerialize::fromXMLDocument(data, istr))
{
LL_WARNS("AvNameCache") << "avatar name cache data xml parse failed" << LL_ENDL;
return false;
}
// by convention LLSD storage is a map
// we only store one entry in the map
LLSD agents = data["agents"];
LLUUID agent_id;
LLAvatarName av_name;
for (const auto& llsd_pair : agents.map())
agent_id.set(llsd_pair.first);
av_name.fromLLSD(llsd_pair.second );
mCache[agent_id] = av_name;
LL_INFOS("AvNameCache") << "LLAvatarNameCache loaded " << mCache.size() << LL_ENDL;
Oz Linden
committed
// Some entries may have expired since the cache was stored,
// but they will be flushed in the first call to eraseUnrefreshed
// from LLAvatarNameResponder::idle
return true;
}
void LLAvatarNameCache::exportFile(std::ostream& ostr)
{
F64 max_unrefreshed = LLFrameTimer::getTotalSeconds() - MAX_UNREFRESHED_TIME;
LL_INFOS("AvNameCache") << "LLAvatarNameCache at exit cache has " << mCache.size() << LL_ENDL;
cache_t::const_iterator it = mCache.begin();
for ( ; it != mCache.end(); ++it)
{
const LLUUID& agent_id = it->first;
const LLAvatarName& av_name = it->second;
Oz Linden
committed
// Do not write temporary or expired entries to the stored cache
Merov Linden
committed
if (av_name.isValidName(max_unrefreshed))
{
// key must be a string
agents[agent_id.asString()] = av_name.asLLSD();
}
LL_INFOS("AvNameCache") << "LLAvatarNameCache returning " << agents.size() << LL_ENDL;
LLSD data;
data["agents"] = agents;
LLSDSerialize::toPrettyXML(data, ostr);
void LLAvatarNameCache::setNameLookupURL(const std::string& name_lookup_url)
mNameLookupURL = name_lookup_url;
bool LLAvatarNameCache::hasNameLookupURL()
{
return !mNameLookupURL.empty();
}
void LLAvatarNameCache::setUsePeopleAPI(bool use_api)
{
mUsePeopleAPI = use_api;
bool LLAvatarNameCache::usePeopleAPI()
{
return hasNameLookupURL() && mUsePeopleAPI;
}
void LLAvatarNameCache::idle()
{
// By convention, start running at first idle() call
// *TODO: Possibly re-enabled this based on People API load measurements
// 100 ms is the threshold for "user speed" operations, so we can
// stall for about that long to batch up requests.
const F32 SECS_BETWEEN_REQUESTS = 0.1f;
if (!sRequestTimer.hasExpired())
{
return;
}
if (!mAskQueue.empty())
if (usePeopleAPI())
Oz Linden
committed
{
requestNamesViaCapability();
}
else
{
LL_WARNS_ONCE("AvNameCache") << "LLAvatarNameCache still using legacy api" << LL_ENDL;
Oz Linden
committed
requestNamesViaLegacy();
}
if (mAskQueue.empty())
{
// cleared the list, reset the request timer.
sRequestTimer.resetWithExpiry(SECS_BETWEEN_REQUESTS);
}
Oz Linden
committed
// erase anything that has not been refreshed for more than MAX_UNREFRESHED_TIME
eraseUnrefreshed();
}
bool LLAvatarNameCache::isRequestPending(const LLUUID& agent_id)
{
Oz Linden
committed
bool isPending = false;
const F64 PENDING_TIMEOUT_SECS = 5.0 * 60.0;
pending_queue_t::const_iterator it = mPendingQueue.find(agent_id);
if (it != mPendingQueue.end())
Oz Linden
committed
// in the list of requests in flight, retry if too old
F64 expire_time = LLFrameTimer::getTotalSeconds() - PENDING_TIMEOUT_SECS;
isPending = (it->second > expire_time);
Oz Linden
committed
return isPending;
Oz Linden
committed
void LLAvatarNameCache::eraseUnrefreshed()
F64 now = LLFrameTimer::getTotalSeconds();
Oz Linden
committed
F64 max_unrefreshed = now - MAX_UNREFRESHED_TIME;
if (!mLastExpireCheck || mLastExpireCheck < max_unrefreshed)
Oz Linden
committed
{
mLastExpireCheck = now;
S32 expired = 0;
for (cache_t::iterator it = mCache.begin(); it != mCache.end();)
Oz Linden
committed
{
const LLAvatarName& av_name = it->second;
Oz Linden
committed
if (av_name.mExpires < max_unrefreshed)
{
LL_DEBUGS("AvNameCacheExpired") << "LLAvatarNameCache " << it->first
Merov Linden
committed
<< " user '" << av_name.getAccountName() << "' "
Oz Linden
committed
<< "expired " << now - av_name.mExpires << " secs ago"
<< LL_ENDL;
Oz Linden
committed
}
else
{
++it;
}
Oz Linden
committed
}
LL_INFOS("AvNameCache") << "LLAvatarNameCache expired " << expired << " cached avatar names, "
<< mCache.size() << " remaining" << LL_ENDL;
//static, wrapper
bool LLAvatarNameCache::get(const LLUUID& agent_id, LLAvatarName *av_name)
{
return LLAvatarNameCache::getInstance()->getName(agent_id, av_name);
}
Leyla Farazha
committed
// fills in av_name if it has it in the cache, even if expired (can check expiry time)
// returns bool specifying if av_name was filled, false otherwise
bool LLAvatarNameCache::getName(const LLUUID& agent_id, LLAvatarName *av_name)
// ...only do immediate lookups when cache is running
if (it != mCache.end())
*av_name = it->second;
// re-request name if entry is expired
if (av_name->mExpires < LLFrameTimer::getTotalSeconds())
{
if (!isRequestPending(agent_id))
LL_DEBUGS("AvNameCache") << "LLAvatarNameCache refresh agent " << agent_id
<< LL_ENDL;
mAskQueue.insert(agent_id);
return true;
if (!isRequestPending(agent_id))
{
LL_DEBUGS("AvNameCache") << "LLAvatarNameCache queue request for agent " << agent_id << LL_ENDL;
mAskQueue.insert(agent_id);
void LLAvatarNameCache::fireSignal(const LLUUID& agent_id,
const callback_slot_t& slot,
const LLAvatarName& av_name)
{
callback_signal_t signal;
signal.connect(slot);
signal(agent_id, av_name);
}
LLAvatarNameCache::callback_connection_t LLAvatarNameCache::get(const LLUUID& agent_id, callback_slot_t slot)
{
return LLAvatarNameCache::getInstance()->getNameCallback(agent_id, slot);
}
LLAvatarNameCache::callback_connection_t LLAvatarNameCache::getNameCallback(const LLUUID& agent_id, callback_slot_t slot)
callback_connection_t connection;
// ...only do immediate lookups when cache is running
if (it != mCache.end())
const LLAvatarName& av_name = it->second;
if (av_name.mExpires > LLFrameTimer::getTotalSeconds())
// ...name already exists in cache, fire callback now
fireSignal(agent_id, slot, av_name);
return connection;
}
// schedule a request
if (!isRequestPending(agent_id))
{
mAskQueue.insert(agent_id);
}
// always store additional callback, even if request is pending
signal_map_t::iterator sig_it = mSignalMap.find(agent_id);
if (sig_it == mSignalMap.end())
{
// ...new callback for this id
callback_signal_t* signal = new callback_signal_t();
connection = signal->connect(slot);
mSignalMap[agent_id] = signal;
}
else
{
// ...existing callback, bind additional slot
callback_signal_t* signal = sig_it->second;
connection = signal->connect(slot);
return connection;
// [RLVa:KB] - Checked: 2010-12-08 (RLVa-1.4.0a) | Added: RLVa-1.2.2c
bool LLAvatarNameCache::getForceDisplayNames()
{
return mRlvForceDisplayNames;
}
void LLAvatarNameCache::setForceDisplayNames(bool force)
{
mRlvForceDisplayNames = force;
if ( (!LLAvatarName::useDisplayNames()) && (force) )
{
LLAvatarName::setUseDisplayNames(true);
}
}
// [/RLVa:KB]
void LLAvatarNameCache::setUseDisplayNames(bool use)
// [RLVa:KB] - Checked: 2010-12-08 (RLVa-1.4.0a) | Added: RLVa-1.2.2c
// We need to force the use of the "display names" cache when @shownames=n restricted (and disallow toggling it)
use |= getForceDisplayNames();
// [/RLVa:KB]
Merov Linden
committed
if (use != LLAvatarName::useDisplayNames())
Leyla Farazha
committed
Merov Linden
committed
LLAvatarName::setUseDisplayNames(use);
Leyla Farazha
committed
mUseDisplayNamesSignal();
Mnikolenko ProductEngine
committed
void LLAvatarNameCache::setUseUsernames(bool use)
{
if (use != LLAvatarName::useUsernames())
{
LLAvatarName::setUseUsernames(use);
mUseDisplayNamesSignal();
}
}
void LLAvatarNameCache::erase(const LLUUID& agent_id)
{
mCache.erase(agent_id);
}
void LLAvatarNameCache::insert(const LLUUID& agent_id, const LLAvatarName& av_name)
{
// *TODO: update timestamp if zero?
mCache[agent_id] = av_name;
LLUUID LLAvatarNameCache::findIdByName(const std::string& name)
{
for (auto it = mCache.begin(), end = mCache.end(); it != end; ++it)
{
if (it->second.getUserName() == name)
{
return it->first;
}
}
// Legacy method
LLUUID id;
if (gCacheName && gCacheName->getUUID(name, id))
{
return id;
}
return LLUUID::null;
}
Rider Linden
committed
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
#if 0
F64 LLAvatarNameCache::nameExpirationFromHeaders(LLCore::HttpHeaders *headers)
{
F64 expires = 0.0;
if (expirationFromCacheControl(headers, &expires))
{
return expires;
}
else
{
// With no expiration info, default to an hour
const F64 DEFAULT_EXPIRES = 60.0 * 60.0;
F64 now = LLFrameTimer::getTotalSeconds();
return now + DEFAULT_EXPIRES;
}
}
bool LLAvatarNameCache::expirationFromCacheControl(LLCore::HttpHeaders *headers, F64 *expires)
{
bool fromCacheControl = false;
F64 now = LLFrameTimer::getTotalSeconds();
// Allow the header to override the default
const std::string *cache_control;
cache_control = headers->find(HTTP_IN_HEADER_CACHE_CONTROL);
if (cache_control && !cache_control->empty())
{
S32 max_age = 0;
if (max_age_from_cache_control(*cache_control, &max_age))
{
*expires = now + (F64)max_age;
fromCacheControl = true;
}
}
LL_DEBUGS("AvNameCache")
<< ( fromCacheControl ? "expires based on cache control " : "default expiration " )
<< "in " << *expires - now << " seconds"
<< LL_ENDL;
return fromCacheControl;
}
#else
Don Kjer
committed
F64 LLAvatarNameCache::nameExpirationFromHeaders(const LLSD& headers)
F64 expires = 0.0;
if (expirationFromCacheControl(headers, &expires))
{
return expires;
}
else
{
Leyla Farazha
committed
// With no expiration info, default to an hour
const F64 DEFAULT_EXPIRES = 60.0 * 60.0;
F64 now = LLFrameTimer::getTotalSeconds();
return now + DEFAULT_EXPIRES;
}
}
Don Kjer
committed
bool LLAvatarNameCache::expirationFromCacheControl(const LLSD& headers, F64 *expires)
Oz Linden
committed
bool fromCacheControl = false;
F64 now = LLFrameTimer::getTotalSeconds();
// Allow the header to override the default
Don Kjer
committed
std::string cache_control;
if (headers.has(HTTP_IN_HEADER_CACHE_CONTROL))
Don Kjer
committed
{
cache_control = headers[HTTP_IN_HEADER_CACHE_CONTROL].asString();
Don Kjer
committed
}
if (!cache_control.empty())
{
S32 max_age = 0;
if (max_age_from_cache_control(cache_control, &max_age))
{
*expires = now + (F64)max_age;
Oz Linden
committed
fromCacheControl = true;
}
}
LL_DEBUGS("AvNameCache") << "LLAvatarNameCache "
Oz Linden
committed
<< ( fromCacheControl ? "expires based on cache control " : "default expiration " )
<< "in " << *expires - now << " seconds"
<< LL_ENDL;
return fromCacheControl;
Rider Linden
committed
#endif
Leyla Farazha
committed
void LLAvatarNameCache::addUseDisplayNamesCallback(const use_display_name_signal_t::slot_type& cb)
{
mUseDisplayNamesSignal.connect(cb);
}
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
static const std::string MAX_AGE("max-age");
static const boost::char_separator<char> EQUALS_SEPARATOR("=");
static const boost::char_separator<char> COMMA_SEPARATOR(",");
bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age)
{
// Split the string on "," to get a list of directives
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
tokenizer directives(cache_control, COMMA_SEPARATOR);
tokenizer::iterator token_it = directives.begin();
for ( ; token_it != directives.end(); ++token_it)
{
// Tokens may have leading or trailing whitespace
std::string token = *token_it;
LLStringUtil::trim(token);
if (token.compare(0, MAX_AGE.size(), MAX_AGE) == 0)
{
// ...this token starts with max-age, so let's chop it up by "="
tokenizer subtokens(token, EQUALS_SEPARATOR);
tokenizer::iterator subtoken_it = subtokens.begin();
// Must have a token
if (subtoken_it == subtokens.end()) return false;
std::string subtoken = *subtoken_it;
// Must exactly equal "max-age"
LLStringUtil::trim(subtoken);
if (subtoken != MAX_AGE) return false;
// Must have another token
++subtoken_it;
if (subtoken_it == subtokens.end()) return false;
subtoken = *subtoken_it;
// Must be a valid integer
// *NOTE: atoi() returns 0 for invalid values, so we have to
// check the string first.
// *TODO: Do servers ever send "0000" for zero? We don't handle it
LLStringUtil::trim(subtoken);
if (subtoken == "0")
{
*max_age = 0;
return true;
}
S32 val = atoi( subtoken.c_str() );
if (val > 0 && val < S32_MAX)
{
*max_age = val;
return true;
}
return false;
}
}
return false;
}