PageRenderTime 121ms CodeModel.GetById 21ms app.highlight 87ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/newview/llmediadataclient.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 1070 lines | 722 code | 176 blank | 172 comment | 106 complexity | fab296ae8ceaefb9a25fa3d64843bf43 MD5 | raw file
   1/** 
   2 * @file llmediadataclient.cpp
   3 * @brief class for queueing up requests for media data
   4 *
   5 * $LicenseInfo:firstyear=2001&license=viewerlgpl$
   6 * Second Life Viewer Source Code
   7 * Copyright (C) 2010, Linden Research, Inc.
   8 * 
   9 * This library is free software; you can redistribute it and/or
  10 * modify it under the terms of the GNU Lesser General Public
  11 * License as published by the Free Software Foundation;
  12 * version 2.1 of the License only.
  13 * 
  14 * This library is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  17 * Lesser General Public License for more details.
  18 * 
  19 * You should have received a copy of the GNU Lesser General Public
  20 * License along with this library; if not, write to the Free Software
  21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  22 * 
  23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  24 * $/LicenseInfo$
  25 */
  26
  27#include "llviewerprecompiledheaders.h"
  28
  29#include "llmediadataclient.h"
  30
  31#if LL_MSVC
  32// disable boost::lexical_cast warning
  33#pragma warning (disable:4702)
  34#endif
  35
  36#include <boost/lexical_cast.hpp>
  37
  38#include "llhttpstatuscodes.h"
  39#include "llsdutil.h"
  40#include "llmediaentry.h"
  41#include "lltextureentry.h"
  42#include "llviewerregion.h"
  43
  44//
  45// When making a request
  46// - obtain the "overall interest score" of the object.	 
  47//	 This would be the sum of the impls' interest scores.
  48// - put the request onto a queue sorted by this score 
  49//	 (highest score at the front of the queue)
  50// - On a timer, once a second, pull off the head of the queue and send 
  51//	 the request.
  52// - Any request that gets a 503 still goes through the retry logic
  53//
  54
  55/***************************************************************************************************************
  56	What's up with this queueing code?
  57
  58	First, a bit of background:
  59
  60	Media on a prim was added into the system in the Viewer 2.0 timeframe.  In order to avoid changing the 
  61	network format of objects, an unused field in the object (the "MediaURL" string) was repurposed to 
  62	indicate that the object had media data, and also hold a sequence number and the UUID of the agent 
  63	who last updated the data.  The actual media data for objects is accessed via the "ObjectMedia" capability.  
  64	Due to concerns about sim performance, requests to this capability are rate-limited to 5 requests every 
  65	5 seconds per agent.
  66
  67	The initial implementation of LLMediaDataClient used a single queue to manage requests to the "ObjectMedia" cap.  
  68	Requests to the cap were queued so that objects closer to the avatar were loaded in first, since they were most 
  69	likely to be the ones the media performance manager would load.
  70
  71	This worked in some cases, but we found that it was possible for a scripted object that constantly updated its 
  72	media data to starve other objects, since the same queue contained both requests to load previously unseen media 
  73	data and requests to fetch media data in response to object updates.
  74
  75	The solution for this we came up with was to have two queues.  The sorted queue contains requests to fetch media 
  76	data for objects that don't have it yet, and the round-robin queue contains requests to update media data for 
  77	objects that have already completed their initial load.  When both queues are non-empty, the code ping-pongs 
  78	between them so that updates can't completely block initial load-in.
  79**************************************************************************************************************/
  80
  81//
  82// Forward decls
  83//
  84const F32 LLMediaDataClient::QUEUE_TIMER_DELAY = 1.0; // seconds(s)
  85const F32 LLMediaDataClient::UNAVAILABLE_RETRY_TIMER_DELAY = 5.0; // secs
  86const U32 LLMediaDataClient::MAX_RETRIES = 4;
  87const U32 LLMediaDataClient::MAX_SORTED_QUEUE_SIZE = 10000;
  88const U32 LLMediaDataClient::MAX_ROUND_ROBIN_QUEUE_SIZE = 10000;
  89
  90// << operators
  91std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q);
  92std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &q);
  93
  94template <typename T>
  95static typename T::iterator find_matching_request(T &c, const LLMediaDataClient::Request *request, LLMediaDataClient::Request::Type match_type)
  96{
  97	for(typename T::iterator iter = c.begin(); iter != c.end(); ++iter)
  98	{
  99		if(request->isMatch(*iter, match_type))
 100		{
 101			return iter;
 102		}
 103	}
 104	
 105	return c.end();
 106}
 107
 108template <typename T>
 109static typename T::iterator find_matching_request(T &c, const LLUUID &id, LLMediaDataClient::Request::Type match_type)
 110{
 111	for(typename T::iterator iter = c.begin(); iter != c.end(); ++iter)
 112	{
 113		if(((*iter)->getID() == id) && ((match_type == LLMediaDataClient::Request::ANY) || (match_type == (*iter)->getType())))
 114		{
 115			return iter;
 116		}
 117	}
 118	
 119	return c.end();
 120}
 121
 122// NOTE: remove_matching_requests will not work correctly for containers where deleting an element may invalidate iterators
 123// to other elements in the container (such as std::vector).
 124// If the implementation is changed to use a container with this property, this will need to be revisited.
 125template <typename T>
 126static void remove_matching_requests(T &c, const LLUUID &id, LLMediaDataClient::Request::Type match_type)
 127{
 128	for(typename T::iterator iter = c.begin(); iter != c.end();)
 129	{
 130		typename T::value_type i = *iter;
 131		typename T::iterator next = iter;
 132		next++;
 133		if((i->getID() == id) && ((match_type == LLMediaDataClient::Request::ANY) || (match_type == i->getType())))
 134		{
 135			i->markDead();
 136			c.erase(iter);
 137		}
 138		iter = next;
 139	}
 140}
 141
 142//////////////////////////////////////////////////////////////////////////////////////
 143//
 144// LLMediaDataClient
 145//
 146//////////////////////////////////////////////////////////////////////////////////////
 147
 148LLMediaDataClient::LLMediaDataClient(F32 queue_timer_delay,
 149									 F32 retry_timer_delay,
 150									 U32 max_retries,
 151									 U32 max_sorted_queue_size,
 152									 U32 max_round_robin_queue_size)
 153	: mQueueTimerDelay(queue_timer_delay),
 154	  mRetryTimerDelay(retry_timer_delay),
 155	  mMaxNumRetries(max_retries),
 156	  mMaxSortedQueueSize(max_sorted_queue_size),
 157	  mMaxRoundRobinQueueSize(max_round_robin_queue_size),
 158	  mQueueTimerIsRunning(false)
 159{
 160}
 161
 162LLMediaDataClient::~LLMediaDataClient()
 163{
 164	stopQueueTimer();
 165}
 166
 167bool LLMediaDataClient::isEmpty() const
 168{
 169	return mQueue.empty();
 170}
 171
 172bool LLMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object)
 173{
 174	if(find_matching_request(mQueue, object->getID()) != mQueue.end())
 175		return true;
 176	
 177	if(find_matching_request(mUnQueuedRequests, object->getID()) != mUnQueuedRequests.end())
 178		return true;
 179	
 180	return false;
 181}
 182
 183void LLMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object)
 184{
 185	LL_DEBUGS("LLMediaDataClient") << "removing requests matching ID " << object->getID() << LL_ENDL;
 186	remove_matching_requests(mQueue, object->getID());
 187	remove_matching_requests(mUnQueuedRequests, object->getID());
 188}
 189
 190void LLMediaDataClient::startQueueTimer() 
 191{
 192	if (! mQueueTimerIsRunning)
 193	{
 194		LL_DEBUGS("LLMediaDataClient") << "starting queue timer (delay=" << mQueueTimerDelay << " seconds)" << LL_ENDL;
 195		// LLEventTimer automagically takes care of the lifetime of this object
 196		new QueueTimer(mQueueTimerDelay, this);
 197	}
 198	else { 
 199		LL_DEBUGS("LLMediaDataClient") << "queue timer is already running" << LL_ENDL;
 200	}
 201}
 202
 203void LLMediaDataClient::stopQueueTimer()
 204{
 205	mQueueTimerIsRunning = false;
 206}
 207
 208bool LLMediaDataClient::processQueueTimer()
 209{
 210	if(isEmpty())
 211		return true;
 212
 213	LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() started, queue size is:	  " << mQueue.size() << LL_ENDL;
 214	LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() started, SORTED queue is:	  " << mQueue << LL_ENDL;
 215			
 216	serviceQueue();
 217	
 218	LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() finished, queue size is:	  " << mQueue.size() << LL_ENDL;
 219	LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() finished, SORTED queue is:	  " << mQueue << LL_ENDL;
 220	
 221	return isEmpty();
 222}
 223
 224LLMediaDataClient::request_ptr_t LLMediaDataClient::dequeue()
 225{
 226	request_ptr_t request;
 227	request_queue_t *queue_p = getQueue();
 228	
 229	if (queue_p->empty())
 230	{
 231		LL_DEBUGS("LLMediaDataClient") << "queue empty: " << (*queue_p) << LL_ENDL;
 232	}
 233	else
 234	{
 235		request = queue_p->front();
 236		
 237		if(canServiceRequest(request))
 238		{
 239			// We will be returning this request, so remove it from the queue.
 240			queue_p->pop_front();
 241		}
 242		else
 243		{
 244			// Don't return this request -- it's not ready to be serviced.
 245			request = NULL;
 246		}
 247	}
 248
 249	return request;
 250}
 251
 252void LLMediaDataClient::pushBack(request_ptr_t request)
 253{
 254	request_queue_t *queue_p = getQueue();
 255	queue_p->push_front(request);
 256}
 257
 258void LLMediaDataClient::trackRequest(request_ptr_t request)
 259{
 260	request_set_t::iterator iter = mUnQueuedRequests.find(request);
 261	
 262	if(iter != mUnQueuedRequests.end())
 263	{
 264		LL_WARNS("LLMediaDataClient") << "Tracking already tracked request: " << *request << LL_ENDL;
 265	}
 266	else
 267	{
 268		mUnQueuedRequests.insert(request);
 269	}
 270}
 271
 272void LLMediaDataClient::stopTrackingRequest(request_ptr_t request)
 273{
 274	request_set_t::iterator iter = mUnQueuedRequests.find(request);
 275	
 276	if (iter != mUnQueuedRequests.end())
 277	{
 278		mUnQueuedRequests.erase(iter);
 279	}
 280	else
 281	{
 282		LL_WARNS("LLMediaDataClient") << "Removing an untracked request: " << *request << LL_ENDL;
 283	}
 284}
 285
 286void LLMediaDataClient::serviceQueue()
 287{	
 288	// Peel one off of the items from the queue and execute it
 289	request_ptr_t request;
 290	
 291	do
 292	{
 293		request = dequeue();
 294
 295		if(request.isNull())
 296		{
 297			// Queue is empty.
 298			return;
 299		}
 300
 301		if(request->isDead())
 302		{
 303			LL_INFOS("LLMediaDataClient") << "Skipping dead request " << *request << LL_ENDL;
 304			continue;
 305		}
 306
 307	} while(false);
 308		
 309	// try to send the HTTP message to the cap url
 310	std::string url = request->getCapability();
 311	if (!url.empty())
 312	{
 313		const LLSD &sd_payload = request->getPayload();
 314		LL_INFOS("LLMediaDataClient") << "Sending request for " << *request << LL_ENDL;
 315		
 316		// Add this request to the non-queued tracking list
 317		trackRequest(request);
 318		
 319		// and make the post
 320		LLHTTPClient::post(url, sd_payload, request->createResponder());
 321	}
 322	else 
 323	{
 324		// Cap url doesn't exist.
 325		
 326		if(request->getRetryCount() < mMaxNumRetries)
 327		{
 328			LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " (empty cap url), will retry." << LL_ENDL; 
 329			// Put this request back at the head of its queue, and retry next time the queue timer fires.
 330			request->incRetryCount();
 331			pushBack(request);
 332		}
 333		else
 334		{
 335			// This request has exceeded its maxumim retry count.  It will be dropped.
 336			LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " for " << mMaxNumRetries << " tries, dropping request." << LL_ENDL; 
 337		}
 338
 339	}
 340}
 341
 342
 343// dump the queue
 344std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q)
 345{
 346	int i = 0;
 347	LLMediaDataClient::request_queue_t::const_iterator iter = q.begin();
 348	LLMediaDataClient::request_queue_t::const_iterator end = q.end();
 349	while (iter != end)
 350	{
 351		s << "\t" << i << "]: " << (*iter)->getID().asString() << "(" << (*iter)->getObject()->getMediaInterest() << ")";
 352		iter++;
 353		i++;
 354	}
 355	return s;
 356}
 357
 358//////////////////////////////////////////////////////////////////////////////////////
 359//
 360// LLMediaDataClient::QueueTimer
 361// Queue of LLMediaDataClientObject smart pointers to request media for.
 362//
 363//////////////////////////////////////////////////////////////////////////////////////
 364
 365LLMediaDataClient::QueueTimer::QueueTimer(F32 time, LLMediaDataClient *mdc)
 366: LLEventTimer(time), mMDC(mdc)
 367{
 368	mMDC->setIsRunning(true);
 369}
 370
 371// virtual
 372BOOL LLMediaDataClient::QueueTimer::tick()
 373{
 374	BOOL result = TRUE;
 375	
 376	if (!mMDC.isNull())
 377	{
 378		result = mMDC->processQueueTimer();
 379	
 380		if(result)
 381		{
 382			// This timer won't fire again.  
 383			mMDC->setIsRunning(false);
 384			mMDC = NULL;
 385		}
 386	}
 387
 388	return result;
 389}
 390
 391
 392//////////////////////////////////////////////////////////////////////////////////////
 393//
 394// LLMediaDataClient::Responder::RetryTimer
 395//
 396//////////////////////////////////////////////////////////////////////////////////////
 397
 398LLMediaDataClient::RetryTimer::RetryTimer(F32 time, request_ptr_t request)
 399: LLEventTimer(time), mRequest(request)
 400{
 401	mRequest->startTracking();
 402}
 403
 404// virtual
 405BOOL LLMediaDataClient::RetryTimer::tick()
 406{
 407	mRequest->stopTracking();
 408
 409	if(mRequest->isDead())
 410	{
 411		LL_INFOS("LLMediaDataClient") << "RetryTimer fired for dead request: " << *mRequest << ", aborting." << LL_ENDL;
 412	}
 413	else
 414	{
 415		LL_INFOS("LLMediaDataClient") << "RetryTimer fired for: " << *mRequest << ", retrying." << LL_ENDL;
 416		mRequest->reEnqueue();
 417	}
 418	
 419	// Release the ref to the request.
 420	mRequest = NULL;
 421
 422	// Don't fire again
 423	return TRUE;
 424}
 425
 426
 427//////////////////////////////////////////////////////////////////////////////////////
 428//
 429// LLMediaDataClient::Request
 430//
 431//////////////////////////////////////////////////////////////////////////////////////
 432/*static*/U32 LLMediaDataClient::Request::sNum = 0;
 433
 434LLMediaDataClient::Request::Request(Type in_type,
 435									LLMediaDataClientObject *obj, 
 436									LLMediaDataClient *mdc,
 437									S32 face)
 438: mType(in_type),
 439  mObject(obj),
 440  mNum(++sNum), 
 441  mRetryCount(0),
 442  mMDC(mdc),
 443  mScore((F64)0.0),
 444  mFace(face)
 445{
 446	mObjectID = mObject->getID();
 447}
 448
 449const char *LLMediaDataClient::Request::getCapName() const
 450{
 451	if(mMDC)
 452		return mMDC->getCapabilityName();
 453	
 454	return "";
 455}
 456
 457std::string LLMediaDataClient::Request::getCapability() const
 458{
 459	if(mMDC)
 460	{
 461		return getObject()->getCapabilityUrl(getCapName());
 462	}
 463	
 464	return "";
 465}
 466
 467const char *LLMediaDataClient::Request::getTypeAsString() const
 468{
 469	Type t = getType();
 470	switch (t)
 471	{
 472		case GET:
 473			return "GET";
 474			break;
 475		case UPDATE:
 476			return "UPDATE";
 477			break;
 478		case NAVIGATE:
 479			return "NAVIGATE";
 480			break;
 481		case ANY:
 482			return "ANY";
 483			break;
 484	}
 485	return "";
 486}
 487
 488
 489void LLMediaDataClient::Request::reEnqueue()
 490{
 491	if(mMDC)
 492	{
 493		mMDC->enqueue(this);
 494	}
 495}
 496
 497F32 LLMediaDataClient::Request::getRetryTimerDelay() const
 498{
 499	if(mMDC)
 500		return mMDC->mRetryTimerDelay; 
 501		
 502	return 0.0f;
 503}
 504
 505U32 LLMediaDataClient::Request::getMaxNumRetries() const
 506{
 507	if(mMDC)
 508		return mMDC->mMaxNumRetries;
 509	
 510	return 0;
 511}
 512
 513void LLMediaDataClient::Request::updateScore()
 514{				
 515	F64 tmp = mObject->getMediaInterest();
 516	if (tmp != mScore)
 517	{
 518		LL_DEBUGS("LLMediaDataClient") << "Score for " << mObject->getID() << " changed from " << mScore << " to " << tmp << LL_ENDL; 
 519		mScore = tmp;
 520	}
 521}
 522		   
 523void LLMediaDataClient::Request::markDead() 
 524{ 
 525	mMDC = NULL;
 526}
 527
 528bool LLMediaDataClient::Request::isDead() 
 529{ 
 530	return ((mMDC == NULL) || mObject->isDead()); 
 531}
 532
 533void LLMediaDataClient::Request::startTracking() 
 534{ 
 535	if(mMDC) 
 536		mMDC->trackRequest(this); 
 537}
 538
 539void LLMediaDataClient::Request::stopTracking() 
 540{ 
 541	if(mMDC) 
 542		mMDC->stopTrackingRequest(this); 
 543}
 544
 545std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &r)
 546{
 547	s << "request: num=" << r.getNum() 
 548	<< " type=" << r.getTypeAsString() 
 549	<< " ID=" << r.getID() 
 550	<< " face=" << r.getFace() 
 551	<< " #retries=" << r.getRetryCount();
 552	return s;
 553}
 554			
 555//////////////////////////////////////////////////////////////////////////////////////
 556//
 557// LLMediaDataClient::Responder
 558//
 559//////////////////////////////////////////////////////////////////////////////////////
 560
 561LLMediaDataClient::Responder::Responder(const request_ptr_t &request)
 562: mRequest(request)
 563{
 564}
 565
 566/*virtual*/
 567void LLMediaDataClient::Responder::error(U32 status, const std::string& reason)
 568{
 569	mRequest->stopTracking();
 570
 571	if(mRequest->isDead())
 572	{
 573		LL_WARNS("LLMediaDataClient") << "dead request " << *mRequest << LL_ENDL;
 574		return;
 575	}
 576	
 577	if (status == HTTP_SERVICE_UNAVAILABLE)
 578	{
 579		F32 retry_timeout = mRequest->getRetryTimerDelay();
 580		
 581		mRequest->incRetryCount();
 582		
 583		if (mRequest->getRetryCount() < mRequest->getMaxNumRetries()) 
 584		{
 585			LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retrying in " << retry_timeout << " seconds" << LL_ENDL;
 586			
 587			// Start timer (instances are automagically tracked by
 588			// InstanceTracker<> and LLEventTimer)
 589			new RetryTimer(F32(retry_timeout/*secs*/), mRequest);
 590		}
 591		else 
 592		{
 593			LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retry count " 
 594				<< mRequest->getRetryCount() << " exceeds " << mRequest->getMaxNumRetries() << ", not retrying" << LL_ENDL;
 595		}
 596	}
 597	else 
 598	{
 599		std::string msg = boost::lexical_cast<std::string>(status) + ": " + reason;
 600		LL_WARNS("LLMediaDataClient") << *mRequest << " http error(" << msg << ")" << LL_ENDL;
 601	}
 602}
 603
 604/*virtual*/
 605void LLMediaDataClient::Responder::result(const LLSD& content)
 606{
 607	mRequest->stopTracking();
 608
 609	if(mRequest->isDead())
 610	{
 611		LL_WARNS("LLMediaDataClient") << "dead request " << *mRequest << LL_ENDL;
 612		return;
 613	}
 614
 615	LL_DEBUGS("LLMediaDataClientResponse") << *mRequest << " result : " << ll_print_sd(content) << LL_ENDL;
 616}
 617
 618//////////////////////////////////////////////////////////////////////////////////////
 619//
 620// LLObjectMediaDataClient
 621// Subclass of LLMediaDataClient for the ObjectMedia cap
 622//
 623//////////////////////////////////////////////////////////////////////////////////////
 624
 625void LLObjectMediaDataClient::fetchMedia(LLMediaDataClientObject *object)
 626{
 627	// Create a get request and put it in the queue.
 628	enqueue(new RequestGet(object, this));
 629}
 630
 631const char *LLObjectMediaDataClient::getCapabilityName() const 
 632{
 633	return "ObjectMedia";
 634}
 635
 636LLObjectMediaDataClient::request_queue_t *LLObjectMediaDataClient::getQueue()
 637{
 638	return (mCurrentQueueIsTheSortedQueue) ? &mQueue : &mRoundRobinQueue;
 639}
 640
 641void LLObjectMediaDataClient::sortQueue()
 642{
 643	if(!mQueue.empty())
 644	{
 645		// score all elements in the sorted queue.
 646		for(request_queue_t::iterator iter = mQueue.begin(); iter != mQueue.end(); iter++)
 647		{
 648			(*iter)->updateScore();
 649		}
 650		
 651		// Re-sort the list...
 652		mQueue.sort(compareRequestScores);
 653		
 654		// ...then cull items over the max
 655		U32 size = mQueue.size();
 656		if (size > mMaxSortedQueueSize) 
 657		{
 658			U32 num_to_cull = (size - mMaxSortedQueueSize);
 659			LL_INFOS_ONCE("LLMediaDataClient") << "sorted queue MAXED OUT!  Culling " 
 660				<< num_to_cull << " items" << LL_ENDL;
 661			while (num_to_cull-- > 0)
 662			{
 663				mQueue.back()->markDead();
 664				mQueue.pop_back();
 665			}
 666		}
 667	}
 668	
 669}
 670
 671// static
 672bool LLObjectMediaDataClient::compareRequestScores(const request_ptr_t &o1, const request_ptr_t &o2)
 673{
 674	if (o2.isNull()) return true;
 675	if (o1.isNull()) return false;
 676	return ( o1->getScore() > o2->getScore() );
 677}
 678
 679void LLObjectMediaDataClient::enqueue(Request *request)
 680{
 681	if(request->isDead())
 682	{
 683		LL_DEBUGS("LLMediaDataClient") << "not queueing dead request " << *request << LL_ENDL;
 684		return;
 685	}
 686
 687	// Invariants:
 688	// new requests always go into the sorted queue.
 689	//  
 690	
 691	bool is_new = request->isNew();
 692	
 693	if(!is_new && (request->getType() == Request::GET))
 694	{
 695		// For GET requests that are not new, if a matching request is already in the round robin queue, 
 696		// in flight, or being retried, leave it at its current position.
 697		request_queue_t::iterator iter = find_matching_request(mRoundRobinQueue, request->getID(), Request::GET);
 698		request_set_t::iterator iter2 = find_matching_request(mUnQueuedRequests, request->getID(), Request::GET);
 699		
 700		if( (iter != mRoundRobinQueue.end()) || (iter2 != mUnQueuedRequests.end()) )
 701		{
 702			LL_DEBUGS("LLMediaDataClient") << "ALREADY THERE: NOT Queuing request for " << *request << LL_ENDL;
 703
 704			return;
 705		}
 706	}
 707	
 708	// TODO: should an UPDATE cause pending GET requests for the same object to be removed from the queue?
 709	// IF the update will cause an object update message to be sent out at some point in the future, it probably should.
 710	
 711	// Remove any existing requests of this type for this object
 712	remove_matching_requests(mQueue, request->getID(), request->getType());
 713	remove_matching_requests(mRoundRobinQueue, request->getID(), request->getType());
 714	remove_matching_requests(mUnQueuedRequests, request->getID(), request->getType());
 715
 716	if (is_new)
 717	{
 718		LL_DEBUGS("LLMediaDataClient") << "Queuing SORTED request for " << *request << LL_ENDL;
 719		
 720		mQueue.push_back(request);
 721		
 722		LL_DEBUGS("LLMediaDataClientQueue") << "SORTED queue:" << mQueue << LL_ENDL;
 723	}
 724	else
 725	{
 726		if (mRoundRobinQueue.size() > mMaxRoundRobinQueueSize) 
 727		{
 728			LL_INFOS_ONCE("LLMediaDataClient") << "RR QUEUE MAXED OUT!!!" << LL_ENDL;
 729			LL_DEBUGS("LLMediaDataClient") << "Not queuing " << *request << LL_ENDL;
 730			return;
 731		}
 732				
 733		LL_DEBUGS("LLMediaDataClient") << "Queuing RR request for " << *request << LL_ENDL;
 734		// Push the request on the pending queue
 735		mRoundRobinQueue.push_back(request);
 736		
 737		LL_DEBUGS("LLMediaDataClientQueue") << "RR queue:" << mRoundRobinQueue << LL_ENDL;			
 738	}	
 739	// Start the timer if not already running
 740	startQueueTimer();
 741}
 742
 743bool LLObjectMediaDataClient::canServiceRequest(request_ptr_t request) 
 744{
 745	if(mCurrentQueueIsTheSortedQueue)
 746	{
 747		if(!request->getObject()->isInterestingEnough())
 748		{
 749			LL_DEBUGS("LLMediaDataClient") << "Not fetching " << *request << ": not interesting enough" << LL_ENDL;
 750			return false;
 751		}
 752	}
 753	
 754	return true; 
 755};
 756
 757void LLObjectMediaDataClient::swapCurrentQueue()
 758{
 759	// Swap
 760	mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue;
 761	// If its empty, swap back
 762	if (getQueue()->empty()) 
 763	{
 764		mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue;
 765	}
 766}
 767
 768bool LLObjectMediaDataClient::isEmpty() const
 769{
 770	return mQueue.empty() && mRoundRobinQueue.empty();
 771}
 772
 773bool LLObjectMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object)
 774{
 775	// First, call parent impl.
 776	if(LLMediaDataClient::isInQueue(object))
 777		return true;
 778
 779	if(find_matching_request(mRoundRobinQueue, object->getID()) != mRoundRobinQueue.end())
 780		return true;
 781	
 782	return false;
 783}
 784
 785void LLObjectMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object)
 786{
 787	// First, call parent impl.
 788	LLMediaDataClient::removeFromQueue(object);
 789	
 790	remove_matching_requests(mRoundRobinQueue, object->getID());
 791}
 792
 793bool LLObjectMediaDataClient::processQueueTimer()
 794{
 795	if(isEmpty())
 796		return true;
 797		
 798	LL_DEBUGS("LLMediaDataClient") << "started, SORTED queue size is:	  " << mQueue.size() 
 799		<< ", RR queue size is:	  " << mRoundRobinQueue.size() << LL_ENDL;
 800	LL_DEBUGS("LLMediaDataClientQueue") << "    SORTED queue is:	  " << mQueue << LL_ENDL;
 801	LL_DEBUGS("LLMediaDataClientQueue") << "    RR queue is:	  " << mRoundRobinQueue << LL_ENDL;
 802
 803//	purgeDeadRequests();
 804
 805	sortQueue();
 806
 807	LL_DEBUGS("LLMediaDataClientQueue") << "after sort, SORTED queue is:	  " << mQueue << LL_ENDL;
 808	
 809	serviceQueue();
 810
 811	swapCurrentQueue();
 812	
 813	LL_DEBUGS("LLMediaDataClient") << "finished, SORTED queue size is:	  " << mQueue.size() 
 814		<< ", RR queue size is:	  " << mRoundRobinQueue.size() << LL_ENDL;
 815	LL_DEBUGS("LLMediaDataClientQueue") << "    SORTED queue is:	  " << mQueue << LL_ENDL;
 816	LL_DEBUGS("LLMediaDataClientQueue") << "    RR queue is:	  " << mRoundRobinQueue << LL_ENDL;
 817	
 818	return isEmpty();
 819}
 820
 821LLObjectMediaDataClient::RequestGet::RequestGet(LLMediaDataClientObject *obj, LLMediaDataClient *mdc):
 822	LLMediaDataClient::Request(LLMediaDataClient::Request::GET, obj, mdc)
 823{
 824}
 825
 826LLSD LLObjectMediaDataClient::RequestGet::getPayload() const
 827{
 828	LLSD result;
 829	result["verb"] = "GET";
 830	result[LLTextureEntry::OBJECT_ID_KEY] = mObject->getID();
 831	
 832	return result;
 833}
 834
 835LLMediaDataClient::Responder *LLObjectMediaDataClient::RequestGet::createResponder()
 836{
 837	return new LLObjectMediaDataClient::Responder(this);
 838}
 839
 840
 841void LLObjectMediaDataClient::updateMedia(LLMediaDataClientObject *object)
 842{
 843	// Create an update request and put it in the queue.
 844	enqueue(new RequestUpdate(object, this));
 845}
 846
 847LLObjectMediaDataClient::RequestUpdate::RequestUpdate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc):
 848	LLMediaDataClient::Request(LLMediaDataClient::Request::UPDATE, obj, mdc)
 849{
 850}
 851
 852LLSD LLObjectMediaDataClient::RequestUpdate::getPayload() const
 853{
 854	LLSD result;
 855	result["verb"] = "UPDATE";
 856	result[LLTextureEntry::OBJECT_ID_KEY] = mObject->getID();
 857
 858	LLSD object_media_data;
 859	int i = 0;
 860	int end = mObject->getMediaDataCount();
 861	for ( ; i < end ; ++i) 
 862	{
 863		object_media_data.append(mObject->getMediaDataLLSD(i));
 864	}
 865	
 866	result[LLTextureEntry::OBJECT_MEDIA_DATA_KEY] = object_media_data;
 867	
 868	return result;
 869}
 870
 871LLMediaDataClient::Responder *LLObjectMediaDataClient::RequestUpdate::createResponder()
 872{
 873	// This just uses the base class's responder.
 874	return new LLMediaDataClient::Responder(this);
 875}
 876
 877
 878/*virtual*/
 879void LLObjectMediaDataClient::Responder::result(const LLSD& content)
 880{
 881	getRequest()->stopTracking();
 882
 883	if(getRequest()->isDead())
 884	{
 885		LL_WARNS("LLMediaDataClient") << "dead request " << *(getRequest()) << LL_ENDL;
 886		return;
 887	}
 888
 889	// This responder is only used for GET requests, not UPDATE.
 890
 891	LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << " GET returned: " << ll_print_sd(content) << LL_ENDL;
 892	
 893	// Look for an error
 894	if (content.has("error"))
 895	{
 896		const LLSD &error = content["error"];
 897		LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error getting media data for object: code=" << 
 898			error["code"].asString() << ": " << error["message"].asString() << LL_ENDL;
 899		
 900		// XXX Warn user?
 901	}
 902	else 
 903	{
 904		// Check the data
 905		const LLUUID &object_id = content[LLTextureEntry::OBJECT_ID_KEY];
 906		if (object_id != getRequest()->getObject()->getID()) 
 907		{
 908			// NOT good, wrong object id!!
 909			LL_WARNS("LLMediaDataClient") << *(getRequest()) << " DROPPING response with wrong object id (" << object_id << ")" << LL_ENDL;
 910			return;
 911		}
 912		
 913		// Otherwise, update with object media data
 914		getRequest()->getObject()->updateObjectMediaData(content[LLTextureEntry::OBJECT_MEDIA_DATA_KEY],
 915														 content[LLTextureEntry::MEDIA_VERSION_KEY]);
 916	}
 917}
 918
 919//////////////////////////////////////////////////////////////////////////////////////
 920//
 921// LLObjectMediaNavigateClient
 922// Subclass of LLMediaDataClient for the ObjectMediaNavigate cap
 923//
 924//////////////////////////////////////////////////////////////////////////////////////
 925
 926const char *LLObjectMediaNavigateClient::getCapabilityName() const 
 927{
 928	return "ObjectMediaNavigate";
 929}
 930
 931void LLObjectMediaNavigateClient::enqueue(Request *request)
 932{
 933	if(request->isDead())
 934	{
 935		LL_DEBUGS("LLMediaDataClient") << "not queueing dead request " << *request << LL_ENDL;
 936		return;
 937	}
 938	
 939	// If there's already a matching request in the queue, remove it.
 940	request_queue_t::iterator iter = find_matching_request(mQueue, request);
 941	if(iter != mQueue.end())
 942	{
 943		LL_DEBUGS("LLMediaDataClient") << "removing matching queued request " << (**iter) << LL_ENDL;
 944		mQueue.erase(iter);
 945	}
 946	else
 947	{
 948		request_set_t::iterator set_iter = find_matching_request(mUnQueuedRequests, request);
 949		if(set_iter != mUnQueuedRequests.end())
 950		{
 951			LL_DEBUGS("LLMediaDataClient") << "removing matching unqueued request " << (**set_iter) << LL_ENDL;
 952			mUnQueuedRequests.erase(set_iter);
 953		}
 954	}
 955
 956#if 0
 957	// Sadly, this doesn't work.  It ends up creating a race condition when the user navigates and then hits the "back" button
 958	// where the navigate-back appears to be spurious and doesn't get broadcast.	
 959	if(request->getObject()->isCurrentMediaUrl(request->getFace(), request->getURL()))
 960	{
 961		// This navigate request is trying to send the face to the current URL.  Drop it.
 962		LL_DEBUGS("LLMediaDataClient") << "dropping spurious request " << (*request) << LL_ENDL;
 963	}
 964	else
 965#endif
 966	{
 967		LL_DEBUGS("LLMediaDataClient") << "queueing new request " << (*request) << LL_ENDL;
 968		mQueue.push_back(request);
 969		
 970		// Start the timer if not already running
 971		startQueueTimer();
 972	}
 973}
 974
 975void LLObjectMediaNavigateClient::navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url)
 976{
 977
 978//	LL_INFOS("LLMediaDataClient") << "navigate() initiated: " << ll_print_sd(sd_payload) << LL_ENDL;
 979	
 980	// Create a get request and put it in the queue.
 981	enqueue(new RequestNavigate(object, this, texture_index, url));
 982}
 983
 984LLObjectMediaNavigateClient::RequestNavigate::RequestNavigate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc, U8 texture_index, const std::string &url):
 985	LLMediaDataClient::Request(LLMediaDataClient::Request::NAVIGATE, obj, mdc, (S32)texture_index),
 986	mURL(url)
 987{
 988}
 989
 990LLSD LLObjectMediaNavigateClient::RequestNavigate::getPayload() const
 991{
 992	LLSD result;
 993	result[LLTextureEntry::OBJECT_ID_KEY] = getID();
 994	result[LLMediaEntry::CURRENT_URL_KEY] = mURL;
 995	result[LLTextureEntry::TEXTURE_INDEX_KEY] = (LLSD::Integer)getFace();
 996	
 997	return result;
 998}
 999
1000LLMediaDataClient::Responder *LLObjectMediaNavigateClient::RequestNavigate::createResponder()
1001{
1002	return new LLObjectMediaNavigateClient::Responder(this);
1003}
1004
1005/*virtual*/
1006void LLObjectMediaNavigateClient::Responder::error(U32 status, const std::string& reason)
1007{
1008	getRequest()->stopTracking();
1009
1010	if(getRequest()->isDead())
1011	{
1012		LL_WARNS("LLMediaDataClient") << "dead request " << *(getRequest()) << LL_ENDL;
1013		return;
1014	}
1015
1016	// Bounce back (unless HTTP_SERVICE_UNAVAILABLE, in which case call base
1017	// class
1018	if (status == HTTP_SERVICE_UNAVAILABLE)
1019	{
1020		LLMediaDataClient::Responder::error(status, reason);
1021	}
1022	else
1023	{
1024		// bounce the face back
1025		LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating: http code=" << status << LL_ENDL;
1026		const LLSD &payload = getRequest()->getPayload();
1027		// bounce the face back
1028		getRequest()->getObject()->mediaNavigateBounceBack((LLSD::Integer)payload[LLTextureEntry::TEXTURE_INDEX_KEY]);
1029	}
1030}
1031
1032/*virtual*/
1033void LLObjectMediaNavigateClient::Responder::result(const LLSD& content)
1034{
1035	getRequest()->stopTracking();
1036
1037	if(getRequest()->isDead())
1038	{
1039		LL_WARNS("LLMediaDataClient") << "dead request " << *(getRequest()) << LL_ENDL;
1040		return;
1041	}
1042
1043	LL_INFOS("LLMediaDataClient") << *(getRequest()) << " NAVIGATE returned " << ll_print_sd(content) << LL_ENDL;
1044	
1045	if (content.has("error"))
1046	{
1047		const LLSD &error = content["error"];
1048		int error_code = error["code"];
1049		
1050		if (ERROR_PERMISSION_DENIED_CODE == error_code)
1051		{
1052			LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Navigation denied: bounce back" << LL_ENDL;
1053			const LLSD &payload = getRequest()->getPayload();
1054			// bounce the face back
1055			getRequest()->getObject()->mediaNavigateBounceBack((LLSD::Integer)payload[LLTextureEntry::TEXTURE_INDEX_KEY]);
1056		}
1057		else
1058		{
1059			LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating: code=" << 
1060				error["code"].asString() << ": " << error["message"].asString() << LL_ENDL;
1061		}			 
1062
1063		// XXX Warn user?
1064	}
1065	else 
1066	{
1067		// No action required.
1068		LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << " result : " << ll_print_sd(content) << LL_ENDL;
1069	}
1070}