diff options
Diffstat (limited to 'indra/llmessage/llexperiencecache.cpp')
| -rw-r--r-- | indra/llmessage/llexperiencecache.cpp | 1332 | 
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;  } + + + +  | 
