llexperiencecache.cpp 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064
  1. /**
  2. * @file llexperiencecache.cpp
  3. * @brief Caches information relating to experience keys
  4. *
  5. * $LicenseInfo:firstyear=2012&license=viewergpl$
  6. *
  7. * Copyright (c) 2012, Linden Research, Inc.
  8. *
  9. * Second Life Viewer Source Code
  10. * The source code in this file ("Source Code") is provided by Linden Lab
  11. * to you under the terms of the GNU General Public License, version 2.0
  12. * ("GPL"), unless you have obtained a separate licensing agreement
  13. * ("Other License"), formally executed by you and Linden Lab. Terms of
  14. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  15. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  16. *
  17. * There are special exceptions to the terms and conditions of the GPL as
  18. * it is applied to this Source Code. View the full text of the exception
  19. * in the file doc/FLOSS-exception.txt in this software distribution, or
  20. * online at
  21. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  22. *
  23. * By copying, modifying or distributing this software, you acknowledge
  24. * that you have read and understood your obligations described above,
  25. * and agree to abide by those obligations.
  26. *
  27. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  28. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  29. * COMPLETENESS OR PERFORMANCE.
  30. * $/LicenseInfo$
  31. */
  32. #include "linden_common.h"
  33. #include "boost/concept_check.hpp"
  34. #include "boost/tokenizer.hpp"
  35. #include "llexperiencecache.h"
  36. #include "llcoproceduremanager.h"
  37. #include "llsdserialize.h"
  38. #include "lluri.h"
  39. const std::string LLExperienceCache::PRIVATE_KEY = "private_id";
  40. const std::string LLExperienceCache::MISSING = "DoesNotExist";
  41. const std::string LLExperienceCache::AGENT_ID = "agent_id";
  42. const std::string LLExperienceCache::GROUP_ID = "group_id";
  43. const std::string LLExperienceCache::EXPERIENCE_ID = "public_id";
  44. const std::string LLExperienceCache::NAME = "name";
  45. const std::string LLExperienceCache::PROPERTIES = "properties";
  46. const std::string LLExperienceCache::EXPIRES = "expiration";
  47. const std::string LLExperienceCache::DESCRIPTION = "description";
  48. const std::string LLExperienceCache::QUOTA = "quota";
  49. const std::string LLExperienceCache::MATURITY = "maturity";
  50. const std::string LLExperienceCache::METADATA = "extended_metadata";
  51. const std::string LLExperienceCache::SLURL = "slurl";
  52. static const std::string MAX_AGE = "max-age";
  53. const boost::char_separator<char> EQUALS_SEPARATOR("=");
  54. const boost::char_separator<char> COMMA_SEPARATOR(",");
  55. //static
  56. std::string LLExperienceCache::sLookupURL;
  57. bool LLExperienceCache::sShutdown = false;
  58. LLExperienceCache::LLExperienceCache()
  59. : // NOTE: by using these instead of omitting the corresponding
  60. // xxxAndSuspend() parameters, we avoid seeing such classes constructed
  61. // and destroyed each time...
  62. mHttpOptions(new LLCore::HttpOptions),
  63. mHttpHeaders(new LLCore::HttpHeaders)
  64. {
  65. }
  66. LLExperienceCache::~LLExperienceCache()
  67. {
  68. mHttpOptions.reset();
  69. mHttpHeaders.reset();
  70. }
  71. //virtual
  72. void LLExperienceCache::initSingleton()
  73. {
  74. LLCoprocedureManager::getInstance()->initializePool("ExpCache");
  75. gCoros.launch("LLExperienceCache::idleCoro",
  76. boost::bind(&LLExperienceCache::idleCoro, this));
  77. }
  78. void LLExperienceCache::cleanup()
  79. {
  80. sShutdown = true;
  81. }
  82. void LLExperienceCache::importFile(std::istream& istr)
  83. {
  84. LLSD data;
  85. S32 parse_count = LLSDSerialize::fromXMLDocument(data, istr);
  86. if (parse_count < 1) return;
  87. LLSD experiences = data["experiences"];
  88. LLUUID public_key;
  89. for (LLSD::map_const_iterator it = experiences.beginMap(),
  90. end = experiences.endMap();
  91. it != end; ++it)
  92. {
  93. public_key.set(it->first);
  94. mCache[public_key] = it->second;
  95. }
  96. LL_DEBUGS("ExperienceCache") << "Loaded " << mCache.size()
  97. << " experiences." << LL_ENDL;
  98. }
  99. void LLExperienceCache::exportFile(std::ostream& ostr) const
  100. {
  101. LLSD experiences;
  102. for (cache_t::const_iterator it = mCache.begin(), end = mCache.end();
  103. it != end; ++it)
  104. {
  105. if (!it->second.has(EXPERIENCE_ID) ||
  106. it->second[EXPERIENCE_ID].asUUID().isNull() ||
  107. it->second.has("DoesNotExist") ||
  108. (it->second.has(PROPERTIES) &&
  109. (it->second[PROPERTIES].asInteger() & PROPERTY_INVALID)))
  110. {
  111. continue;
  112. }
  113. experiences[it->first.asString()] = it->second;
  114. }
  115. LLSD data;
  116. data["experiences"] = experiences;
  117. LLSDSerialize::toPrettyXML(data, ostr);
  118. }
  119. #if 0 // Not used
  120. LLExperienceCache::key_map_t LLExperienceCache::sPrivateToPublicKeyMap;
  121. void LLExperienceCache::bootstrap(const LLSD& legacy_keys,
  122. S32 initial_expiration)
  123. {
  124. mapKeys(legacy_keys);
  125. for (LLSD::array_const_iterator it = legacy_keys.beginArray(),
  126. end = legacy_keys.endArray();
  127. it != end; ++it)
  128. {
  129. LLSD experience = *it;
  130. if (experience.has(EXPERIENCE_ID))
  131. {
  132. if (!experience.has(EXPIRES))
  133. {
  134. experience[EXPIRES] = initial_expiration;
  135. }
  136. processExperience(experience[EXPERIENCE_ID].asUUID(), experience);
  137. }
  138. else
  139. {
  140. llwarns << "Skipping bootstrap entry which is missing "
  141. << EXPERIENCE_ID << llendl;
  142. }
  143. }
  144. }
  145. LLUUID LLExperienceCache::getExperienceId(const LLUUID& private_key,
  146. bool null_if_not_found)
  147. {
  148. if (private_key.isNull())
  149. {
  150. return LLUUID::null;
  151. }
  152. key_map_t::const_iterator it = sPrivateToPublicKeyMap.find(private_key);
  153. if (it == sPrivateToPublicKeyMap.end())
  154. {
  155. return null_if_not_found ? LLUUID::null : private_key;
  156. }
  157. llwarns << "Converted private key " << private_key << " to experience_id "
  158. << it->second << llendl;
  159. return it->second;
  160. }
  161. void LLExperienceCache::mapKeys(const LLSD& legacy_keys)
  162. {
  163. for (LLSD::array_const_iterator exp = legacy_keys.beginArray(),
  164. end = legacy_keys.endArray();
  165. exp != end; ++exp)
  166. {
  167. if (exp->has(EXPERIENCE_ID) && exp->has(PRIVATE_KEY))
  168. {
  169. sPrivateToPublicKeyMap[(*exp)[PRIVATE_KEY].asUUID()] =
  170. (*exp)[EXPERIENCE_ID].asUUID();
  171. }
  172. }
  173. }
  174. #endif
  175. void LLExperienceCache::processExperience(const LLUUID& public_key,
  176. const LLSD& experience)
  177. {
  178. LL_DEBUGS("ExperienceCache") << "Processing experience: "
  179. << experience[NAME] << " - Key: "
  180. << public_key.asString() << LL_ENDL;
  181. mCache[public_key] = experience;
  182. LLSD& row = mCache[public_key];
  183. if (row.has(EXPIRES))
  184. {
  185. row[EXPIRES] = row[EXPIRES].asReal() + LLFrameTimer::getTotalSeconds();
  186. }
  187. if (row.has(EXPERIENCE_ID))
  188. {
  189. mPendingQueue.erase(row[EXPERIENCE_ID].asUUID());
  190. }
  191. // signal
  192. signal_map_t::iterator sig_it = mSignalMap.find(public_key);
  193. if (sig_it != mSignalMap.end())
  194. {
  195. signal_ptr signal = sig_it->second;
  196. if (signal)
  197. {
  198. (*signal)(experience);
  199. }
  200. mSignalMap.erase(public_key);
  201. }
  202. }
  203. void LLExperienceCache::requestExperiencesCoro(adapter_ptr_t& adapter,
  204. std::string url,
  205. uuid_list_t requests)
  206. {
  207. LLSD result = adapter->getAndSuspend(url, mHttpOptions, mHttpHeaders);
  208. LLCore::HttpStatus status =
  209. LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
  210. if (!status)
  211. {
  212. F64 now = LLFrameTimer::getTotalSeconds();
  213. // Compute the retry delay, depending on the HTTP error or header
  214. S32 hstatus = status.getType();
  215. const LLSD& http_results =
  216. result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
  217. const LLSD& headers =
  218. http_results[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS];
  219. F64 retry_after = getErrorRetryDeltaTime(hstatus, headers);
  220. // build dummy entries for the failed requests
  221. for (uuid_list_t::const_iterator it = requests.begin(),
  222. end = requests.end();
  223. it != end; ++it)
  224. {
  225. const LLUUID& id = *it;
  226. if (id.notNull())
  227. {
  228. LLSD exp = get(id);
  229. if (exp.isUndefined())
  230. {
  231. // Leave the properties alone if we already have a cache entry
  232. // for this experience
  233. exp[PROPERTIES] = PROPERTY_INVALID;
  234. }
  235. exp[EXPIRES] = now + retry_after;
  236. exp[EXPERIENCE_ID] = id;
  237. exp["key_type"] = EXPERIENCE_ID;
  238. exp["uuid"] = id;
  239. exp["error"] = (LLSD::Integer)hstatus;
  240. exp[QUOTA] = DEFAULT_QUOTA;
  241. processExperience(id, exp);
  242. }
  243. }
  244. return;
  245. }
  246. const LLSD& experiences = result["experience_keys"];
  247. for (LLSD::array_const_iterator it = experiences.beginArray(),
  248. end = experiences.endArray();
  249. it != end; ++it)
  250. {
  251. const LLSD& row = *it;
  252. LLUUID public_key = row[EXPERIENCE_ID].asUUID();
  253. LL_DEBUGS("ExperienceCache") << "Received result for " << public_key
  254. << " display '" << row[NAME].asString()
  255. << "'" << LL_ENDL ;
  256. processExperience(public_key, row);
  257. }
  258. const LLSD& error_ids = result["error_ids"];
  259. for (LLSD::array_const_iterator it = error_ids.beginArray(),
  260. end = error_ids.endArray();
  261. it != end; ++it)
  262. {
  263. LLUUID id = it->asUUID();
  264. if (id.notNull())
  265. {
  266. LLSD exp;
  267. exp[EXPIRES] = DEFAULT_EXPIRATION;
  268. exp[EXPERIENCE_ID] = id;
  269. exp[PROPERTIES] = PROPERTY_INVALID;
  270. exp[MISSING] = true;
  271. exp[QUOTA] = DEFAULT_QUOTA;
  272. processExperience(id, exp);
  273. llwarns << "Error result for " << id << llendl;
  274. }
  275. }
  276. }
  277. void LLExperienceCache::requestExperiences()
  278. {
  279. if (sLookupURL.empty())
  280. {
  281. return;
  282. }
  283. F64 now = LLFrameTimer::getTotalSeconds();
  284. constexpr U32 EXP_URL_SEND_THRESHOLD = 3000;
  285. // Note: "PAGE_SIZE" is #defined somewhere in macOS headers... So we cannot
  286. // use it.
  287. constexpr U32 PAGE_SIZE_ = EXP_URL_SEND_THRESHOLD / UUID_STR_LENGTH;
  288. const std::string page_size = llformat("?page_size=%d", PAGE_SIZE_);
  289. const std::string key_query = "&" + EXPERIENCE_ID + "=";
  290. std::string uri = sLookupURL + page_size;
  291. LLUUID key;
  292. uuid_list_t requests;
  293. while (!mRequestQueue.empty() && !sShutdown)
  294. {
  295. uuid_list_t::iterator it = mRequestQueue.begin();
  296. key = *it;
  297. mRequestQueue.hset_erase(it);
  298. if (key.isNull()) continue;
  299. requests.emplace(key);
  300. uri += key_query + key.asString();
  301. mPendingQueue[key] = now;
  302. if (mRequestQueue.empty() || uri.size() > EXP_URL_SEND_THRESHOLD)
  303. {
  304. LL_DEBUGS("ExperienceCache") << "Query: " << uri << LL_ENDL;
  305. LLCoprocedureManager* cpmgr = LLCoprocedureManager::getInstance();
  306. cpmgr->enqueueCoprocedure("ExpCache", "RequestExperiences",
  307. boost::bind(&LLExperienceCache::requestExperiencesCoro,
  308. this, _1, uri, requests));
  309. uri = sLookupURL + page_size;
  310. requests.clear();
  311. }
  312. }
  313. }
  314. bool LLExperienceCache::isRequestPending(const LLUUID& public_key)
  315. {
  316. bool is_pending = false;
  317. constexpr F64 PENDING_TIMEOUT_SECS = 300.0;
  318. pending_map_t::const_iterator it = mPendingQueue.find(public_key);
  319. if (it != mPendingQueue.end())
  320. {
  321. F64 expire_time = LLFrameTimer::getTotalSeconds() -
  322. PENDING_TIMEOUT_SECS;
  323. is_pending = it->second > expire_time;
  324. }
  325. return is_pending;
  326. }
  327. void LLExperienceCache::idleCoro()
  328. {
  329. constexpr F32 SECS_BETWEEN_REQUESTS = 0.5f;
  330. constexpr F32 ERASE_EXPIRED_TIMEOUT = 60.f; // seconds
  331. llinfos << "Launching Experience cache idle coro." << llendl;
  332. do
  333. {
  334. if (mEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT))
  335. {
  336. eraseExpired();
  337. }
  338. if (!mRequestQueue.empty())
  339. {
  340. requestExperiences();
  341. }
  342. llcoro::suspendUntilTimeout(SECS_BETWEEN_REQUESTS);
  343. }
  344. while (!sShutdown);
  345. llinfos << "Experience cache idle coroutine exited." << llendl;
  346. }
  347. void LLExperienceCache::erase(const LLUUID& key)
  348. {
  349. cache_t::iterator it = mCache.find(key);
  350. if (it != mCache.end())
  351. {
  352. mCache.hmap_erase(it);
  353. }
  354. }
  355. void LLExperienceCache::eraseExpired()
  356. {
  357. F64 now = LLFrameTimer::getTotalSeconds();
  358. cache_t::iterator it = mCache.begin();
  359. while (it != mCache.end())
  360. {
  361. cache_t::iterator cur = it++;
  362. const LLSD& exp = cur->second;
  363. if (exp.has(EXPIRES) && exp[EXPIRES].asReal() < now)
  364. {
  365. if (!exp.has(EXPERIENCE_ID))
  366. {
  367. llwarns << "Removing an experience with no id" << llendl;
  368. mCache.erase(cur);
  369. }
  370. else
  371. {
  372. LLUUID id = exp[EXPERIENCE_ID].asUUID();
  373. LLUUID private_key;
  374. if (exp.has(PRIVATE_KEY))
  375. {
  376. private_key = exp[PRIVATE_KEY].asUUID();
  377. }
  378. if (private_key.notNull() || !exp.has("DoesNotExist"))
  379. {
  380. fetch(id, true);
  381. }
  382. else
  383. {
  384. llwarns << "Removing invalid experience: " << id << llendl;
  385. mCache.erase(cur);
  386. }
  387. }
  388. }
  389. }
  390. }
  391. bool LLExperienceCache::fetch(const LLUUID& key, bool refresh)
  392. {
  393. if (!key.isNull() && !isRequestPending(key) &&
  394. (refresh || mCache.count(key) == 0))
  395. {
  396. LL_DEBUGS("ExperienceCache") << "Queue request for " << EXPERIENCE_ID
  397. << " " << key << LL_ENDL;
  398. mRequestQueue.emplace(key);
  399. return true;
  400. }
  401. return false;
  402. }
  403. void LLExperienceCache::insert(const LLSD& exp_data)
  404. {
  405. if (exp_data.has(EXPERIENCE_ID))
  406. {
  407. processExperience(exp_data[EXPERIENCE_ID].asUUID(), exp_data);
  408. }
  409. else
  410. {
  411. llwarns << "Ignoring cache insert of experience which is missing "
  412. << EXPERIENCE_ID << llendl;
  413. }
  414. }
  415. const LLSD& LLExperienceCache::get(const LLUUID& key)
  416. {
  417. static const LLSD empty;
  418. if (key.isNull())
  419. {
  420. return empty;
  421. }
  422. cache_t::const_iterator it = mCache.find(key);
  423. if (it != mCache.end())
  424. {
  425. return it->second;
  426. }
  427. fetch(key);
  428. return empty;
  429. }
  430. void LLExperienceCache::get(const LLUUID& key, experience_get_fn_t slot)
  431. {
  432. if (key.isNull()) return;
  433. cache_t::const_iterator it = mCache.find(key);
  434. if (it != mCache.end())
  435. {
  436. // ...name already exists in cache, fire callback now
  437. callback_signal_t signal;
  438. signal.connect(slot);
  439. signal(it->second);
  440. return;
  441. }
  442. fetch(key);
  443. signal_ptr signal = signal_ptr(new callback_signal_t());
  444. std::pair<signal_map_t::iterator, bool> result =
  445. mSignalMap.emplace(key, signal);
  446. if (!result.second)
  447. {
  448. signal = (*result.first).second;
  449. }
  450. if (signal)
  451. {
  452. signal->connect(slot);
  453. }
  454. }
  455. void LLExperienceCache::fetchAssociatedExperience(const LLUUID& object_id,
  456. const LLUUID& item_id,
  457. const std::string& cap_url,
  458. experience_get_fn_t fn)
  459. {
  460. if (mCapability.empty())
  461. {
  462. llwarns << "Capability query method not set." << llendl;
  463. return;
  464. }
  465. LLCoprocedureManager* cpmgr = LLCoprocedureManager::getInstance();
  466. cpmgr->enqueueCoprocedure("ExpCache", "Fetch Associated",
  467. boost::bind(&LLExperienceCache::fetchAssociatedExperienceCoro,
  468. this, _1, object_id, item_id,
  469. cap_url, fn));
  470. }
  471. void LLExperienceCache::fetchAssociatedExperienceCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t& adapter,
  472. LLUUID object_id,
  473. LLUUID item_id,
  474. std::string url,
  475. experience_get_fn_t fn)
  476. {
  477. if (url.empty())
  478. {
  479. url = mCapability("GetMetadata");
  480. if (url.empty())
  481. {
  482. llwarns << "No GetMetadata capability." << llendl;
  483. return;
  484. }
  485. }
  486. LLSD fields;
  487. fields.append("experience");
  488. LLSD data;
  489. data["object-id"] = object_id;
  490. data["item-id"] = item_id;
  491. data["fields"] = fields;
  492. LLSD result = adapter->postAndSuspend(url, data, mHttpOptions,
  493. mHttpHeaders);
  494. LLCore::HttpStatus status =
  495. LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
  496. if (!status || !result.has("experience"))
  497. {
  498. LLSD failure;
  499. if (!status)
  500. {
  501. failure["error"] = (LLSD::Integer)status.getType();
  502. failure["message"] = status.getMessage();
  503. }
  504. else
  505. {
  506. failure["error"] = -1;
  507. failure["message"] = "no experience";
  508. }
  509. if (fn && !fn.empty())
  510. {
  511. fn(failure);
  512. }
  513. }
  514. else
  515. {
  516. LLUUID exp_id = result["experience"].asUUID();
  517. get(exp_id, fn);
  518. }
  519. }
  520. void LLExperienceCache::findExperienceByName(std::string text, int page,
  521. experience_get_fn_t fn)
  522. {
  523. if (mCapability.empty())
  524. {
  525. llwarns << "Capability query method not set." << llendl;
  526. return;
  527. }
  528. LLCoprocedureManager* cpmgr = LLCoprocedureManager::getInstance();
  529. cpmgr->enqueueCoprocedure("ExpCache", "Search Name",
  530. boost::bind(&LLExperienceCache::findExperienceByNameCoro,
  531. this, _1, text, page, fn));
  532. }
  533. void LLExperienceCache::findExperienceByNameCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t& adapter,
  534. std::string text, int page,
  535. experience_get_fn_t fn)
  536. {
  537. std::ostringstream url;
  538. url << mCapability("FindExperienceByName") << "?page=" << page
  539. << "&page_size=" << SEARCH_PAGE_SIZE << "&query="
  540. << LLURI::escape(text);
  541. LLSD result = adapter->getAndSuspend(url.str(), mHttpOptions,
  542. mHttpHeaders);
  543. LLCore::HttpStatus status =
  544. LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
  545. if (!status)
  546. {
  547. fn(LLSD());
  548. return;
  549. }
  550. result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
  551. const LLSD& experiences = result["experience_keys"];
  552. for (LLSD::array_const_iterator it = experiences.beginArray(),
  553. end = experiences.endArray();
  554. it != end; ++it)
  555. {
  556. insert(*it);
  557. }
  558. fn(result);
  559. }
  560. void LLExperienceCache::getGroupExperiences(const LLUUID& group_id,
  561. experience_get_fn_t fn)
  562. {
  563. if (mCapability.empty())
  564. {
  565. llwarns << "Capability query method not set." << llendl;
  566. return;
  567. }
  568. LLCoprocedureManager* cpmgr = LLCoprocedureManager::getInstance();
  569. cpmgr->enqueueCoprocedure("ExpCache", "Group Experiences",
  570. boost::bind(&LLExperienceCache::getGroupExperiencesCoro,
  571. this, _1, group_id, fn));
  572. }
  573. void LLExperienceCache::getGroupExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t& adapter,
  574. LLUUID group_id,
  575. experience_get_fn_t fn)
  576. {
  577. // search for experiences owned by the current group
  578. std::string url = mCapability("GroupExperiences");
  579. if (url.empty())
  580. {
  581. llwarns << "No GroupExperiences capability" << llendl;
  582. return;
  583. }
  584. url += "?" + group_id.asString();
  585. LLSD result = adapter->getAndSuspend(url, mHttpOptions, mHttpHeaders);
  586. LLCore::HttpStatus status =
  587. LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
  588. if (!status)
  589. {
  590. fn(LLSD());
  591. return;
  592. }
  593. const LLSD& exp_ids = result["experience_ids"];
  594. fn(exp_ids);
  595. }
  596. void LLExperienceCache::getRegionExperiences(cap_query_fn_t regioncaps,
  597. experience_get_fn_t fn)
  598. {
  599. LLCoprocedureManager* cpmgr = LLCoprocedureManager::getInstance();
  600. cpmgr->enqueueCoprocedure("ExpCache", "Region Experiences",
  601. boost::bind(&LLExperienceCache::regionExperiencesCoro,
  602. this, _1, regioncaps, false, LLSD(),
  603. fn));
  604. }
  605. void LLExperienceCache::setRegionExperiences(cap_query_fn_t regioncaps,
  606. const LLSD& experiences,
  607. experience_get_fn_t fn)
  608. {
  609. LLCoprocedureManager* cpmgr = LLCoprocedureManager::getInstance();
  610. cpmgr->enqueueCoprocedure("ExpCache", "Region Experiences",
  611. boost::bind(&LLExperienceCache::regionExperiencesCoro,
  612. this, _1, regioncaps, true,
  613. experiences, fn));
  614. }
  615. void LLExperienceCache::regionExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t& adapter,
  616. cap_query_fn_t regioncaps,
  617. bool update, LLSD experiences,
  618. experience_get_fn_t fn)
  619. {
  620. // Search for experiences owned by the current group
  621. const std::string& url = regioncaps("RegionExperiences");
  622. if (url.empty())
  623. {
  624. llwarns << "No RegionExperiences capability" << llendl;
  625. return;
  626. }
  627. LLSD result;
  628. if (update)
  629. {
  630. result = adapter->postAndSuspend(url, experiences, mHttpOptions,
  631. mHttpHeaders);
  632. }
  633. else
  634. {
  635. result = adapter->getAndSuspend(url, mHttpOptions, mHttpHeaders);
  636. }
  637. LLCore::HttpStatus status =
  638. LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
  639. if (!status)
  640. {
  641. #if 0
  642. fn(LLSD());
  643. #endif
  644. return;
  645. }
  646. result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
  647. fn(result);
  648. }
  649. #define COROCAST(T) static_cast<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::*)(const std::string&, const LLSD&, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t)>(T)
  650. #define COROCAST2(T) static_cast<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::*)(const std::string&, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t)>(T)
  651. void LLExperienceCache::getExperiencePermission(const LLUUID& exp_id,
  652. experience_get_fn_t fn)
  653. {
  654. if (mCapability.empty())
  655. {
  656. llwarns << "Capability query method not set." << llendl;
  657. return;
  658. }
  659. std::string url = mCapability("ExperiencePreferences") + "?" +
  660. exp_id.asString();
  661. permissionInvoker_fn invoker(boost::bind(COROCAST2(&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend),
  662. // _1 -> adapter
  663. // _2 -> url
  664. _1, _2,
  665. LLCore::HttpOptions::ptr_t(),
  666. LLCore::HttpHeaders::ptr_t()));
  667. LLCoprocedureManager* cpmgr = LLCoprocedureManager::getInstance();
  668. cpmgr->enqueueCoprocedure("ExpCache", "Preferences Set",
  669. boost::bind(&LLExperienceCache::experiencePermissionCoro,
  670. this, _1, invoker, url, fn));
  671. }
  672. void LLExperienceCache::setExperiencePermission(const LLUUID& exp_id,
  673. const std::string& perm,
  674. experience_get_fn_t fn)
  675. {
  676. if (mCapability.empty())
  677. {
  678. llwarns << "Capability query method not set." << llendl;
  679. return;
  680. }
  681. std::string url = mCapability("ExperiencePreferences");
  682. if (url.empty())
  683. {
  684. llwarns << "No ExperiencePreferences capability" << llendl;
  685. return;
  686. }
  687. LLSD perm_data;
  688. perm_data["permission"] = perm;
  689. LLSD data;
  690. data[exp_id.asString()] = perm_data;
  691. permissionInvoker_fn invoker(boost::bind(COROCAST(&LLCoreHttpUtil::HttpCoroutineAdapter::putAndSuspend),
  692. // _1 -> adapter
  693. // _2 -> url
  694. _1, _2, data,
  695. LLCore::HttpOptions::ptr_t(),
  696. LLCore::HttpHeaders::ptr_t()));
  697. LLCoprocedureManager* cpmgr = LLCoprocedureManager::getInstance();
  698. cpmgr->enqueueCoprocedure("ExpCache", "Preferences Set",
  699. boost::bind(&LLExperienceCache::experiencePermissionCoro,
  700. this, _1, invoker, url, fn));
  701. }
  702. void LLExperienceCache::forgetExperiencePermission(const LLUUID& exp_id,
  703. experience_get_fn_t fn)
  704. {
  705. if (mCapability.empty())
  706. {
  707. llwarns << "Capability query method not set." << llendl;
  708. return;
  709. }
  710. std::string url = mCapability("ExperiencePreferences") + "?" + exp_id.asString();
  711. permissionInvoker_fn invoker(boost::bind(COROCAST2(&LLCoreHttpUtil::HttpCoroutineAdapter::deleteAndSuspend),
  712. // _1 -> adapter
  713. // _2 -> url
  714. _1, _2,
  715. LLCore::HttpOptions::ptr_t(),
  716. LLCore::HttpHeaders::ptr_t()));
  717. LLCoprocedureManager* cpmgr = LLCoprocedureManager::getInstance();
  718. cpmgr->enqueueCoprocedure("ExpCache", "Preferences Set",
  719. boost::bind(&LLExperienceCache::experiencePermissionCoro,
  720. this, _1, invoker, url, fn));
  721. }
  722. void LLExperienceCache::experiencePermissionCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t& adapter,
  723. permissionInvoker_fn invokerfn,
  724. std::string url,
  725. experience_get_fn_t fn)
  726. {
  727. // search for experiences owned by the current group
  728. LLSD result = invokerfn(adapter, url);
  729. LLCore::HttpStatus status =
  730. LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
  731. if (status)
  732. {
  733. result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
  734. fn(result);
  735. }
  736. }
  737. void LLExperienceCache::getExperienceAdmin(const LLUUID& exp_id,
  738. experience_get_fn_t fn)
  739. {
  740. if (mCapability.empty())
  741. {
  742. llwarns << "Capability query method not set." << llendl;
  743. return;
  744. }
  745. LLCoprocedureManager* cpmgr = LLCoprocedureManager::getInstance();
  746. cpmgr->enqueueCoprocedure("ExpCache", "IsAdmin",
  747. boost::bind(&LLExperienceCache::getExperienceAdminCoro,
  748. this, _1, exp_id, fn));
  749. }
  750. void LLExperienceCache::getExperienceAdminCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t& adapter,
  751. LLUUID exp_id,
  752. experience_get_fn_t fn)
  753. {
  754. std::string url = mCapability("IsExperienceAdmin");
  755. if (url.empty())
  756. {
  757. llwarns << "No Region Experiences capability" << llendl;
  758. return;
  759. }
  760. url += "?experience_id=" + exp_id.asString();
  761. LLSD result = adapter->getAndSuspend(url, mHttpOptions, mHttpHeaders);
  762. #if 0
  763. LLCore::HttpStatus status =
  764. LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
  765. #endif
  766. fn(result);
  767. }
  768. void LLExperienceCache::updateExperience(LLSD upd_data, experience_get_fn_t fn)
  769. {
  770. if (mCapability.empty())
  771. {
  772. llwarns << "Capability query method not set." << llendl;
  773. return;
  774. }
  775. LLCoprocedureManager* cpmgr = LLCoprocedureManager::getInstance();
  776. cpmgr->enqueueCoprocedure("ExpCache", "IsAdmin",
  777. boost::bind(&LLExperienceCache::updateExperienceCoro,
  778. this, _1, upd_data, fn));
  779. }
  780. void LLExperienceCache::updateExperienceCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t& adapter,
  781. LLSD upd_data,
  782. experience_get_fn_t fn)
  783. {
  784. std::string url = mCapability("UpdateExperience");
  785. if (url.empty())
  786. {
  787. llwarns << "No UpdateExperience capability" << llendl;
  788. return;
  789. }
  790. upd_data.erase(QUOTA);
  791. upd_data.erase(EXPIRES);
  792. upd_data.erase(AGENT_ID);
  793. LLSD result = adapter->postAndSuspend(url, upd_data, mHttpOptions,
  794. mHttpHeaders);
  795. #if 0
  796. LLCore::HttpStatus status =
  797. LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(result);
  798. #endif
  799. fn(result);
  800. }
  801. // Returns time to retry a request that generated an error, based on error type
  802. // and headers. Return value is seconds-since-epoch.
  803. //static
  804. F64 LLExperienceCache::getErrorRetryDeltaTime(S32 status, LLSD headers)
  805. {
  806. // Retry-After takes priority
  807. LLSD retry_after = headers["retry-after"];
  808. if (retry_after.isDefined())
  809. {
  810. // We only support the delta-seconds type
  811. S32 delta_seconds = retry_after.asInteger();
  812. if (delta_seconds > 0)
  813. {
  814. // ...valid delta-seconds
  815. return (F64)delta_seconds;
  816. }
  817. }
  818. // If no Retry-After, look for Cache-Control max-age
  819. // Allow the header to override the default
  820. LLSD cache_control_header = headers["cache-control"];
  821. if (cache_control_header.isDefined())
  822. {
  823. S32 max_age = 0;
  824. std::string cache_control = cache_control_header.asString();
  825. if (maxAgeFromCacheControl(cache_control, &max_age))
  826. {
  827. llwarns << "Got EXPIRES from headers, max_age = " << max_age
  828. << llendl;
  829. return (F64)max_age;
  830. }
  831. }
  832. // No information in header, make a guess
  833. if (status == 503)
  834. {
  835. // ...service unavailable, retry soon
  836. constexpr F64 SERVICE_UNAVAILABLE_DELAY = 600.0; // 10 min
  837. return SERVICE_UNAVAILABLE_DELAY;
  838. }
  839. else if (status == 499)
  840. {
  841. // ...we were probably too busy, retry quickly
  842. constexpr F64 BUSY_DELAY = 10.0; // 10 seconds
  843. return BUSY_DELAY;
  844. }
  845. else
  846. {
  847. // ...other unexpected error
  848. constexpr F64 DEFAULT_DELAY = 3600.0; // 1 hour
  849. return DEFAULT_DELAY;
  850. }
  851. }
  852. bool LLExperienceCache::maxAgeFromCacheControl(const std::string& cache_control,
  853. S32* max_age)
  854. {
  855. // Split the string on "," to get a list of directives
  856. typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
  857. tokenizer directives(cache_control, COMMA_SEPARATOR);
  858. for (tokenizer::iterator token_it = directives.begin();
  859. token_it != directives.end(); ++token_it)
  860. {
  861. // Tokens may have leading or trailing whitespace
  862. std::string token = *token_it;
  863. LLStringUtil::trim(token);
  864. if (token.compare(0, MAX_AGE.size(), MAX_AGE) == 0)
  865. {
  866. // this token starts with max-age, so let's chop it up by "="
  867. tokenizer subtokens(token, EQUALS_SEPARATOR);
  868. tokenizer::iterator subtoken_it = subtokens.begin();
  869. // Must have a token
  870. if (subtoken_it == subtokens.end()) return false;
  871. std::string subtoken = *subtoken_it;
  872. // Must exactly equal "max-age"
  873. LLStringUtil::trim(subtoken);
  874. if (subtoken != MAX_AGE) return false;
  875. // Must have another token
  876. if (++subtoken_it == subtokens.end()) return false;
  877. subtoken = *subtoken_it;
  878. // Must be a valid integer
  879. // *NOTE: atoi() returns 0 for invalid values, so we have to check
  880. // the string first.
  881. // *TODO: Do servers ever send "0000" for zero ? We don't handle
  882. // it
  883. LLStringUtil::trim(subtoken);
  884. if (subtoken == "0")
  885. {
  886. *max_age = 0;
  887. return true;
  888. }
  889. S32 val = atoi(subtoken.c_str());
  890. if (val > 0 && val < S32_MAX)
  891. {
  892. *max_age = val;
  893. return true;
  894. }
  895. return false;
  896. }
  897. }
  898. return false;
  899. }
  900. //static
  901. void LLExperienceCache::setLookupURL(const std::string& lookup_url)
  902. {
  903. sLookupURL = lookup_url;
  904. if (!sLookupURL.empty())
  905. {
  906. sLookupURL += "id/";
  907. }
  908. }
  909. //static
  910. bool LLExperienceCache::FilterWithProperty(const LLSD& experience, S32 prop)
  911. {
  912. return (experience[PROPERTIES].asInteger() & prop) != 0;
  913. }
  914. //static
  915. bool LLExperienceCache::FilterWithoutProperties(const LLSD& experience, S32 prop)
  916. {
  917. return (experience[PROPERTIES].asInteger() & prop) == prop;
  918. }
  919. //static
  920. bool LLExperienceCache::FilterWithoutProperty(const LLSD& experience, S32 prop)
  921. {
  922. return (experience[PROPERTIES].asInteger() & prop) == 0;
  923. }
  924. //static
  925. bool LLExperienceCache::FilterMatching(const LLSD& experience,
  926. const LLUUID& id)
  927. {
  928. if (experience.isUUID())
  929. {
  930. return experience.asUUID() == id;
  931. }
  932. return experience[EXPERIENCE_ID].asUUID() == id;
  933. }