/indra/newview/llmediadataclient.cpp
C++ | 1070 lines | 722 code | 176 blank | 172 comment | 106 complexity | fab296ae8ceaefb9a25fa3d64843bf43 MD5 | raw file
Possible License(s): LGPL-2.1
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}