aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/llmessage/llavatarnamecache.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--linden/indra/llmessage/llavatarnamecache.cpp859
1 files changed, 859 insertions, 0 deletions
diff --git a/linden/indra/llmessage/llavatarnamecache.cpp b/linden/indra/llmessage/llavatarnamecache.cpp
new file mode 100644
index 0000000..45048e1
--- /dev/null
+++ b/linden/indra/llmessage/llavatarnamecache.cpp
@@ -0,0 +1,859 @@
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: 0 = legacy names,
47 // 1 = display name and legacy name, 2 = display name (legacy if absent)
48 U32 sUseDisplayNames = 0;
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 // Periodically clean out expired entries from the cache
85 //LLFrameTimer sEraseExpiredTimer;
86
87 //-----------------------------------------------------------------------
88 // Internal methods
89 //-----------------------------------------------------------------------
90
91 // Handle name response off network.
92 // Optionally skip adding to cache, used when this is a fallback to the
93 // legacy name system.
94 void processName(const LLUUID& agent_id,
95 const LLAvatarName& av_name,
96 bool add_to_cache);
97
98 void requestNamesViaCapability();
99
100 // Legacy name system callback
101 void legacyNameCallback(const LLUUID& agent_id,
102 const std::string& first, const std::string& last,
103 BOOL is_group, void* data);
104
105 void requestNamesViaLegacy();
106
107 // Fill in an LLAvatarName with the legacy name data
108 void buildLegacyName(const std::string& full_name,
109 LLAvatarName* av_name);
110
111 // Do a single callback to a given slot
112 void fireSignal(const LLUUID& agent_id,
113 const callback_slot_t& slot,
114 const LLAvatarName& av_name);
115
116 // Is a request in-flight over the network?
117 bool isRequestPending(const LLUUID& agent_id);
118
119 // Erase expired names from cache
120 void eraseExpired();
121
122 bool expirationFromCacheControl(LLSD headers, F64 *expires);
123}
124
125/* Sample response:
126<?xml version="1.0"?>
127<llsd>
128 <map>
129 <key>agents</key>
130 <array>
131 <map>
132 <key>display_name_next_update</key>
133 <date>2010-04-16T21:34:02+00:00Z</date>
134 <key>display_name_expires</key>
135 <date>2010-04-16T21:32:26.142178+00:00Z</date>
136 <key>display_name</key>
137 <string>MickBot390 LLQABot</string>
138 <key>sl_id</key>
139 <string>mickbot390.llqabot</string>
140 <key>id</key>
141 <string>0012809d-7d2d-4c24-9609-af1230a37715</string>
142 <key>is_display_name_default</key>
143 <boolean>false</boolean>
144 </map>
145 <map>
146 <key>display_name_next_update</key>
147 <date>2010-04-16T21:34:02+00:00Z</date>
148 <key>display_name_expires</key>
149 <date>2010-04-16T21:32:26.142178+00:00Z</date>
150 <key>display_name</key>
151 <string>Bjork Gudmundsdottir</string>
152 <key>sl_id</key>
153 <string>sardonyx.linden</string>
154 <key>id</key>
155 <string>3941037e-78ab-45f0-b421-bd6e77c1804d</string>
156 <key>is_display_name_default</key>
157 <boolean>true</boolean>
158 </map>
159 </array>
160 </map>
161</llsd>
162*/
163
164class LLAvatarNameResponder : public LLHTTPClient::Responder
165{
166private:
167 // need to store agent ids that are part of this request in case of
168 // an error, so we can flag them as unavailable
169 std::vector<LLUUID> mAgentIDs;
170
171 // Need the headers to look up Expires: and Retry-After:
172 LLSD mHeaders;
173
174public:
175 LLAvatarNameResponder(const std::vector<LLUUID>& agent_ids)
176 : mAgentIDs(agent_ids),
177 mHeaders()
178 { }
179
180 /*virtual*/ void completedHeader(U32 status, const std::string& reason,
181 const LLSD& headers)
182 {
183 mHeaders = headers;
184 }
185
186 /*virtual*/ void result(const LLSD& content)
187 {
188 // Pull expiration out of headers if available
189 F64 expires = LLAvatarNameCache::nameExpirationFromHeaders(mHeaders);
190
191 LLSD agents = content["agents"];
192 LLSD::array_const_iterator it;
193 for (it = agents.beginArray(); it != agents.endArray(); ++it)
194 {
195 const LLSD& row = *it;
196 LLUUID agent_id = row["id"].asUUID();
197
198 LLAvatarName av_name;
199 av_name.fromLLSD(row);
200
201 // Use expiration time from header
202 av_name.mExpires = expires;
203
204 // Some avatars don't have explicit display names set
205 if (av_name.mDisplayName.empty())
206 {
207 av_name.mDisplayName = av_name.mUsername;
208 }
209
210 // cache it and fire signals
211 LLAvatarNameCache::processName(agent_id, av_name, true);
212 }
213
214 // Same logic as error response case
215 LLSD unresolved_agents = content["bad_ids"];
216 if (unresolved_agents.size() > 0)
217 {
218 std::string first, last;
219 LLAvatarName av_name;
220 av_name.mIsDisplayNameDefault = false;
221 av_name.mIsDummy = true;
222 av_name.mExpires = expires;
223
224 for (it = unresolved_agents.beginArray(); it != unresolved_agents.endArray(); ++it)
225 {
226 const LLUUID& agent_id = *it;
227 gCacheName->getName(agent_id, first, last);
228 av_name.mLegacyFirstName = first;
229 av_name.mLegacyLastName = last;
230 av_name.mDisplayName = first;
231 if (last == "Resident")
232 {
233 av_name.mDisplayName = first;
234 av_name.mUsername = first;
235 }
236 else
237 {
238 av_name.mDisplayName = first + " " + last;
239 av_name.mUsername = first + "." + last;
240 }
241 LLStringUtil::toLower(av_name.mUsername);
242 // cache it and fire signals
243 LLAvatarNameCache::processName(agent_id, av_name, true);
244 }
245 }
246 }
247
248 /*virtual*/ void error(U32 status, const std::string& reason)
249 {
250 // We're going to construct a dummy record and cache it for a while,
251 // either briefly for a 503 Service Unavailable, or longer for other
252 // errors.
253 F64 retry_timestamp = errorRetryTimestamp(status);
254
255 std::string first, last;
256 LLAvatarName av_name;
257 av_name.mIsDisplayNameDefault = false;
258 av_name.mIsDummy = true;
259 av_name.mExpires = retry_timestamp;
260
261 // Add dummy records for all agent IDs in this request
262 std::vector<LLUUID>::const_iterator it;
263 for (it = mAgentIDs.begin(); it != mAgentIDs.end(); ++it)
264 {
265 const LLUUID& agent_id = *it;
266 gCacheName->getName(agent_id, first, last);
267 av_name.mLegacyFirstName = first;
268 av_name.mLegacyLastName = last;
269 av_name.mDisplayName = first;
270 if (last == "Resident")
271 {
272 av_name.mDisplayName = first;
273 av_name.mUsername = first;
274 }
275 else
276 {
277 av_name.mDisplayName = first + " " + last;
278 av_name.mUsername = first + "." + last;
279 }
280 LLStringUtil::toLower(av_name.mUsername);
281 // cache it and fire signals
282 LLAvatarNameCache::processName(agent_id, av_name, true);
283 }
284 }
285
286 // Return time to retry a request that generated an error, based on
287 // error type and headers. Return value is seconds-since-epoch.
288 F64 errorRetryTimestamp(S32 status)
289 {
290 F64 now = LLFrameTimer::getTotalSeconds();
291
292 // Retry-After takes priority
293 LLSD retry_after = mHeaders["retry-after"];
294 if (retry_after.isDefined())
295 {
296 // We only support the delta-seconds type
297 S32 delta_seconds = retry_after.asInteger();
298 if (delta_seconds > 0)
299 {
300 // ...valid delta-seconds
301 return now + F64(delta_seconds);
302 }
303 }
304
305 // If no Retry-After, look for Cache-Control max-age
306 F64 expires = 0.0;
307 if (LLAvatarNameCache::expirationFromCacheControl(mHeaders, &expires))
308 {
309 return expires;
310 }
311
312 // No information in header, make a guess
313 if (status == 503)
314 {
315 // ...service unavailable, retry soon
316 const F64 SERVICE_UNAVAILABLE_DELAY = 600.0; // 10 min
317 return now + SERVICE_UNAVAILABLE_DELAY;
318 }
319 else
320 {
321 // ...other unexpected error
322 const F64 DEFAULT_DELAY = 3600.0; // 1 hour
323 return now + DEFAULT_DELAY;
324 }
325 }
326};
327
328void LLAvatarNameCache::processName(const LLUUID& agent_id,
329 const LLAvatarName& av_name,
330 bool add_to_cache)
331{
332 if (add_to_cache)
333 {
334 sCache[agent_id] = av_name;
335 }
336
337 sPendingQueue.erase(agent_id);
338
339 // signal everyone waiting on this name
340 signal_map_t::iterator sig_it = sSignalMap.find(agent_id);
341 if (sig_it != sSignalMap.end())
342 {
343 callback_signal_t* signal = sig_it->second;
344 (*signal)(agent_id, av_name);
345
346 sSignalMap.erase(agent_id);
347
348 delete signal;
349 signal = NULL;
350 }
351}
352
353void LLAvatarNameCache::requestNamesViaCapability()
354{
355 F64 now = LLFrameTimer::getTotalSeconds();
356
357 // URL format is like:
358 // http://pdp60.lindenlab.com:8000/agents/?ids=3941037e-78ab-45f0-b421-bd6e77c1804d&ids=0012809d-7d2d-4c24-9609-af1230a37715&ids=0019aaba-24af-4f0a-aa72-6457953cf7f0
359 //
360 // Apache can handle URLs of 4096 chars, but let's be conservative
361 const U32 NAME_URL_MAX = 4096;
362 const U32 NAME_URL_SEND_THRESHOLD = 3000;
363 std::string url;
364 url.reserve(NAME_URL_MAX);
365
366 std::vector<LLUUID> agent_ids;
367 agent_ids.reserve(128);
368
369 ask_queue_t::const_iterator it = sAskQueue.begin();
370 for ( ; it != sAskQueue.end(); ++it)
371 {
372 const LLUUID& agent_id = *it;
373
374 if (url.empty())
375 {
376 // ...starting new request
377 url += sNameLookupURL;
378 url += "?ids=";
379 }
380 else
381 {
382 // ...continuing existing request
383 url += "&ids=";
384 }
385 url += agent_id.asString();
386 agent_ids.push_back(agent_id);
387
388 // mark request as pending
389 sPendingQueue[agent_id] = now;
390
391 if (url.size() > NAME_URL_SEND_THRESHOLD)
392 {
393 //llinfos << "requestNames " << url << llendl;
394 LLHTTPClient::get(url, new LLAvatarNameResponder(agent_ids));
395 url.clear();
396 agent_ids.clear();
397 }
398 }
399
400 if (!url.empty())
401 {
402 //llinfos << "requestNames " << url << llendl;
403 LLHTTPClient::get(url, new LLAvatarNameResponder(agent_ids));
404 url.clear();
405 agent_ids.clear();
406 }
407
408 // We've moved all asks to the pending request queue
409 sAskQueue.clear();
410}
411
412void LLAvatarNameCache::legacyNameCallback(const LLUUID& agent_id,
413 const std::string& first, const std::string& last,
414 BOOL is_group, void* data)
415{
416 std::string full_name = first + " " + last;
417 // Construct a dummy record for this name. By convention, SLID is blank
418 // Never expires, but not written to disk, so lasts until end of session.
419 LLAvatarName av_name;
420 buildLegacyName(full_name, &av_name);
421
422 // Don't add to cache, the data already exists in the legacy name system
423 // cache and we don't want or need duplicate storage, because keeping the
424 // two copies in sync is complex.
425 processName(agent_id, av_name, false);
426}
427
428void LLAvatarNameCache::requestNamesViaLegacy()
429{
430 F64 now = LLFrameTimer::getTotalSeconds();
431 std::string full_name;
432 ask_queue_t::const_iterator it = sAskQueue.begin();
433 for (; it != sAskQueue.end(); ++it)
434 {
435 const LLUUID& agent_id = *it;
436
437 // Mark as pending first, just in case the callback is immediately
438 // invoked below. This should never happen in practice.
439 sPendingQueue[agent_id] = now;
440
441 gCacheName->get(agent_id, false, legacyNameCallback);
442 }
443
444 // We've either answered immediately or moved all asks to the
445 // pending queue
446 sAskQueue.clear();
447}
448
449void LLAvatarNameCache::initClass(bool running)
450{
451 sRunning = running;
452}
453
454void LLAvatarNameCache::cleanupClass()
455{
456}
457
458void LLAvatarNameCache::importFile(std::istream& istr)
459{
460 LLSD data;
461 S32 parse_count = LLSDSerialize::fromXMLDocument(data, istr);
462 if (parse_count < 1) return;
463
464 // by convention LLSD storage is a map
465 // we only store one entry in the map
466 LLSD agents = data["agents"];
467
468 LLUUID agent_id;
469 LLAvatarName av_name;
470 LLSD::map_const_iterator it = agents.beginMap();
471 for ( ; it != agents.endMap(); ++it)
472 {
473 agent_id.set(it->first);
474 av_name.fromLLSD( it->second );
475 sCache[agent_id] = av_name;
476 }
477 // entries may have expired since we last ran the viewer, just
478 // clean them out now
479 eraseExpired();
480 llinfos << "loaded " << sCache.size() << llendl;
481}
482
483void LLAvatarNameCache::exportFile(std::ostream& ostr)
484{
485 LLSD agents;
486 cache_t::const_iterator it = sCache.begin();
487 for ( ; it != sCache.end(); ++it)
488 {
489 const LLUUID& agent_id = it->first;
490 const LLAvatarName& av_name = it->second;
491 if (!av_name.mIsDummy)
492 {
493 // key must be a string
494 agents[agent_id.asString()] = av_name.asLLSD();
495 }
496 }
497 LLSD data;
498 data["agents"] = agents;
499 LLSDSerialize::toPrettyXML(data, ostr);
500}
501
502void LLAvatarNameCache::setNameLookupURL(const std::string& name_lookup_url)
503{
504 sNameLookupURL = name_lookup_url;
505}
506
507bool LLAvatarNameCache::hasNameLookupURL()
508{
509 return !sNameLookupURL.empty();
510}
511
512void LLAvatarNameCache::idle()
513{
514 // By convention, start running at first idle() call
515 sRunning = true;
516
517 // *TODO: Possibly re-enabled this based on People API load measurements
518 // 100 ms is the threshold for "user speed" operations, so we can
519 // stall for about that long to batch up requests.
520 //const F32 SECS_BETWEEN_REQUESTS = 0.1f;
521 //if (!sRequestTimer.checkExpirationAndReset(SECS_BETWEEN_REQUESTS))
522 //{
523 // return;
524 //}
525
526 // Must be large relative to above
527
528 // No longer deleting expired entries, just re-requesting in the get
529 // this way first synchronous get call on an expired entry won't return
530 // legacy name. LF
531
532 //const F32 ERASE_EXPIRED_TIMEOUT = 60.f; // seconds
533 //if (sEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT))
534 //{
535 // eraseExpired();
536 //}
537
538 if (sAskQueue.empty())
539 {
540 return;
541 }
542
543 if (useDisplayNames())
544 {
545 requestNamesViaCapability();
546 }
547 else
548 {
549 // ...fall back to legacy name cache system
550 requestNamesViaLegacy();
551 }
552}
553
554bool LLAvatarNameCache::isRequestPending(const LLUUID& agent_id)
555{
556 const F64 PENDING_TIMEOUT_SECS = 5.0 * 60.0;
557 F64 now = LLFrameTimer::getTotalSeconds();
558 F64 expire_time = now - PENDING_TIMEOUT_SECS;
559
560 pending_queue_t::const_iterator it = sPendingQueue.find(agent_id);
561 if (it != sPendingQueue.end())
562 {
563 bool request_expired = (it->second < expire_time);
564 return !request_expired;
565 }
566 return false;
567}
568
569void LLAvatarNameCache::eraseExpired()
570{
571 F64 now = LLFrameTimer::getTotalSeconds();
572 cache_t::iterator it = sCache.begin();
573 while (it != sCache.end())
574 {
575 cache_t::iterator cur = it;
576 ++it;
577 const LLAvatarName& av_name = cur->second;
578 if (av_name.mExpires < now)
579 {
580 sCache.erase(cur);
581 }
582 }
583}
584
585void LLAvatarNameCache::buildLegacyName(const std::string& full_name,
586 LLAvatarName* av_name)
587{
588 llassert(av_name);
589 av_name->mUsername = "";
590 av_name->mDisplayName = full_name;
591 av_name->mIsDisplayNameDefault = true;
592 av_name->mIsDummy = true;
593 av_name->mExpires = F64_MAX;
594}
595
596// fills in av_name if it has it in the cache, even if expired (can check expiry time)
597// returns bool specifying if av_name was filled, false otherwise
598bool LLAvatarNameCache::get(const LLUUID& agent_id, LLAvatarName *av_name)
599{
600 if (sRunning)
601 {
602 // ...only do immediate lookups when cache is running
603 if (useDisplayNames())
604 {
605 // ...use display names cache
606 std::map<LLUUID,LLAvatarName>::iterator it = sCache.find(agent_id);
607 if (it != sCache.end())
608 {
609 *av_name = it->second;
610
611 // re-request name if entry is expired
612 if (av_name->mExpires < LLFrameTimer::getTotalSeconds())
613 {
614 if (!isRequestPending(agent_id))
615 {
616 sAskQueue.insert(agent_id);
617 }
618 }
619
620 return true;
621 }
622 }
623 else
624 {
625 // ...use legacy names cache
626 std::string full_name;
627 if (gCacheName->getFullName(agent_id, full_name))
628 {
629 buildLegacyName(full_name, av_name);
630 return true;
631 }
632 }
633 }
634
635 if (!isRequestPending(agent_id))
636 {
637 sAskQueue.insert(agent_id);
638 }
639
640 return false;
641}
642
643void LLAvatarNameCache::fireSignal(const LLUUID& agent_id,
644 const callback_slot_t& slot,
645 const LLAvatarName& av_name)
646{
647 callback_signal_t signal;
648 signal.connect(slot);
649 signal(agent_id, av_name);
650}
651
652void LLAvatarNameCache::get(const LLUUID& agent_id, callback_slot_t slot)
653{
654 if (sRunning)
655 {
656 // ...only do immediate lookups when cache is running
657 if (useDisplayNames())
658 {
659 // ...use new cache
660 std::map<LLUUID,LLAvatarName>::iterator it = sCache.find(agent_id);
661 if (it != sCache.end())
662 {
663 const LLAvatarName& av_name = it->second;
664
665 if (av_name.mExpires > LLFrameTimer::getTotalSeconds())
666 {
667 // ...name already exists in cache, fire callback now
668 fireSignal(agent_id, slot, av_name);
669
670 return;
671 }
672 }
673 }
674 else
675 {
676 // ...use old name system
677 std::string full_name;
678 if (gCacheName->getFullName(agent_id, full_name))
679 {
680 LLAvatarName av_name;
681 buildLegacyName(full_name, &av_name);
682 fireSignal(agent_id, slot, av_name);
683 return;
684 }
685 }
686 }
687
688 // schedule a request
689 if (!isRequestPending(agent_id))
690 {
691 sAskQueue.insert(agent_id);
692 }
693
694 // always store additional callback, even if request is pending
695 signal_map_t::iterator sig_it = sSignalMap.find(agent_id);
696 if (sig_it == sSignalMap.end())
697 {
698 // ...new callback for this id
699 callback_signal_t* signal = new callback_signal_t();
700 signal->connect(slot);
701 sSignalMap[agent_id] = signal;
702 }
703 else
704 {
705 // ...existing callback, bind additional slot
706 callback_signal_t* signal = sig_it->second;
707 signal->connect(slot);
708 }
709}
710
711
712void LLAvatarNameCache::setUseDisplayNames(U32 use)
713{
714 if (use != sUseDisplayNames)
715 {
716 if (use > 2)
717 {
718 sUseDisplayNames = 1;
719 }
720 else
721 {
722 sUseDisplayNames = use;
723 }
724 // flush our cache
725 sCache.clear();
726
727 mUseDisplayNamesSignal();
728 }
729}
730
731U32 LLAvatarNameCache::useDisplayNames()
732{
733 // Must be both manually set on and able to look up names.
734 if (sNameLookupURL.empty())
735 {
736 return 0;
737 }
738 else
739 {
740 return sUseDisplayNames;
741 }
742}
743
744void LLAvatarNameCache::erase(const LLUUID& agent_id)
745{
746 sCache.erase(agent_id);
747}
748
749void LLAvatarNameCache::fetch(const LLUUID& agent_id)
750{
751 // re-request, even if request is already pending
752 sAskQueue.insert(agent_id);
753}
754
755void LLAvatarNameCache::insert(const LLUUID& agent_id, const LLAvatarName& av_name)
756{
757 // *TODO: update timestamp if zero?
758 sCache[agent_id] = av_name;
759}
760
761F64 LLAvatarNameCache::nameExpirationFromHeaders(LLSD headers)
762{
763 F64 expires = 0.0;
764 if (expirationFromCacheControl(headers, &expires))
765 {
766 return expires;
767 }
768 else
769 {
770 // With no expiration info, default to an hour
771 const F64 DEFAULT_EXPIRES = 60.0 * 60.0;
772 F64 now = LLFrameTimer::getTotalSeconds();
773 return now + DEFAULT_EXPIRES;
774 }
775}
776
777bool LLAvatarNameCache::expirationFromCacheControl(LLSD headers, F64 *expires)
778{
779 // Allow the header to override the default
780 LLSD cache_control_header = headers["cache-control"];
781 if (cache_control_header.isDefined())
782 {
783 S32 max_age = 0;
784 std::string cache_control = cache_control_header.asString();
785 if (max_age_from_cache_control(cache_control, &max_age))
786 {
787 F64 now = LLFrameTimer::getTotalSeconds();
788 *expires = now + (F64)max_age;
789 return true;
790 }
791 }
792 return false;
793}
794
795
796void LLAvatarNameCache::addUseDisplayNamesCallback(const use_display_name_signal_t::slot_type& cb)
797{
798 mUseDisplayNamesSignal.connect(cb);
799}
800
801
802static const std::string MAX_AGE("max-age");
803static const boost::char_separator<char> EQUALS_SEPARATOR("=");
804static const boost::char_separator<char> COMMA_SEPARATOR(",");
805
806bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age)
807{
808 // Split the string on "," to get a list of directives
809 typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
810 tokenizer directives(cache_control, COMMA_SEPARATOR);
811
812 tokenizer::iterator token_it = directives.begin();
813 for ( ; token_it != directives.end(); ++token_it)
814 {
815 // Tokens may have leading or trailing whitespace
816 std::string token = *token_it;
817 LLStringUtil::trim(token);
818
819 if (token.compare(0, MAX_AGE.size(), MAX_AGE) == 0)
820 {
821 // ...this token starts with max-age, so let's chop it up by "="
822 tokenizer subtokens(token, EQUALS_SEPARATOR);
823 tokenizer::iterator subtoken_it = subtokens.begin();
824
825 // Must have a token
826 if (subtoken_it == subtokens.end()) return false;
827 std::string subtoken = *subtoken_it;
828
829 // Must exactly equal "max-age"
830 LLStringUtil::trim(subtoken);
831 if (subtoken != MAX_AGE) return false;
832
833 // Must have another token
834 ++subtoken_it;
835 if (subtoken_it == subtokens.end()) return false;
836 subtoken = *subtoken_it;
837
838 // Must be a valid integer
839 // *NOTE: atoi() returns 0 for invalid values, so we have to
840 // check the string first.
841 // *TODO: Do servers ever send "0000" for zero? We don't handle it
842 LLStringUtil::trim(subtoken);
843 if (subtoken == "0")
844 {
845 *max_age = 0;
846 return true;
847 }
848 S32 val = atoi( subtoken.c_str() );
849 if (val > 0 && val < S32_MAX)
850 {
851 *max_age = val;
852 return true;
853 }
854 return false;
855 }
856 }
857 return false;
858}
859