PageRenderTime 36ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

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