PageRenderTime 94ms CodeModel.GetById 21ms app.highlight 66ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/llmessage/llavatarnamecache.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 853 lines | 555 code | 118 blank | 180 comment | 73 complexity | f2b212bdc5f0d994af5efcba4693be57 MD5 | raw file
  1/** 
  2 * @file llavatarnamecache.cpp
  3 * @brief Provides lookup of avatar SLIDs ("bobsmith123") and display names
  4 * ("James Cook") from avatar UUIDs.
  5 *
  6 * $LicenseInfo:firstyear=2010&license=viewerlgpl$
  7 * Second Life Viewer Source Code
  8 * Copyright (C) 2010, Linden Research, Inc.
  9 * 
 10 * This library is free software; you can redistribute it and/or
 11 * modify it under the terms of the GNU Lesser General Public
 12 * License as published by the Free Software Foundation;
 13 * version 2.1 of the License only.
 14 * 
 15 * This library is distributed in the hope that it will be useful,
 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 18 * Lesser General Public License for more details.
 19 * 
 20 * You should have received a copy of the GNU Lesser General Public
 21 * License along with this library; if not, write to the Free Software
 22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 23 * 
 24 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 25 * $/LicenseInfo$
 26 */
 27#include "linden_common.h"
 28
 29#include "llavatarnamecache.h"
 30
 31#include "llcachename.h"		// we wrap this system
 32#include "llframetimer.h"
 33#include "llhttpclient.h"
 34#include "llsd.h"
 35#include "llsdserialize.h"
 36
 37#include <boost/tokenizer.hpp>
 38
 39#include <map>
 40#include <set>
 41
 42namespace LLAvatarNameCache
 43{
 44	use_display_name_signal_t mUseDisplayNamesSignal;
 45
 46	// Manual override for display names - can disable even if the region
 47	// supports it.
 48	bool sUseDisplayNames = true;
 49
 50	// Cache starts in a paused state until we can determine if the
 51	// current region supports display names.
 52	bool sRunning = false;
 53	
 54	// Base lookup URL for name service.
 55	// On simulator, loaded from indra.xml
 56	// On viewer, usually a simulator capability (at People API team's request)
 57	// Includes the trailing slash, like "http://pdp60.lindenlab.com:8000/agents/"
 58	std::string sNameLookupURL;
 59
 60	// accumulated agent IDs for next query against service
 61	typedef std::set<LLUUID> ask_queue_t;
 62	ask_queue_t sAskQueue;
 63
 64	// agent IDs that have been requested, but with no reply
 65	// maps agent ID to frame time request was made
 66	typedef std::map<LLUUID, F64> pending_queue_t;
 67	pending_queue_t sPendingQueue;
 68
 69	// Callbacks to fire when we received a name.
 70	// May have multiple callbacks for a single ID, which are
 71	// represented as multiple slots bound to the signal.
 72	// Avoid copying signals via pointers.
 73	typedef std::map<LLUUID, callback_signal_t*> signal_map_t;
 74	signal_map_t sSignalMap;
 75
 76	// names we know about
 77	typedef std::map<LLUUID, LLAvatarName> cache_t;
 78	cache_t sCache;
 79
 80	// Send bulk lookup requests a few times a second at most
 81	// only need per-frame timing resolution
 82	LLFrameTimer sRequestTimer;
 83
 84    /// Maximum time an unrefreshed cache entry is allowed
 85    const F64 MAX_UNREFRESHED_TIME = 20.0 * 60.0;
 86
 87    /// Time when unrefreshed cached names were checked last
 88    static F64 sLastExpireCheck;
 89
 90	//-----------------------------------------------------------------------
 91	// Internal methods
 92	//-----------------------------------------------------------------------
 93
 94	// Handle name response off network.
 95	// Optionally skip adding to cache, used when this is a fallback to the
 96	// legacy name system.
 97	void processName(const LLUUID& agent_id,
 98					 const LLAvatarName& av_name,
 99					 bool add_to_cache);
100
101	void requestNamesViaCapability();
102
103	// Legacy name system callback
104	void legacyNameCallback(const LLUUID& agent_id,
105							const std::string& full_name,
106							bool is_group
107							);
108
109	void requestNamesViaLegacy();
110
111	// Fill in an LLAvatarName with the legacy name data
112	void buildLegacyName(const std::string& full_name,
113						 LLAvatarName* av_name);
114
115	// Do a single callback to a given slot
116	void fireSignal(const LLUUID& agent_id,
117					const callback_slot_t& slot,
118					const LLAvatarName& av_name);
119	
120	// Is a request in-flight over the network?
121	bool isRequestPending(const LLUUID& agent_id);
122
123	// Erase expired names from cache
124	void eraseUnrefreshed();
125
126	bool expirationFromCacheControl(LLSD headers, F64 *expires);
127}
128
129/* Sample response:
130<?xml version="1.0"?>
131<llsd>
132  <map>
133    <key>agents</key>
134    <array>
135      <map>
136        <key>display_name_next_update</key>
137        <date>2010-04-16T21:34:02+00:00Z</date>
138        <key>display_name_expires</key>
139        <date>2010-04-16T21:32:26.142178+00:00Z</date>
140        <key>display_name</key>
141        <string>MickBot390 LLQABot</string>
142        <key>sl_id</key>
143        <string>mickbot390.llqabot</string>
144        <key>id</key>
145        <string>0012809d-7d2d-4c24-9609-af1230a37715</string>
146        <key>is_display_name_default</key>
147        <boolean>false</boolean>
148      </map>
149      <map>
150        <key>display_name_next_update</key>
151        <date>2010-04-16T21:34:02+00:00Z</date>
152        <key>display_name_expires</key>
153        <date>2010-04-16T21:32:26.142178+00:00Z</date>
154        <key>display_name</key>
155        <string>Bjork Gudmundsdottir</string>
156        <key>sl_id</key>
157        <string>sardonyx.linden</string>
158        <key>id</key>
159        <string>3941037e-78ab-45f0-b421-bd6e77c1804d</string>
160        <key>is_display_name_default</key>
161        <boolean>true</boolean>
162      </map>
163    </array>
164  </map>
165</llsd>
166*/
167
168class LLAvatarNameResponder : public LLHTTPClient::Responder
169{
170private:
171	// need to store agent ids that are part of this request in case of
172	// an error, so we can flag them as unavailable
173	std::vector<LLUUID> mAgentIDs;
174
175	// Need the headers to look up Expires: and Retry-After:
176	LLSD mHeaders;
177	
178public:
179	LLAvatarNameResponder(const std::vector<LLUUID>& agent_ids)
180	:	mAgentIDs(agent_ids),
181		mHeaders()
182	{ }
183	
184	/*virtual*/ void completedHeader(U32 status, const std::string& reason, 
185		const LLSD& headers)
186	{
187		mHeaders = headers;
188	}
189
190	/*virtual*/ void result(const LLSD& content)
191	{
192		// Pull expiration out of headers if available
193		F64 expires = LLAvatarNameCache::nameExpirationFromHeaders(mHeaders);
194		F64 now = LLFrameTimer::getTotalSeconds();
195
196		LLSD agents = content["agents"];
197		LLSD::array_const_iterator it = agents.beginArray();
198		for ( ; it != agents.endArray(); ++it)
199		{
200			const LLSD& row = *it;
201			LLUUID agent_id = row["id"].asUUID();
202
203			LLAvatarName av_name;
204			av_name.fromLLSD(row);
205
206			// Use expiration time from header
207			av_name.mExpires = expires;
208
209			// Some avatars don't have explicit display names set
210			if (av_name.mDisplayName.empty())
211			{
212				av_name.mDisplayName = av_name.mUsername;
213			}
214
215			LL_DEBUGS("AvNameCache") << "LLAvatarNameResponder::result for " << agent_id << " "
216									 << "user '" << av_name.mUsername << "' "
217									 << "display '" << av_name.mDisplayName << "' "
218									 << "expires in " << expires - now << " seconds"
219									 << LL_ENDL;
220			
221			// cache it and fire signals
222			LLAvatarNameCache::processName(agent_id, av_name, true);
223		}
224
225		// Same logic as error response case
226		LLSD unresolved_agents = content["bad_ids"];
227		S32  num_unresolved = unresolved_agents.size();
228		if (num_unresolved > 0)
229		{
230            LL_WARNS("AvNameCache") << "LLAvatarNameResponder::result " << num_unresolved << " unresolved ids; "
231                                    << "expires in " << expires - now << " seconds"
232                                    << LL_ENDL;
233			it = unresolved_agents.beginArray();
234			for ( ; it != unresolved_agents.endArray(); ++it)
235			{
236				const LLUUID& agent_id = *it;
237
238				LL_WARNS("AvNameCache") << "LLAvatarNameResponder::result "
239                                        << "failed id " << agent_id
240                                        << LL_ENDL;
241
242                LLAvatarNameCache::handleAgentError(agent_id);
243			}
244		}
245        LL_DEBUGS("AvNameCache") << "LLAvatarNameResponder::result " 
246                                 << LLAvatarNameCache::sCache.size() << " cached names"
247                                 << LL_ENDL;
248    }
249
250	/*virtual*/ void error(U32 status, const std::string& reason)
251	{
252		// If there's an error, it might be caused by PeopleApi,
253		// or when loading textures on startup and using a very slow 
254		// network, this query may time out.
255		// What we should do depends on whether or not we have a cached name
256		LL_WARNS("AvNameCache") << "LLAvatarNameResponder::error " << status << " " << reason
257								<< LL_ENDL;
258
259		// Add dummy records for any agent IDs in this request that we do not have cached already
260		std::vector<LLUUID>::const_iterator it = mAgentIDs.begin();
261		for ( ; it != mAgentIDs.end(); ++it)
262		{
263			const LLUUID& agent_id = *it;
264			LLAvatarNameCache::handleAgentError(agent_id);
265		}
266	}
267};
268
269// Provide some fallback for agents that return errors
270void LLAvatarNameCache::handleAgentError(const LLUUID& agent_id)
271{
272	std::map<LLUUID,LLAvatarName>::iterator existing = sCache.find(agent_id);
273	if (existing == sCache.end())
274    {
275        // there is no existing cache entry, so make a temporary name from legacy
276        LL_WARNS("AvNameCache") << "LLAvatarNameCache get legacy for agent "
277                                << agent_id << LL_ENDL;
278        gCacheName->get(agent_id, false,  // legacy compatibility
279                        boost::bind(&LLAvatarNameCache::legacyNameCallback,
280                                    _1, _2, _3));
281    }
282	else
283    {
284        // we have a chached (but probably expired) entry - since that would have
285        // been returned by the get method, there is no need to signal anyone
286
287        // Clear this agent from the pending list
288        LLAvatarNameCache::sPendingQueue.erase(agent_id);
289
290        const LLAvatarName& av_name = existing->second;
291        LL_DEBUGS("AvNameCache") << "LLAvatarNameCache use cache for agent "
292                                 << agent_id 
293                                 << "user '" << av_name.mUsername << "' "
294                                 << "display '" << av_name.mDisplayName << "' "
295                                 << "expires in " << av_name.mExpires - LLFrameTimer::getTotalSeconds() << " seconds"
296                                 << LL_ENDL;
297    }
298}
299
300void LLAvatarNameCache::processName(const LLUUID& agent_id,
301									const LLAvatarName& av_name,
302									bool add_to_cache)
303{
304	if (add_to_cache)
305	{
306		sCache[agent_id] = av_name;
307	}
308
309	sPendingQueue.erase(agent_id);
310
311	// signal everyone waiting on this name
312	signal_map_t::iterator sig_it =	sSignalMap.find(agent_id);
313	if (sig_it != sSignalMap.end())
314	{
315		callback_signal_t* signal = sig_it->second;
316		(*signal)(agent_id, av_name);
317
318		sSignalMap.erase(agent_id);
319
320		delete signal;
321		signal = NULL;
322	}
323}
324
325void LLAvatarNameCache::requestNamesViaCapability()
326{
327	F64 now = LLFrameTimer::getTotalSeconds();
328
329	// URL format is like:
330	// http://pdp60.lindenlab.com:8000/agents/?ids=3941037e-78ab-45f0-b421-bd6e77c1804d&ids=0012809d-7d2d-4c24-9609-af1230a37715&ids=0019aaba-24af-4f0a-aa72-6457953cf7f0
331	//
332	// Apache can handle URLs of 4096 chars, but let's be conservative
333	const U32 NAME_URL_MAX = 4096;
334	const U32 NAME_URL_SEND_THRESHOLD = 3000;
335	std::string url;
336	url.reserve(NAME_URL_MAX);
337
338	std::vector<LLUUID> agent_ids;
339	agent_ids.reserve(128);
340	
341	U32 ids = 0;
342	ask_queue_t::const_iterator it = sAskQueue.begin();
343	for ( ; it != sAskQueue.end(); ++it)
344	{
345		const LLUUID& agent_id = *it;
346
347		if (url.empty())
348		{
349			// ...starting new request
350			url += sNameLookupURL;
351			url += "?ids=";
352			ids = 1;
353		}
354		else
355		{
356			// ...continuing existing request
357			url += "&ids=";
358			ids++;
359		}
360		url += agent_id.asString();
361		agent_ids.push_back(agent_id);
362
363		// mark request as pending
364		sPendingQueue[agent_id] = now;
365
366		if (url.size() > NAME_URL_SEND_THRESHOLD)
367		{
368			LL_DEBUGS("AvNameCache") << "LLAvatarNameCache::requestNamesViaCapability first "
369									 << ids << " ids"
370									 << LL_ENDL;
371			LLHTTPClient::get(url, new LLAvatarNameResponder(agent_ids));
372			url.clear();
373			agent_ids.clear();
374		}
375	}
376
377	if (!url.empty())
378	{
379		LL_DEBUGS("AvNameCache") << "LLAvatarNameCache::requestNamesViaCapability all "
380								 << ids << " ids"
381								 << LL_ENDL;
382		LLHTTPClient::get(url, new LLAvatarNameResponder(agent_ids));
383		url.clear();
384		agent_ids.clear();
385	}
386
387	// We've moved all asks to the pending request queue
388	sAskQueue.clear();
389}
390
391void LLAvatarNameCache::legacyNameCallback(const LLUUID& agent_id,
392										   const std::string& full_name,
393										   bool is_group)
394{
395	// Construct a dummy record for this name.  By convention, SLID is blank
396	// Never expires, but not written to disk, so lasts until end of session.
397	LLAvatarName av_name;
398	LL_DEBUGS("AvNameCache") << "LLAvatarNameCache::legacyNameCallback "
399							 << "agent " << agent_id << " "
400							 << "full name '" << full_name << "'"
401							 << ( is_group ? " [group]" : "" )
402							 << LL_ENDL;
403	buildLegacyName(full_name, &av_name);
404
405	// Don't add to cache, the data already exists in the legacy name system
406	// cache and we don't want or need duplicate storage, because keeping the
407	// two copies in sync is complex.
408	processName(agent_id, av_name, false);
409}
410
411void LLAvatarNameCache::requestNamesViaLegacy()
412{
413	F64 now = LLFrameTimer::getTotalSeconds();
414	std::string full_name;
415	ask_queue_t::const_iterator it = sAskQueue.begin();
416	for (; it != sAskQueue.end(); ++it)
417	{
418		const LLUUID& agent_id = *it;
419
420		// Mark as pending first, just in case the callback is immediately
421		// invoked below.  This should never happen in practice.
422		sPendingQueue[agent_id] = now;
423
424		LL_DEBUGS("AvNameCache") << "LLAvatarNameCache::requestNamesViaLegacy agent " << agent_id << LL_ENDL;
425
426		gCacheName->get(agent_id, false,  // legacy compatibility
427			boost::bind(&LLAvatarNameCache::legacyNameCallback,
428				_1, _2, _3));
429	}
430
431	// We've either answered immediately or moved all asks to the
432	// pending queue
433	sAskQueue.clear();
434}
435
436void LLAvatarNameCache::initClass(bool running)
437{
438	sRunning = running;
439}
440
441void LLAvatarNameCache::cleanupClass()
442{
443}
444
445void LLAvatarNameCache::importFile(std::istream& istr)
446{
447	LLSD data;
448	S32 parse_count = LLSDSerialize::fromXMLDocument(data, istr);
449	if (parse_count < 1) return;
450
451	// by convention LLSD storage is a map
452	// we only store one entry in the map
453	LLSD agents = data["agents"];
454
455	LLUUID agent_id;
456	LLAvatarName av_name;
457	LLSD::map_const_iterator it = agents.beginMap();
458	for ( ; it != agents.endMap(); ++it)
459	{
460		agent_id.set(it->first);
461		av_name.fromLLSD( it->second );
462		sCache[agent_id] = av_name;
463	}
464    LL_INFOS("AvNameCache") << "loaded " << sCache.size() << LL_ENDL;
465
466	// Some entries may have expired since the cache was stored,
467    // but they will be flushed in the first call to eraseUnrefreshed
468    // from LLAvatarNameResponder::idle
469}
470
471void LLAvatarNameCache::exportFile(std::ostream& ostr)
472{
473	LLSD agents;
474	F64 max_unrefreshed = LLFrameTimer::getTotalSeconds() - MAX_UNREFRESHED_TIME;
475	cache_t::const_iterator it = sCache.begin();
476	for ( ; it != sCache.end(); ++it)
477	{
478		const LLUUID& agent_id = it->first;
479		const LLAvatarName& av_name = it->second;
480		// Do not write temporary or expired entries to the stored cache
481		if (!av_name.mIsTemporaryName && av_name.mExpires >= max_unrefreshed)
482		{
483			// key must be a string
484			agents[agent_id.asString()] = av_name.asLLSD();
485		}
486	}
487	LLSD data;
488	data["agents"] = agents;
489	LLSDSerialize::toPrettyXML(data, ostr);
490}
491
492void LLAvatarNameCache::setNameLookupURL(const std::string& name_lookup_url)
493{
494	sNameLookupURL = name_lookup_url;
495}
496
497bool LLAvatarNameCache::hasNameLookupURL()
498{
499	return !sNameLookupURL.empty();
500}
501
502void LLAvatarNameCache::idle()
503{
504	// By convention, start running at first idle() call
505	sRunning = true;
506
507	// *TODO: Possibly re-enabled this based on People API load measurements
508	// 100 ms is the threshold for "user speed" operations, so we can
509	// stall for about that long to batch up requests.
510	//const F32 SECS_BETWEEN_REQUESTS = 0.1f;
511	//if (!sRequestTimer.checkExpirationAndReset(SECS_BETWEEN_REQUESTS))
512	//{
513	//	return;
514	//}
515
516	if (!sAskQueue.empty())
517	{
518        if (useDisplayNames())
519        {
520            requestNamesViaCapability();
521        }
522        else
523        {
524            // ...fall back to legacy name cache system
525            requestNamesViaLegacy();
526        }
527	}
528
529    // erase anything that has not been refreshed for more than MAX_UNREFRESHED_TIME
530    eraseUnrefreshed();
531}
532
533bool LLAvatarNameCache::isRequestPending(const LLUUID& agent_id)
534{
535	bool isPending = false;
536	const F64 PENDING_TIMEOUT_SECS = 5.0 * 60.0;
537
538	pending_queue_t::const_iterator it = sPendingQueue.find(agent_id);
539	if (it != sPendingQueue.end())
540	{
541		// in the list of requests in flight, retry if too old
542		F64 expire_time = LLFrameTimer::getTotalSeconds() - PENDING_TIMEOUT_SECS;
543		isPending = (it->second > expire_time);
544	}
545	return isPending;
546}
547
548void LLAvatarNameCache::eraseUnrefreshed()
549{
550	F64 now = LLFrameTimer::getTotalSeconds();
551	F64 max_unrefreshed = now - MAX_UNREFRESHED_TIME;
552
553    if (!sLastExpireCheck || sLastExpireCheck < max_unrefreshed)
554    {
555        sLastExpireCheck = now;
556
557        for (cache_t::iterator it = sCache.begin(); it != sCache.end();)
558        {
559            const LLAvatarName& av_name = it->second;
560            if (av_name.mExpires < max_unrefreshed)
561            {
562                const LLUUID& agent_id = it->first;
563                LL_DEBUGS("AvNameCache") << agent_id 
564                                         << " user '" << av_name.mUsername << "' "
565                                         << "expired " << now - av_name.mExpires << " secs ago"
566                                         << LL_ENDL;
567                sCache.erase(it++);
568            }
569			else
570			{
571				++it;
572			}
573        }
574        LL_INFOS("AvNameCache") << sCache.size() << " cached avatar names" << LL_ENDL;
575	}
576}
577
578void LLAvatarNameCache::buildLegacyName(const std::string& full_name,
579										LLAvatarName* av_name)
580{
581	llassert(av_name);
582	av_name->mUsername = "";
583	av_name->mDisplayName = full_name;
584	av_name->mIsDisplayNameDefault = true;
585	av_name->mIsTemporaryName = true;
586	av_name->mExpires = F64_MAX; // not used because these are not cached
587	LL_DEBUGS("AvNameCache") << "LLAvatarNameCache::buildLegacyName "
588							 << full_name
589							 << LL_ENDL;
590}
591
592// fills in av_name if it has it in the cache, even if expired (can check expiry time)
593// returns bool specifying  if av_name was filled, false otherwise
594bool LLAvatarNameCache::get(const LLUUID& agent_id, LLAvatarName *av_name)
595{
596	if (sRunning)
597	{
598		// ...only do immediate lookups when cache is running
599		if (useDisplayNames())
600		{
601			// ...use display names cache
602			std::map<LLUUID,LLAvatarName>::iterator it = sCache.find(agent_id);
603			if (it != sCache.end())
604			{
605				*av_name = it->second;
606
607				// re-request name if entry is expired
608				if (av_name->mExpires < LLFrameTimer::getTotalSeconds())
609				{
610					if (!isRequestPending(agent_id))
611					{
612						LL_DEBUGS("AvNameCache") << "LLAvatarNameCache::get "
613												 << "refresh agent " << agent_id
614												 << LL_ENDL;
615						sAskQueue.insert(agent_id);
616					}
617				}
618				
619				return true;
620			}
621		}
622		else
623		{
624			// ...use legacy names cache
625			std::string full_name;
626			if (gCacheName->getFullName(agent_id, full_name))
627			{
628				buildLegacyName(full_name, av_name);
629				return true;
630			}
631		}
632	}
633
634	if (!isRequestPending(agent_id))
635	{
636		LL_DEBUGS("AvNameCache") << "LLAvatarNameCache::get "
637								 << "queue request for agent " << agent_id
638								 << LL_ENDL;
639		sAskQueue.insert(agent_id);
640	}
641
642	return false;
643}
644
645void LLAvatarNameCache::fireSignal(const LLUUID& agent_id,
646								   const callback_slot_t& slot,
647								   const LLAvatarName& av_name)
648{
649	callback_signal_t signal;
650	signal.connect(slot);
651	signal(agent_id, av_name);
652}
653
654void LLAvatarNameCache::get(const LLUUID& agent_id, callback_slot_t slot)
655{
656	if (sRunning)
657	{
658		// ...only do immediate lookups when cache is running
659		if (useDisplayNames())
660		{
661			// ...use new cache
662			std::map<LLUUID,LLAvatarName>::iterator it = sCache.find(agent_id);
663			if (it != sCache.end())
664			{
665				const LLAvatarName& av_name = it->second;
666				
667				if (av_name.mExpires > LLFrameTimer::getTotalSeconds())
668				{
669					// ...name already exists in cache, fire callback now
670					fireSignal(agent_id, slot, av_name);
671					return;
672				}
673			}
674		}
675		else
676		{
677			// ...use old name system
678			std::string full_name;
679			if (gCacheName->getFullName(agent_id, full_name))
680			{
681				LLAvatarName av_name;
682				buildLegacyName(full_name, &av_name);
683				fireSignal(agent_id, slot, av_name);
684				return;
685			}
686		}
687	}
688
689	// schedule a request
690	if (!isRequestPending(agent_id))
691	{
692		sAskQueue.insert(agent_id);
693	}
694
695	// always store additional callback, even if request is pending
696	signal_map_t::iterator sig_it = sSignalMap.find(agent_id);
697	if (sig_it == sSignalMap.end())
698	{
699		// ...new callback for this id
700		callback_signal_t* signal = new callback_signal_t();
701		signal->connect(slot);
702		sSignalMap[agent_id] = signal;
703	}
704	else
705	{
706		// ...existing callback, bind additional slot
707		callback_signal_t* signal = sig_it->second;
708		signal->connect(slot);
709	}
710}
711
712
713void LLAvatarNameCache::setUseDisplayNames(bool use)
714{
715	if (use != sUseDisplayNames)
716	{
717		sUseDisplayNames = use;
718		// flush our cache
719		sCache.clear();
720
721		mUseDisplayNamesSignal();
722	}
723}
724
725bool LLAvatarNameCache::useDisplayNames()
726{
727	// Must be both manually set on and able to look up names.
728	return sUseDisplayNames && !sNameLookupURL.empty();
729}
730
731void LLAvatarNameCache::erase(const LLUUID& agent_id)
732{
733	sCache.erase(agent_id);
734}
735
736void LLAvatarNameCache::fetch(const LLUUID& agent_id)
737{
738	// re-request, even if request is already pending
739	sAskQueue.insert(agent_id);
740}
741
742void LLAvatarNameCache::insert(const LLUUID& agent_id, const LLAvatarName& av_name)
743{
744	// *TODO: update timestamp if zero?
745	sCache[agent_id] = av_name;
746}
747
748F64 LLAvatarNameCache::nameExpirationFromHeaders(LLSD headers)
749{
750	F64 expires = 0.0;
751	if (expirationFromCacheControl(headers, &expires))
752	{
753		return expires;
754	}
755	else
756	{
757		// With no expiration info, default to an hour
758		const F64 DEFAULT_EXPIRES = 60.0 * 60.0;
759		F64 now = LLFrameTimer::getTotalSeconds();
760		return now + DEFAULT_EXPIRES;
761	}
762}
763
764bool LLAvatarNameCache::expirationFromCacheControl(LLSD headers, F64 *expires)
765{
766	bool fromCacheControl = false;
767	F64 now = LLFrameTimer::getTotalSeconds();
768
769	// Allow the header to override the default
770	LLSD cache_control_header = headers["cache-control"];
771	if (cache_control_header.isDefined())
772	{
773		S32 max_age = 0;
774		std::string cache_control = cache_control_header.asString();
775		if (max_age_from_cache_control(cache_control, &max_age))
776		{
777			*expires = now + (F64)max_age;
778			fromCacheControl = true;
779		}
780	}
781	LL_DEBUGS("AvNameCache")
782		<< ( fromCacheControl ? "expires based on cache control " : "default expiration " )
783		<< "in " << *expires - now << " seconds"
784		<< LL_ENDL;
785	
786	return fromCacheControl;
787}
788
789
790void LLAvatarNameCache::addUseDisplayNamesCallback(const use_display_name_signal_t::slot_type& cb) 
791{ 
792	mUseDisplayNamesSignal.connect(cb); 
793}
794
795
796static const std::string MAX_AGE("max-age");
797static const boost::char_separator<char> EQUALS_SEPARATOR("=");
798static const boost::char_separator<char> COMMA_SEPARATOR(",");
799
800bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age)
801{
802	// Split the string on "," to get a list of directives
803	typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
804	tokenizer directives(cache_control, COMMA_SEPARATOR);
805
806	tokenizer::iterator token_it = directives.begin();
807	for ( ; token_it != directives.end(); ++token_it)
808	{
809		// Tokens may have leading or trailing whitespace
810		std::string token = *token_it;
811		LLStringUtil::trim(token);
812
813		if (token.compare(0, MAX_AGE.size(), MAX_AGE) == 0)
814		{
815			// ...this token starts with max-age, so let's chop it up by "="
816			tokenizer subtokens(token, EQUALS_SEPARATOR);
817			tokenizer::iterator subtoken_it = subtokens.begin();
818
819			// Must have a token
820			if (subtoken_it == subtokens.end()) return false;
821			std::string subtoken = *subtoken_it;
822
823			// Must exactly equal "max-age"
824			LLStringUtil::trim(subtoken);
825			if (subtoken != MAX_AGE) return false;
826
827			// Must have another token
828			++subtoken_it;
829			if (subtoken_it == subtokens.end()) return false;
830			subtoken = *subtoken_it;
831
832			// Must be a valid integer
833			// *NOTE: atoi() returns 0 for invalid values, so we have to
834			// check the string first.
835			// *TODO: Do servers ever send "0000" for zero?  We don't handle it
836			LLStringUtil::trim(subtoken);
837			if (subtoken == "0")
838			{
839				*max_age = 0;
840				return true;
841			}
842			S32 val = atoi( subtoken.c_str() );
843			if (val > 0 && val < S32_MAX)
844			{
845				*max_age = val;
846				return true;
847			}
848			return false;
849		}
850	}
851	return false;
852}
853