diff options
Diffstat (limited to '')
-rw-r--r-- | linden/indra/llmessage/llavatarnamecache.cpp | 859 |
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 | |||
42 | namespace 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 | |||
164 | class LLAvatarNameResponder : public LLHTTPClient::Responder | ||
165 | { | ||
166 | private: | ||
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 | |||
174 | public: | ||
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 | |||
328 | void 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 | |||
353 | void 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 | |||
412 | void 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 | |||
428 | void 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 | |||
449 | void LLAvatarNameCache::initClass(bool running) | ||
450 | { | ||
451 | sRunning = running; | ||
452 | } | ||
453 | |||
454 | void LLAvatarNameCache::cleanupClass() | ||
455 | { | ||
456 | } | ||
457 | |||
458 | void 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 | |||
483 | void 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 | |||
502 | void LLAvatarNameCache::setNameLookupURL(const std::string& name_lookup_url) | ||
503 | { | ||
504 | sNameLookupURL = name_lookup_url; | ||
505 | } | ||
506 | |||
507 | bool LLAvatarNameCache::hasNameLookupURL() | ||
508 | { | ||
509 | return !sNameLookupURL.empty(); | ||
510 | } | ||
511 | |||
512 | void 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 | |||
554 | bool 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 | |||
569 | void 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 | |||
585 | void 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 | ||
598 | bool 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 | |||
643 | void 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 | |||
652 | void 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 | |||
712 | void 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 | |||
731 | U32 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 | |||
744 | void LLAvatarNameCache::erase(const LLUUID& agent_id) | ||
745 | { | ||
746 | sCache.erase(agent_id); | ||
747 | } | ||
748 | |||
749 | void LLAvatarNameCache::fetch(const LLUUID& agent_id) | ||
750 | { | ||
751 | // re-request, even if request is already pending | ||
752 | sAskQueue.insert(agent_id); | ||
753 | } | ||
754 | |||
755 | void 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 | |||
761 | F64 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 | |||
777 | bool 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 | |||
796 | void LLAvatarNameCache::addUseDisplayNamesCallback(const use_display_name_signal_t::slot_type& cb) | ||
797 | { | ||
798 | mUseDisplayNamesSignal.connect(cb); | ||
799 | } | ||
800 | |||
801 | |||
802 | static const std::string MAX_AGE("max-age"); | ||
803 | static const boost::char_separator<char> EQUALS_SEPARATOR("="); | ||
804 | static const boost::char_separator<char> COMMA_SEPARATOR(","); | ||
805 | |||
806 | bool 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 | |||