diff options
Diffstat (limited to 'indra/newview/llmediadataclient.cpp')
| -rw-r--r-- | indra/newview/llmediadataclient.cpp | 2254 | 
1 files changed, 1127 insertions, 1127 deletions
diff --git a/indra/newview/llmediadataclient.cpp b/indra/newview/llmediadataclient.cpp index 77c1927e61..f08d2dccb4 100644 --- a/indra/newview/llmediadataclient.cpp +++ b/indra/newview/llmediadataclient.cpp @@ -1,1127 +1,1127 @@ -/**  - * @file llmediadataclient.cpp - * @brief class for queueing up requests for media data - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - *  - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - *  - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU - * Lesser General Public License for more details. - *  - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA - *  - * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llmediadataclient.h" -#include "llviewercontrol.h" - -#if LL_MSVC -// disable boost::lexical_cast warning -#pragma warning (disable:4702) -#endif - -#include <algorithm> -#include <boost/lexical_cast.hpp> - -#include "llhttpconstants.h" -#include "llsdutil.h" -#include "llmediaentry.h" -#include "lltextureentry.h" -#include "llviewerregion.h" -#include "llcorehttputil.h" - -// -// When making a request -// - obtain the "overall interest score" of the object.	  -//	 This would be the sum of the impls' interest scores. -// - put the request onto a queue sorted by this score  -//	 (highest score at the front of the queue) -// - On a timer, once a second, pull off the head of the queue and send  -//	 the request. -// - Any request that gets a 503 still goes through the retry logic -// - -/*************************************************************************************************************** -	What's up with this queueing code? - -	First, a bit of background: - -	Media on a prim was added into the system in the Viewer 2.0 timeframe.  In order to avoid changing the  -	network format of objects, an unused field in the object (the "MediaURL" string) was repurposed to  -	indicate that the object had media data, and also hold a sequence number and the UUID of the agent  -	who last updated the data.  The actual media data for objects is accessed via the "ObjectMedia" capability.   -	Due to concerns about sim performance, requests to this capability are rate-limited to 5 requests every  -	5 seconds per agent. - -	The initial implementation of LLMediaDataClient used a single queue to manage requests to the "ObjectMedia" cap.   -	Requests to the cap were queued so that objects closer to the avatar were loaded in first, since they were most  -	likely to be the ones the media performance manager would load. - -	This worked in some cases, but we found that it was possible for a scripted object that constantly updated its  -	media data to starve other objects, since the same queue contained both requests to load previously unseen media  -	data and requests to fetch media data in response to object updates. - -	The solution for this we came up with was to have two queues.  The sorted queue contains requests to fetch media  -	data for objects that don't have it yet, and the round-robin queue contains requests to update media data for  -	objects that have already completed their initial load.  When both queues are non-empty, the code ping-pongs  -	between them so that updates can't completely block initial load-in. -**************************************************************************************************************/ - -// -// Forward decls -// -const F32 LLMediaDataClient::QUEUE_TIMER_DELAY = 1.0; // seconds(s) -const F32 LLMediaDataClient::UNAVAILABLE_RETRY_TIMER_DELAY = 5.0; // secs -const U32 LLMediaDataClient::MAX_RETRIES = 4; -const U32 LLMediaDataClient::MAX_SORTED_QUEUE_SIZE = 10000; -const U32 LLMediaDataClient::MAX_ROUND_ROBIN_QUEUE_SIZE = 10000; - -// << operators -std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q); -std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &q); - - -//========================================================================= -/// Uniary Predicate for matching requests in collections by either the request -/// or by UUID -///  -class PredicateMatchRequest -{ -public: -    PredicateMatchRequest(const LLMediaDataClient::Request::ptr_t &request, LLMediaDataClient::Request::Type matchType = LLMediaDataClient::Request::ANY); -    PredicateMatchRequest(const LLUUID &id, LLMediaDataClient::Request::Type matchType = LLMediaDataClient::Request::ANY); - -    PredicateMatchRequest(const PredicateMatchRequest &other); - -    bool operator()(const LLMediaDataClient::Request::ptr_t &test) const; - -private: -    LLMediaDataClient::Request::ptr_t mRequest; -    LLMediaDataClient::Request::Type  mMatchType; -    LLUUID                            mId; -}; - - -PredicateMatchRequest::PredicateMatchRequest(const LLMediaDataClient::Request::ptr_t &request, LLMediaDataClient::Request::Type matchType) : -    mRequest(request), -    mMatchType(matchType), -    mId() -{} -     -PredicateMatchRequest::PredicateMatchRequest(const LLUUID &id, LLMediaDataClient::Request::Type matchType) : -    mRequest(), -    mMatchType(matchType), -    mId(id) -{} - -PredicateMatchRequest::PredicateMatchRequest(const PredicateMatchRequest &other) -{ -    mRequest = other.mRequest; -    mMatchType = other.mMatchType; -    mId = other.mId; -} - -bool PredicateMatchRequest::operator()(const LLMediaDataClient::Request::ptr_t &test) const -{ -    if (mRequest) -        return (mRequest->isMatch(test, mMatchType)); -    else if (!mId.isNull()) -        return ((test->getID() == mId) && ((mMatchType == LLMediaDataClient::Request::ANY) || (mMatchType == test->getType()))); -    return false; -} - -//========================================================================= -///  -template <typename T> -void mark_dead_and_remove_if(T &c, const PredicateMatchRequest &matchPred) -{ -    for (typename T::iterator it = c.begin(); it != c.end();) -    { -        if (matchPred(*it)) -        { -            (*it)->markDead(); -            it = c.erase(it); -        } -        else -        { -            ++it; -        } -    } -} - -////////////////////////////////////////////////////////////////////////////////////// -// -// LLMediaDataClient -// -////////////////////////////////////////////////////////////////////////////////////// - -LLMediaDataClient::LLMediaDataClient(F32 queue_timer_delay, F32 retry_timer_delay,   -        U32 max_retries, U32 max_sorted_queue_size, U32 max_round_robin_queue_size): -    mQueueTimerDelay(queue_timer_delay), -    mRetryTimerDelay(retry_timer_delay), -    mMaxNumRetries(max_retries), -    mMaxSortedQueueSize(max_sorted_queue_size), -    mMaxRoundRobinQueueSize(max_round_robin_queue_size), -    mQueueTimerIsRunning(false), -    mHttpRequest(new LLCore::HttpRequest()), -    mHttpHeaders(new LLCore::HttpHeaders()), -    mHttpOpts(new LLCore::HttpOptions()), -    mHttpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID) -{ -    // *TODO: Look up real Policy ID -} - -LLMediaDataClient::~LLMediaDataClient() -{ -	stopQueueTimer(); -} - -bool LLMediaDataClient::isEmpty() const -{ -	return mQueue.empty(); -} - -bool LLMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object) -{ -    PredicateMatchRequest upred(object->getID()); - -    if (std::find_if(mQueue.begin(), mQueue.end(), upred) != mQueue.end()) -        return true; -    if (std::find_if(mUnQueuedRequests.begin(), mUnQueuedRequests.end(), upred) != mUnQueuedRequests.end()) -        return true; -     -	return false; -} - -void LLMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object) -{ -	LL_DEBUGS("LLMediaDataClient") << "removing requests matching ID " << object->getID() << LL_ENDL; -    PredicateMatchRequest upred(object->getID()); - -    mark_dead_and_remove_if(mQueue, upred); -    mark_dead_and_remove_if(mUnQueuedRequests, upred); -} - -void LLMediaDataClient::startQueueTimer()  -{ -	if (! mQueueTimerIsRunning) -	{ -		LL_DEBUGS("LLMediaDataClient") << "starting queue timer (delay=" << mQueueTimerDelay << " seconds)" << LL_ENDL; -		// LLEventTimer automagically takes care of the lifetime of this object -		new QueueTimer(mQueueTimerDelay, this); -	} -	else {  -		LL_DEBUGS("LLMediaDataClient") << "queue timer is already running" << LL_ENDL; -	} -} - -void LLMediaDataClient::stopQueueTimer() -{ -	mQueueTimerIsRunning = false; -} - -bool LLMediaDataClient::processQueueTimer() -{ -    if (isDoneProcessing()) -		return true; - -	LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() started, queue size is:	  " << mQueue.size() << LL_ENDL; -	LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() started, SORTED queue is:	  " << mQueue << LL_ENDL; -			 -	serviceQueue(); -    serviceHttp(); - -	LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() finished, queue size is:	  " << mQueue.size() << LL_ENDL; -	LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() finished, SORTED queue is:	  " << mQueue << LL_ENDL; -	 -    return isDoneProcessing(); -} - -LLMediaDataClient::Request::ptr_t LLMediaDataClient::dequeue() -{ -	Request::ptr_t request; -	request_queue_t *queue_p = getQueue(); -	 -	if (queue_p->empty()) -	{ -		LL_DEBUGS("LLMediaDataClient") << "queue empty: " << (*queue_p) << LL_ENDL; -	} -	else -	{ -		request = queue_p->front(); -		 -		if(canServiceRequest(request)) -		{ -			// We will be returning this request, so remove it from the queue. -			queue_p->pop_front(); -		} -		else -		{ -			// Don't return this request -- it's not ready to be serviced. -            request.reset(); -		} -	} - -	return request; -} - -void LLMediaDataClient::pushBack(Request::ptr_t request) -{ -	request_queue_t *queue_p = getQueue(); -	queue_p->push_front(request); -} - -void LLMediaDataClient::trackRequest(Request::ptr_t request) -{ -	request_set_t::iterator iter = mUnQueuedRequests.find(request); -	 -	if(iter != mUnQueuedRequests.end()) -	{ -		LL_WARNS("LLMediaDataClient") << "Tracking already tracked request: " << *request << LL_ENDL; -	} -	else -	{ -		mUnQueuedRequests.insert(request); -	} -} - -void LLMediaDataClient::stopTrackingRequest(Request::ptr_t request) -{ -	request_set_t::iterator iter = mUnQueuedRequests.find(request); -	 -	if (iter != mUnQueuedRequests.end()) -	{ -		mUnQueuedRequests.erase(iter); -	} -	else -	{ -		LL_WARNS("LLMediaDataClient") << "Removing an untracked request: " << *request << LL_ENDL; -	} -} - -bool LLMediaDataClient::isDoneProcessing() const -{ -    return (isEmpty() && mUnQueuedRequests.empty()); -} - - -void LLMediaDataClient::serviceQueue() -{	 -	// Peel one off of the items from the queue and execute it -	Request::ptr_t request; -	 -	do -	{ -		request = dequeue(); - -		if(!request) -		{ -			// Queue is empty. -			return; -		} - -		if(request->isDead()) -		{ -			LL_INFOS("LLMediaDataClient") << "Skipping dead request " << *request << LL_ENDL; -			continue; -		} - -	} while(false); -		 -	// try to send the HTTP message to the cap url -	std::string url = request->getCapability(); -	if (!url.empty()) -	{ -		const LLSD &sd_payload = request->getPayload(); -		LL_INFOS("LLMediaDataClient") << "Sending request for " << *request << LL_ENDL; -		 -		// Add this request to the non-queued tracking list -		trackRequest(request); -		 -		// and make the post -        LLCore::HttpHandler::ptr_t handler = request->createHandler(); -        LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, mHttpPolicy, -            url, sd_payload, mHttpOpts, mHttpHeaders, handler); - -        if (handle == LLCORE_HTTP_HANDLE_INVALID) -        { -            LLCore::HttpStatus status = mHttpRequest->getStatus(); -            LL_WARNS("LLMediaDataClient") << "'" << url << "' request POST failed. Reason " -                << status.toTerseString() << " \"" << status.toString() << "\"" << LL_ENDL; -        } -	} -	else  -	{ -		// Cap url doesn't exist. -		 -		if(request->getRetryCount() < mMaxNumRetries) -		{ -			LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " (empty cap url), will retry." << LL_ENDL;  -			// Put this request back at the head of its queue, and retry next time the queue timer fires. -			request->incRetryCount(); -			pushBack(request); -		} -		else -		{ -			// This request has exceeded its maximum retry count.  It will be dropped. -			LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " for " << mMaxNumRetries << " tries, dropping request." << LL_ENDL;  -		} - -	} -} - -void LLMediaDataClient::serviceHttp() -{ -    mHttpRequest->update(0); -} - -// dump the queue -std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q) -{ -	int i = 0; -	LLMediaDataClient::request_queue_t::const_iterator iter = q.begin(); -	LLMediaDataClient::request_queue_t::const_iterator end = q.end(); -	while (iter != end) -	{ -		s << "\t" << i << "]: " << (*iter)->getID().asString() << "(" << (*iter)->getObject()->getMediaInterest() << ")"; -		iter++; -		i++; -	} -	return s; -} - -////////////////////////////////////////////////////////////////////////////////////// -// -// LLMediaDataClient::QueueTimer -// Queue of LLMediaDataClientObject smart pointers to request media for. -// -////////////////////////////////////////////////////////////////////////////////////// - -LLMediaDataClient::QueueTimer::QueueTimer(F32 time, LLMediaDataClient *mdc) -: LLEventTimer(time), mMDC(mdc) -{ -	mMDC->setIsRunning(true); -} - -// virtual -bool LLMediaDataClient::QueueTimer::tick() -{ -	bool result = true; -	 -	if (!mMDC.isNull()) -	{ -		result = mMDC->processQueueTimer(); -	 -		if(result) -		{ -			// This timer won't fire again.   -			mMDC->setIsRunning(false); -			mMDC = NULL; -		} -	} - -	return result; -} - - -////////////////////////////////////////////////////////////////////////////////////// -// -// LLMediaDataClient::Responder::RetryTimer -// -////////////////////////////////////////////////////////////////////////////////////// - -LLMediaDataClient::RetryTimer::RetryTimer(F32 time, Request::ptr_t request) -: LLEventTimer(time), mRequest(request) -{ -	mRequest->startTracking(); -} - -// virtual -bool LLMediaDataClient::RetryTimer::tick() -{ -	mRequest->stopTracking(); - -	if(mRequest->isDead()) -	{ -		LL_INFOS("LLMediaDataClient") << "RetryTimer fired for dead request: " << *mRequest << ", aborting." << LL_ENDL; -	} -	else -	{ -		LL_INFOS("LLMediaDataClient") << "RetryTimer fired for: " << *mRequest << ", retrying." << LL_ENDL; -		mRequest->reEnqueue(); -	} -	 -	// Release the ref to the request. -    mRequest.reset(); - -	// Don't fire again -	return true; -} - - -////////////////////////////////////////////////////////////////////////////////////// -// -// LLMediaDataClient::Request -// -////////////////////////////////////////////////////////////////////////////////////// -/*static*/U32 LLMediaDataClient::Request::sNum = 0; - -LLMediaDataClient::Request::Request(Type in_type, -									LLMediaDataClientObject *obj,  -									LLMediaDataClient *mdc, -									S32 face) -: mType(in_type), -  mObject(obj), -  mNum(++sNum),  -  mRetryCount(0), -  mMDC(mdc), -  mScore((F64)0.0), -  mFace(face) -{ -	mObjectID = mObject->getID(); -} - -const char *LLMediaDataClient::Request::getCapName() const -{ -	if(mMDC) -		return mMDC->getCapabilityName(); -	 -	return ""; -} - -std::string LLMediaDataClient::Request::getCapability() const -{ -	if(mMDC) -	{ -		return getObject()->getCapabilityUrl(getCapName()); -	} -	 -	return ""; -} - -const char *LLMediaDataClient::Request::getTypeAsString() const -{ -	Type t = getType(); -	switch (t) -	{ -		case GET: -			return "GET"; -			break; -		case UPDATE: -			return "UPDATE"; -			break; -		case NAVIGATE: -			return "NAVIGATE"; -			break; -		case ANY: -			return "ANY"; -			break; -	} -	return ""; -} - - -void LLMediaDataClient::Request::reEnqueue() -{ -	if(mMDC) -	{ -		mMDC->enqueue(shared_from_this()); -	} -} - -F32 LLMediaDataClient::Request::getRetryTimerDelay() const -{ -	if(mMDC) -		return mMDC->mRetryTimerDelay;  -		 -	return 0.0f; -} - -U32 LLMediaDataClient::Request::getMaxNumRetries() const -{ -	if(mMDC) -		return mMDC->mMaxNumRetries; -	 -	return 0; -} - -void LLMediaDataClient::Request::updateScore() -{				 -	F64 tmp = mObject->getMediaInterest(); -	if (tmp != mScore) -	{ -		LL_DEBUGS("LLMediaDataClient") << "Score for " << mObject->getID() << " changed from " << mScore << " to " << tmp << LL_ENDL;  -		mScore = tmp; -	} -} -		    -void LLMediaDataClient::Request::markDead()  -{  -	mMDC = NULL; -} - -bool LLMediaDataClient::Request::isDead()  -{  -	return ((mMDC == NULL) || mObject->isDead());  -} - -void LLMediaDataClient::Request::startTracking()  -{  -	if(mMDC)  -        mMDC->trackRequest(shared_from_this()); -} - -void LLMediaDataClient::Request::stopTracking()  -{  -	if(mMDC)  -        mMDC->stopTrackingRequest(shared_from_this()); -} - -std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &r) -{ -	s << "request: num=" << r.getNum()  -	<< " type=" << r.getTypeAsString()  -	<< " ID=" << r.getID()  -	<< " face=" << r.getFace()  -	<< " #retries=" << r.getRetryCount(); -	return s; -} - -//======================================================================== - -LLMediaDataClient::Handler::Handler(const Request::ptr_t &request): -    mRequest(request) -{ -} - - -void LLMediaDataClient::Handler::onSuccess(LLCore::HttpResponse * response, const LLSD &content) -{ -    mRequest->stopTracking(); - -    if (mRequest->isDead()) -    { -        LL_WARNS("LLMediaDataClient") << "dead request " << *mRequest << LL_ENDL; -        return; -    } - -    LL_DEBUGS("LLMediaDataClientResponse") << *mRequest << LL_ENDL; -} - -void LLMediaDataClient::Handler::onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status) -{ -    mRequest->stopTracking(); - -    if (status == LLCore::HttpStatus(HTTP_SERVICE_UNAVAILABLE)) -    { -        F32 retry_timeout; - -        retry_timeout = mRequest->getRetryTimerDelay(); - -        mRequest->incRetryCount(); - -        if (mRequest->getRetryCount() < mRequest->getMaxNumRetries())  -        { -            LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retrying in " << retry_timeout << " seconds" << LL_ENDL; - -            // Start timer (instances are automagically tracked by -            // InstanceTracker<> and LLEventTimer) -            new RetryTimer(F32(retry_timeout/*secs*/), mRequest); -        } -        else  -        { -            LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retry count "  -                << mRequest->getRetryCount() << " exceeds " << mRequest->getMaxNumRetries() << ", not retrying" << LL_ENDL; -        } -    } -    else -    { -        LL_WARNS("LLMediaDataClient") << *mRequest << " HTTP failure " << LL_ENDL; -    } -} - - -////////////////////////////////////////////////////////////////////////////////////// -// -// LLObjectMediaDataClient -// Subclass of LLMediaDataClient for the ObjectMedia cap -// -////////////////////////////////////////////////////////////////////////////////////// - -void LLObjectMediaDataClient::fetchMedia(LLMediaDataClientObject *object) -{ -	// Create a get request and put it in the queue. -	enqueue(Request::ptr_t(new RequestGet(object, this))); -} - -const char *LLObjectMediaDataClient::getCapabilityName() const  -{ -	return "ObjectMedia"; -} - -LLObjectMediaDataClient::request_queue_t *LLObjectMediaDataClient::getQueue() -{ -	return (mCurrentQueueIsTheSortedQueue) ? &mQueue : &mRoundRobinQueue; -} - -void LLObjectMediaDataClient::sortQueue() -{ -	if(!mQueue.empty()) -	{ -		// score all elements in the sorted queue. -		for(request_queue_t::iterator iter = mQueue.begin(); iter != mQueue.end(); iter++) -		{ -			(*iter)->updateScore(); -		} -		 -		// Re-sort the list... -		mQueue.sort(compareRequestScores); -		 -		// ...then cull items over the max -		U32 size = mQueue.size(); -		if (size > mMaxSortedQueueSize)  -		{ -			U32 num_to_cull = (size - mMaxSortedQueueSize); -			LL_INFOS_ONCE("LLMediaDataClient") << "sorted queue MAXED OUT!  Culling "  -				<< num_to_cull << " items" << LL_ENDL; -			while (num_to_cull-- > 0) -			{ -				mQueue.back()->markDead(); -				mQueue.pop_back(); -			} -		} -	} -	 -} - -// static -bool LLObjectMediaDataClient::compareRequestScores(const Request::ptr_t &o1, const Request::ptr_t &o2) -{ -	if (!o2) return true; -	if (!o1) return false; -	return ( o1->getScore() > o2->getScore() ); -} - -void LLObjectMediaDataClient::enqueue(Request::ptr_t request) -{ -	static LLCachedControl<bool> audio_streaming_enabled(gSavedSettings, "AudioStreamingMedia", true); -	if (!audio_streaming_enabled) -	{ -		LL_DEBUGS("LLMediaDataClient") << "not queueing request when Media is disabled " << *request << LL_ENDL; -		return; -	} - -	if(request->isDead()) -	{ -		LL_DEBUGS("LLMediaDataClient") << "not queueing dead request " << *request << LL_ENDL; -		return; -	} - -	// Invariants: -	// new requests always go into the sorted queue. -	//   -	 -	bool is_new = request->isNew(); -	 -	if(!is_new && (request->getType() == Request::GET)) -	{ -		// For GET requests that are not new, if a matching request is already in the round robin queue,  -		// in flight, or being retried, leave it at its current position. -        PredicateMatchRequest upred(request->getID(), Request::GET); -        request_queue_t::iterator iter = std::find_if(mRoundRobinQueue.begin(), mRoundRobinQueue.end(), upred); -        request_set_t::iterator iter2 = std::find_if(mUnQueuedRequests.begin(), mUnQueuedRequests.end(), upred); - -		if( (iter != mRoundRobinQueue.end()) || (iter2 != mUnQueuedRequests.end()) ) -		{ -			LL_DEBUGS("LLMediaDataClient") << "ALREADY THERE: NOT Queuing request for " << *request << LL_ENDL; - -			return; -		} -	} -	 -	// TODO: should an UPDATE cause pending GET requests for the same object to be removed from the queue? -	// IF the update will cause an object update message to be sent out at some point in the future, it probably should. -	 -	// Remove any existing requests of this type for this object -    PredicateMatchRequest upred(request->getID(), request->getType()); - -    mark_dead_and_remove_if(mQueue, upred); -    mark_dead_and_remove_if(mRoundRobinQueue, upred); -    mark_dead_and_remove_if(mUnQueuedRequests, upred); - -	if (is_new) -	{ -		LL_DEBUGS("LLMediaDataClient") << "Queuing SORTED request for " << *request << LL_ENDL; -		 -		mQueue.push_back(request); -		 -		LL_DEBUGS("LLMediaDataClientQueue") << "SORTED queue:" << mQueue << LL_ENDL; -	} -	else -	{ -		if (mRoundRobinQueue.size() > mMaxRoundRobinQueueSize)  -		{ -			LL_INFOS_ONCE("LLMediaDataClient") << "RR QUEUE MAXED OUT!!!" << LL_ENDL; -			LL_DEBUGS("LLMediaDataClient") << "Not queuing " << *request << LL_ENDL; -			return; -		} -				 -		LL_DEBUGS("LLMediaDataClient") << "Queuing RR request for " << *request << LL_ENDL; -		// Push the request on the pending queue -		mRoundRobinQueue.push_back(request); -		 -		LL_DEBUGS("LLMediaDataClientQueue") << "RR queue:" << mRoundRobinQueue << LL_ENDL;			 -	}	 -	// Start the timer if not already running -	startQueueTimer(); -} - -bool LLObjectMediaDataClient::canServiceRequest(Request::ptr_t request)  -{ -	if(mCurrentQueueIsTheSortedQueue) -	{ -		if(!request->getObject()->isInterestingEnough()) -		{ -			LL_DEBUGS("LLMediaDataClient") << "Not fetching " << *request << ": not interesting enough" << LL_ENDL; -			return false; -		} -	} -	 -	return true;  -}; - -void LLObjectMediaDataClient::swapCurrentQueue() -{ -	// Swap -	mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; -	// If its empty, swap back -	if (getQueue()->empty())  -	{ -		mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; -	} -} - -bool LLObjectMediaDataClient::isEmpty() const -{ -	return mQueue.empty() && mRoundRobinQueue.empty(); -} - -bool LLObjectMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object) -{ -	// First, call parent impl. -	if(LLMediaDataClient::isInQueue(object)) -		return true; - -    if (std::find_if(mRoundRobinQueue.begin(), mRoundRobinQueue.end(), PredicateMatchRequest(object->getID())) != mRoundRobinQueue.end()) -        return true; - -	return false; -} - -void LLObjectMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object) -{ -	// First, call parent impl. -	LLMediaDataClient::removeFromQueue(object); -	 -    mark_dead_and_remove_if(mRoundRobinQueue, PredicateMatchRequest(object->getID())); -} - -bool LLObjectMediaDataClient::processQueueTimer() -{ -    if (isDoneProcessing()) -		return true; -		 -	LL_DEBUGS("LLMediaDataClient") << "started, SORTED queue size is:	  " << mQueue.size()  -		<< ", RR queue size is:	  " << mRoundRobinQueue.size() << LL_ENDL; -	LL_DEBUGS("LLMediaDataClientQueue") << "    SORTED queue is:	  " << mQueue << LL_ENDL; -	LL_DEBUGS("LLMediaDataClientQueue") << "    RR queue is:	  " << mRoundRobinQueue << LL_ENDL; - -//	purgeDeadRequests(); - -	sortQueue(); - -	LL_DEBUGS("LLMediaDataClientQueue") << "after sort, SORTED queue is:	  " << mQueue << LL_ENDL; -	 -	serviceQueue(); -    serviceHttp(); - -	swapCurrentQueue(); -	 -	LL_DEBUGS("LLMediaDataClient") << "finished, SORTED queue size is:	  " << mQueue.size()  -		<< ", RR queue size is:	  " << mRoundRobinQueue.size() << LL_ENDL; -	LL_DEBUGS("LLMediaDataClientQueue") << "    SORTED queue is:	  " << mQueue << LL_ENDL; -	LL_DEBUGS("LLMediaDataClientQueue") << "    RR queue is:	  " << mRoundRobinQueue << LL_ENDL; -	 -    return isDoneProcessing(); -} - -LLObjectMediaDataClient::RequestGet::RequestGet(LLMediaDataClientObject *obj, LLMediaDataClient *mdc): -	LLMediaDataClient::Request(LLMediaDataClient::Request::GET, obj, mdc) -{ -} - -LLSD LLObjectMediaDataClient::RequestGet::getPayload() const -{ -	LLSD result; -	result["verb"] = "GET"; -	result[LLTextureEntry::OBJECT_ID_KEY] = mObject->getID(); -	 -	return result; -} - -LLCore::HttpHandler::ptr_t LLObjectMediaDataClient::RequestGet::createHandler() -{ -    return LLCore::HttpHandler::ptr_t(new LLObjectMediaDataClient::Handler(shared_from_this())); -} - - -void LLObjectMediaDataClient::updateMedia(LLMediaDataClientObject *object) -{ -	// Create an update request and put it in the queue. -	enqueue(Request::ptr_t(new RequestUpdate(object, this))); -} - -LLObjectMediaDataClient::RequestUpdate::RequestUpdate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc): -	LLMediaDataClient::Request(LLMediaDataClient::Request::UPDATE, obj, mdc) -{ -} - -LLSD LLObjectMediaDataClient::RequestUpdate::getPayload() const -{ -	LLSD result; -	result["verb"] = "UPDATE"; -	result[LLTextureEntry::OBJECT_ID_KEY] = mObject->getID(); - -	LLSD object_media_data; -	int i = 0; -	int end = mObject->getMediaDataCount(); -	for ( ; i < end ; ++i)  -	{ -		object_media_data.append(mObject->getMediaDataLLSD(i)); -	} -	 -	result[LLTextureEntry::OBJECT_MEDIA_DATA_KEY] = object_media_data; -	 -	return result; -} - -LLCore::HttpHandler::ptr_t LLObjectMediaDataClient::RequestUpdate::createHandler() -{ -	// This just uses the base class's responder. -    return LLCore::HttpHandler::ptr_t(new LLMediaDataClient::Handler(shared_from_this())); -} - -void LLObjectMediaDataClient::Handler::onSuccess(LLCore::HttpResponse * response, const LLSD &content) -{ -    LLMediaDataClient::Handler::onSuccess(response, content); - -    if (getRequest()->isDead()) -    {   // warning emitted from base method. -        return; -    } - -    if (!content.isMap()) -    { -        onFailure(response, LLCore::HttpStatus(HTTP_INTERNAL_ERROR, "Malformed response contents")); -        return; -    } - -    // This responder is only used for GET requests, not UPDATE. -    LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << " " << LL_ENDL; - -    // Look for an error -    if (content.has("error")) -    { -        const LLSD &error = content["error"]; -        LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error getting media data for object: code=" <<  -            error["code"].asString() << ": " << error["message"].asString() << LL_ENDL; - -        // XXX Warn user? -    } -    else  -    { -        // Check the data -        const LLUUID &object_id = content[LLTextureEntry::OBJECT_ID_KEY]; -        if (object_id != getRequest()->getObject()->getID())  -        { -            // NOT good, wrong object id!! -            LL_WARNS("LLMediaDataClient") << *(getRequest()) << " DROPPING response with wrong object id (" << object_id << ")" << LL_ENDL; -            return; -        } - -        // Otherwise, update with object media data -        getRequest()->getObject()->updateObjectMediaData(content[LLTextureEntry::OBJECT_MEDIA_DATA_KEY], -            content[LLTextureEntry::MEDIA_VERSION_KEY]); -    } - -} - - -////////////////////////////////////////////////////////////////////////////////////// -// -// LLObjectMediaNavigateClient -// Subclass of LLMediaDataClient for the ObjectMediaNavigate cap -// -////////////////////////////////////////////////////////////////////////////////////// - -const char *LLObjectMediaNavigateClient::getCapabilityName() const  -{ -	return "ObjectMediaNavigate"; -} - -void LLObjectMediaNavigateClient::enqueue(Request::ptr_t request) -{ -	static LLCachedControl<bool> audio_streaming_enabled(gSavedSettings, "AudioStreamingMedia", true); -	if (!audio_streaming_enabled) -	{ -		LL_DEBUGS("LLMediaDataClient") << "not queueing request when Media is disabled " << *request << LL_ENDL; -		return; -	} - -	if(request->isDead()) -	{ -		LL_DEBUGS("LLMediaDataClient") << "not queuing dead request " << *request << LL_ENDL; -		return; -	} -	 -    PredicateMatchRequest upred(request); - -	// If there's already a matching request in the queue, remove it. -    request_queue_t::iterator iter = std::find_if(mQueue.begin(), mQueue.end(), upred); -	if(iter != mQueue.end()) -	{ -		LL_DEBUGS("LLMediaDataClient") << "removing matching queued request " << (**iter) << LL_ENDL; -		mQueue.erase(iter); -	} -	else -	{ -        request_set_t::iterator set_iter = std::find_if(mUnQueuedRequests.begin(), mUnQueuedRequests.end(), upred); -		if(set_iter != mUnQueuedRequests.end()) -		{ -			LL_DEBUGS("LLMediaDataClient") << "removing matching unqueued request " << (**set_iter) << LL_ENDL; -			mUnQueuedRequests.erase(set_iter); -		} -	} - -#if 0 -	// Sadly, this doesn't work.  It ends up creating a race condition when the user navigates and then hits the "back" button -	// where the navigate-back appears to be spurious and doesn't get broadcast.	 -	if(request->getObject()->isCurrentMediaUrl(request->getFace(), request->getURL())) -	{ -		// This navigate request is trying to send the face to the current URL.  Drop it. -		LL_DEBUGS("LLMediaDataClient") << "dropping spurious request " << (*request) << LL_ENDL; -	} -	else -#endif -	{ -		LL_DEBUGS("LLMediaDataClient") << "queuing new request " << (*request) << LL_ENDL; -		mQueue.push_back(request); -		 -		// Start the timer if not already running -		startQueueTimer(); -	} -} - -void LLObjectMediaNavigateClient::navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url) -{ - -//	LL_INFOS("LLMediaDataClient") << "navigate() initiated: " << ll_print_sd(sd_payload) << LL_ENDL; -	 -	// Create a get request and put it in the queue. -	enqueue(Request::ptr_t(new RequestNavigate(object, this, texture_index, url))); -} - -LLObjectMediaNavigateClient::RequestNavigate::RequestNavigate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc, U8 texture_index, const std::string &url): -	LLMediaDataClient::Request(LLMediaDataClient::Request::NAVIGATE, obj, mdc, (S32)texture_index), -	mURL(url) -{ -} - -LLSD LLObjectMediaNavigateClient::RequestNavigate::getPayload() const -{ -	LLSD result; -	result[LLTextureEntry::OBJECT_ID_KEY] = getID(); -	result[LLMediaEntry::CURRENT_URL_KEY] = mURL; -	result[LLTextureEntry::TEXTURE_INDEX_KEY] = (LLSD::Integer)getFace(); -	 -	return result; -} - -LLCore::HttpHandler::ptr_t LLObjectMediaNavigateClient::RequestNavigate::createHandler() -{ -    return LLCore::HttpHandler::ptr_t(new LLObjectMediaNavigateClient::Handler(shared_from_this())); -} - -void LLObjectMediaNavigateClient::Handler::onSuccess(LLCore::HttpResponse * response, const LLSD &content) -{ -    LLMediaDataClient::Handler::onSuccess(response, content); - -    if (getRequest()->isDead()) -    {   // already warned. -        return; -    } - -    LL_INFOS("LLMediaDataClient") << *(getRequest()) << " NAVIGATE returned" << LL_ENDL; - -    if (content.has("error")) -    { -        const LLSD &error = content["error"]; -        int error_code = error["code"]; - -        if (ERROR_PERMISSION_DENIED_CODE == error_code) -        { -            mediaNavigateBounceBack(); -        } -        else -        { -            LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating: code=" << -                error["code"].asString() << ": " << error["message"].asString() << LL_ENDL; -        } - -        // XXX Warn user? -    } -    else -    { -        // No action required. -        LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << LL_ENDL; -    } - -} - -void LLObjectMediaNavigateClient::Handler::onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status) -{ -    LLMediaDataClient::Handler::onFailure(response, status); - -    if (getRequest()->isDead()) -    {   // already warned. -        return; -    } - -    if (status != LLCore::HttpStatus(HTTP_SERVICE_UNAVAILABLE)) -    { -        mediaNavigateBounceBack(); -    } -} - -void LLObjectMediaNavigateClient::Handler::mediaNavigateBounceBack() -{ -    LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating or denied." << LL_ENDL; -    const LLSD &payload = getRequest()->getPayload(); -     -    // bounce the face back -    getRequest()->getObject()->mediaNavigateBounceBack((LLSD::Integer)payload[LLTextureEntry::TEXTURE_INDEX_KEY]); -} +/**
 + * @file llmediadataclient.cpp
 + * @brief class for queueing up requests for media data
 + *
 + * $LicenseInfo:firstyear=2001&license=viewerlgpl$
 + * Second Life Viewer Source Code
 + * Copyright (C) 2010, Linden Research, Inc.
 + *
 + * This library is free software; you can redistribute it and/or
 + * modify it under the terms of the GNU Lesser General Public
 + * License as published by the Free Software Foundation;
 + * version 2.1 of the License only.
 + *
 + * This library is distributed in the hope that it will be useful,
 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 + * Lesser General Public License for more details.
 + *
 + * You should have received a copy of the GNU Lesser General Public
 + * License along with this library; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 + *
 + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 + * $/LicenseInfo$
 + */
 +
 +#include "llviewerprecompiledheaders.h"
 +
 +#include "llmediadataclient.h"
 +#include "llviewercontrol.h"
 +
 +#if LL_MSVC
 +// disable boost::lexical_cast warning
 +#pragma warning (disable:4702)
 +#endif
 +
 +#include <algorithm>
 +#include <boost/lexical_cast.hpp>
 +
 +#include "llhttpconstants.h"
 +#include "llsdutil.h"
 +#include "llmediaentry.h"
 +#include "lltextureentry.h"
 +#include "llviewerregion.h"
 +#include "llcorehttputil.h"
 +
 +//
 +// When making a request
 +// - obtain the "overall interest score" of the object.
 +//   This would be the sum of the impls' interest scores.
 +// - put the request onto a queue sorted by this score
 +//   (highest score at the front of the queue)
 +// - On a timer, once a second, pull off the head of the queue and send
 +//   the request.
 +// - Any request that gets a 503 still goes through the retry logic
 +//
 +
 +/***************************************************************************************************************
 +    What's up with this queueing code?
 +
 +    First, a bit of background:
 +
 +    Media on a prim was added into the system in the Viewer 2.0 timeframe.  In order to avoid changing the
 +    network format of objects, an unused field in the object (the "MediaURL" string) was repurposed to
 +    indicate that the object had media data, and also hold a sequence number and the UUID of the agent
 +    who last updated the data.  The actual media data for objects is accessed via the "ObjectMedia" capability.
 +    Due to concerns about sim performance, requests to this capability are rate-limited to 5 requests every
 +    5 seconds per agent.
 +
 +    The initial implementation of LLMediaDataClient used a single queue to manage requests to the "ObjectMedia" cap.
 +    Requests to the cap were queued so that objects closer to the avatar were loaded in first, since they were most
 +    likely to be the ones the media performance manager would load.
 +
 +    This worked in some cases, but we found that it was possible for a scripted object that constantly updated its
 +    media data to starve other objects, since the same queue contained both requests to load previously unseen media
 +    data and requests to fetch media data in response to object updates.
 +
 +    The solution for this we came up with was to have two queues.  The sorted queue contains requests to fetch media
 +    data for objects that don't have it yet, and the round-robin queue contains requests to update media data for
 +    objects that have already completed their initial load.  When both queues are non-empty, the code ping-pongs
 +    between them so that updates can't completely block initial load-in.
 +**************************************************************************************************************/
 +
 +//
 +// Forward decls
 +//
 +const F32 LLMediaDataClient::QUEUE_TIMER_DELAY = 1.0; // seconds(s)
 +const F32 LLMediaDataClient::UNAVAILABLE_RETRY_TIMER_DELAY = 5.0; // secs
 +const U32 LLMediaDataClient::MAX_RETRIES = 4;
 +const U32 LLMediaDataClient::MAX_SORTED_QUEUE_SIZE = 10000;
 +const U32 LLMediaDataClient::MAX_ROUND_ROBIN_QUEUE_SIZE = 10000;
 +
 +// << operators
 +std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q);
 +std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &q);
 +
 +
 +//=========================================================================
 +/// Uniary Predicate for matching requests in collections by either the request
 +/// or by UUID
 +///
 +class PredicateMatchRequest
 +{
 +public:
 +    PredicateMatchRequest(const LLMediaDataClient::Request::ptr_t &request, LLMediaDataClient::Request::Type matchType = LLMediaDataClient::Request::ANY);
 +    PredicateMatchRequest(const LLUUID &id, LLMediaDataClient::Request::Type matchType = LLMediaDataClient::Request::ANY);
 +
 +    PredicateMatchRequest(const PredicateMatchRequest &other);
 +
 +    bool operator()(const LLMediaDataClient::Request::ptr_t &test) const;
 +
 +private:
 +    LLMediaDataClient::Request::ptr_t mRequest;
 +    LLMediaDataClient::Request::Type  mMatchType;
 +    LLUUID                            mId;
 +};
 +
 +
 +PredicateMatchRequest::PredicateMatchRequest(const LLMediaDataClient::Request::ptr_t &request, LLMediaDataClient::Request::Type matchType) :
 +    mRequest(request),
 +    mMatchType(matchType),
 +    mId()
 +{}
 +
 +PredicateMatchRequest::PredicateMatchRequest(const LLUUID &id, LLMediaDataClient::Request::Type matchType) :
 +    mRequest(),
 +    mMatchType(matchType),
 +    mId(id)
 +{}
 +
 +PredicateMatchRequest::PredicateMatchRequest(const PredicateMatchRequest &other)
 +{
 +    mRequest = other.mRequest;
 +    mMatchType = other.mMatchType;
 +    mId = other.mId;
 +}
 +
 +bool PredicateMatchRequest::operator()(const LLMediaDataClient::Request::ptr_t &test) const
 +{
 +    if (mRequest)
 +        return (mRequest->isMatch(test, mMatchType));
 +    else if (!mId.isNull())
 +        return ((test->getID() == mId) && ((mMatchType == LLMediaDataClient::Request::ANY) || (mMatchType == test->getType())));
 +    return false;
 +}
 +
 +//=========================================================================
 +///
 +template <typename T>
 +void mark_dead_and_remove_if(T &c, const PredicateMatchRequest &matchPred)
 +{
 +    for (typename T::iterator it = c.begin(); it != c.end();)
 +    {
 +        if (matchPred(*it))
 +        {
 +            (*it)->markDead();
 +            it = c.erase(it);
 +        }
 +        else
 +        {
 +            ++it;
 +        }
 +    }
 +}
 +
 +//////////////////////////////////////////////////////////////////////////////////////
 +//
 +// LLMediaDataClient
 +//
 +//////////////////////////////////////////////////////////////////////////////////////
 +
 +LLMediaDataClient::LLMediaDataClient(F32 queue_timer_delay, F32 retry_timer_delay,
 +        U32 max_retries, U32 max_sorted_queue_size, U32 max_round_robin_queue_size):
 +    mQueueTimerDelay(queue_timer_delay),
 +    mRetryTimerDelay(retry_timer_delay),
 +    mMaxNumRetries(max_retries),
 +    mMaxSortedQueueSize(max_sorted_queue_size),
 +    mMaxRoundRobinQueueSize(max_round_robin_queue_size),
 +    mQueueTimerIsRunning(false),
 +    mHttpRequest(new LLCore::HttpRequest()),
 +    mHttpHeaders(new LLCore::HttpHeaders()),
 +    mHttpOpts(new LLCore::HttpOptions()),
 +    mHttpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID)
 +{
 +    // *TODO: Look up real Policy ID
 +}
 +
 +LLMediaDataClient::~LLMediaDataClient()
 +{
 +    stopQueueTimer();
 +}
 +
 +bool LLMediaDataClient::isEmpty() const
 +{
 +    return mQueue.empty();
 +}
 +
 +bool LLMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object)
 +{
 +    PredicateMatchRequest upred(object->getID());
 +
 +    if (std::find_if(mQueue.begin(), mQueue.end(), upred) != mQueue.end())
 +        return true;
 +    if (std::find_if(mUnQueuedRequests.begin(), mUnQueuedRequests.end(), upred) != mUnQueuedRequests.end())
 +        return true;
 +
 +    return false;
 +}
 +
 +void LLMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object)
 +{
 +    LL_DEBUGS("LLMediaDataClient") << "removing requests matching ID " << object->getID() << LL_ENDL;
 +    PredicateMatchRequest upred(object->getID());
 +
 +    mark_dead_and_remove_if(mQueue, upred);
 +    mark_dead_and_remove_if(mUnQueuedRequests, upred);
 +}
 +
 +void LLMediaDataClient::startQueueTimer()
 +{
 +    if (! mQueueTimerIsRunning)
 +    {
 +        LL_DEBUGS("LLMediaDataClient") << "starting queue timer (delay=" << mQueueTimerDelay << " seconds)" << LL_ENDL;
 +        // LLEventTimer automagically takes care of the lifetime of this object
 +        new QueueTimer(mQueueTimerDelay, this);
 +    }
 +    else {
 +        LL_DEBUGS("LLMediaDataClient") << "queue timer is already running" << LL_ENDL;
 +    }
 +}
 +
 +void LLMediaDataClient::stopQueueTimer()
 +{
 +    mQueueTimerIsRunning = false;
 +}
 +
 +bool LLMediaDataClient::processQueueTimer()
 +{
 +    if (isDoneProcessing())
 +        return true;
 +
 +    LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() started, queue size is:     " << mQueue.size() << LL_ENDL;
 +    LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() started, SORTED queue is:      " << mQueue << LL_ENDL;
 +
 +    serviceQueue();
 +    serviceHttp();
 +
 +    LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() finished, queue size is:    " << mQueue.size() << LL_ENDL;
 +    LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() finished, SORTED queue is:     " << mQueue << LL_ENDL;
 +
 +    return isDoneProcessing();
 +}
 +
 +LLMediaDataClient::Request::ptr_t LLMediaDataClient::dequeue()
 +{
 +    Request::ptr_t request;
 +    request_queue_t *queue_p = getQueue();
 +
 +    if (queue_p->empty())
 +    {
 +        LL_DEBUGS("LLMediaDataClient") << "queue empty: " << (*queue_p) << LL_ENDL;
 +    }
 +    else
 +    {
 +        request = queue_p->front();
 +
 +        if(canServiceRequest(request))
 +        {
 +            // We will be returning this request, so remove it from the queue.
 +            queue_p->pop_front();
 +        }
 +        else
 +        {
 +            // Don't return this request -- it's not ready to be serviced.
 +            request.reset();
 +        }
 +    }
 +
 +    return request;
 +}
 +
 +void LLMediaDataClient::pushBack(Request::ptr_t request)
 +{
 +    request_queue_t *queue_p = getQueue();
 +    queue_p->push_front(request);
 +}
 +
 +void LLMediaDataClient::trackRequest(Request::ptr_t request)
 +{
 +    request_set_t::iterator iter = mUnQueuedRequests.find(request);
 +
 +    if(iter != mUnQueuedRequests.end())
 +    {
 +        LL_WARNS("LLMediaDataClient") << "Tracking already tracked request: " << *request << LL_ENDL;
 +    }
 +    else
 +    {
 +        mUnQueuedRequests.insert(request);
 +    }
 +}
 +
 +void LLMediaDataClient::stopTrackingRequest(Request::ptr_t request)
 +{
 +    request_set_t::iterator iter = mUnQueuedRequests.find(request);
 +
 +    if (iter != mUnQueuedRequests.end())
 +    {
 +        mUnQueuedRequests.erase(iter);
 +    }
 +    else
 +    {
 +        LL_WARNS("LLMediaDataClient") << "Removing an untracked request: " << *request << LL_ENDL;
 +    }
 +}
 +
 +bool LLMediaDataClient::isDoneProcessing() const
 +{
 +    return (isEmpty() && mUnQueuedRequests.empty());
 +}
 +
 +
 +void LLMediaDataClient::serviceQueue()
 +{
 +    // Peel one off of the items from the queue and execute it
 +    Request::ptr_t request;
 +
 +    do
 +    {
 +        request = dequeue();
 +
 +        if(!request)
 +        {
 +            // Queue is empty.
 +            return;
 +        }
 +
 +        if(request->isDead())
 +        {
 +            LL_INFOS("LLMediaDataClient") << "Skipping dead request " << *request << LL_ENDL;
 +            continue;
 +        }
 +
 +    } while(false);
 +
 +    // try to send the HTTP message to the cap url
 +    std::string url = request->getCapability();
 +    if (!url.empty())
 +    {
 +        const LLSD &sd_payload = request->getPayload();
 +        LL_INFOS("LLMediaDataClient") << "Sending request for " << *request << LL_ENDL;
 +
 +        // Add this request to the non-queued tracking list
 +        trackRequest(request);
 +
 +        // and make the post
 +        LLCore::HttpHandler::ptr_t handler = request->createHandler();
 +        LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, mHttpPolicy,
 +            url, sd_payload, mHttpOpts, mHttpHeaders, handler);
 +
 +        if (handle == LLCORE_HTTP_HANDLE_INVALID)
 +        {
 +            LLCore::HttpStatus status = mHttpRequest->getStatus();
 +            LL_WARNS("LLMediaDataClient") << "'" << url << "' request POST failed. Reason "
 +                << status.toTerseString() << " \"" << status.toString() << "\"" << LL_ENDL;
 +        }
 +    }
 +    else
 +    {
 +        // Cap url doesn't exist.
 +
 +        if(request->getRetryCount() < mMaxNumRetries)
 +        {
 +            LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " (empty cap url), will retry." << LL_ENDL;
 +            // Put this request back at the head of its queue, and retry next time the queue timer fires.
 +            request->incRetryCount();
 +            pushBack(request);
 +        }
 +        else
 +        {
 +            // This request has exceeded its maximum retry count.  It will be dropped.
 +            LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " for " << mMaxNumRetries << " tries, dropping request." << LL_ENDL;
 +        }
 +
 +    }
 +}
 +
 +void LLMediaDataClient::serviceHttp()
 +{
 +    mHttpRequest->update(0);
 +}
 +
 +// dump the queue
 +std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q)
 +{
 +    int i = 0;
 +    LLMediaDataClient::request_queue_t::const_iterator iter = q.begin();
 +    LLMediaDataClient::request_queue_t::const_iterator end = q.end();
 +    while (iter != end)
 +    {
 +        s << "\t" << i << "]: " << (*iter)->getID().asString() << "(" << (*iter)->getObject()->getMediaInterest() << ")";
 +        iter++;
 +        i++;
 +    }
 +    return s;
 +}
 +
 +//////////////////////////////////////////////////////////////////////////////////////
 +//
 +// LLMediaDataClient::QueueTimer
 +// Queue of LLMediaDataClientObject smart pointers to request media for.
 +//
 +//////////////////////////////////////////////////////////////////////////////////////
 +
 +LLMediaDataClient::QueueTimer::QueueTimer(F32 time, LLMediaDataClient *mdc)
 +: LLEventTimer(time), mMDC(mdc)
 +{
 +    mMDC->setIsRunning(true);
 +}
 +
 +// virtual
 +bool LLMediaDataClient::QueueTimer::tick()
 +{
 +    bool result = true;
 +
 +    if (!mMDC.isNull())
 +    {
 +        result = mMDC->processQueueTimer();
 +
 +        if(result)
 +        {
 +            // This timer won't fire again.
 +            mMDC->setIsRunning(false);
 +            mMDC = NULL;
 +        }
 +    }
 +
 +    return result;
 +}
 +
 +
 +//////////////////////////////////////////////////////////////////////////////////////
 +//
 +// LLMediaDataClient::Responder::RetryTimer
 +//
 +//////////////////////////////////////////////////////////////////////////////////////
 +
 +LLMediaDataClient::RetryTimer::RetryTimer(F32 time, Request::ptr_t request)
 +: LLEventTimer(time), mRequest(request)
 +{
 +    mRequest->startTracking();
 +}
 +
 +// virtual
 +bool LLMediaDataClient::RetryTimer::tick()
 +{
 +    mRequest->stopTracking();
 +
 +    if(mRequest->isDead())
 +    {
 +        LL_INFOS("LLMediaDataClient") << "RetryTimer fired for dead request: " << *mRequest << ", aborting." << LL_ENDL;
 +    }
 +    else
 +    {
 +        LL_INFOS("LLMediaDataClient") << "RetryTimer fired for: " << *mRequest << ", retrying." << LL_ENDL;
 +        mRequest->reEnqueue();
 +    }
 +
 +    // Release the ref to the request.
 +    mRequest.reset();
 +
 +    // Don't fire again
 +    return true;
 +}
 +
 +
 +//////////////////////////////////////////////////////////////////////////////////////
 +//
 +// LLMediaDataClient::Request
 +//
 +//////////////////////////////////////////////////////////////////////////////////////
 +/*static*/U32 LLMediaDataClient::Request::sNum = 0;
 +
 +LLMediaDataClient::Request::Request(Type in_type,
 +                                    LLMediaDataClientObject *obj,
 +                                    LLMediaDataClient *mdc,
 +                                    S32 face)
 +: mType(in_type),
 +  mObject(obj),
 +  mNum(++sNum),
 +  mRetryCount(0),
 +  mMDC(mdc),
 +  mScore((F64)0.0),
 +  mFace(face)
 +{
 +    mObjectID = mObject->getID();
 +}
 +
 +const char *LLMediaDataClient::Request::getCapName() const
 +{
 +    if(mMDC)
 +        return mMDC->getCapabilityName();
 +
 +    return "";
 +}
 +
 +std::string LLMediaDataClient::Request::getCapability() const
 +{
 +    if(mMDC)
 +    {
 +        return getObject()->getCapabilityUrl(getCapName());
 +    }
 +
 +    return "";
 +}
 +
 +const char *LLMediaDataClient::Request::getTypeAsString() const
 +{
 +    Type t = getType();
 +    switch (t)
 +    {
 +        case GET:
 +            return "GET";
 +            break;
 +        case UPDATE:
 +            return "UPDATE";
 +            break;
 +        case NAVIGATE:
 +            return "NAVIGATE";
 +            break;
 +        case ANY:
 +            return "ANY";
 +            break;
 +    }
 +    return "";
 +}
 +
 +
 +void LLMediaDataClient::Request::reEnqueue()
 +{
 +    if(mMDC)
 +    {
 +        mMDC->enqueue(shared_from_this());
 +    }
 +}
 +
 +F32 LLMediaDataClient::Request::getRetryTimerDelay() const
 +{
 +    if(mMDC)
 +        return mMDC->mRetryTimerDelay;
 +
 +    return 0.0f;
 +}
 +
 +U32 LLMediaDataClient::Request::getMaxNumRetries() const
 +{
 +    if(mMDC)
 +        return mMDC->mMaxNumRetries;
 +
 +    return 0;
 +}
 +
 +void LLMediaDataClient::Request::updateScore()
 +{
 +    F64 tmp = mObject->getMediaInterest();
 +    if (tmp != mScore)
 +    {
 +        LL_DEBUGS("LLMediaDataClient") << "Score for " << mObject->getID() << " changed from " << mScore << " to " << tmp << LL_ENDL;
 +        mScore = tmp;
 +    }
 +}
 +
 +void LLMediaDataClient::Request::markDead()
 +{
 +    mMDC = NULL;
 +}
 +
 +bool LLMediaDataClient::Request::isDead()
 +{
 +    return ((mMDC == NULL) || mObject->isDead());
 +}
 +
 +void LLMediaDataClient::Request::startTracking()
 +{
 +    if(mMDC)
 +        mMDC->trackRequest(shared_from_this());
 +}
 +
 +void LLMediaDataClient::Request::stopTracking()
 +{
 +    if(mMDC)
 +        mMDC->stopTrackingRequest(shared_from_this());
 +}
 +
 +std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &r)
 +{
 +    s << "request: num=" << r.getNum()
 +    << " type=" << r.getTypeAsString()
 +    << " ID=" << r.getID()
 +    << " face=" << r.getFace()
 +    << " #retries=" << r.getRetryCount();
 +    return s;
 +}
 +
 +//========================================================================
 +
 +LLMediaDataClient::Handler::Handler(const Request::ptr_t &request):
 +    mRequest(request)
 +{
 +}
 +
 +
 +void LLMediaDataClient::Handler::onSuccess(LLCore::HttpResponse * response, const LLSD &content)
 +{
 +    mRequest->stopTracking();
 +
 +    if (mRequest->isDead())
 +    {
 +        LL_WARNS("LLMediaDataClient") << "dead request " << *mRequest << LL_ENDL;
 +        return;
 +    }
 +
 +    LL_DEBUGS("LLMediaDataClientResponse") << *mRequest << LL_ENDL;
 +}
 +
 +void LLMediaDataClient::Handler::onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status)
 +{
 +    mRequest->stopTracking();
 +
 +    if (status == LLCore::HttpStatus(HTTP_SERVICE_UNAVAILABLE))
 +    {
 +        F32 retry_timeout;
 +
 +        retry_timeout = mRequest->getRetryTimerDelay();
 +
 +        mRequest->incRetryCount();
 +
 +        if (mRequest->getRetryCount() < mRequest->getMaxNumRetries())
 +        {
 +            LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retrying in " << retry_timeout << " seconds" << LL_ENDL;
 +
 +            // Start timer (instances are automagically tracked by
 +            // InstanceTracker<> and LLEventTimer)
 +            new RetryTimer(F32(retry_timeout/*secs*/), mRequest);
 +        }
 +        else
 +        {
 +            LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retry count "
 +                << mRequest->getRetryCount() << " exceeds " << mRequest->getMaxNumRetries() << ", not retrying" << LL_ENDL;
 +        }
 +    }
 +    else
 +    {
 +        LL_WARNS("LLMediaDataClient") << *mRequest << " HTTP failure " << LL_ENDL;
 +    }
 +}
 +
 +
 +//////////////////////////////////////////////////////////////////////////////////////
 +//
 +// LLObjectMediaDataClient
 +// Subclass of LLMediaDataClient for the ObjectMedia cap
 +//
 +//////////////////////////////////////////////////////////////////////////////////////
 +
 +void LLObjectMediaDataClient::fetchMedia(LLMediaDataClientObject *object)
 +{
 +    // Create a get request and put it in the queue.
 +    enqueue(Request::ptr_t(new RequestGet(object, this)));
 +}
 +
 +const char *LLObjectMediaDataClient::getCapabilityName() const
 +{
 +    return "ObjectMedia";
 +}
 +
 +LLObjectMediaDataClient::request_queue_t *LLObjectMediaDataClient::getQueue()
 +{
 +    return (mCurrentQueueIsTheSortedQueue) ? &mQueue : &mRoundRobinQueue;
 +}
 +
 +void LLObjectMediaDataClient::sortQueue()
 +{
 +    if(!mQueue.empty())
 +    {
 +        // score all elements in the sorted queue.
 +        for(request_queue_t::iterator iter = mQueue.begin(); iter != mQueue.end(); iter++)
 +        {
 +            (*iter)->updateScore();
 +        }
 +
 +        // Re-sort the list...
 +        mQueue.sort(compareRequestScores);
 +
 +        // ...then cull items over the max
 +        U32 size = mQueue.size();
 +        if (size > mMaxSortedQueueSize)
 +        {
 +            U32 num_to_cull = (size - mMaxSortedQueueSize);
 +            LL_INFOS_ONCE("LLMediaDataClient") << "sorted queue MAXED OUT!  Culling "
 +                << num_to_cull << " items" << LL_ENDL;
 +            while (num_to_cull-- > 0)
 +            {
 +                mQueue.back()->markDead();
 +                mQueue.pop_back();
 +            }
 +        }
 +    }
 +
 +}
 +
 +// static
 +bool LLObjectMediaDataClient::compareRequestScores(const Request::ptr_t &o1, const Request::ptr_t &o2)
 +{
 +    if (!o2) return true;
 +    if (!o1) return false;
 +    return ( o1->getScore() > o2->getScore() );
 +}
 +
 +void LLObjectMediaDataClient::enqueue(Request::ptr_t request)
 +{
 +    static LLCachedControl<bool> audio_streaming_enabled(gSavedSettings, "AudioStreamingMedia", true);
 +    if (!audio_streaming_enabled)
 +    {
 +        LL_DEBUGS("LLMediaDataClient") << "not queueing request when Media is disabled " << *request << LL_ENDL;
 +        return;
 +    }
 +
 +    if(request->isDead())
 +    {
 +        LL_DEBUGS("LLMediaDataClient") << "not queueing dead request " << *request << LL_ENDL;
 +        return;
 +    }
 +
 +    // Invariants:
 +    // new requests always go into the sorted queue.
 +    //
 +
 +    bool is_new = request->isNew();
 +
 +    if(!is_new && (request->getType() == Request::GET))
 +    {
 +        // For GET requests that are not new, if a matching request is already in the round robin queue,
 +        // in flight, or being retried, leave it at its current position.
 +        PredicateMatchRequest upred(request->getID(), Request::GET);
 +        request_queue_t::iterator iter = std::find_if(mRoundRobinQueue.begin(), mRoundRobinQueue.end(), upred);
 +        request_set_t::iterator iter2 = std::find_if(mUnQueuedRequests.begin(), mUnQueuedRequests.end(), upred);
 +
 +        if( (iter != mRoundRobinQueue.end()) || (iter2 != mUnQueuedRequests.end()) )
 +        {
 +            LL_DEBUGS("LLMediaDataClient") << "ALREADY THERE: NOT Queuing request for " << *request << LL_ENDL;
 +
 +            return;
 +        }
 +    }
 +
 +    // TODO: should an UPDATE cause pending GET requests for the same object to be removed from the queue?
 +    // IF the update will cause an object update message to be sent out at some point in the future, it probably should.
 +
 +    // Remove any existing requests of this type for this object
 +    PredicateMatchRequest upred(request->getID(), request->getType());
 +
 +    mark_dead_and_remove_if(mQueue, upred);
 +    mark_dead_and_remove_if(mRoundRobinQueue, upred);
 +    mark_dead_and_remove_if(mUnQueuedRequests, upred);
 +
 +    if (is_new)
 +    {
 +        LL_DEBUGS("LLMediaDataClient") << "Queuing SORTED request for " << *request << LL_ENDL;
 +
 +        mQueue.push_back(request);
 +
 +        LL_DEBUGS("LLMediaDataClientQueue") << "SORTED queue:" << mQueue << LL_ENDL;
 +    }
 +    else
 +    {
 +        if (mRoundRobinQueue.size() > mMaxRoundRobinQueueSize)
 +        {
 +            LL_INFOS_ONCE("LLMediaDataClient") << "RR QUEUE MAXED OUT!!!" << LL_ENDL;
 +            LL_DEBUGS("LLMediaDataClient") << "Not queuing " << *request << LL_ENDL;
 +            return;
 +        }
 +
 +        LL_DEBUGS("LLMediaDataClient") << "Queuing RR request for " << *request << LL_ENDL;
 +        // Push the request on the pending queue
 +        mRoundRobinQueue.push_back(request);
 +
 +        LL_DEBUGS("LLMediaDataClientQueue") << "RR queue:" << mRoundRobinQueue << LL_ENDL;
 +    }
 +    // Start the timer if not already running
 +    startQueueTimer();
 +}
 +
 +bool LLObjectMediaDataClient::canServiceRequest(Request::ptr_t request)
 +{
 +    if(mCurrentQueueIsTheSortedQueue)
 +    {
 +        if(!request->getObject()->isInterestingEnough())
 +        {
 +            LL_DEBUGS("LLMediaDataClient") << "Not fetching " << *request << ": not interesting enough" << LL_ENDL;
 +            return false;
 +        }
 +    }
 +
 +    return true;
 +};
 +
 +void LLObjectMediaDataClient::swapCurrentQueue()
 +{
 +    // Swap
 +    mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue;
 +    // If its empty, swap back
 +    if (getQueue()->empty())
 +    {
 +        mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue;
 +    }
 +}
 +
 +bool LLObjectMediaDataClient::isEmpty() const
 +{
 +    return mQueue.empty() && mRoundRobinQueue.empty();
 +}
 +
 +bool LLObjectMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object)
 +{
 +    // First, call parent impl.
 +    if(LLMediaDataClient::isInQueue(object))
 +        return true;
 +
 +    if (std::find_if(mRoundRobinQueue.begin(), mRoundRobinQueue.end(), PredicateMatchRequest(object->getID())) != mRoundRobinQueue.end())
 +        return true;
 +
 +    return false;
 +}
 +
 +void LLObjectMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object)
 +{
 +    // First, call parent impl.
 +    LLMediaDataClient::removeFromQueue(object);
 +
 +    mark_dead_and_remove_if(mRoundRobinQueue, PredicateMatchRequest(object->getID()));
 +}
 +
 +bool LLObjectMediaDataClient::processQueueTimer()
 +{
 +    if (isDoneProcessing())
 +        return true;
 +
 +    LL_DEBUGS("LLMediaDataClient") << "started, SORTED queue size is:     " << mQueue.size()
 +        << ", RR queue size is:   " << mRoundRobinQueue.size() << LL_ENDL;
 +    LL_DEBUGS("LLMediaDataClientQueue") << "    SORTED queue is:      " << mQueue << LL_ENDL;
 +    LL_DEBUGS("LLMediaDataClientQueue") << "    RR queue is:      " << mRoundRobinQueue << LL_ENDL;
 +
 +//  purgeDeadRequests();
 +
 +    sortQueue();
 +
 +    LL_DEBUGS("LLMediaDataClientQueue") << "after sort, SORTED queue is:      " << mQueue << LL_ENDL;
 +
 +    serviceQueue();
 +    serviceHttp();
 +
 +    swapCurrentQueue();
 +
 +    LL_DEBUGS("LLMediaDataClient") << "finished, SORTED queue size is:    " << mQueue.size()
 +        << ", RR queue size is:   " << mRoundRobinQueue.size() << LL_ENDL;
 +    LL_DEBUGS("LLMediaDataClientQueue") << "    SORTED queue is:      " << mQueue << LL_ENDL;
 +    LL_DEBUGS("LLMediaDataClientQueue") << "    RR queue is:      " << mRoundRobinQueue << LL_ENDL;
 +
 +    return isDoneProcessing();
 +}
 +
 +LLObjectMediaDataClient::RequestGet::RequestGet(LLMediaDataClientObject *obj, LLMediaDataClient *mdc):
 +    LLMediaDataClient::Request(LLMediaDataClient::Request::GET, obj, mdc)
 +{
 +}
 +
 +LLSD LLObjectMediaDataClient::RequestGet::getPayload() const
 +{
 +    LLSD result;
 +    result["verb"] = "GET";
 +    result[LLTextureEntry::OBJECT_ID_KEY] = mObject->getID();
 +
 +    return result;
 +}
 +
 +LLCore::HttpHandler::ptr_t LLObjectMediaDataClient::RequestGet::createHandler()
 +{
 +    return LLCore::HttpHandler::ptr_t(new LLObjectMediaDataClient::Handler(shared_from_this()));
 +}
 +
 +
 +void LLObjectMediaDataClient::updateMedia(LLMediaDataClientObject *object)
 +{
 +    // Create an update request and put it in the queue.
 +    enqueue(Request::ptr_t(new RequestUpdate(object, this)));
 +}
 +
 +LLObjectMediaDataClient::RequestUpdate::RequestUpdate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc):
 +    LLMediaDataClient::Request(LLMediaDataClient::Request::UPDATE, obj, mdc)
 +{
 +}
 +
 +LLSD LLObjectMediaDataClient::RequestUpdate::getPayload() const
 +{
 +    LLSD result;
 +    result["verb"] = "UPDATE";
 +    result[LLTextureEntry::OBJECT_ID_KEY] = mObject->getID();
 +
 +    LLSD object_media_data;
 +    int i = 0;
 +    int end = mObject->getMediaDataCount();
 +    for ( ; i < end ; ++i)
 +    {
 +        object_media_data.append(mObject->getMediaDataLLSD(i));
 +    }
 +
 +    result[LLTextureEntry::OBJECT_MEDIA_DATA_KEY] = object_media_data;
 +
 +    return result;
 +}
 +
 +LLCore::HttpHandler::ptr_t LLObjectMediaDataClient::RequestUpdate::createHandler()
 +{
 +    // This just uses the base class's responder.
 +    return LLCore::HttpHandler::ptr_t(new LLMediaDataClient::Handler(shared_from_this()));
 +}
 +
 +void LLObjectMediaDataClient::Handler::onSuccess(LLCore::HttpResponse * response, const LLSD &content)
 +{
 +    LLMediaDataClient::Handler::onSuccess(response, content);
 +
 +    if (getRequest()->isDead())
 +    {   // warning emitted from base method.
 +        return;
 +    }
 +
 +    if (!content.isMap())
 +    {
 +        onFailure(response, LLCore::HttpStatus(HTTP_INTERNAL_ERROR, "Malformed response contents"));
 +        return;
 +    }
 +
 +    // This responder is only used for GET requests, not UPDATE.
 +    LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << " " << LL_ENDL;
 +
 +    // Look for an error
 +    if (content.has("error"))
 +    {
 +        const LLSD &error = content["error"];
 +        LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error getting media data for object: code=" <<
 +            error["code"].asString() << ": " << error["message"].asString() << LL_ENDL;
 +
 +        // XXX Warn user?
 +    }
 +    else
 +    {
 +        // Check the data
 +        const LLUUID &object_id = content[LLTextureEntry::OBJECT_ID_KEY];
 +        if (object_id != getRequest()->getObject()->getID())
 +        {
 +            // NOT good, wrong object id!!
 +            LL_WARNS("LLMediaDataClient") << *(getRequest()) << " DROPPING response with wrong object id (" << object_id << ")" << LL_ENDL;
 +            return;
 +        }
 +
 +        // Otherwise, update with object media data
 +        getRequest()->getObject()->updateObjectMediaData(content[LLTextureEntry::OBJECT_MEDIA_DATA_KEY],
 +            content[LLTextureEntry::MEDIA_VERSION_KEY]);
 +    }
 +
 +}
 +
 +
 +//////////////////////////////////////////////////////////////////////////////////////
 +//
 +// LLObjectMediaNavigateClient
 +// Subclass of LLMediaDataClient for the ObjectMediaNavigate cap
 +//
 +//////////////////////////////////////////////////////////////////////////////////////
 +
 +const char *LLObjectMediaNavigateClient::getCapabilityName() const
 +{
 +    return "ObjectMediaNavigate";
 +}
 +
 +void LLObjectMediaNavigateClient::enqueue(Request::ptr_t request)
 +{
 +    static LLCachedControl<bool> audio_streaming_enabled(gSavedSettings, "AudioStreamingMedia", true);
 +    if (!audio_streaming_enabled)
 +    {
 +        LL_DEBUGS("LLMediaDataClient") << "not queueing request when Media is disabled " << *request << LL_ENDL;
 +        return;
 +    }
 +
 +    if(request->isDead())
 +    {
 +        LL_DEBUGS("LLMediaDataClient") << "not queuing dead request " << *request << LL_ENDL;
 +        return;
 +    }
 +
 +    PredicateMatchRequest upred(request);
 +
 +    // If there's already a matching request in the queue, remove it.
 +    request_queue_t::iterator iter = std::find_if(mQueue.begin(), mQueue.end(), upred);
 +    if(iter != mQueue.end())
 +    {
 +        LL_DEBUGS("LLMediaDataClient") << "removing matching queued request " << (**iter) << LL_ENDL;
 +        mQueue.erase(iter);
 +    }
 +    else
 +    {
 +        request_set_t::iterator set_iter = std::find_if(mUnQueuedRequests.begin(), mUnQueuedRequests.end(), upred);
 +        if(set_iter != mUnQueuedRequests.end())
 +        {
 +            LL_DEBUGS("LLMediaDataClient") << "removing matching unqueued request " << (**set_iter) << LL_ENDL;
 +            mUnQueuedRequests.erase(set_iter);
 +        }
 +    }
 +
 +#if 0
 +    // Sadly, this doesn't work.  It ends up creating a race condition when the user navigates and then hits the "back" button
 +    // where the navigate-back appears to be spurious and doesn't get broadcast.
 +    if(request->getObject()->isCurrentMediaUrl(request->getFace(), request->getURL()))
 +    {
 +        // This navigate request is trying to send the face to the current URL.  Drop it.
 +        LL_DEBUGS("LLMediaDataClient") << "dropping spurious request " << (*request) << LL_ENDL;
 +    }
 +    else
 +#endif
 +    {
 +        LL_DEBUGS("LLMediaDataClient") << "queuing new request " << (*request) << LL_ENDL;
 +        mQueue.push_back(request);
 +
 +        // Start the timer if not already running
 +        startQueueTimer();
 +    }
 +}
 +
 +void LLObjectMediaNavigateClient::navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url)
 +{
 +
 +//  LL_INFOS("LLMediaDataClient") << "navigate() initiated: " << ll_print_sd(sd_payload) << LL_ENDL;
 +
 +    // Create a get request and put it in the queue.
 +    enqueue(Request::ptr_t(new RequestNavigate(object, this, texture_index, url)));
 +}
 +
 +LLObjectMediaNavigateClient::RequestNavigate::RequestNavigate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc, U8 texture_index, const std::string &url):
 +    LLMediaDataClient::Request(LLMediaDataClient::Request::NAVIGATE, obj, mdc, (S32)texture_index),
 +    mURL(url)
 +{
 +}
 +
 +LLSD LLObjectMediaNavigateClient::RequestNavigate::getPayload() const
 +{
 +    LLSD result;
 +    result[LLTextureEntry::OBJECT_ID_KEY] = getID();
 +    result[LLMediaEntry::CURRENT_URL_KEY] = mURL;
 +    result[LLTextureEntry::TEXTURE_INDEX_KEY] = (LLSD::Integer)getFace();
 +
 +    return result;
 +}
 +
 +LLCore::HttpHandler::ptr_t LLObjectMediaNavigateClient::RequestNavigate::createHandler()
 +{
 +    return LLCore::HttpHandler::ptr_t(new LLObjectMediaNavigateClient::Handler(shared_from_this()));
 +}
 +
 +void LLObjectMediaNavigateClient::Handler::onSuccess(LLCore::HttpResponse * response, const LLSD &content)
 +{
 +    LLMediaDataClient::Handler::onSuccess(response, content);
 +
 +    if (getRequest()->isDead())
 +    {   // already warned.
 +        return;
 +    }
 +
 +    LL_INFOS("LLMediaDataClient") << *(getRequest()) << " NAVIGATE returned" << LL_ENDL;
 +
 +    if (content.has("error"))
 +    {
 +        const LLSD &error = content["error"];
 +        int error_code = error["code"];
 +
 +        if (ERROR_PERMISSION_DENIED_CODE == error_code)
 +        {
 +            mediaNavigateBounceBack();
 +        }
 +        else
 +        {
 +            LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating: code=" <<
 +                error["code"].asString() << ": " << error["message"].asString() << LL_ENDL;
 +        }
 +
 +        // XXX Warn user?
 +    }
 +    else
 +    {
 +        // No action required.
 +        LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << LL_ENDL;
 +    }
 +
 +}
 +
 +void LLObjectMediaNavigateClient::Handler::onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status)
 +{
 +    LLMediaDataClient::Handler::onFailure(response, status);
 +
 +    if (getRequest()->isDead())
 +    {   // already warned.
 +        return;
 +    }
 +
 +    if (status != LLCore::HttpStatus(HTTP_SERVICE_UNAVAILABLE))
 +    {
 +        mediaNavigateBounceBack();
 +    }
 +}
 +
 +void LLObjectMediaNavigateClient::Handler::mediaNavigateBounceBack()
 +{
 +    LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating or denied." << LL_ENDL;
 +    const LLSD &payload = getRequest()->getPayload();
 +
 +    // bounce the face back
 +    getRequest()->getObject()->mediaNavigateBounceBack((LLSD::Integer)payload[LLTextureEntry::TEXTURE_INDEX_KEY]);
 +}
  | 
