summaryrefslogtreecommitdiff
path: root/indra/llmessage/llexperiencecache.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llmessage/llexperiencecache.cpp')
-rw-r--r--indra/llmessage/llexperiencecache.cpp1332
1 files changed, 851 insertions, 481 deletions
diff --git a/indra/llmessage/llexperiencecache.cpp b/indra/llmessage/llexperiencecache.cpp
index 52b60a176e..779d1d9d99 100644
--- a/indra/llmessage/llexperiencecache.cpp
+++ b/indra/llmessage/llexperiencecache.cpp
@@ -26,616 +26,986 @@
#include "llexperiencecache.h"
#include "llavatarname.h"
-#include "llframetimer.h"
-#include "llhttpclient.h"
#include "llsdserialize.h"
+#include "llcoros.h"
+#include "lleventcoro.h"
+#include "lleventfilter.h"
+#include "llcoproceduremanager.h"
+#include "lldir.h"
#include <set>
#include <map>
-#include "boost/tokenizer.hpp"
+#include <boost/tokenizer.hpp>
+#include <boost/concept_check.hpp>
-
-namespace LLExperienceCache
+//=========================================================================
+namespace LLExperienceCacheImpl
{
+ void mapKeys(const LLSD& legacyKeys);
+ F64 getErrorRetryDeltaTime(S32 status, LLSD headers);
+ bool maxAgeFromCacheControl(const std::string& cache_control, S32 *max_age);
+
+ static const std::string PRIVATE_KEY = "private_id";
+ static const std::string EXPERIENCE_ID = "public_id";
+
+ static const std::string MAX_AGE("max-age");
+ static const boost::char_separator<char> EQUALS_SEPARATOR("=");
+ static const boost::char_separator<char> COMMA_SEPARATOR(",");
+ // *TODO$: this seems to be tied to mapKeys which is used by bootstrap.... but I don't think that bootstrap is used.
typedef std::map<LLUUID, LLUUID> KeyMap;
KeyMap privateToPublicKeyMap;
+}
- void mapKeys(const LLSD& legacyKeys);
+//=========================================================================
+const std::string LLExperienceCache::PRIVATE_KEY = "private_id";
+const std::string LLExperienceCache::MISSING = "DoesNotExist";
+
+const std::string LLExperienceCache::AGENT_ID = "agent_id";
+const std::string LLExperienceCache::GROUP_ID = "group_id";
+const std::string LLExperienceCache::EXPERIENCE_ID = "public_id";
+const std::string LLExperienceCache::NAME = "name";
+const std::string LLExperienceCache::PROPERTIES = "properties";
+const std::string LLExperienceCache::EXPIRES = "expiration";
+const std::string LLExperienceCache::DESCRIPTION = "description";
+const std::string LLExperienceCache::QUOTA = "quota";
+const std::string LLExperienceCache::MATURITY = "maturity";
+const std::string LLExperienceCache::METADATA = "extended_metadata";
+const std::string LLExperienceCache::SLURL = "slurl";
+
+// should be in sync with experience-api/experiences/models.py
+const int LLExperienceCache::PROPERTY_INVALID = 1 << 0;
+const int LLExperienceCache::PROPERTY_PRIVILEGED = 1 << 3;
+const int LLExperienceCache::PROPERTY_GRID = 1 << 4;
+const int LLExperienceCache::PROPERTY_PRIVATE = 1 << 5;
+const int LLExperienceCache::PROPERTY_DISABLED = 1 << 6;
+const int LLExperienceCache::PROPERTY_SUSPENDED = 1 << 7;
+
+// default values
+const F64 LLExperienceCache::DEFAULT_EXPIRATION = 600.0;
+const S32 LLExperienceCache::DEFAULT_QUOTA = 128; // this is megabytes
+const int LLExperienceCache::SEARCH_PAGE_SIZE = 30;
+
+//=========================================================================
+LLExperienceCache::LLExperienceCache():
+ mShutdown(false)
+{
+}
- std::string sLookupURL;
+LLExperienceCache::~LLExperienceCache()
+{
- typedef std::map<LLUUID, std::string> ask_queue_t;
- ask_queue_t sAskQueue;
+}
- typedef std::map<LLUUID, F64> pending_queue_t;
- pending_queue_t sPendingQueue;
+void LLExperienceCache::initSingleton()
+{
+ mCacheFileName = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "experience_cache.xml");
- cache_t sCache;
- int sMaximumLookups = 10;
+ LL_INFOS("ExperienceCache") << "Loading " << mCacheFileName << LL_ENDL;
+ llifstream cache_stream(mCacheFileName.c_str());
- LLFrameTimer sRequestTimer;
+ if (cache_stream.is_open())
+ {
+ cache_stream >> (*this);
+ }
- // Periodically clean out expired entries from the cache
- LLFrameTimer sEraseExpiredTimer;
+ LLCoros::instance().launch("LLExperienceCache::idleCoro",
+ boost::bind(&LLExperienceCache::idleCoro, this));
- // May have multiple callbacks for a single ID, which are
- // represented as multiple slots bound to the signal.
- // Avoid copying signals via pointers.
- typedef std::map<LLUUID, callback_signal_t*> signal_map_t;
- signal_map_t sSignalMap;
+}
+void LLExperienceCache::cleanup()
+{
+ LL_INFOS("ExperienceCache") << "Saving " << mCacheFileName << LL_ENDL;
+
+ llofstream cache_stream(mCacheFileName.c_str());
+ if (cache_stream.is_open())
+ {
+ cache_stream << (*this);
+ }
+ mShutdown = true;
+}
+//-------------------------------------------------------------------------
+void LLExperienceCache::importFile(std::istream& istr)
+{
+ LLSD data;
+ S32 parse_count = LLSDSerialize::fromXMLDocument(data, istr);
+ if (parse_count < 1) return;
- bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age);
- void eraseExpired();
+ LLSD experiences = data["experiences"];
- void processExperience( const LLUUID& public_key, const LLSD& experience )
- {
- sCache[public_key]=experience;
- LLSD & row = sCache[public_key];
+ LLUUID public_key;
+ LLSD::map_const_iterator it = experiences.beginMap();
+ for (; it != experiences.endMap(); ++it)
+ {
+ public_key.set(it->first);
+ mCache[public_key] = it->second;
+ }
- if(row.has(EXPIRES))
- {
- row[EXPIRES] = row[EXPIRES].asReal() + LLFrameTimer::getTotalSeconds();
- }
+ LL_DEBUGS("ExperienceCache") << "importFile() loaded " << mCache.size() << LL_ENDL;
+}
- if(row.has(EXPERIENCE_ID))
- {
- sPendingQueue.erase(row[EXPERIENCE_ID].asUUID());
- }
+void LLExperienceCache::exportFile(std::ostream& ostr) const
+{
+ LLSD experiences;
- //signal
- signal_map_t::iterator sig_it = sSignalMap.find(public_key);
- if (sig_it != sSignalMap.end())
- {
- callback_signal_t* signal = sig_it->second;
- (*signal)(experience);
+ cache_t::const_iterator it = mCache.begin();
+ for (; it != mCache.end(); ++it)
+ {
+ if (!it->second.has(EXPERIENCE_ID) || it->second[EXPERIENCE_ID].asUUID().isNull() ||
+ it->second.has("DoesNotExist") || (it->second.has(PROPERTIES) && it->second[PROPERTIES].asInteger() & PROPERTY_INVALID))
+ continue;
- sSignalMap.erase(public_key);
+ experiences[it->first.asString()] = it->second;
+ }
- delete signal;
- }
- }
+ LLSD data;
+ data["experiences"] = experiences;
- void initClass( )
- {
- }
+ LLSDSerialize::toPrettyXML(data, ostr);
+}
+
+// *TODO$: Rider: This method does not seem to be used... it may be useful in testing.
+void LLExperienceCache::bootstrap(const LLSD& legacyKeys, int initialExpiration)
+{
+ LLExperienceCacheImpl::mapKeys(legacyKeys);
+ LLSD::array_const_iterator it = legacyKeys.beginArray();
+ for (/**/; it != legacyKeys.endArray(); ++it)
+ {
+ LLSD experience = *it;
+ if (experience.has(EXPERIENCE_ID))
+ {
+ if (!experience.has(EXPIRES))
+ {
+ experience[EXPIRES] = initialExpiration;
+ }
+ processExperience(experience[EXPERIENCE_ID].asUUID(), experience);
+ }
+ else
+ {
+ LL_WARNS("ExperienceCache")
+ << "Skipping bootstrap entry which is missing " << EXPERIENCE_ID
+ << LL_ENDL;
+ }
+ }
+}
+
+LLUUID LLExperienceCache::getExperienceId(const LLUUID& private_key, bool null_if_not_found)
+{
+ if (private_key.isNull())
+ return LLUUID::null;
+
+ LLExperienceCacheImpl::KeyMap::const_iterator it = LLExperienceCacheImpl::privateToPublicKeyMap.find(private_key);
+ if (it == LLExperienceCacheImpl::privateToPublicKeyMap.end())
+ {
+ if (null_if_not_found)
+ {
+ return LLUUID::null;
+ }
+ return private_key;
+ }
+ LL_WARNS("LLExperience") << "converted private key " << private_key << " to experience_id " << it->second << LL_ENDL;
+ return it->second;
+}
- const cache_t& getCached()
+//=========================================================================
+void LLExperienceCache::processExperience(const LLUUID& public_key, const LLSD& experience)
+{
+ LL_INFOS("ExperienceCache") << "Processing experience \"" << experience[NAME] << "\" with key " << public_key.asString() << LL_ENDL;
+
+ mCache[public_key]=experience;
+ LLSD & row = mCache[public_key];
+
+ if(row.has(EXPIRES))
{
- return sCache;
+ row[EXPIRES] = row[EXPIRES].asReal() + LLFrameTimer::getTotalSeconds();
}
- void setMaximumLookups( int maximumLookups)
+ if(row.has(EXPERIENCE_ID))
{
- sMaximumLookups = maximumLookups;
+ mPendingQueue.erase(row[EXPERIENCE_ID].asUUID());
}
- void bootstrap(const LLSD& legacyKeys, int initialExpiration)
+ //signal
+ signal_map_t::iterator sig_it = mSignalMap.find(public_key);
+ if (sig_it != mSignalMap.end())
{
- mapKeys(legacyKeys);
- LLSD::array_const_iterator it = legacyKeys.beginArray();
- for(/**/; it != legacyKeys.endArray(); ++it)
- {
- LLSD experience = *it;
- if(experience.has(EXPERIENCE_ID))
- {
- if(!experience.has(EXPIRES))
- {
- experience[EXPIRES] = initialExpiration;
- }
- processExperience(experience[EXPERIENCE_ID].asUUID(), experience);
- }
- else
- {
- LL_WARNS("ExperienceCache")
- << "Skipping bootstrap entry which is missing " << EXPERIENCE_ID
- << LL_ENDL;
- }
- }
+ signal_ptr signal = sig_it->second;
+ (*signal)(experience);
+
+ mSignalMap.erase(public_key);
}
+}
+
+const LLExperienceCache::cache_t& LLExperienceCache::getCached()
+{
+ return mCache;
+}
+void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter, std::string url, RequestQueue_t requests)
+{
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest());
+
+ //LL_INFOS("requestExperiencesCoro") << "url: " << url << LL_ENDL;
+
+ LLSD result = httpAdapter->getAndSuspend(httpRequest, url);
+
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+ if (!status)
+ {
+ F64 now = LLFrameTimer::getTotalSeconds();
+
+ LLSD headers = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS];
+ // build dummy entries for the failed requests
+ for (RequestQueue_t::const_iterator it = requests.begin(); it != requests.end(); ++it)
+ {
+ LLSD exp = get(*it);
+ //leave the properties alone if we already have a cache entry for this xp
+ if (exp.isUndefined())
+ {
+ exp[PROPERTIES] = PROPERTY_INVALID;
+ }
+ exp[EXPIRES] = now + LLExperienceCacheImpl::getErrorRetryDeltaTime(status, headers);
+ exp[EXPERIENCE_ID] = *it;
+ exp["key_type"] = EXPERIENCE_ID;
+ exp["uuid"] = *it;
+ exp["error"] = (LLSD::Integer)status.getType();
+ exp[QUOTA] = DEFAULT_QUOTA;
+
+ processExperience(*it, exp);
+ }
+ return;
+ }
+
+ LLSD experiences = result["experience_keys"];
+
+ for (LLSD::array_const_iterator it = experiences.beginArray();
+ it != experiences.endArray(); ++it)
+ {
+ const LLSD& row = *it;
+ LLUUID public_key = row[EXPERIENCE_ID].asUUID();
+
+ LL_DEBUGS("ExperienceCache") << "Received result for " << public_key
+ << " display '" << row[LLExperienceCache::NAME].asString() << "'" << LL_ENDL;
+
+ processExperience(public_key, row);
+ }
+
+ LLSD error_ids = result["error_ids"];
+
+ for (LLSD::array_const_iterator errIt = error_ids.beginArray();
+ errIt != error_ids.endArray(); ++errIt)
+ {
+ LLUUID id = errIt->asUUID();
+ LLSD exp;
+ exp[EXPIRES] = DEFAULT_EXPIRATION;
+ exp[EXPERIENCE_ID] = id;
+ exp[PROPERTIES] = PROPERTY_INVALID;
+ exp[MISSING] = true;
+ exp[QUOTA] = DEFAULT_QUOTA;
+
+ processExperience(id, exp);
+ LL_WARNS("ExperienceCache") << "LLExperienceResponder::result() error result for " << id << LL_ENDL;
+ }
+}
- bool expirationFromCacheControl(LLSD headers, F64 *expires)
+
+void LLExperienceCache::requestExperiences()
+{
+ if (mCapability.empty())
+ {
+ LL_WARNS("ExperienceCache") << "Capability query method not set." << LL_ENDL;
+ return;
+ }
+
+ std::string urlBase = mCapability("GetExperienceInfo");
+ if (urlBase.empty())
+ {
+ LL_WARNS("ExperienceCache") << "No Experience capability." << LL_ENDL;
+ return;
+ }
+
+ if (*urlBase.rbegin() != '/')
+ {
+ urlBase += "/";
+ }
+ urlBase += "id/";
+
+
+ F64 now = LLFrameTimer::getTotalSeconds();
+
+ const U32 EXP_URL_SEND_THRESHOLD = 3000;
+ const U32 PAGE_SIZE = EXP_URL_SEND_THRESHOLD / UUID_STR_LENGTH;
+
+ std::ostringstream ostr;
+ ostr << urlBase << "?page_size=" << PAGE_SIZE;
+ RequestQueue_t requests;
+
+ while (!mRequestQueue.empty())
+ {
+ RequestQueue_t::iterator it = mRequestQueue.begin();
+ LLUUID key = (*it);
+ mRequestQueue.erase(it);
+ requests.insert(key);
+
+ ostr << "&" << EXPERIENCE_ID << "=" << key.asString();
+ mPendingQueue[key] = now;
+
+ if (mRequestQueue.empty() || (ostr.tellp() > EXP_URL_SEND_THRESHOLD))
+ { // request is placed in the coprocedure pool for the ExpCache cache. Throttling is done by the pool itself.
+ LLCoprocedureManager::instance().enqueueCoprocedure("ExpCache", "RequestExperiences",
+ boost::bind(&LLExperienceCache::requestExperiencesCoro, this, _1, ostr.str(), requests) );
+
+ ostr.str(std::string());
+ ostr << urlBase << "?page_size=" << PAGE_SIZE;
+ requests.clear();
+ }
+ }
+
+}
+
+
+bool LLExperienceCache::isRequestPending(const LLUUID& public_key)
+{
+ bool isPending = false;
+ const F64 PENDING_TIMEOUT_SECS = 5.0 * 60.0;
+
+ PendingQueue_t::const_iterator it = mPendingQueue.find(public_key);
+
+ if(it != mPendingQueue.end())
{
- // Allow the header to override the default
- LLSD cache_control_header = headers["cache-control"];
- if (cache_control_header.isDefined())
- {
- S32 max_age = 0;
- std::string cache_control = cache_control_header.asString();
- if (max_age_from_cache_control(cache_control, &max_age))
- {
- LL_WARNS("ExperienceCache")
- << "got EXPIRES from headers, max_age " << max_age
- << LL_ENDL;
- F64 now = LLFrameTimer::getTotalSeconds();
- *expires = now + (F64)max_age;
- return true;
- }
- }
- return false;
+ F64 expire_time = LLFrameTimer::getTotalSeconds() - PENDING_TIMEOUT_SECS;
+ isPending = (it->second > expire_time);
}
+ return isPending;
+}
+
+void LLExperienceCache::setCapabilityQuery(LLExperienceCache::CapabilityQuery_t queryfn)
+{
+ mCapability = queryfn;
+}
+
+
+void LLExperienceCache::idleCoro()
+{
+ const F32 SECS_BETWEEN_REQUESTS = 0.5f;
+ const F32 ERASE_EXPIRED_TIMEOUT = 60.f; // seconds
- static const std::string MAX_AGE("max-age");
- static const boost::char_separator<char> EQUALS_SEPARATOR("=");
- static const boost::char_separator<char> COMMA_SEPARATOR(",");
+ LL_INFOS("ExperienceCache") << "Launching Experience cache idle coro." << LL_ENDL;
+ do
+ {
+ llcoro::suspendUntilTimeout(SECS_BETWEEN_REQUESTS);
- bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age)
+ if (mEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT))
+ {
+ eraseExpired();
+ }
+
+ if (!mRequestQueue.empty())
+ {
+ requestExperiences();
+ }
+
+ } while (!mShutdown);
+
+ // The coroutine system will likely be shut down by the time we get to this point
+ // (or at least no further cycling will occur on it since the user has decided to quit.)
+}
+
+void LLExperienceCache::erase(const LLUUID& key)
+{
+ cache_t::iterator it = mCache.find(key);
+
+ if(it != mCache.end())
{
- // Split the string on "," to get a list of directives
- typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
- tokenizer directives(cache_control, COMMA_SEPARATOR);
+ mCache.erase(it);
+ }
+}
- tokenizer::iterator token_it = directives.begin();
- for ( ; token_it != directives.end(); ++token_it)
- {
- // Tokens may have leading or trailing whitespace
- std::string token = *token_it;
- LLStringUtil::trim(token);
+void LLExperienceCache::eraseExpired()
+{
+ F64 now = LLFrameTimer::getTotalSeconds();
+ cache_t::iterator it = mCache.begin();
+ while (it != mCache.end())
+ {
+ cache_t::iterator cur = it;
+ LLSD& exp = cur->second;
+ ++it;
- if (token.compare(0, MAX_AGE.size(), MAX_AGE) == 0)
+ //LL_INFOS("ExperienceCache") << "Testing experience \"" << exp[NAME] << "\" with exp time " << exp[EXPIRES].asReal() << "(now = " << now << ")" << LL_ENDL;
+
+ if(exp.has(EXPIRES) && exp[EXPIRES].asReal() < now)
+ {
+ if(!exp.has(EXPERIENCE_ID))
{
- // ...this token starts with max-age, so let's chop it up by "="
- tokenizer subtokens(token, EQUALS_SEPARATOR);
- tokenizer::iterator subtoken_it = subtokens.begin();
-
- // Must have a token
- if (subtoken_it == subtokens.end()) return false;
- std::string subtoken = *subtoken_it;
-
- // Must exactly equal "max-age"
- LLStringUtil::trim(subtoken);
- if (subtoken != MAX_AGE) return false;
-
- // Must have another token
- ++subtoken_it;
- if (subtoken_it == subtokens.end()) return false;
- subtoken = *subtoken_it;
-
- // Must be a valid integer
- // *NOTE: atoi() returns 0 for invalid values, so we have to
- // check the string first.
- // *TODO: Do servers ever send "0000" for zero? We don't handle it
- LLStringUtil::trim(subtoken);
- if (subtoken == "0")
+ LL_WARNS("ExperienceCache") << "Removing experience with no id " << LL_ENDL ;
+ mCache.erase(cur);
+ }
+ else
+ {
+ LLUUID id = exp[EXPERIENCE_ID].asUUID();
+ LLUUID private_key = exp.has(LLExperienceCache::PRIVATE_KEY) ? exp[LLExperienceCache::PRIVATE_KEY].asUUID():LLUUID::null;
+ if(private_key.notNull() || !exp.has("DoesNotExist"))
{
- *max_age = 0;
- return true;
+ fetch(id, true);
}
- S32 val = atoi( subtoken.c_str() );
- if (val > 0 && val < S32_MAX)
+ else
{
- *max_age = val;
- return true;
+ LL_WARNS("ExperienceCache") << "Removing invalid experience " << id << LL_ENDL ;
+ mCache.erase(cur);
}
- return false;
}
}
- return false;
}
-
-
- void importFile(std::istream& istr)
+}
+
+bool LLExperienceCache::fetch(const LLUUID& key, bool refresh/* = true*/)
+{
+ if(!key.isNull() && !isRequestPending(key) && (refresh || mCache.find(key)==mCache.end()))
{
- LLSD data;
- S32 parse_count = LLSDSerialize::fromXMLDocument(data, istr);
- if(parse_count < 1) return;
+ LL_DEBUGS("ExperienceCache") << " queue request for " << EXPERIENCE_ID << " " << key << LL_ENDL;
- LLSD experiences = data["experiences"];
-
- LLUUID public_key;
- LLSD::map_const_iterator it = experiences.beginMap();
- for(; it != experiences.endMap() ; ++it)
- {
- public_key.set(it->first);
- sCache[public_key]=it->second;
- }
-
- LL_DEBUGS("ExperienceCache") << "importFile() loaded " << sCache.size() << LL_ENDL;
+ mRequestQueue.insert(key);
+ return true;
}
+ return false;
+}
- void exportFile(std::ostream& ostr)
+void LLExperienceCache::insert(const LLSD& experience_data)
+{
+ if(experience_data.has(EXPERIENCE_ID))
{
- LLSD experiences;
-
- cache_t::const_iterator it =sCache.begin();
- for( ; it != sCache.end() ; ++it)
- {
- if(!it->second.has(EXPERIENCE_ID) || it->second[EXPERIENCE_ID].asUUID().isNull() ||
- it->second.has("DoesNotExist") || (it->second.has(PROPERTIES) && it->second[PROPERTIES].asInteger() & PROPERTY_INVALID))
- continue;
-
- experiences[it->first.asString()] = it->second;
- }
+ processExperience(experience_data[EXPERIENCE_ID].asUUID(), experience_data);
+ }
+ else
+ {
+ LL_WARNS("ExperienceCache") << ": Ignoring cache insert of experience which is missing " << EXPERIENCE_ID << LL_ENDL;
+ }
+}
- LLSD data;
- data["experiences"] = experiences;
+const LLSD& LLExperienceCache::get(const LLUUID& key)
+{
+ static const LLSD empty;
+
+ if(key.isNull())
+ return empty;
+ cache_t::const_iterator it = mCache.find(key);
- LLSDSerialize::toPrettyXML(data, ostr);
+ if (it != mCache.end())
+ {
+ return it->second;
}
+ fetch(key);
- class LLExperienceResponder : public LLHTTPClient::Responder
- {
- public:
- LLExperienceResponder(const ask_queue_t& keys)
- :mKeys(keys)
- {
+ return empty;
+}
- }
+void LLExperienceCache::get(const LLUUID& key, LLExperienceCache::ExperienceGetFn_t slot)
+{
+ if(key.isNull())
+ return;
- /*virtual*/ void httpCompleted()
- {
- LLSD experiences = getContent()["experience_keys"];
- LLSD::array_const_iterator it = experiences.beginArray();
- for( /**/ ; it != experiences.endArray(); ++it)
- {
- const LLSD& row = *it;
- LLUUID public_key = row[EXPERIENCE_ID].asUUID();
+ cache_t::const_iterator it = mCache.find(key);
+ if (it != mCache.end())
+ {
+ // ...name already exists in cache, fire callback now
+ callback_signal_t signal;
+ signal.connect(slot);
+
+ signal(it->second);
+ return;
+ }
+ fetch(key);
- LL_DEBUGS("ExperienceCache") << "Received result for " << public_key
- << " display '" << row[LLExperienceCache::NAME].asString() << "'" << LL_ENDL ;
+ signal_ptr signal = signal_ptr(new callback_signal_t());
+
+ std::pair<signal_map_t::iterator, bool> result = mSignalMap.insert(signal_map_t::value_type(key, signal));
+ if (!result.second)
+ signal = (*result.first).second;
+ signal->connect(slot);
+}
- processExperience(public_key, row);
- }
+//=========================================================================
+void LLExperienceCache::fetchAssociatedExperience(const LLUUID& objectId, const LLUUID& itemId, ExperienceGetFn_t fn)
+{
+ if (mCapability.empty())
+ {
+ LL_WARNS("ExperienceCache") << "Capability query method not set." << LL_ENDL;
+ return;
+ }
+
+ LLCoprocedureManager::instance().enqueueCoprocedure("ExpCache", "Fetch Associated",
+ boost::bind(&LLExperienceCache::fetchAssociatedExperienceCoro, this, _1, objectId, itemId, fn));
+}
- LLSD error_ids = getContent()["error_ids"];
- LLSD::array_const_iterator errIt = error_ids.beginArray();
- for( /**/ ; errIt != error_ids.endArray() ; ++errIt )
- {
- LLUUID id = errIt->asUUID();
- LLSD exp;
- exp[EXPIRES]=DEFAULT_EXPIRATION;
- exp[EXPERIENCE_ID] = id;
- exp[PROPERTIES]=PROPERTY_INVALID;
- exp[MISSING]=true;
- exp[QUOTA] = DEFAULT_QUOTA;
-
- processExperience(id, exp);
- LL_WARNS("ExperienceCache") << "LLExperienceResponder::result() error result for " << id << LL_ENDL ;
- }
+void LLExperienceCache::fetchAssociatedExperienceCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter, LLUUID objectId, LLUUID itemId, ExperienceGetFn_t fn)
+{
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest());
+ std::string url = mCapability("GetMetadata");
+
+ if (url.empty())
+ {
+ LL_WARNS("ExperienceCache") << "No Metadata capability." << LL_ENDL;
+ return;
+ }
+
+ LLSD fields;
+ fields.append("experience");
+ LLSD data;
+ data["object-id"] = objectId;
+ data["item-id"] = itemId;
+ data["fields"] = fields;
+
+ LLSD result = httpAdapter->postAndSuspend(httpRequest, url, data);
+
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+ if ((!status) || (!result.has("experience")))
+ {
+ LLSD failure;
+ if (!status)
+ {
+ failure["error"] = (LLSD::Integer)status.getType();
+ failure["message"] = status.getMessage();
+ }
+ else
+ {
+ failure["error"] = -1;
+ failure["message"] = "no experience";
+ }
+ if (fn && !fn.empty())
+ fn(failure);
+ return;
+ }
+
+ LLUUID expId = result["experience"].asUUID();
+ get(expId, fn);
+}
- LL_DEBUGS("ExperienceCache") << sCache.size() << " cached experiences" << LL_ENDL;
- }
+//-------------------------------------------------------------------------
+void LLExperienceCache::findExperienceByName(const std::string text, int page, ExperienceGetFn_t fn)
+{
+ if (mCapability.empty())
+ {
+ LL_WARNS("ExperienceCache") << "Capability query method not set." << LL_ENDL;
+ return;
+ }
+
+ LLCoprocedureManager::instance().enqueueCoprocedure("ExpCache", "Search Name",
+ boost::bind(&LLExperienceCache::findExperienceByNameCoro, this, _1, text, page, fn));
+}
- /*virtual*/ void httpFailure()
- {
- LL_WARNS("ExperienceCache") << "Request failed "<<getStatus()<<" "<<getReason()<< LL_ENDL;
- // We're going to construct a dummy record and cache it for a while,
- // either briefly for a 503 Service Unavailable, or longer for other
- // errors.
- F64 retry_timestamp = errorRetryTimestamp(getStatus());
-
-
- // Add dummy records for all agent IDs in this request
- ask_queue_t::const_iterator it = mKeys.begin();
- for ( ; it != mKeys.end(); ++it)
- {
+void LLExperienceCache::findExperienceByNameCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter, std::string text, int page, ExperienceGetFn_t fn)
+{
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest());
+ std::ostringstream url;
- LLSD exp = get(it->first);
- //leave the properties alone if we already have a cache entry for this xp
- if(exp.isUndefined())
- {
- exp[PROPERTIES]=PROPERTY_INVALID;
- }
- exp[EXPIRES]=retry_timestamp;
- exp[EXPERIENCE_ID] = it->first;
- exp["key_type"] = it->second;
- exp["uuid"] = it->first;
- exp["error"] = (LLSD::Integer)getStatus();
- exp[QUOTA] = DEFAULT_QUOTA;
-
- LLExperienceCache::processExperience(it->first, exp);
- }
- }
+ url << mCapability("FindExperienceByName") << "?page=" << page << "&page_size=" << SEARCH_PAGE_SIZE << "&query=" << LLURI::escape(text);
- // Return time to retry a request that generated an error, based on
- // error type and headers. Return value is seconds-since-epoch.
- F64 errorRetryTimestamp(S32 status)
- {
+ LLSD result = httpAdapter->getAndSuspend(httpRequest, url.str());
- // Retry-After takes priority
- LLSD retry_after = getResponseHeaders()["retry-after"];
- if (retry_after.isDefined())
- {
- // We only support the delta-seconds type
- S32 delta_seconds = retry_after.asInteger();
- if (delta_seconds > 0)
- {
- // ...valid delta-seconds
- return F64(delta_seconds);
- }
- }
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
- // If no Retry-After, look for Cache-Control max-age
- F64 expires = 0.0;
- if (LLExperienceCache::expirationFromCacheControl(getResponseHeaders(), &expires))
- {
- return expires;
- }
+ if (!status)
+ {
+ fn(LLSD());
+ return;
+ }
- // No information in header, make a guess
- if (status == 503)
- {
- // ...service unavailable, retry soon
- const F64 SERVICE_UNAVAILABLE_DELAY = 600.0; // 10 min
- return SERVICE_UNAVAILABLE_DELAY;
- }
- else if (status == 499)
- {
- // ...we were probably too busy, retry quickly
- const F64 BUSY_DELAY = 10.0; // 10 seconds
- return BUSY_DELAY;
+ result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
- }
- else
- {
- // ...other unexpected error
- const F64 DEFAULT_DELAY = 3600.0; // 1 hour
- return DEFAULT_DELAY;
- }
- }
+ const LLSD& experiences = result["experience_keys"];
+ for (LLSD::array_const_iterator it = experiences.beginArray(); it != experiences.endArray(); ++it)
+ {
+ insert(*it);
+ }
- private:
- ask_queue_t mKeys;
- };
+ fn(result);
+}
- void requestExperiences()
- {
- if(sAskQueue.empty() || sLookupURL.empty())
- return;
+//-------------------------------------------------------------------------
+void LLExperienceCache::getGroupExperiences(const LLUUID &groupId, ExperienceGetFn_t fn)
+{
+ if (mCapability.empty())
+ {
+ LL_WARNS("ExperienceCache") << "Capability query method not set." << LL_ENDL;
+ return;
+ }
+
+ LLCoprocedureManager::instance().enqueueCoprocedure("ExpCache", "Group Experiences",
+ boost::bind(&LLExperienceCache::getGroupExperiencesCoro, this, _1, groupId, fn));
+}
- F64 now = LLFrameTimer::getTotalSeconds();
+void LLExperienceCache::getGroupExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter, LLUUID groupId, ExperienceGetFn_t fn)
+{
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest());
- const U32 EXP_URL_SEND_THRESHOLD = 3000;
- const U32 PAGE_SIZE = EXP_URL_SEND_THRESHOLD/UUID_STR_LENGTH;
+ // search for experiences owned by the current group
+ std::string url = mCapability("GroupExperiences");
+ if (url.empty())
+ {
+ LL_WARNS("ExperienceCache") << "No Group Experiences capability" << LL_ENDL;
+ return;
+ }
- std::ostringstream ostr;
+ url += "?" + groupId.asString();
- ask_queue_t keys;
+ LLSD result = httpAdapter->getAndSuspend(httpRequest, url);
- ostr << sLookupURL << "?page_size=" << PAGE_SIZE;
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+ if (!status)
+ {
+ fn(LLSD());
+ return;
+ }
- int request_count = 0;
- while(!sAskQueue.empty() && request_count < sMaximumLookups)
- {
- ask_queue_t::iterator it = sAskQueue.begin();
- const LLUUID& key = it->first;
- const std::string& key_type = it->second;
+ const LLSD& experienceIds = result["experience_ids"];
+ fn(experienceIds);
+}
- ostr << '&' << key_type << '=' << key.asString() ;
-
- keys[key]=key_type;
- request_count++;
+//-------------------------------------------------------------------------
+void LLExperienceCache::getRegionExperiences(CapabilityQuery_t regioncaps, ExperienceGetFn_t fn)
+{
+ LLCoprocedureManager::instance().enqueueCoprocedure("ExpCache", "Region Experiences",
+ boost::bind(&LLExperienceCache::regionExperiencesCoro, this, _1, regioncaps, false, LLSD(), fn));
+}
- sPendingQueue[key] = now;
-
- if(ostr.tellp() > EXP_URL_SEND_THRESHOLD)
- {
- LL_DEBUGS("ExperienceCache") << "requestExperiences() query: " << ostr.str() << LL_ENDL;
- LLHTTPClient::get(ostr.str(), new LLExperienceResponder(keys));
- ostr.clear();
- ostr.str(sLookupURL);
- ostr << "?page_size=" << PAGE_SIZE;
- keys.clear();
- }
- sAskQueue.erase(it);
- }
+void LLExperienceCache::setRegionExperiences(CapabilityQuery_t regioncaps, const LLSD &experiences, ExperienceGetFn_t fn)
+{
+ LLCoprocedureManager::instance().enqueueCoprocedure("ExpCache", "Region Experiences",
+ boost::bind(&LLExperienceCache::regionExperiencesCoro, this, _1, regioncaps, true, experiences, fn));
+}
- if(ostr.tellp() > sLookupURL.size())
- {
- LL_DEBUGS("ExperienceCache") << "requestExperiences() query 2: " << ostr.str() << LL_ENDL;
- LLHTTPClient::get(ostr.str(), new LLExperienceResponder(keys));
- }
- }
+void LLExperienceCache::regionExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter,
+ CapabilityQuery_t regioncaps, bool update, LLSD experiences, ExperienceGetFn_t fn)
+{
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest());
+
+ // search for experiences owned by the current group
+ std::string url = regioncaps("RegionExperiences");
+ if (url.empty())
+ {
+ LL_WARNS("ExperienceCache") << "No Region Experiences capability" << LL_ENDL;
+ return;
+ }
+
+ LLSD result;
+ if (update)
+ result = httpAdapter->postAndSuspend(httpRequest, url, experiences);
+ else
+ result = httpAdapter->getAndSuspend(httpRequest, url);
+
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+ if (!status)
+ {
+// fn(LLSD());
+ return;
+ }
+
+ result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
+ fn(result);
- bool isRequestPending(const LLUUID& public_key)
- {
- bool isPending = false;
- const F64 PENDING_TIMEOUT_SECS = 5.0 * 60.0;
+}
- pending_queue_t::const_iterator it = sPendingQueue.find(public_key);
+//-------------------------------------------------------------------------
+void LLExperienceCache::getExperiencePermission(const LLUUID &experienceId, ExperienceGetFn_t fn)
+{
+ if (mCapability.empty())
+ {
+ LL_WARNS("ExperienceCache") << "Capability query method not set." << LL_ENDL;
+ return;
+ }
+
+ std::string url = mCapability("ExperiencePreferences") + "?" + experienceId.asString();
+
+ permissionInvoker_fn invoker(boost::bind(
+ // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload.
+ static_cast<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::*)(LLCore::HttpRequest::ptr_t, const std::string &, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t)>
+ //----
+ // _1 -> httpAdapter
+ // _2 -> httpRequest
+ // _3 -> url
+ (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, LLCore::HttpOptions::ptr_t(), LLCore::HttpHeaders::ptr_t()));
+
+
+ LLCoprocedureManager::instance().enqueueCoprocedure("ExpCache", "Preferences Set",
+ boost::bind(&LLExperienceCache::experiencePermissionCoro, this, _1, invoker, url, fn));
+}
- if(it != sPendingQueue.end())
- {
- F64 expire_time = LLFrameTimer::getTotalSeconds() - PENDING_TIMEOUT_SECS;
- isPending = (it->second > expire_time);
- }
+void LLExperienceCache::setExperiencePermission(const LLUUID &experienceId, const std::string &permission, ExperienceGetFn_t fn)
+{
+ if (mCapability.empty())
+ {
+ LL_WARNS("ExperienceCache") << "Capability query method not set." << LL_ENDL;
+ return;
+ }
+
+ std::string url = mCapability("ExperiencePreferences");
+ if (url.empty())
+ return;
+ LLSD permData;
+ LLSD data;
+ permData["permission"] = permission;
+ data[experienceId.asString()] = permData;
+
+ permissionInvoker_fn invoker(boost::bind(
+ // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload.
+ static_cast<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::*)(LLCore::HttpRequest::ptr_t, const std::string &, const LLSD &, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t)>
+ //----
+ // _1 -> httpAdapter
+ // _2 -> httpRequest
+ // _3 -> url
+ (&LLCoreHttpUtil::HttpCoroutineAdapter::putAndSuspend), _1, _2, _3, data, LLCore::HttpOptions::ptr_t(), LLCore::HttpHeaders::ptr_t()));
+
+
+ LLCoprocedureManager::instance().enqueueCoprocedure("ExpCache", "Preferences Set",
+ boost::bind(&LLExperienceCache::experiencePermissionCoro, this, _1, invoker, url, fn));
+}
- return isPending;
- }
+void LLExperienceCache::forgetExperiencePermission(const LLUUID &experienceId, ExperienceGetFn_t fn)
+{
+ if (mCapability.empty())
+ {
+ LL_WARNS("ExperienceCache") << "Capability query method not set." << LL_ENDL;
+ return;
+ }
+ std::string url = mCapability("ExperiencePreferences") + "?" + experienceId.asString();
- void setLookupURL( const std::string& lookup_url )
- {
- sLookupURL = lookup_url;
- if(!sLookupURL.empty())
- {
- sLookupURL += "id/";
- }
- }
- bool hasLookupURL()
- {
- return !sLookupURL.empty();
- }
+ permissionInvoker_fn invoker(boost::bind(
+ // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload.
+ static_cast<LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::*)(LLCore::HttpRequest::ptr_t, const std::string &, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t)>
+ //----
+ // _1 -> httpAdapter
+ // _2 -> httpRequest
+ // _3 -> url
+ (&LLCoreHttpUtil::HttpCoroutineAdapter::deleteAndSuspend), _1, _2, _3, LLCore::HttpOptions::ptr_t(), LLCore::HttpHeaders::ptr_t()));
- void idle()
- {
- const F32 SECS_BETWEEN_REQUESTS = 0.1f;
- if (!sRequestTimer.checkExpirationAndReset(SECS_BETWEEN_REQUESTS))
- {
- return;
- }
+ LLCoprocedureManager::instance().enqueueCoprocedure("ExpCache", "Preferences Set",
+ boost::bind(&LLExperienceCache::experiencePermissionCoro, this, _1, invoker, url, fn));
+}
- // Must be large relative to above
- const F32 ERASE_EXPIRED_TIMEOUT = 60.f; // seconds
- if (sEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT))
- {
- eraseExpired();
- }
+void LLExperienceCache::experiencePermissionCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter, permissionInvoker_fn invokerfn, std::string url, ExperienceGetFn_t fn)
+{
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest());
+ // search for experiences owned by the current group
- if(!sAskQueue.empty())
- {
- requestExperiences();
- }
- }
+ LLSD result = invokerfn(httpAdapter, httpRequest, url);
- void erase( const LLUUID& key )
- {
- cache_t::iterator it = sCache.find(key);
-
- if(it != sCache.end())
- {
- sCache.erase(it);
- }
- }
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+ result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
- void eraseExpired()
- {
- F64 now = LLFrameTimer::getTotalSeconds();
- cache_t::iterator it = sCache.begin();
- while (it != sCache.end())
- {
- cache_t::iterator cur = it;
- LLSD& exp = cur->second;
- ++it;
+ if (status)
+ {
+ fn(result);
+ }
+}
- if(exp.has(EXPIRES) && exp[EXPIRES].asReal() < now)
- {
- if(!exp.has(EXPERIENCE_ID))
- {
- LL_WARNS("ExperienceCache") << "Removing experience with no id " << LL_ENDL ;
- sCache.erase(cur);
- }
- else
- {
- LLUUID id = exp[EXPERIENCE_ID].asUUID();
- LLUUID private_key = exp.has(LLExperienceCache::PRIVATE_KEY) ? exp[LLExperienceCache::PRIVATE_KEY].asUUID():LLUUID::null;
- if(private_key.notNull() || !exp.has("DoesNotExist"))
- {
- fetch(id, true);
- }
- else
- {
- LL_WARNS("ExperienceCache") << "Removing invalid experience " << id << LL_ENDL ;
- sCache.erase(cur);
- }
- }
- }
- }
- }
+//-------------------------------------------------------------------------
+void LLExperienceCache::getExperienceAdmin(const LLUUID &experienceId, ExperienceGetFn_t fn)
+{
+ if (mCapability.empty())
+ {
+ LL_WARNS("ExperienceCache") << "Capability query method not set." << LL_ENDL;
+ return;
+ }
+
+ LLCoprocedureManager::instance().enqueueCoprocedure("ExpCache", "IsAdmin",
+ boost::bind(&LLExperienceCache::getExperienceAdminCoro, this, _1, experienceId, fn));
+}
-
- bool fetch( const LLUUID& key, bool refresh/* = true*/ )
- {
- if(!key.isNull() && !isRequestPending(key) && (refresh || sCache.find(key)==sCache.end()))
- {
- LL_DEBUGS("ExperienceCache") << " queue request for " << EXPERIENCE_ID << " " << key << LL_ENDL ;
- sAskQueue[key]=EXPERIENCE_ID;
+void LLExperienceCache::getExperienceAdminCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter, LLUUID experienceId, ExperienceGetFn_t fn)
+{
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest());
- return true;
- }
- return false;
- }
+ std::string url = mCapability("IsExperienceAdmin");
+ if (url.empty())
+ {
+ LL_WARNS("ExperienceCache") << "No Region Experiences capability" << LL_ENDL;
+ return;
+ }
+ url += "?experience_id=" + experienceId.asString();
- void insert(const LLSD& experience_data )
- {
- if(experience_data.has(EXPERIENCE_ID))
- {
- processExperience(experience_data[EXPERIENCE_ID].asUUID(), experience_data);
- }
- else
- {
- LL_WARNS("ExperienceCache") << ": Ignoring cache insert of experience which is missing " << EXPERIENCE_ID << LL_ENDL;
- }
- }
- static LLSD empty;
- const LLSD& get(const LLUUID& key)
- {
- if(key.isNull()) return empty;
- cache_t::const_iterator it = sCache.find(key);
+ LLSD result = httpAdapter->getAndSuspend(httpRequest, url);
+// LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+// LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
- if (it != sCache.end())
- {
- return it->second;
- }
+ fn(result);
+}
- fetch(key);
+//-------------------------------------------------------------------------
+void LLExperienceCache::updateExperience(LLSD updateData, ExperienceGetFn_t fn)
+{
+ if (mCapability.empty())
+ {
+ LL_WARNS("ExperienceCache") << "Capability query method not set." << LL_ENDL;
+ return;
+ }
+
+ LLCoprocedureManager::instance().enqueueCoprocedure("ExpCache", "IsAdmin",
+ boost::bind(&LLExperienceCache::updateExperienceCoro, this, _1, updateData, fn));
+}
- return empty;
- }
- void get( const LLUUID& key, callback_slot_t slot )
- {
- if(key.isNull()) return;
+void LLExperienceCache::updateExperienceCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter, LLSD updateData, ExperienceGetFn_t fn)
+{
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest());
- cache_t::const_iterator it = sCache.find(key);
- if (it != sCache.end())
- {
- // ...name already exists in cache, fire callback now
- callback_signal_t signal;
- signal.connect(slot);
-
- signal(it->second);
- return;
- }
+ std::string url = mCapability("UpdateExperience");
+ if (url.empty())
+ {
+ LL_WARNS("ExperienceCache") << "No Region Experiences capability" << LL_ENDL;
+ return;
+ }
- fetch(key);
+ updateData.erase(LLExperienceCache::QUOTA);
+ updateData.erase(LLExperienceCache::EXPIRES);
+ updateData.erase(LLExperienceCache::AGENT_ID);
- // always store additional callback, even if request is pending
- signal_map_t::iterator sig_it = sSignalMap.find(key);
- if (sig_it == sSignalMap.end())
- {
- // ...new callback for this id
- callback_signal_t* signal = new callback_signal_t();
- signal->connect(slot);
- sSignalMap[key] = signal;
- }
- else
- {
- // ...existing callback, bind additional slot
- callback_signal_t* signal = sig_it->second;
- signal->connect(slot);
- }
- }
+ LLSD result = httpAdapter->postAndSuspend(httpRequest, url, updateData);
+ fn(result);
}
-
-void LLExperienceCache::mapKeys( const LLSD& legacyKeys )
+//=========================================================================
+void LLExperienceCacheImpl::mapKeys(const LLSD& legacyKeys)
{
LLSD::array_const_iterator exp = legacyKeys.beginArray();
- for(/**/ ; exp != legacyKeys.endArray() ; ++exp)
+ for (/**/; exp != legacyKeys.endArray(); ++exp)
{
- if(exp->has(LLExperienceCache::EXPERIENCE_ID) && exp->has(LLExperienceCache::PRIVATE_KEY))
+ if (exp->has(LLExperienceCacheImpl::EXPERIENCE_ID) && exp->has(LLExperienceCacheImpl::PRIVATE_KEY))
{
- privateToPublicKeyMap[(*exp)[LLExperienceCache::PRIVATE_KEY].asUUID()]=(*exp)[LLExperienceCache::EXPERIENCE_ID].asUUID();
+ LLExperienceCacheImpl::privateToPublicKeyMap[(*exp)[LLExperienceCacheImpl::PRIVATE_KEY].asUUID()] =
+ (*exp)[LLExperienceCacheImpl::EXPERIENCE_ID].asUUID();
}
}
}
-
-LLUUID LLExperienceCache::getExperienceId(const LLUUID& private_key, bool null_if_not_found)
+// Return time to retry a request that generated an error, based on
+// error type and headers. Return value is seconds-since-epoch.
+F64 LLExperienceCacheImpl::getErrorRetryDeltaTime(S32 status, LLSD headers)
{
- if (private_key.isNull())
- return LLUUID::null;
- KeyMap::const_iterator it=privateToPublicKeyMap.find(private_key);
- if(it == privateToPublicKeyMap.end())
+ // Retry-After takes priority
+ LLSD retry_after = headers["retry-after"];
+ if (retry_after.isDefined())
+ {
+ // We only support the delta-seconds type
+ S32 delta_seconds = retry_after.asInteger();
+ if (delta_seconds > 0)
+ {
+ // ...valid delta-seconds
+ return F64(delta_seconds);
+ }
+ }
+
+ // If no Retry-After, look for Cache-Control max-age
+ // Allow the header to override the default
+ LLSD cache_control_header = headers["cache-control"];
+ if (cache_control_header.isDefined())
+ {
+ S32 max_age = 0;
+ std::string cache_control = cache_control_header.asString();
+ if (LLExperienceCacheImpl::maxAgeFromCacheControl(cache_control, &max_age))
+ {
+ LL_WARNS("ExperienceCache")
+ << "got EXPIRES from headers, max_age " << max_age
+ << LL_ENDL;
+ return (F64)max_age;
+ }
+ }
+
+ // No information in header, make a guess
+ if (status == 503)
+ {
+ // ...service unavailable, retry soon
+ const F64 SERVICE_UNAVAILABLE_DELAY = 600.0; // 10 min
+ return SERVICE_UNAVAILABLE_DELAY;
+ }
+ else if (status == 499)
+ {
+ // ...we were probably too busy, retry quickly
+ const F64 BUSY_DELAY = 10.0; // 10 seconds
+ return BUSY_DELAY;
+
+ }
+ else
+ {
+ // ...other unexpected error
+ const F64 DEFAULT_DELAY = 3600.0; // 1 hour
+ return DEFAULT_DELAY;
+ }
+}
+
+bool LLExperienceCacheImpl::maxAgeFromCacheControl(const std::string& cache_control, S32 *max_age)
+{
+ // Split the string on "," to get a list of directives
+ typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
+ tokenizer directives(cache_control, COMMA_SEPARATOR);
+
+ tokenizer::iterator token_it = directives.begin();
+ for ( ; token_it != directives.end(); ++token_it)
{
- if(null_if_not_found)
+ // Tokens may have leading or trailing whitespace
+ std::string token = *token_it;
+ LLStringUtil::trim(token);
+
+ if (token.compare(0, MAX_AGE.size(), MAX_AGE) == 0)
{
- return LLUUID::null;
+ // ...this token starts with max-age, so let's chop it up by "="
+ tokenizer subtokens(token, EQUALS_SEPARATOR);
+ tokenizer::iterator subtoken_it = subtokens.begin();
+
+ // Must have a token
+ if (subtoken_it == subtokens.end()) return false;
+ std::string subtoken = *subtoken_it;
+
+ // Must exactly equal "max-age"
+ LLStringUtil::trim(subtoken);
+ if (subtoken != MAX_AGE) return false;
+
+ // Must have another token
+ ++subtoken_it;
+ if (subtoken_it == subtokens.end()) return false;
+ subtoken = *subtoken_it;
+
+ // Must be a valid integer
+ // *NOTE: atoi() returns 0 for invalid values, so we have to
+ // check the string first.
+ // *TODO: Do servers ever send "0000" for zero? We don't handle it
+ LLStringUtil::trim(subtoken);
+ if (subtoken == "0")
+ {
+ *max_age = 0;
+ return true;
+ }
+ S32 val = atoi( subtoken.c_str() );
+ if (val > 0 && val < S32_MAX)
+ {
+ *max_age = val;
+ return true;
+ }
+ return false;
}
- return private_key;
}
- LL_WARNS("LLExperience") << "converted private key " << private_key << " to experience_id " << it->second << LL_ENDL;
- return it->second;
+ return false;
}
+
+
+
+