diff options
Diffstat (limited to 'indra/llmessage')
| -rw-r--r-- | indra/llmessage/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | indra/llmessage/llavatarnamecache.cpp | 818 | ||||
| -rw-r--r-- | indra/llmessage/llavatarnamecache.h | 103 | ||||
| -rw-r--r-- | indra/llmessage/llcachename.cpp | 227 | ||||
| -rw-r--r-- | indra/llmessage/llcachename.h | 51 | ||||
| -rw-r--r-- | indra/llmessage/mean_collision_data.h | 5 | ||||
| -rw-r--r-- | indra/llmessage/tests/llavatarnamecache_test.cpp | 109 | 
7 files changed, 1189 insertions, 127 deletions
| diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 1f8ee26716..1cad0f6d22 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -25,6 +25,7 @@ set(llmessage_SOURCE_FILES      llares.cpp      llareslistener.cpp      llassetstorage.cpp +    llavatarnamecache.cpp      llblowfishcipher.cpp      llbuffer.cpp      llbufferstream.cpp @@ -110,6 +111,7 @@ set(llmessage_HEADER_FILES      llares.h      llareslistener.h      llassetstorage.h +    llavatarnamecache.h      llblowfishcipher.h      llbuffer.h      llbufferstream.h @@ -248,6 +250,7 @@ if (LL_TESTS)      "${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llsdmessage_peer.py"      ) +  LL_ADD_INTEGRATION_TEST(llavatarnamecache "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(llhost "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(llpartdata "" "${test_libs}")    LL_ADD_INTEGRATION_TEST(llxfer_file "" "${test_libs}") diff --git a/indra/llmessage/llavatarnamecache.cpp b/indra/llmessage/llavatarnamecache.cpp new file mode 100644 index 0000000000..0571973c23 --- /dev/null +++ b/indra/llmessage/llavatarnamecache.cpp @@ -0,0 +1,818 @@ +/**  + * @file llavatarnamecache.cpp + * @brief Provides lookup of avatar SLIDs ("bobsmith123") and display names + * ("James Cook") from avatar UUIDs. + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + *  + * Copyright (c) 2010, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ +#include "linden_common.h" + +#include "llavatarnamecache.h" + +#include "llcachename.h"		// we wrap this system +#include "llframetimer.h" +#include "llhttpclient.h" +#include "llsd.h" +#include "llsdserialize.h" + +#include <boost/tokenizer.hpp> + +#include <map> +#include <set> + +namespace LLAvatarNameCache +{ +	// Manual override for display names - can disable even if the region +	// supports it. +	bool sUseDisplayNames = true; + +	// Cache starts in a paused state until we can determine if the +	// current region supports display names. +	bool sRunning = false; +	 +	// Base lookup URL for name service. +	// On simulator, loaded from indra.xml +	// On viewer, usually a simulator capability (at People API team's request) +	// Includes the trailing slash, like "http://pdp60.lindenlab.com:8000/agents/" +	std::string sNameLookupURL; + +	// accumulated agent IDs for next query against service +	typedef std::set<LLUUID> ask_queue_t; +	ask_queue_t sAskQueue; + +	// agent IDs that have been requested, but with no reply +	// maps agent ID to frame time request was made +	typedef std::map<LLUUID, F64> pending_queue_t; +	pending_queue_t sPendingQueue; + +	// Callbacks to fire when we received a name. +	// May have multiple callbacks for a single ID, which are +	// represented as multiple slots bound to the signal. +	// Avoid copying signals via pointers. +	typedef std::map<LLUUID, callback_signal_t*> signal_map_t; +	signal_map_t sSignalMap; + +	// names we know about +	typedef std::map<LLUUID, LLAvatarName> cache_t; +	cache_t sCache; + +	// Send bulk lookup requests a few times a second at most +	// only need per-frame timing resolution +	LLFrameTimer sRequestTimer; + +	// Periodically clean out expired entries from the cache +	//LLFrameTimer sEraseExpiredTimer; + +	//----------------------------------------------------------------------- +	// Internal methods +	//----------------------------------------------------------------------- + +	// Handle name response off network. +	// Optionally skip adding to cache, used when this is a fallback to the +	// legacy name system. +	void processName(const LLUUID& agent_id, +					 const LLAvatarName& av_name, +					 bool add_to_cache); + +	void requestNamesViaCapability(); + +	// Legacy name system callback +	void legacyNameCallback(const LLUUID& agent_id, +		const std::string& full_name, +		bool is_group); + +	void requestNamesViaLegacy(); + +	// Fill in an LLAvatarName with the legacy name data +	void buildLegacyName(const std::string& full_name, +						 LLAvatarName* av_name); + +	// Do a single callback to a given slot +	void fireSignal(const LLUUID& agent_id, +					const callback_slot_t& slot, +					const LLAvatarName& av_name); +	 +	// Is a request in-flight over the network? +	bool isRequestPending(const LLUUID& agent_id); + +	// Erase expired names from cache +	void eraseExpired(); + +	bool expirationFromCacheControl(LLSD headers, F64 *expires); +} + +/* 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> +*/ + +class LLAvatarNameResponder : public LLHTTPClient::Responder +{ +private: +	// need to store agent ids that are part of this request in case of +	// an error, so we can flag them as unavailable +	std::vector<LLUUID> mAgentIDs; + +	// Need the headers to look up Expires: and Retry-After: +	LLSD mHeaders; +	 +public: +	LLAvatarNameResponder(const std::vector<LLUUID>& agent_ids) +	:	mAgentIDs(agent_ids), +		mHeaders() +	{ } +	 +	/*virtual*/ void completedHeader(U32 status, const std::string& reason,  +		const LLSD& headers) +	{ +		mHeaders = headers; +	} + +	/*virtual*/ void result(const LLSD& content) +	{ +		// Pull expiration out of headers if available +		F64 expires = LLAvatarNameCache::nameExpirationFromHeaders(mHeaders); + +		LLSD agents = content["agents"]; +		LLSD::array_const_iterator it = agents.beginArray(); +		for ( ; it != agents.endArray(); ++it) +		{ +			const LLSD& row = *it; +			LLUUID agent_id = row["id"].asUUID(); + +			LLAvatarName av_name; +			av_name.fromLLSD(row); + +			// Use expiration time from header +			av_name.mExpires = expires; + +			// Some avatars don't have explicit display names set +			if (av_name.mDisplayName.empty()) +			{ +				av_name.mDisplayName = av_name.mUsername; +			} + +			// cache it and fire signals +			LLAvatarNameCache::processName(agent_id, av_name, true); +		} + +		// Same logic as error response case +		LLSD unresolved_agents = content["bad_ids"]; +		if (unresolved_agents.size() > 0) +		{ +			const std::string DUMMY_NAME("\?\?\?"); +			LLAvatarName av_name; +			av_name.mUsername = DUMMY_NAME; +			av_name.mDisplayName = DUMMY_NAME; +			av_name.mIsDisplayNameDefault = false; +			av_name.mIsDummy = true; +			av_name.mExpires = expires; + +			it = unresolved_agents.beginArray(); +			for ( ; it != unresolved_agents.endArray(); ++it) +			{ +				const LLUUID& agent_id = *it; +				// cache it and fire signals +				LLAvatarNameCache::processName(agent_id, av_name, true); +			} +		} +	} + +	/*virtual*/ void error(U32 status, const std::string& reason) +	{ +		// We're going to construct a dummy record and cache it for a while, +		// either briefly for a 503 Service Unavailable, or longer for other +		// errors. +		F64 retry_timestamp = errorRetryTimestamp(status); + +		// *NOTE: "??" starts trigraphs in C/C++, escape the question marks. +		const std::string DUMMY_NAME("\?\?\?"); +		LLAvatarName av_name; +		av_name.mUsername = DUMMY_NAME; +		av_name.mDisplayName = DUMMY_NAME; +		av_name.mIsDisplayNameDefault = false; +		av_name.mIsDummy = true; +		av_name.mExpires = retry_timestamp; + +		// Add dummy records for all agent IDs in this request +		std::vector<LLUUID>::const_iterator it = mAgentIDs.begin(); +		for ( ; it != mAgentIDs.end(); ++it) +		{ +			const LLUUID& agent_id = *it; +			// cache it and fire signals +			LLAvatarNameCache::processName(agent_id, av_name, true); +		} +	} + +	// Return time to retry a request that generated an error, based on +	// error type and headers.  Return value is seconds-since-epoch. +	F64 errorRetryTimestamp(S32 status) +	{ +		F64 now = LLFrameTimer::getTotalSeconds(); + +		// Retry-After takes priority +		LLSD retry_after = mHeaders["retry-after"]; +		if (retry_after.isDefined()) +		{ +			// We only support the delta-seconds type +			S32 delta_seconds = retry_after.asInteger(); +			if (delta_seconds > 0) +			{ +				// ...valid delta-seconds +				return now + F64(delta_seconds); +			} +		} + +		// If no Retry-After, look for Cache-Control max-age +		F64 expires = 0.0; +		if (LLAvatarNameCache::expirationFromCacheControl(mHeaders, &expires)) +		{ +			return expires; +		} + +		// No information in header, make a guess +		if (status == 503) +		{ +			// ...service unavailable, retry soon +			const F64 SERVICE_UNAVAILABLE_DELAY = 600.0; // 10 min +			return now + SERVICE_UNAVAILABLE_DELAY; +		} +		else +		{ +			// ...other unexpected error +			const F64 DEFAULT_DELAY = 3600.0; // 1 hour +			return now + DEFAULT_DELAY; +		} +	} +}; + +void LLAvatarNameCache::processName(const LLUUID& agent_id, +									const LLAvatarName& av_name, +									bool add_to_cache) +{ +	if (add_to_cache) +	{ +		sCache[agent_id] = av_name; +	} + +	sPendingQueue.erase(agent_id); + +	// signal everyone waiting on this name +	signal_map_t::iterator sig_it =	sSignalMap.find(agent_id); +	if (sig_it != sSignalMap.end()) +	{ +		callback_signal_t* signal = sig_it->second; +		(*signal)(agent_id, av_name); + +		sSignalMap.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 +	const U32 NAME_URL_MAX = 4096; +	const U32 NAME_URL_SEND_THRESHOLD = 3000; +	std::string url; +	url.reserve(NAME_URL_MAX); + +	std::vector<LLUUID> agent_ids; +	agent_ids.reserve(128); +	 +	ask_queue_t::const_iterator it = sAskQueue.begin(); +	for ( ; it != sAskQueue.end(); ++it) +	{ +		const LLUUID& agent_id = *it; + +		if (url.empty()) +		{ +			// ...starting new request +			url += sNameLookupURL; +			url += "?ids="; +		} +		else +		{ +			// ...continuing existing request +			url += "&ids="; +		} +		url += agent_id.asString(); +		agent_ids.push_back(agent_id); + +		// mark request as pending +		sPendingQueue[agent_id] = now; + +		if (url.size() > NAME_URL_SEND_THRESHOLD) +		{ +			//llinfos << "requestNames " << url << llendl; +			LLHTTPClient::get(url, new LLAvatarNameResponder(agent_ids)); +			url.clear(); +			agent_ids.clear(); +		} +	} + +	if (!url.empty()) +	{ +		//llinfos << "requestNames " << url << llendl; +		LLHTTPClient::get(url, new LLAvatarNameResponder(agent_ids)); +		url.clear(); +		agent_ids.clear(); +	} + +	// We've moved all asks to the pending request queue +	sAskQueue.clear(); +} + +void LLAvatarNameCache::legacyNameCallback(const LLUUID& agent_id, +										   const std::string& full_name, +										   bool is_group) +{ +	// Construct a dummy record for this name.  By convention, SLID is blank +	// Never expires, but not written to disk, so lasts until end of session. +	LLAvatarName av_name; +	buildLegacyName(full_name, &av_name); + +	// Don't add to cache, the data already exists in the legacy name system +	// cache and we don't want or need duplicate storage, because keeping the +	// two copies in sync is complex. +	processName(agent_id, av_name, false); +} + +void LLAvatarNameCache::requestNamesViaLegacy() +{ +	F64 now = LLFrameTimer::getTotalSeconds(); +	std::string full_name; +	ask_queue_t::const_iterator it = sAskQueue.begin(); +	for (; it != sAskQueue.end(); ++it) +	{ +		const LLUUID& agent_id = *it; + +		// Mark as pending first, just in case the callback is immediately +		// invoked below.  This should never happen in practice. +		sPendingQueue[agent_id] = now; + +		gCacheName->get(agent_id, false,  // legacy compatibility +			boost::bind(&LLAvatarNameCache::legacyNameCallback, +				_1, _2, _3)); +	} + +	// We've either answered immediately or moved all asks to the +	// pending queue +	sAskQueue.clear(); +} + +void LLAvatarNameCache::initClass(bool running) +{ +	sRunning = running; +} + +void LLAvatarNameCache::cleanupClass() +{ +} + +void LLAvatarNameCache::importFile(std::istream& istr) +{ +	LLSD data; +	S32 parse_count = LLSDSerialize::fromXMLDocument(data, istr); +	if (parse_count < 1) return; + +	// 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; +	LLSD::map_const_iterator it = agents.beginMap(); +	for ( ; it != agents.endMap(); ++it) +	{ +		agent_id.set(it->first); +		av_name.fromLLSD( it->second ); +		sCache[agent_id] = av_name; +	} +	// entries may have expired since we last ran the viewer, just +	// clean them out now +	eraseExpired(); +	llinfos << "loaded " << sCache.size() << llendl; +} + +void LLAvatarNameCache::exportFile(std::ostream& ostr) +{ +	LLSD agents; +	cache_t::const_iterator it = sCache.begin(); +	for ( ; it != sCache.end(); ++it) +	{ +		const LLUUID& agent_id = it->first; +		const LLAvatarName& av_name = it->second; +		if (!av_name.mIsDummy) +		{ +			// key must be a string +			agents[agent_id.asString()] = av_name.asLLSD(); +		} +	} +	LLSD data; +	data["agents"] = agents; +	LLSDSerialize::toPrettyXML(data, ostr); +} + +void LLAvatarNameCache::setNameLookupURL(const std::string& name_lookup_url) +{ +	sNameLookupURL = name_lookup_url; +} + +bool LLAvatarNameCache::hasNameLookupURL() +{ +	return !sNameLookupURL.empty(); +} + +void LLAvatarNameCache::idle() +{ +	// By convention, start running at first idle() call +	sRunning = true; + +	// *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.checkExpirationAndReset(SECS_BETWEEN_REQUESTS)) +	//{ +	//	return; +	//} + +	// Must be large relative to above + +	// No longer deleting expired entries, just re-requesting in the get +	// this way first synchronous get call on an expired entry won't return +	// legacy name.  LF + +	//const F32 ERASE_EXPIRED_TIMEOUT = 60.f; // seconds +	//if (sEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT)) +	//{ +	//	eraseExpired(); +	//} + +	if (sAskQueue.empty()) +	{ +		return; +	} + +	if (useDisplayNames()) +	{ +		requestNamesViaCapability(); +	} +	else +	{ +		// ...fall back to legacy name cache system +		requestNamesViaLegacy(); +	} +} + +bool LLAvatarNameCache::isRequestPending(const LLUUID& agent_id) +{ +	const F64 PENDING_TIMEOUT_SECS = 5.0 * 60.0; +	F64 now = LLFrameTimer::getTotalSeconds(); +	F64 expire_time = now - PENDING_TIMEOUT_SECS; + +	pending_queue_t::const_iterator it = sPendingQueue.find(agent_id); +	if (it != sPendingQueue.end()) +	{ +		bool request_expired = (it->second < expire_time); +		return !request_expired; +	} +	return false; +} + +void LLAvatarNameCache::eraseExpired() +{ +	F64 now = LLFrameTimer::getTotalSeconds(); +	cache_t::iterator it = sCache.begin(); +	while (it != sCache.end()) +	{ +		cache_t::iterator cur = it; +		++it; +		const LLAvatarName& av_name = cur->second; +		if (av_name.mExpires < now) +		{ +			sCache.erase(cur); +		} +	} +} + +void LLAvatarNameCache::buildLegacyName(const std::string& full_name, +										LLAvatarName* av_name) +{ +	llassert(av_name); +	av_name->mUsername = ""; +	av_name->mDisplayName = full_name; +	av_name->mIsDisplayNameDefault = true; +	av_name->mIsDummy = true; +	av_name->mExpires = F64_MAX; +} + +// 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::get(const LLUUID& agent_id, LLAvatarName *av_name) +{ +	if (sRunning) +	{ +		// ...only do immediate lookups when cache is running +		if (useDisplayNames()) +		{ +			// ...use display names cache +			std::map<LLUUID,LLAvatarName>::iterator it = sCache.find(agent_id); +			if (it != sCache.end()) +			{ +				*av_name = it->second; + +				// re-request name if entry is expired +				if (av_name->mExpires < LLFrameTimer::getTotalSeconds()) +				{ +					if (!isRequestPending(agent_id)) +					{ +						sAskQueue.insert(agent_id); +					} +				} +				 +				return true; +			} +		} +		else +		{ +			// ...use legacy names cache +			std::string full_name; +			if (gCacheName->getFullName(agent_id, full_name)) +			{ +				buildLegacyName(full_name, av_name); +				return true; +			} +		} +	} + +	if (!isRequestPending(agent_id)) +	{ +		sAskQueue.insert(agent_id); +	} + +	return false; +} + +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); +} + +void LLAvatarNameCache::get(const LLUUID& agent_id, callback_slot_t slot) +{ +	if (sRunning) +	{ +		// ...only do immediate lookups when cache is running +		if (useDisplayNames()) +		{ +			// ...use new cache +			std::map<LLUUID,LLAvatarName>::iterator it = sCache.find(agent_id); +			if (it != sCache.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; +				} +			} +		} +		else +		{ +			// ...use old name system +			std::string full_name; +			if (gCacheName->getFullName(agent_id, full_name)) +			{ +				LLAvatarName av_name; +				buildLegacyName(full_name, &av_name); +				fireSignal(agent_id, slot, av_name); +				return; +			} +		} +	} + +	// schedule a request +	if (!isRequestPending(agent_id)) +	{ +		sAskQueue.insert(agent_id); +	} + +	// always store additional callback, even if request is pending +	signal_map_t::iterator sig_it = sSignalMap.find(agent_id); +	if (sig_it == sSignalMap.end()) +	{ +		// ...new callback for this id +		callback_signal_t* signal = new callback_signal_t(); +		signal->connect(slot); +		sSignalMap[agent_id] = signal; +	} +	else +	{ +		// ...existing callback, bind additional slot +		callback_signal_t* signal = sig_it->second; +		signal->connect(slot); +	} +} + + +void LLAvatarNameCache::setUseDisplayNames(bool use) +{ +	if (use != sUseDisplayNames) +	{ +		sUseDisplayNames = use; +		// flush our cache +		sCache.clear(); +	} +} + +bool LLAvatarNameCache::useDisplayNames() +{ +	// Must be both manually set on and able to look up names. +	return sUseDisplayNames && !sNameLookupURL.empty(); +} + +void LLAvatarNameCache::erase(const LLUUID& agent_id) +{ +	sCache.erase(agent_id); +} + +void LLAvatarNameCache::fetch(const LLUUID& agent_id) +{ +	// re-request, even if request is already pending +	sAskQueue.insert(agent_id); +} + +void LLAvatarNameCache::insert(const LLUUID& agent_id, const LLAvatarName& av_name) +{ +	// *TODO: update timestamp if zero? +	sCache[agent_id] = av_name; +} + +F64 LLAvatarNameCache::nameExpirationFromHeaders(LLSD 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(LLSD headers, F64 *expires) +{ +	// Allow the header to override the default +	LLSD cache_control_header = headers["cache-control"]; +	if (cache_control_header.isDefined()) +	{ +		S32 max_age = 0; +		std::string cache_control = cache_control_header.asString(); +		if (max_age_from_cache_control(cache_control, &max_age)) +		{ +			F64 now = LLFrameTimer::getTotalSeconds(); +			*expires = now + (F64)max_age; +			return true; +		} +	} +	return false; +} + +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; +} + + diff --git a/indra/llmessage/llavatarnamecache.h b/indra/llmessage/llavatarnamecache.h new file mode 100644 index 0000000000..0a8b987b05 --- /dev/null +++ b/indra/llmessage/llavatarnamecache.h @@ -0,0 +1,103 @@ +/**  + * @file llavatarnamecache.h + * @brief Provides lookup of avatar SLIDs ("bobsmith123") and display names + * ("James Cook") from avatar UUIDs. + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + *  + * Copyright (c) 2010, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ +#ifndef LLAVATARNAMECACHE_H +#define LLAVATARNAMECACHE_H + +#include "llavatarname.h"	// for convenience + +#include <boost/signals2.hpp> + +class LLSD; +class LLUUID; + +namespace LLAvatarNameCache +{ +	// Until the cache is set running, immediate lookups will fail and +	// async lookups will be queued.  This allows us to block requests +	// until we know if the first region supports display names. +	void initClass(bool running); +	void cleanupClass(); + +	void importFile(std::istream& istr); +	void exportFile(std::ostream& ostr); + +	// On the viewer, usually a simulator capabilitity +	// If empty, name cache will fall back to using legacy name +	// lookup system +	void setNameLookupURL(const std::string& name_lookup_url); + +	// Do we have a valid lookup URL, hence are we trying to use the +	// new display name lookup system? +	bool hasNameLookupURL(); +	 +	// Periodically makes a batch request for display names not already in +	// cache.  Call once per frame. +	void idle(); + +	// If name is in cache, returns true and fills in provided LLAvatarName +	// otherwise returns false +	bool get(const LLUUID& agent_id, LLAvatarName *av_name); + +	// Callback types for get() below +	typedef boost::signals2::signal< +		void (const LLUUID& agent_id, const LLAvatarName& av_name)> +			callback_signal_t; +	typedef callback_signal_t::slot_type callback_slot_t; + +	// Fetches name information and calls callback. +	// If name information is in cache, callback will be called immediately. +	void get(const LLUUID& agent_id, callback_slot_t slot); + +	// Allow display names to be explicitly disabled for testing. +	void setUseDisplayNames(bool use); +	bool useDisplayNames(); + +	void erase(const LLUUID& agent_id); + +	// Force a re-fetch of the most recent data, but keep the current +	// data in cache +	void fetch(const LLUUID& agent_id); + +	void insert(const LLUUID& agent_id, const LLAvatarName& av_name); + +	// Compute name expiration time from HTTP Cache-Control header, +	// or return default value, in seconds from epoch. +	F64 nameExpirationFromHeaders(LLSD headers); +} + +// Parse a cache-control header to get the max-age delta-seconds. +// Returns true if header has max-age param and it parses correctly. +// Exported here to ease unit testing. +bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age); + +#endif diff --git a/indra/llmessage/llcachename.cpp b/indra/llmessage/llcachename.cpp index 9871c922f1..ef60fd5c4e 100644 --- a/indra/llmessage/llcachename.cpp +++ b/indra/llmessage/llcachename.cpp @@ -75,6 +75,8 @@ public:  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; @@ -220,7 +222,9 @@ public:  	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); @@ -306,89 +310,10 @@ boost::signals2::connection LLCacheName::addObserver(const LLCacheNameCallback&  	return impl.mSignal.connect(callback);  } -void LLCacheName::importFile(LLFILE* fp) -{ -	S32 count = 0; - -	const S32 BUFFER_SIZE = 1024; -	char buffer[BUFFER_SIZE];	/*Flawfinder: ignore*/ - -	// *NOTE: These buffer sizes are hardcoded into sscanf() below -	char id_string[MAX_STRING]; /*Flawfinder: ignore*/ -	char firstname[MAX_STRING]; /*Flawfinder: ignore*/ -	char lastname[MAX_STRING]; /*Flawfinder: ignore*/ -	U32 create_time; - -	// This is OK if the first line is actually a name.  We just don't load it. -	char* valid = fgets(buffer, BUFFER_SIZE, fp); -	if (!valid) return; - -	// *NOTE: This buffer size is hardcoded into sscanf() below -	char version_string[BUFFER_SIZE]; /*Flawfinder: ignore*/ -	S32 version = 0; -	S32 match = sscanf(	/* Flawfinder: ignore */ -		buffer, -		"%1023s %d", -		version_string, &version); -	if (   match != 2 -		|| strcmp(version_string, "version") -		|| version != CN_FILE_VERSION) -	{ -		llwarns << "Ignoring old cache name file format" << llendl; -		return; -	} - -	// 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); - -	while(!feof(fp)) -	{ -		valid = fgets(buffer, BUFFER_SIZE, fp); -		if (!valid) break; - -		match = sscanf(	/* Flawfinder: ignore */ -			buffer, -			"%254s %u %254s %254s", -			id_string,  -			&create_time, -			firstname,  -			lastname); -		if (4 != match) continue; - -		LLUUID id(id_string); -		if (id.isNull()) continue; - -		// undo trivial XOR -		S32 i; -		for (i = 0; i < UUID_BYTES; i++) -		{ -			id.mData[i] ^= 0x33; -		} - -		// Don't load entries that are more than a week old -		if (create_time < delete_before_time) continue; - -		LLCacheNameEntry* entry = new LLCacheNameEntry(); -		entry->mIsGroup = false; -		entry->mCreateTime = create_time; -		entry->mFirstName = firstname; -		entry->mLastName = lastname; -		impl.mCache[id] = entry; -		std::string fullname = entry->mFirstName + " " + entry->mLastName; -		impl.mReverseCache[fullname] = id; -		 -		count++; -	} - -	llinfos << "LLCacheName loaded " << count << " names" << llendl; -} -  bool LLCacheName::importFile(std::istream& istr)  {  	LLSD data; -	if(LLSDSerialize::fromXML(data, istr) < 1) +	if(LLSDSerialize::fromXMLDocument(data, istr) < 1)  		return false;  	// We'll expire entries more than a week old @@ -414,7 +339,7 @@ bool LLCacheName::importFile(std::istream& istr)  		entry->mFirstName = agent[FIRST].asString();  		entry->mLastName = agent[LAST].asString();  		impl.mCache[id] = entry; -		std::string fullname = entry->mFirstName + " " + entry->mLastName; +		std::string fullname = buildFullName(entry->mFirstName, entry->mLastName);  		impl.mReverseCache[fullname] = id;  		++count; @@ -463,6 +388,7 @@ void LLCacheName::exportFile(std::ostream& ostr)  		// 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; @@ -480,7 +406,7 @@ void LLCacheName::exportFile(std::ostream& ostr)  } -BOOL LLCacheName::getName(const LLUUID& id, std::string& first, std::string& last) +BOOL LLCacheName::Impl::getName(const LLUUID& id, std::string& first, std::string& last)  {  	if(id.isNull())  	{ @@ -489,7 +415,7 @@ BOOL LLCacheName::getName(const LLUUID& id, std::string& first, std::string& las  		return TRUE;  	} -	LLCacheNameEntry* entry = get_ptr_in_map(impl.mCache, id ); +	LLCacheNameEntry* entry = get_ptr_in_map(mCache, id );  	if (entry)  	{  		first = entry->mFirstName; @@ -500,16 +426,17 @@ BOOL LLCacheName::getName(const LLUUID& id, std::string& first, std::string& las  	{  		first = sCacheName["waiting"];  		last.clear(); -		if (!impl.isRequestPending(id)) +		if (!isRequestPending(id))  		{ -			impl.mAskNameQueue.insert(id); +			mAskNameQueue.insert(id);  		}	  		return FALSE;  	}  } +  // static -void LLCacheName::LocalizeCacheName(std::string key, std::string value) +void LLCacheName::localizeCacheName(std::string key, std::string value)  {  	if (key!="" && value!= "" )  		sCacheName[key]=value; @@ -520,11 +447,13 @@ void LLCacheName::LocalizeCacheName(std::string key, std::string value)  BOOL LLCacheName::getFullName(const LLUUID& id, std::string& fullname)  {  	std::string first_name, last_name; -	BOOL res = getName(id, first_name, last_name); -	fullname = first_name + " " + last_name; +	BOOL res = impl.getName(id, first_name, last_name); +	fullname = buildFullName(first_name, last_name);  	return res;  } + +  BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group)  {  	if(id.isNull()) @@ -561,13 +490,13 @@ BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group)  BOOL LLCacheName::getUUID(const std::string& first, const std::string& last, LLUUID& id)  { -	std::string fullname = first + " " + last; -	return getUUID(fullname, id); +	std::string full_name = buildFullName(first, last); +	return getUUID(full_name, id);  } -BOOL LLCacheName::getUUID(const std::string& fullname, LLUUID& id) +BOOL LLCacheName::getUUID(const std::string& full_name, LLUUID& id)  { -	ReverseCache::iterator iter = impl.mReverseCache.find(fullname); +	ReverseCache::iterator iter = impl.mReverseCache.find(full_name);  	if (iter != impl.mReverseCache.end())  	{  		id = iter->second; @@ -579,6 +508,55 @@ BOOL LLCacheName::getUUID(const std::string& fullname, LLUUID& id)  	}  } +//static +std::string LLCacheName::buildFullName(const std::string& first, const std::string& last) +{ +	std::string fullname = first; +	if (!last.empty() +		&& last != "Resident") +	{ +		fullname += ' '; +		fullname += last; +	} +	return fullname; +} + +//static +std::string LLCacheName::cleanFullName(const std::string& full_name) +{ +	return full_name.substr(0, full_name.find(" Resident")); +} + +//static  +std::string LLCacheName::buildUsername(const std::string& full_name) +{ +	// rare, but handle hard-coded error names returned from server +	if (full_name == "(\?\?\?) (\?\?\?)") +	{ +		return "(\?\?\?)"; +	} +	 +	std::string::size_type index = full_name.find(' '); + +	if (index != std::string::npos) +	{ +		std::string username; +		username = full_name.substr(0, index); +		std::string lastname = full_name.substr(index+1); + +		if (lastname != "Resident") +		{ +			username = username + "." + lastname; +		} +		 +		LLStringUtil::toLower(username); +		return username; +	} + +	// if the input wasn't a correctly formatted legacy name just return it unchanged +	return full_name; +} +  // 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 @@ -586,7 +564,7 @@ BOOL LLCacheName::getUUID(const std::string& fullname, LLUUID& id)  //  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 LLCacheName::get(const LLUUID& id, bool is_group, const LLCacheNameCallback& callback)  {  	boost::signals2::connection res; @@ -594,7 +572,7 @@ boost::signals2::connection LLCacheName::get(const LLUUID& id, BOOL is_group, co  	{  		LLCacheNameSignal signal;  		signal.connect(callback); -		signal(id, sCacheName["nobody"], "", is_group); +		signal(id, sCacheName["nobody"], is_group);  		return res;  	} @@ -606,11 +584,13 @@ boost::signals2::connection LLCacheName::get(const LLUUID& id, BOOL is_group, co  		// id found in map therefore we can call the callback immediately.  		if (entry->mIsGroup)  		{ -			signal(id, entry->mGroupName, "", entry->mIsGroup); +			signal(id, entry->mGroupName, entry->mIsGroup);  		}  		else  		{ -			signal(id, entry->mFirstName, entry->mLastName, entry->mIsGroup); +			std::string fullname = +				buildFullName(entry->mFirstName, entry->mLastName); +			signal(id, fullname, entry->mIsGroup);  		}  	}  	else @@ -632,9 +612,15 @@ boost::signals2::connection LLCacheName::get(const LLUUID& id, BOOL is_group, co  	return res;  } -boost::signals2::connection LLCacheName::get(const LLUUID& id, BOOL is_group, old_callback_t callback, void* user_data) +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, _4, user_data)); +	return get(id, is_group, boost::bind(callback, _1, _2, _3, user_data));  }  void LLCacheName::processPending() @@ -706,7 +692,7 @@ void LLCacheName::dump()  		{  			llinfos  				<< iter->first << " = " -				<< entry->mFirstName << " " << entry->mLastName +				<< buildFullName(entry->mFirstName, entry->mLastName)  				<< " @ " << entry->mCreateTime  				<< llendl;  		} @@ -725,12 +711,24 @@ void LLCacheName::dumpStats()  			<< llendl;  } +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()  {  	LLMemType mt_ppa(LLMemType::MTYPE_CACHE_PROCESS_PENDING_ASKS); @@ -752,11 +750,13 @@ void LLCacheName::Impl::processPendingReplies()  		if (!entry->mIsGroup)  		{ -			(reply->mSignal)(reply->mID, entry->mFirstName, entry->mLastName, FALSE); +			std::string fullname = +				LLCacheName::buildFullName(entry->mFirstName, entry->mLastName); +			(reply->mSignal)(reply->mID, fullname, false);  		}  		else  		{ -			(reply->mSignal)(reply->mID, entry->mGroupName, "", TRUE); +			(reply->mSignal)(reply->mID, entry->mGroupName, true);  		}  	} @@ -927,13 +927,27 @@ void LLCacheName::Impl::processUUIDReply(LLMessageSystem* msg, bool isGroup)  		if (!isGroup)  		{ -			mSignal(id, entry->mFirstName, entry->mLastName, FALSE); -			std::string fullname = entry->mFirstName + " " + entry->mLastName; -			mReverseCache[fullname] = id; +			// 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); +			} +			else +			{ +				full_name = LLCacheName::buildFullName(entry->mFirstName, entry->mLastName); +			} +			mSignal(id, full_name, false); +			mReverseCache[full_name] = id;  		}  		else  		{ -			mSignal(id, entry->mGroupName, "", TRUE); +			mSignal(id, entry->mGroupName, true);  			mReverseCache[entry->mGroupName] = id;  		}  	} @@ -962,4 +976,3 @@ void LLCacheName::Impl::handleUUIDGroupNameReply(LLMessageSystem* msg, void** us  {  	((LLCacheName::Impl*)userData)->processUUIDReply(msg, true);  } - diff --git a/indra/llmessage/llcachename.h b/indra/llmessage/llcachename.h index 111cc8b650..697bb27556 100644 --- a/indra/llmessage/llcachename.h +++ b/indra/llmessage/llcachename.h @@ -42,13 +42,12 @@ class LLUUID;  typedef boost::signals2::signal<void (const LLUUID& id, -                                      const std::string& first_name, -                                      const std::string& last_name, -                                      BOOL is_group)> LLCacheNameSignal; +                                      const std::string& name, +                                      bool is_group)> LLCacheNameSignal;  typedef LLCacheNameSignal::slot_type LLCacheNameCallback;  // Old callback with user data for compatability -typedef void (*old_callback_t)(const LLUUID&, const std::string&, const std::string&, BOOL, void*); +typedef void (*old_callback_t)(const LLUUID&, const std::string&, bool, void*);  // Here's the theory:  // If you request a name that isn't in the cache, it returns "waiting" @@ -71,24 +70,31 @@ public:  	boost::signals2::connection addObserver(const LLCacheNameCallback& callback); -	// janky old format. Remove after a while. Phoenix. 2008-01-30 -	void importFile(LLFILE* fp); -  	// storing cache on disk; for viewer, in name.cache  	bool importFile(std::istream& istr);  	void exportFile(std::ostream& ostr); -	// If available, copies the first and last name into the strings provided. -	// first must be at least DB_FIRST_NAME_BUF_SIZE characters. -	// last must be at least DB_LAST_NAME_BUF_SIZE characters. +	// If available, copies name ("bobsmith123" or "James Linden") into string  	// If not available, copies the string "waiting".  	// Returns TRUE iff available. -	BOOL getName(const LLUUID& id, std::string& first, std::string& last); -	BOOL getFullName(const LLUUID& id, std::string& fullname); -	 +	BOOL getFullName(const LLUUID& id, std::string& full_name); +  	// Reverse lookup of UUID from name  	BOOL getUUID(const std::string& first, const std::string& last, LLUUID& id);  	BOOL getUUID(const std::string& fullname, LLUUID& id); + +	// IDEVO Temporary code +	// Clean up new-style "bobsmith123 Resident" names to "bobsmith123" for display +	static std::string buildFullName(const std::string& first, const std::string& last); + +	// Clean up legacy "bobsmith123 Resident" to "bobsmith123" +	// If name does not contain "Resident" returns it unchanged. +	static std::string cleanFullName(const std::string& full_name); +	 +	// Converts a standard legacy name to a username +	// "bobsmith123 Resident" -> "bobsmith" +	// "Random Linden" -> "random.linden" +	static std::string buildUsername(const std::string& name);  	// If available, this method copies the group name into the string  	// provided. The caller must allocate at least @@ -100,10 +106,15 @@ public:  	// If the data is currently available, may call the callback immediatly  	// otherwise, will request the data, and will call the callback when  	// available.  There is no garuntee the callback will ever be called. -	boost::signals2::connection get(const LLUUID& id, BOOL is_group, const LLCacheNameCallback& callback); -	 +	boost::signals2::connection get(const LLUUID& id, bool is_group, const LLCacheNameCallback& callback); + +	// Convenience method for looking up a group name, so you can +	// tell the difference between avatar lookup and group lookup +	// in global searches +	boost::signals2::connection getGroup(const LLUUID& group_id, const LLCacheNameCallback& callback); +  	// LEGACY -	boost::signals2::connection get(const LLUUID& id, BOOL is_group, old_callback_t callback, void* user_data); +	boost::signals2::connection get(const LLUUID& id, bool is_group, old_callback_t callback, void* user_data);  	// This method needs to be called from time to time to send out  	// requests.  	void processPending(); @@ -114,9 +125,15 @@ public:  	// Debugging  	void dump();		// Dumps the contents of the cache  	void dumpStats();	// Dumps the sizes of the cache and associated queues. +	void clear();		// Deletes all entries from the cache  	static std::string getDefaultName(); -	static void LocalizeCacheName(std::string key, std::string value); + +	// Returns "Resident", the default last name for SLID-based accounts +	// that have no last name. +	static std::string getDefaultLastName(); + +	static void localizeCacheName(std::string key, std::string value);  	static std::map<std::string, std::string> sCacheName;  private: diff --git a/indra/llmessage/mean_collision_data.h b/indra/llmessage/mean_collision_data.h index 03b96f9f90..a6c635e81e 100644 --- a/indra/llmessage/mean_collision_data.h +++ b/indra/llmessage/mean_collision_data.h @@ -61,7 +61,7 @@ public:  	LLMeanCollisionData(LLMeanCollisionData *mcd)  		: mVictim(mcd->mVictim), mPerp(mcd->mPerp), mTime(mcd->mTime), mType(mcd->mType), mMag(mcd->mMag), -		  mFirstName(mcd->mFirstName), mLastName(mcd->mLastName) +		  mFullName(mcd->mFullName)  	{  	}		 @@ -95,8 +95,7 @@ public:  	time_t mTime;  	EMeanCollisionType mType;  	F32	   mMag; -	std::string mFirstName; -	std::string mLastName; +	std::string mFullName;  }; diff --git a/indra/llmessage/tests/llavatarnamecache_test.cpp b/indra/llmessage/tests/llavatarnamecache_test.cpp new file mode 100644 index 0000000000..eeadb703d1 --- /dev/null +++ b/indra/llmessage/tests/llavatarnamecache_test.cpp @@ -0,0 +1,109 @@ +/** + * @file llhost_test.cpp + * @author Adroit + * @date 2007-02 + * @brief llhost test cases. + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + *  + * Copyright (c) 2007-2009, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ +  +#include "linden_common.h" + +#include "../llavatarnamecache.h" + +#include "../test/lltut.h" + +namespace tut +{ +	struct avatarnamecache_data +	{ +	}; +	typedef test_group<avatarnamecache_data> avatarnamecache_test; +	typedef avatarnamecache_test::object avatarnamecache_object; +	tut::avatarnamecache_test avatarnamecache_testcase("llavatarnamecache"); + +	template<> template<> +	void avatarnamecache_object::test<1>() +	{ +		bool valid = false; +		S32 max_age = 0; + +		valid = max_age_from_cache_control("max-age=3600", &max_age); +		ensure("typical input valid", valid); +		ensure_equals("typical input parsed", max_age, 3600); + +		valid = max_age_from_cache_control( +			" max-age=600 , no-cache,private=\"stuff\" ", &max_age); +		ensure("complex input valid", valid); +		ensure_equals("complex input parsed", max_age, 600); + +		valid = max_age_from_cache_control( +			"no-cache, max-age = 123 ", &max_age); +		ensure("complex input 2 valid", valid); +		ensure_equals("complex input 2 parsed", max_age, 123); +	} + +	template<> template<> +	void avatarnamecache_object::test<2>() +	{ +		bool valid = false; +		S32 max_age = -1; + +		valid = max_age_from_cache_control("", &max_age); +		ensure("empty input returns invalid", !valid); +		ensure_equals("empty input doesn't change val", max_age, -1); + +		valid = max_age_from_cache_control("no-cache", &max_age); +		ensure("no max-age field returns invalid", !valid); + +		valid = max_age_from_cache_control("max", &max_age); +		ensure("just 'max' returns invalid", !valid); + +		valid = max_age_from_cache_control("max-age", &max_age); +		ensure("partial max-age is invalid", !valid); + +		valid = max_age_from_cache_control("max-age=", &max_age); +		ensure("longer partial max-age is invalid", !valid); + +		valid = max_age_from_cache_control("max-age=FOO", &max_age); +		ensure("invalid integer max-age is invalid", !valid); + +		valid = max_age_from_cache_control("max-age 234", &max_age); +		ensure("space separated max-age is invalid", !valid); + +		valid = max_age_from_cache_control("max-age=0", &max_age); +		ensure("zero max-age is valid", valid); + +		// *TODO: Handle "0000" as zero +		//valid = max_age_from_cache_control("max-age=0000", &max_age); +		//ensure("multi-zero max-age is valid", valid); + +		valid = max_age_from_cache_control("max-age=-123", &max_age); +		ensure("less than zero max-age is invalid", !valid); +	} +} | 
