diff options
Diffstat (limited to 'linden')
-rw-r--r-- | linden/indra/newview/llmediadataclient.cpp | 813 | ||||
-rw-r--r-- | linden/indra/newview/llmediadataclient.h | 341 | ||||
-rw-r--r-- | linden/indra/newview/rcmoapradar.cpp | 440 | ||||
-rw-r--r-- | linden/indra/newview/rcmoapradar.h | 58 | ||||
-rw-r--r-- | linden/indra/newview/skins/default/xui/en-us/floater_moap_radar.xml | 54 |
5 files changed, 1706 insertions, 0 deletions
diff --git a/linden/indra/newview/llmediadataclient.cpp b/linden/indra/newview/llmediadataclient.cpp new file mode 100644 index 0000000..e2fa700 --- /dev/null +++ b/linden/indra/newview/llmediadataclient.cpp | |||
@@ -0,0 +1,813 @@ | |||
1 | /** | ||
2 | * @file llmediadataclient.cpp | ||
3 | * @brief class for queueing up requests for media data | ||
4 | * | ||
5 | * $LicenseInfo:firstyear=2001&license=viewergpl$ | ||
6 | * | ||
7 | * Copyright (c) 2001-2010, Linden Research, Inc. | ||
8 | * | ||
9 | * Second Life Viewer Source Code | ||
10 | * The source code in this file ("Source Code") is provided by Linden Lab | ||
11 | * to you under the terms of the GNU General Public License, version 2.0 | ||
12 | * ("GPL"), unless you have obtained a separate licensing agreement | ||
13 | * ("Other License"), formally executed by you and Linden Lab. Terms of | ||
14 | * the GPL can be found in doc/GPL-license.txt in this distribution, or | ||
15 | * online at http://secondlife.com/developers/opensource/gplv2 | ||
16 | * | ||
17 | * There are special exceptions to the terms and conditions of the GPL as | ||
18 | * it is applied to this Source Code. View the full text of the exception | ||
19 | * in the file doc/FLOSS-exception.txt in this software distribution, or | ||
20 | * online at | ||
21 | * http://secondlife.com/developers/opensource/flossexception | ||
22 | * | ||
23 | * By copying, modifying or distributing this software, you acknowledge | ||
24 | * that you have read and understood your obligations described above, | ||
25 | * and agree to abide by those obligations. | ||
26 | * | ||
27 | * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | ||
28 | * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
29 | * COMPLETENESS OR PERFORMANCE. | ||
30 | * $/LicenseInfo$ | ||
31 | * | ||
32 | */ | ||
33 | |||
34 | #include "llviewerprecompiledheaders.h" | ||
35 | |||
36 | #include "llmediadataclient.h" | ||
37 | |||
38 | #if LL_MSVC | ||
39 | // disable boost::lexical_cast warning | ||
40 | #pragma warning (disable:4702) | ||
41 | #endif | ||
42 | |||
43 | #include <boost/lexical_cast.hpp> | ||
44 | |||
45 | #include "llhttpstatuscodes.h" | ||
46 | #include "llsdutil.h" | ||
47 | #include "llmediaentry.h" | ||
48 | #include "lltextureentry.h" | ||
49 | #include "llviewerregion.h" | ||
50 | |||
51 | // | ||
52 | // When making a request | ||
53 | // - obtain the "overall interest score" of the object. | ||
54 | // This would be the sum of the impls' interest scores. | ||
55 | // - put the request onto a queue sorted by this score | ||
56 | // (highest score at the front of the queue) | ||
57 | // - On a timer, once a second, pull off the head of the queue and send | ||
58 | // the request. | ||
59 | // - Any request that gets a 503 still goes through the retry logic | ||
60 | // | ||
61 | |||
62 | // | ||
63 | // Forward decls | ||
64 | // | ||
65 | const F32 LLMediaDataClient::QUEUE_TIMER_DELAY = 1.0; // seconds(s) | ||
66 | const F32 LLMediaDataClient::UNAVAILABLE_RETRY_TIMER_DELAY = 10.0; // secs | ||
67 | const U32 LLMediaDataClient::MAX_RETRIES = 10; | ||
68 | const U32 LLMediaDataClient::MAX_SORTED_QUEUE_SIZE = 10000; | ||
69 | const U32 LLMediaDataClient::MAX_ROUND_ROBIN_QUEUE_SIZE = 10000; | ||
70 | |||
71 | // << operators | ||
72 | std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q); | ||
73 | std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &q); | ||
74 | |||
75 | ////////////////////////////////////////////////////////////////////////////////////// | ||
76 | // | ||
77 | // LLMediaDataClient | ||
78 | // | ||
79 | ////////////////////////////////////////////////////////////////////////////////////// | ||
80 | |||
81 | LLMediaDataClient::LLMediaDataClient(F32 queue_timer_delay, | ||
82 | F32 retry_timer_delay, | ||
83 | U32 max_retries, | ||
84 | U32 max_sorted_queue_size, | ||
85 | U32 max_round_robin_queue_size) | ||
86 | : mQueueTimerDelay(queue_timer_delay), | ||
87 | mRetryTimerDelay(retry_timer_delay), | ||
88 | mMaxNumRetries(max_retries), | ||
89 | mMaxSortedQueueSize(max_sorted_queue_size), | ||
90 | mMaxRoundRobinQueueSize(max_round_robin_queue_size), | ||
91 | mQueueTimerIsRunning(false), | ||
92 | mCurrentQueueIsTheSortedQueue(true) | ||
93 | { | ||
94 | } | ||
95 | |||
96 | LLMediaDataClient::~LLMediaDataClient() | ||
97 | { | ||
98 | stopQueueTimer(); | ||
99 | |||
100 | // This should clear the queue, and hopefully call all the destructors. | ||
101 | LL_DEBUGS("LLMediaDataClient") << "~LLMediaDataClient destructor: queue: " << | ||
102 | (isEmpty() ? "<empty> " : "<not empty> ") << LL_ENDL; | ||
103 | |||
104 | mSortedQueue.clear(); | ||
105 | mRoundRobinQueue.clear(); | ||
106 | } | ||
107 | |||
108 | bool LLMediaDataClient::isEmpty() const | ||
109 | { | ||
110 | return mSortedQueue.empty() && mRoundRobinQueue.empty(); | ||
111 | } | ||
112 | |||
113 | bool LLMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object) | ||
114 | { | ||
115 | return (LLMediaDataClient::findOrRemove(mSortedQueue, object, false/*remove*/, LLMediaDataClient::Request::ANY).notNull() | ||
116 | || (LLMediaDataClient::findOrRemove(mRoundRobinQueue, object, false/*remove*/, LLMediaDataClient::Request::ANY).notNull())); | ||
117 | } | ||
118 | |||
119 | bool LLMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object) | ||
120 | { | ||
121 | bool removedFromSortedQueue = LLMediaDataClient::findOrRemove(mSortedQueue, object, true/*remove*/, LLMediaDataClient::Request::ANY).notNull(); | ||
122 | bool removedFromRoundRobinQueue = LLMediaDataClient::findOrRemove(mRoundRobinQueue, object, true/*remove*/, LLMediaDataClient::Request::ANY).notNull(); | ||
123 | return removedFromSortedQueue || removedFromRoundRobinQueue; | ||
124 | } | ||
125 | |||
126 | //static | ||
127 | LLMediaDataClient::request_ptr_t LLMediaDataClient::findOrRemove(request_queue_t &queue, const LLMediaDataClientObject::ptr_t &obj, bool remove, LLMediaDataClient::Request::Type type) | ||
128 | { | ||
129 | request_ptr_t result; | ||
130 | request_queue_t::iterator iter = queue.begin(); | ||
131 | request_queue_t::iterator end = queue.end(); | ||
132 | while (iter != end) | ||
133 | { | ||
134 | if (obj->getID() == (*iter)->getObject()->getID() && (type == LLMediaDataClient::Request::ANY || type == (*iter)->getType())) | ||
135 | { | ||
136 | result = *iter; | ||
137 | if (remove) queue.erase(iter); | ||
138 | break; | ||
139 | } | ||
140 | iter++; | ||
141 | } | ||
142 | return result; | ||
143 | } | ||
144 | |||
145 | void LLMediaDataClient::request(const LLMediaDataClientObject::ptr_t &object, const LLSD &payload) | ||
146 | { | ||
147 | if (object.isNull() || ! object->hasMedia()) return; | ||
148 | |||
149 | // Push the object on the queue | ||
150 | enqueue(new Request(getCapabilityName(), payload, object, this)); | ||
151 | } | ||
152 | |||
153 | void LLMediaDataClient::enqueue(const Request *request) | ||
154 | { | ||
155 | if (request->isNew()) | ||
156 | { | ||
157 | // Add to sorted queue | ||
158 | if (LLMediaDataClient::findOrRemove(mSortedQueue, request->getObject(), true/*remove*/, request->getType()).notNull()) | ||
159 | { | ||
160 | LL_DEBUGS("LLMediaDataClient") << "REMOVING OLD request for " << *request << " ALREADY THERE!" << LL_ENDL; | ||
161 | } | ||
162 | |||
163 | LL_DEBUGS("LLMediaDataClient") << "Queuing SORTED request for " << *request << LL_ENDL; | ||
164 | |||
165 | // Sadly, we have to const-cast because items put into the queue are not const | ||
166 | mSortedQueue.push_back(const_cast<LLMediaDataClient::Request*>(request)); | ||
167 | |||
168 | LL_DEBUGS("LLMediaDataClientQueue") << "SORTED queue:" << mSortedQueue << LL_ENDL; | ||
169 | } | ||
170 | else { | ||
171 | if (mRoundRobinQueue.size() > mMaxRoundRobinQueueSize) | ||
172 | { | ||
173 | LL_INFOS_ONCE("LLMediaDataClient") << "RR QUEUE MAXED OUT!!!" << LL_ENDL; | ||
174 | LL_DEBUGS("LLMediaDataClient") << "Not queuing " << *request << LL_ENDL; | ||
175 | return; | ||
176 | } | ||
177 | |||
178 | // ROUND ROBIN: if it is there, and it is a GET request, leave it. If not, put at front! | ||
179 | request_ptr_t existing_request; | ||
180 | if (request->getType() == Request::GET) | ||
181 | { | ||
182 | existing_request = LLMediaDataClient::findOrRemove(mRoundRobinQueue, request->getObject(), false/*remove*/, request->getType()); | ||
183 | } | ||
184 | if (existing_request.isNull()) | ||
185 | { | ||
186 | LL_DEBUGS("LLMediaDataClient") << "Queuing RR request for " << *request << LL_ENDL; | ||
187 | // Push the request on the pending queue | ||
188 | // Sadly, we have to const-cast because items put into the queue are not const | ||
189 | mRoundRobinQueue.push_front(const_cast<LLMediaDataClient::Request*>(request)); | ||
190 | |||
191 | LL_DEBUGS("LLMediaDataClientQueue") << "RR queue:" << mRoundRobinQueue << LL_ENDL; | ||
192 | } | ||
193 | else | ||
194 | { | ||
195 | LL_DEBUGS("LLMediaDataClient") << "ALREADY THERE: NOT Queuing request for " << *request << LL_ENDL; | ||
196 | |||
197 | existing_request->markSent(false); | ||
198 | } | ||
199 | } | ||
200 | // Start the timer if not already running | ||
201 | startQueueTimer(); | ||
202 | } | ||
203 | |||
204 | void LLMediaDataClient::startQueueTimer() | ||
205 | { | ||
206 | if (! mQueueTimerIsRunning) | ||
207 | { | ||
208 | LL_DEBUGS("LLMediaDataClient") << "starting queue timer (delay=" << mQueueTimerDelay << " seconds)" << LL_ENDL; | ||
209 | // LLEventTimer automagically takes care of the lifetime of this object | ||
210 | new QueueTimer(mQueueTimerDelay, this); | ||
211 | } | ||
212 | else { | ||
213 | LL_DEBUGS("LLMediaDataClient") << "not starting queue timer (it's already running, right???)" << LL_ENDL; | ||
214 | } | ||
215 | } | ||
216 | |||
217 | void LLMediaDataClient::stopQueueTimer() | ||
218 | { | ||
219 | mQueueTimerIsRunning = false; | ||
220 | } | ||
221 | |||
222 | bool LLMediaDataClient::processQueueTimer() | ||
223 | { | ||
224 | sortQueue(); | ||
225 | |||
226 | if(!isEmpty()) | ||
227 | { | ||
228 | LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() started, SORTED queue size is: " << mSortedQueue.size() | ||
229 | << ", RR queue size is: " << mRoundRobinQueue.size() << LL_ENDL; | ||
230 | LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() started, SORTED queue is: " << mSortedQueue << LL_ENDL; | ||
231 | LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() started, RR queue is: " << mRoundRobinQueue << LL_ENDL; | ||
232 | } | ||
233 | |||
234 | serviceQueue(); | ||
235 | |||
236 | LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() finished, SORTED queue size is: " << mSortedQueue.size() | ||
237 | << ", RR queue size is: " << mRoundRobinQueue.size() << LL_ENDL; | ||
238 | LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() finished, SORTED queue is: " << mSortedQueue << LL_ENDL; | ||
239 | LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() finished, RR queue is: " << mRoundRobinQueue << LL_ENDL; | ||
240 | |||
241 | return isEmpty(); | ||
242 | } | ||
243 | |||
244 | void LLMediaDataClient::sortQueue() | ||
245 | { | ||
246 | if(!mSortedQueue.empty()) | ||
247 | { | ||
248 | // Score all items first | ||
249 | request_queue_t::iterator iter = mSortedQueue.begin(); | ||
250 | request_queue_t::iterator end = mSortedQueue.end(); | ||
251 | while (iter != end) | ||
252 | { | ||
253 | (*iter)->updateScore(); | ||
254 | iter++; | ||
255 | } | ||
256 | |||
257 | // Re-sort the list... | ||
258 | // NOTE: should this be a stable_sort? If so we need to change to using a vector. | ||
259 | mSortedQueue.sort(LLMediaDataClient::compareRequests); | ||
260 | |||
261 | // ...then cull items over the max | ||
262 | U32 size = mSortedQueue.size(); | ||
263 | if (size > mMaxSortedQueueSize) | ||
264 | { | ||
265 | U32 num_to_cull = (size - mMaxSortedQueueSize); | ||
266 | LL_INFOS_ONCE("LLMediaDataClient") << "sorted queue MAXED OUT! Culling " | ||
267 | << num_to_cull << " items" << LL_ENDL; | ||
268 | while (num_to_cull-- > 0) | ||
269 | { | ||
270 | mSortedQueue.pop_back(); | ||
271 | } | ||
272 | } | ||
273 | } | ||
274 | } | ||
275 | |||
276 | // static | ||
277 | bool LLMediaDataClient::compareRequests(const request_ptr_t &o1, const request_ptr_t &o2) | ||
278 | { | ||
279 | if (o2.isNull()) return true; | ||
280 | if (o1.isNull()) return false; | ||
281 | return ( o1->getScore() > o2->getScore() ); | ||
282 | } | ||
283 | |||
284 | void LLMediaDataClient::serviceQueue() | ||
285 | { | ||
286 | request_queue_t *queue_p = getCurrentQueue(); | ||
287 | |||
288 | // quick retry loop for cases where we shouldn't wait for the next timer tick | ||
289 | while(true) | ||
290 | { | ||
291 | if (queue_p->empty()) | ||
292 | { | ||
293 | LL_DEBUGS("LLMediaDataClient") << "queue empty: " << (*queue_p) << LL_ENDL; | ||
294 | break; | ||
295 | } | ||
296 | |||
297 | // Peel one off of the items from the queue, and execute request | ||
298 | request_ptr_t request = queue_p->front(); | ||
299 | llassert(!request.isNull()); | ||
300 | const LLMediaDataClientObject *object = (request.isNull()) ? NULL : request->getObject(); | ||
301 | llassert(NULL != object); | ||
302 | |||
303 | // Check for conditions that would make us just pop and rapidly loop through | ||
304 | // the queue. | ||
305 | if(request.isNull() || | ||
306 | request->isMarkedSent() || | ||
307 | NULL == object || | ||
308 | object->isDead() || | ||
309 | !object->hasMedia()) | ||
310 | { | ||
311 | if (request.isNull()) | ||
312 | { | ||
313 | LL_WARNS("LLMediaDataClient") << "Skipping NULL request" << LL_ENDL; | ||
314 | } | ||
315 | else { | ||
316 | LL_INFOS("LLMediaDataClient") << "Skipping : " << *request << " " | ||
317 | << ((request->isMarkedSent()) ? " request is marked sent" : | ||
318 | ((NULL == object) ? " object is NULL " : | ||
319 | ((object->isDead()) ? "object is dead" : | ||
320 | ((!object->hasMedia()) ? "object has no media!" : "BADNESS!")))) << LL_ENDL; | ||
321 | } | ||
322 | queue_p->pop_front(); | ||
323 | continue; // jump back to the start of the quick retry loop | ||
324 | } | ||
325 | |||
326 | // Next, ask if this is "interesting enough" to fetch. If not, just stop | ||
327 | // and wait for the next timer go-round. Only do this for the sorted | ||
328 | // queue. | ||
329 | if (mCurrentQueueIsTheSortedQueue && !object->isInterestingEnough()) | ||
330 | { | ||
331 | LL_DEBUGS("LLMediaDataClient") << "Not fetching " << *request << ": not interesting enough" << LL_ENDL; | ||
332 | break; | ||
333 | } | ||
334 | |||
335 | // Finally, try to send the HTTP message to the cap url | ||
336 | std::string url = request->getCapability(); | ||
337 | bool maybe_retry = false; | ||
338 | if (!url.empty()) | ||
339 | { | ||
340 | const LLSD &sd_payload = request->getPayload(); | ||
341 | LL_INFOS("LLMediaDataClient") << "Sending request for " << *request << LL_ENDL; | ||
342 | |||
343 | // Call the subclass for creating the responder | ||
344 | LLHTTPClient::post(url, sd_payload, createResponder(request)); | ||
345 | } | ||
346 | else { | ||
347 | LL_INFOS("LLMediaDataClient") << "NOT Sending request for " << *request << ": empty cap url!" << LL_ENDL; | ||
348 | maybe_retry = true; | ||
349 | } | ||
350 | |||
351 | bool exceeded_retries = request->getRetryCount() > mMaxNumRetries; | ||
352 | if (maybe_retry && ! exceeded_retries) // Try N times before giving up | ||
353 | { | ||
354 | // We got an empty cap, but in that case we will retry again next | ||
355 | // timer fire. | ||
356 | request->incRetryCount(); | ||
357 | } | ||
358 | else { | ||
359 | if (exceeded_retries) | ||
360 | { | ||
361 | LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " for " | ||
362 | << mMaxNumRetries << " tries...popping object id " << object->getID() << LL_ENDL; | ||
363 | // XXX Should we bring up a warning dialog?? | ||
364 | } | ||
365 | |||
366 | queue_p->pop_front(); | ||
367 | |||
368 | if (! mCurrentQueueIsTheSortedQueue) { | ||
369 | // Round robin | ||
370 | request->markSent(true); | ||
371 | mRoundRobinQueue.push_back(request); | ||
372 | } | ||
373 | } | ||
374 | |||
375 | // end of quick loop -- any cases where we want to loop will use 'continue' to jump back to the start. | ||
376 | break; | ||
377 | } | ||
378 | |||
379 | swapCurrentQueue(); | ||
380 | } | ||
381 | |||
382 | void LLMediaDataClient::swapCurrentQueue() | ||
383 | { | ||
384 | // Swap | ||
385 | mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; | ||
386 | // If its empty, swap back | ||
387 | if (getCurrentQueue()->empty()) | ||
388 | { | ||
389 | mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; | ||
390 | } | ||
391 | } | ||
392 | |||
393 | LLMediaDataClient::request_queue_t *LLMediaDataClient::getCurrentQueue() | ||
394 | { | ||
395 | return (mCurrentQueueIsTheSortedQueue) ? &mSortedQueue : &mRoundRobinQueue; | ||
396 | } | ||
397 | |||
398 | // dump the queue | ||
399 | std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q) | ||
400 | { | ||
401 | int i = 0; | ||
402 | LLMediaDataClient::request_queue_t::const_iterator iter = q.begin(); | ||
403 | LLMediaDataClient::request_queue_t::const_iterator end = q.end(); | ||
404 | while (iter != end) | ||
405 | { | ||
406 | s << "\t" << i << "]: " << (*iter)->getObject()->getID().asString() << "(" << (*iter)->getObject()->getMediaInterest() << ")"; | ||
407 | iter++; | ||
408 | i++; | ||
409 | } | ||
410 | return s; | ||
411 | } | ||
412 | |||
413 | ////////////////////////////////////////////////////////////////////////////////////// | ||
414 | // | ||
415 | // LLMediaDataClient::QueueTimer | ||
416 | // Queue of LLMediaDataClientObject smart pointers to request media for. | ||
417 | // | ||
418 | ////////////////////////////////////////////////////////////////////////////////////// | ||
419 | |||
420 | LLMediaDataClient::QueueTimer::QueueTimer(F32 time, LLMediaDataClient *mdc) | ||
421 | : LLEventTimer(time), mMDC(mdc) | ||
422 | { | ||
423 | mMDC->setIsRunning(true); | ||
424 | } | ||
425 | |||
426 | LLMediaDataClient::QueueTimer::~QueueTimer() | ||
427 | { | ||
428 | LL_DEBUGS("LLMediaDataClient") << "~QueueTimer" << LL_ENDL; | ||
429 | mMDC->setIsRunning(false); | ||
430 | mMDC = NULL; | ||
431 | } | ||
432 | |||
433 | // virtual | ||
434 | BOOL LLMediaDataClient::QueueTimer::tick() | ||
435 | { | ||
436 | if (mMDC.isNull()) return TRUE; | ||
437 | return mMDC->processQueueTimer(); | ||
438 | } | ||
439 | |||
440 | |||
441 | ////////////////////////////////////////////////////////////////////////////////////// | ||
442 | // | ||
443 | // LLMediaDataClient::Responder::RetryTimer | ||
444 | // | ||
445 | ////////////////////////////////////////////////////////////////////////////////////// | ||
446 | |||
447 | LLMediaDataClient::Responder::RetryTimer::RetryTimer(F32 time, Responder *mdr) | ||
448 | : LLEventTimer(time), mResponder(mdr) | ||
449 | { | ||
450 | } | ||
451 | |||
452 | // virtual | ||
453 | LLMediaDataClient::Responder::RetryTimer::~RetryTimer() | ||
454 | { | ||
455 | LL_DEBUGS("LLMediaDataClient") << "~RetryTimer" << *(mResponder->getRequest()) << LL_ENDL; | ||
456 | |||
457 | // XXX This is weird: Instead of doing the work in tick() (which re-schedules | ||
458 | // a timer, which might be risky), do it here, in the destructor. Yes, it is very odd. | ||
459 | // Instead of retrying, we just put the request back onto the queue | ||
460 | LL_INFOS("LLMediaDataClient") << "RetryTimer fired for: " << *(mResponder->getRequest()) << " retrying" << LL_ENDL; | ||
461 | mResponder->getRequest()->reEnqueue(); | ||
462 | |||
463 | // Release the ref to the responder. | ||
464 | mResponder = NULL; | ||
465 | } | ||
466 | |||
467 | // virtual | ||
468 | BOOL LLMediaDataClient::Responder::RetryTimer::tick() | ||
469 | { | ||
470 | // Don't fire again | ||
471 | return TRUE; | ||
472 | } | ||
473 | |||
474 | |||
475 | ////////////////////////////////////////////////////////////////////////////////////// | ||
476 | // | ||
477 | // LLMediaDataClient::Request | ||
478 | // | ||
479 | ////////////////////////////////////////////////////////////////////////////////////// | ||
480 | /*static*/U32 LLMediaDataClient::Request::sNum = 0; | ||
481 | |||
482 | LLMediaDataClient::Request::Request(const char *cap_name, | ||
483 | const LLSD& sd_payload, | ||
484 | LLMediaDataClientObject *obj, | ||
485 | LLMediaDataClient *mdc) | ||
486 | : mCapName(cap_name), | ||
487 | mPayload(sd_payload), | ||
488 | mObject(obj), | ||
489 | mNum(++sNum), | ||
490 | mRetryCount(0), | ||
491 | mMDC(mdc), | ||
492 | mMarkedSent(false), | ||
493 | mScore((F64)0.0) | ||
494 | { | ||
495 | } | ||
496 | |||
497 | LLMediaDataClient::Request::~Request() | ||
498 | { | ||
499 | LL_DEBUGS("LLMediaDataClient") << "~Request" << (*this) << LL_ENDL; | ||
500 | mMDC = NULL; | ||
501 | mObject = NULL; | ||
502 | } | ||
503 | |||
504 | |||
505 | std::string LLMediaDataClient::Request::getCapability() const | ||
506 | { | ||
507 | return getObject()->getCapabilityUrl(getCapName()); | ||
508 | } | ||
509 | |||
510 | // Helper function to get the "type" of request, which just pokes around to | ||
511 | // discover it. | ||
512 | LLMediaDataClient::Request::Type LLMediaDataClient::Request::getType() const | ||
513 | { | ||
514 | if (0 == strcmp(mCapName, "ObjectMediaNavigate")) | ||
515 | { | ||
516 | return NAVIGATE; | ||
517 | } | ||
518 | else if (0 == strcmp(mCapName, "ObjectMedia")) | ||
519 | { | ||
520 | const std::string &verb = mPayload["verb"]; | ||
521 | if (verb == "GET") | ||
522 | { | ||
523 | return GET; | ||
524 | } | ||
525 | else if (verb == "UPDATE") | ||
526 | { | ||
527 | return UPDATE; | ||
528 | } | ||
529 | } | ||
530 | llassert(false); | ||
531 | return GET; | ||
532 | } | ||
533 | |||
534 | const char *LLMediaDataClient::Request::getTypeAsString() const | ||
535 | { | ||
536 | Type t = getType(); | ||
537 | switch (t) | ||
538 | { | ||
539 | case GET: | ||
540 | return "GET"; | ||
541 | break; | ||
542 | case UPDATE: | ||
543 | return "UPDATE"; | ||
544 | break; | ||
545 | case NAVIGATE: | ||
546 | return "NAVIGATE"; | ||
547 | break; | ||
548 | case ANY: | ||
549 | return "ANY"; | ||
550 | break; | ||
551 | } | ||
552 | return ""; | ||
553 | } | ||
554 | |||
555 | |||
556 | void LLMediaDataClient::Request::reEnqueue() const | ||
557 | { | ||
558 | // I sure hope this doesn't deref a bad pointer: | ||
559 | mMDC->enqueue(this); | ||
560 | } | ||
561 | |||
562 | F32 LLMediaDataClient::Request::getRetryTimerDelay() const | ||
563 | { | ||
564 | return (mMDC == NULL) ? LLMediaDataClient::UNAVAILABLE_RETRY_TIMER_DELAY : | ||
565 | mMDC->mRetryTimerDelay; | ||
566 | } | ||
567 | |||
568 | U32 LLMediaDataClient::Request::getMaxNumRetries() const | ||
569 | { | ||
570 | return (mMDC == NULL) ? LLMediaDataClient::MAX_RETRIES : mMDC->mMaxNumRetries; | ||
571 | } | ||
572 | |||
573 | void LLMediaDataClient::Request::markSent(bool flag) | ||
574 | { | ||
575 | if (mMarkedSent != flag) | ||
576 | { | ||
577 | mMarkedSent = flag; | ||
578 | if (!mMarkedSent) | ||
579 | { | ||
580 | mNum = ++sNum; | ||
581 | } | ||
582 | } | ||
583 | } | ||
584 | |||
585 | void LLMediaDataClient::Request::updateScore() | ||
586 | { | ||
587 | F64 tmp = mObject->getMediaInterest(); | ||
588 | if (tmp != mScore) | ||
589 | { | ||
590 | LL_DEBUGS("LLMediaDataClient") << "Score for " << mObject->getID() << " changed from " << mScore << " to " << tmp << LL_ENDL; | ||
591 | mScore = tmp; | ||
592 | } | ||
593 | } | ||
594 | |||
595 | std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &r) | ||
596 | { | ||
597 | s << "request: num=" << r.getNum() | ||
598 | << " type=" << r.getTypeAsString() | ||
599 | << " ID=" << r.getObject()->getID() | ||
600 | << " #retries=" << r.getRetryCount(); | ||
601 | return s; | ||
602 | } | ||
603 | |||
604 | |||
605 | ////////////////////////////////////////////////////////////////////////////////////// | ||
606 | // | ||
607 | // LLMediaDataClient::Responder | ||
608 | // | ||
609 | ////////////////////////////////////////////////////////////////////////////////////// | ||
610 | |||
611 | LLMediaDataClient::Responder::Responder(const request_ptr_t &request) | ||
612 | : mRequest(request) | ||
613 | { | ||
614 | } | ||
615 | |||
616 | LLMediaDataClient::Responder::~Responder() | ||
617 | { | ||
618 | LL_DEBUGS("LLMediaDataClient") << "~Responder" << *(getRequest()) << LL_ENDL; | ||
619 | mRequest = NULL; | ||
620 | } | ||
621 | |||
622 | /*virtual*/ | ||
623 | void LLMediaDataClient::Responder::error(U32 status, const std::string& reason) | ||
624 | { | ||
625 | if (status == HTTP_SERVICE_UNAVAILABLE) | ||
626 | { | ||
627 | F32 retry_timeout = mRequest->getRetryTimerDelay(); | ||
628 | |||
629 | mRequest->incRetryCount(); | ||
630 | |||
631 | if (mRequest->getRetryCount() < mRequest->getMaxNumRetries()) | ||
632 | { | ||
633 | LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retrying in " << retry_timeout << " seconds" << LL_ENDL; | ||
634 | |||
635 | // Start timer (instances are automagically tracked by | ||
636 | // InstanceTracker<> and LLEventTimer) | ||
637 | new RetryTimer(F32(retry_timeout/*secs*/), this); | ||
638 | } | ||
639 | else { | ||
640 | LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retry count " | ||
641 | << mRequest->getRetryCount() << " exceeds " << mRequest->getMaxNumRetries() << ", not retrying" << LL_ENDL; | ||
642 | } | ||
643 | } | ||
644 | else { | ||
645 | std::string msg = boost::lexical_cast<std::string>(status) + ": " + reason; | ||
646 | LL_WARNS("LLMediaDataClient") << *mRequest << " http error(" << msg << ")" << LL_ENDL; | ||
647 | } | ||
648 | } | ||
649 | |||
650 | /*virtual*/ | ||
651 | void LLMediaDataClient::Responder::result(const LLSD& content) | ||
652 | { | ||
653 | LL_DEBUGS("LLMediaDataClientResponse") << *mRequest << " result : " << ll_print_sd(content) << LL_ENDL; | ||
654 | } | ||
655 | |||
656 | ////////////////////////////////////////////////////////////////////////////////////// | ||
657 | // | ||
658 | // LLObjectMediaDataClient | ||
659 | // Subclass of LLMediaDataClient for the ObjectMedia cap | ||
660 | // | ||
661 | ////////////////////////////////////////////////////////////////////////////////////// | ||
662 | |||
663 | LLMediaDataClient::Responder *LLObjectMediaDataClient::createResponder(const request_ptr_t &request) const | ||
664 | { | ||
665 | return new LLObjectMediaDataClient::Responder(request); | ||
666 | } | ||
667 | |||
668 | const char *LLObjectMediaDataClient::getCapabilityName() const | ||
669 | { | ||
670 | return "ObjectMedia"; | ||
671 | } | ||
672 | |||
673 | void LLObjectMediaDataClient::fetchMedia(LLMediaDataClientObject *object) | ||
674 | { | ||
675 | LLSD sd_payload; | ||
676 | sd_payload["verb"] = "GET"; | ||
677 | sd_payload[LLTextureEntry::OBJECT_ID_KEY] = object->getID(); | ||
678 | request(object, sd_payload); | ||
679 | } | ||
680 | |||
681 | void LLObjectMediaDataClient::updateMedia(LLMediaDataClientObject *object) | ||
682 | { | ||
683 | LLSD sd_payload; | ||
684 | sd_payload["verb"] = "UPDATE"; | ||
685 | sd_payload[LLTextureEntry::OBJECT_ID_KEY] = object->getID(); | ||
686 | LLSD object_media_data; | ||
687 | int i = 0; | ||
688 | int end = object->getMediaDataCount(); | ||
689 | for ( ; i < end ; ++i) | ||
690 | { | ||
691 | object_media_data.append(object->getMediaDataLLSD(i)); | ||
692 | } | ||
693 | sd_payload[LLTextureEntry::OBJECT_MEDIA_DATA_KEY] = object_media_data; | ||
694 | |||
695 | LL_DEBUGS("LLMediaDataClient") << "update media data: " << object->getID() << " " << ll_print_sd(sd_payload) << LL_ENDL; | ||
696 | |||
697 | request(object, sd_payload); | ||
698 | } | ||
699 | |||
700 | /*virtual*/ | ||
701 | void LLObjectMediaDataClient::Responder::result(const LLSD& content) | ||
702 | { | ||
703 | const LLMediaDataClient::Request::Type type = getRequest()->getType(); | ||
704 | llassert(type == LLMediaDataClient::Request::GET || type == LLMediaDataClient::Request::UPDATE) | ||
705 | if (type == LLMediaDataClient::Request::GET) | ||
706 | { | ||
707 | LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << " GET returned: " << ll_print_sd(content) << LL_ENDL; | ||
708 | |||
709 | // Look for an error | ||
710 | if (content.has("error")) | ||
711 | { | ||
712 | const LLSD &error = content["error"]; | ||
713 | LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error getting media data for object: code=" << | ||
714 | error["code"].asString() << ": " << error["message"].asString() << LL_ENDL; | ||
715 | |||
716 | // XXX Warn user? | ||
717 | } | ||
718 | else { | ||
719 | // Check the data | ||
720 | const LLUUID &object_id = content[LLTextureEntry::OBJECT_ID_KEY]; | ||
721 | if (object_id != getRequest()->getObject()->getID()) | ||
722 | { | ||
723 | // NOT good, wrong object id!! | ||
724 | LL_WARNS("LLMediaDataClient") << *(getRequest()) << " DROPPING response with wrong object id (" << object_id << ")" << LL_ENDL; | ||
725 | return; | ||
726 | } | ||
727 | |||
728 | // Otherwise, update with object media data | ||
729 | getRequest()->getObject()->updateObjectMediaData(content[LLTextureEntry::OBJECT_MEDIA_DATA_KEY], | ||
730 | content[LLTextureEntry::MEDIA_VERSION_KEY]); | ||
731 | } | ||
732 | } | ||
733 | else if (type == LLMediaDataClient::Request::UPDATE) | ||
734 | { | ||
735 | // just do what our superclass does | ||
736 | LLMediaDataClient::Responder::result(content); | ||
737 | } | ||
738 | } | ||
739 | |||
740 | ////////////////////////////////////////////////////////////////////////////////////// | ||
741 | // | ||
742 | // LLObjectMediaNavigateClient | ||
743 | // Subclass of LLMediaDataClient for the ObjectMediaNavigate cap | ||
744 | // | ||
745 | ////////////////////////////////////////////////////////////////////////////////////// | ||
746 | LLMediaDataClient::Responder *LLObjectMediaNavigateClient::createResponder(const request_ptr_t &request) const | ||
747 | { | ||
748 | return new LLObjectMediaNavigateClient::Responder(request); | ||
749 | } | ||
750 | |||
751 | const char *LLObjectMediaNavigateClient::getCapabilityName() const | ||
752 | { | ||
753 | return "ObjectMediaNavigate"; | ||
754 | } | ||
755 | |||
756 | void LLObjectMediaNavigateClient::navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url) | ||
757 | { | ||
758 | LLSD sd_payload; | ||
759 | sd_payload[LLTextureEntry::OBJECT_ID_KEY] = object->getID(); | ||
760 | sd_payload[LLMediaEntry::CURRENT_URL_KEY] = url; | ||
761 | sd_payload[LLTextureEntry::TEXTURE_INDEX_KEY] = (LLSD::Integer)texture_index; | ||
762 | |||
763 | LL_INFOS("LLMediaDataClient") << "navigate() initiated: " << ll_print_sd(sd_payload) << LL_ENDL; | ||
764 | |||
765 | request(object, sd_payload); | ||
766 | } | ||
767 | |||
768 | /*virtual*/ | ||
769 | void LLObjectMediaNavigateClient::Responder::error(U32 status, const std::string& reason) | ||
770 | { | ||
771 | // Bounce back (unless HTTP_SERVICE_UNAVAILABLE, in which case call base | ||
772 | // class | ||
773 | if (status == HTTP_SERVICE_UNAVAILABLE) | ||
774 | { | ||
775 | LLMediaDataClient::Responder::error(status, reason); | ||
776 | } | ||
777 | else { | ||
778 | // bounce the face back | ||
779 | LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating: http code=" << status << LL_ENDL; | ||
780 | const LLSD &payload = getRequest()->getPayload(); | ||
781 | // bounce the face back | ||
782 | getRequest()->getObject()->mediaNavigateBounceBack((LLSD::Integer)payload[LLTextureEntry::TEXTURE_INDEX_KEY]); | ||
783 | } | ||
784 | } | ||
785 | |||
786 | /*virtual*/ | ||
787 | void LLObjectMediaNavigateClient::Responder::result(const LLSD& content) | ||
788 | { | ||
789 | LL_INFOS("LLMediaDataClient") << *(getRequest()) << " NAVIGATE returned " << ll_print_sd(content) << LL_ENDL; | ||
790 | |||
791 | if (content.has("error")) | ||
792 | { | ||
793 | const LLSD &error = content["error"]; | ||
794 | int error_code = error["code"]; | ||
795 | |||
796 | if (ERROR_PERMISSION_DENIED_CODE == error_code) | ||
797 | { | ||
798 | LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Navigation denied: bounce back" << LL_ENDL; | ||
799 | const LLSD &payload = getRequest()->getPayload(); | ||
800 | // bounce the face back | ||
801 | getRequest()->getObject()->mediaNavigateBounceBack((LLSD::Integer)payload[LLTextureEntry::TEXTURE_INDEX_KEY]); | ||
802 | } | ||
803 | else { | ||
804 | LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating: code=" << | ||
805 | error["code"].asString() << ": " << error["message"].asString() << LL_ENDL; | ||
806 | } | ||
807 | // XXX Warn user? | ||
808 | } | ||
809 | else { | ||
810 | // just do what our superclass does | ||
811 | LLMediaDataClient::Responder::result(content); | ||
812 | } | ||
813 | } | ||
diff --git a/linden/indra/newview/llmediadataclient.h b/linden/indra/newview/llmediadataclient.h new file mode 100644 index 0000000..0ed7c57 --- /dev/null +++ b/linden/indra/newview/llmediadataclient.h | |||
@@ -0,0 +1,341 @@ | |||
1 | /** | ||
2 | * @file llmediadataclient.h | ||
3 | * @brief class for queueing up requests to the media service | ||
4 | * | ||
5 | * $LicenseInfo:firstyear=2007&license=viewergpl$ | ||
6 | * | ||
7 | * Copyright (c) 2007-2010, Linden Research, Inc. | ||
8 | * | ||
9 | * Second Life Viewer Source Code | ||
10 | * The source code in this file ("Source Code") is provided by Linden Lab | ||
11 | * to you under the terms of the GNU General Public License, version 2.0 | ||
12 | * ("GPL"), unless you have obtained a separate licensing agreement | ||
13 | * ("Other License"), formally executed by you and Linden Lab. Terms of | ||
14 | * the GPL can be found in doc/GPL-license.txt in this distribution, or | ||
15 | * online at http://secondlife.com/developers/opensource/gplv2 | ||
16 | * | ||
17 | * There are special exceptions to the terms and conditions of the GPL as | ||
18 | * it is applied to this Source Code. View the full text of the exception | ||
19 | * in the file doc/FLOSS-exception.txt in this software distribution, or | ||
20 | * online at | ||
21 | * http://secondlife.com/developers/opensource/flossexception | ||
22 | * | ||
23 | * By copying, modifying or distributing this software, you acknowledge | ||
24 | * that you have read and understood your obligations described above, | ||
25 | * and agree to abide by those obligations. | ||
26 | * | ||
27 | * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | ||
28 | * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
29 | * COMPLETENESS OR PERFORMANCE. | ||
30 | * $/LicenseInfo$ | ||
31 | * | ||
32 | */ | ||
33 | |||
34 | #ifndef LL_LLMEDIADATACLIENT_H | ||
35 | #define LL_LLMEDIADATACLIENT_H | ||
36 | |||
37 | #include "llhttpclient.h" | ||
38 | #include <queue> | ||
39 | //#include "llrefcount.h" | ||
40 | //#include "llpointer.h" | ||
41 | //#include "lleventtimer.h" | ||
42 | |||
43 | |||
44 | // Link seam for LLVOVolume | ||
45 | class LLMediaDataClientObject : public LLRefCount | ||
46 | { | ||
47 | public: | ||
48 | // Get the number of media data items | ||
49 | virtual U8 getMediaDataCount() const = 0; | ||
50 | // Get the media data at index, as an LLSD | ||
51 | virtual LLSD getMediaDataLLSD(U8 index) const = 0; | ||
52 | // Get this object's UUID | ||
53 | virtual LLUUID getID() const = 0; | ||
54 | // Navigate back to previous URL | ||
55 | virtual void mediaNavigateBounceBack(U8 index) = 0; | ||
56 | // Does this object have media? | ||
57 | virtual bool hasMedia() const = 0; | ||
58 | // Update the object's media data to the given array | ||
59 | virtual void updateObjectMediaData(LLSD const &media_data_array, const std::string &version_string) = 0; | ||
60 | // Return the total "interest" of the media (on-screen area) | ||
61 | virtual F64 getMediaInterest() const = 0; | ||
62 | // Return the given cap url | ||
63 | virtual std::string getCapabilityUrl(const std::string &name) const = 0; | ||
64 | // Return whether the object has been marked dead | ||
65 | virtual bool isDead() const = 0; | ||
66 | // Returns a media version number for the object | ||
67 | virtual U32 getMediaVersion() const = 0; | ||
68 | // Returns whether the object is "interesting enough" to fetch | ||
69 | virtual bool isInterestingEnough() const = 0; | ||
70 | // Returns whether we've seen this object yet or not | ||
71 | virtual bool isNew() const = 0; | ||
72 | |||
73 | // smart pointer | ||
74 | typedef LLPointer<LLMediaDataClientObject> ptr_t; | ||
75 | }; | ||
76 | |||
77 | // This object creates a priority queue for requests. | ||
78 | // Abstracts the Cap URL, the request, and the responder | ||
79 | class LLMediaDataClient : public LLRefCount | ||
80 | { | ||
81 | public: | ||
82 | LOG_CLASS(LLMediaDataClient); | ||
83 | |||
84 | const static F32 QUEUE_TIMER_DELAY;// = 1.0; // seconds(s) | ||
85 | const static F32 UNAVAILABLE_RETRY_TIMER_DELAY;// = 5.0; // secs | ||
86 | const static U32 MAX_RETRIES;// = 4; | ||
87 | const static U32 MAX_SORTED_QUEUE_SIZE;// = 10000; | ||
88 | const static U32 MAX_ROUND_ROBIN_QUEUE_SIZE;// = 10000; | ||
89 | |||
90 | // Constructor | ||
91 | LLMediaDataClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY, | ||
92 | F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY, | ||
93 | U32 max_retries = MAX_RETRIES, | ||
94 | U32 max_sorted_queue_size = MAX_SORTED_QUEUE_SIZE, | ||
95 | U32 max_round_robin_queue_size = MAX_ROUND_ROBIN_QUEUE_SIZE); | ||
96 | |||
97 | // Make the request | ||
98 | void request(const LLMediaDataClientObject::ptr_t &object, const LLSD &payload); | ||
99 | |||
100 | F32 getRetryTimerDelay() const { return mRetryTimerDelay; } | ||
101 | |||
102 | // Returns true iff the queue is empty | ||
103 | bool isEmpty() const; | ||
104 | |||
105 | // Returns true iff the given object is in the queue | ||
106 | bool isInQueue(const LLMediaDataClientObject::ptr_t &object); | ||
107 | |||
108 | // Remove the given object from the queue. Returns true iff the given object is removed. | ||
109 | bool removeFromQueue(const LLMediaDataClientObject::ptr_t &object); | ||
110 | |||
111 | // Called only by the Queue timer and tests (potentially) | ||
112 | bool processQueueTimer(); | ||
113 | |||
114 | protected: | ||
115 | // Destructor | ||
116 | virtual ~LLMediaDataClient(); // use unref | ||
117 | |||
118 | // Request | ||
119 | class Request : public LLRefCount | ||
120 | { | ||
121 | public: | ||
122 | enum Type { | ||
123 | GET, | ||
124 | UPDATE, | ||
125 | NAVIGATE, | ||
126 | ANY | ||
127 | }; | ||
128 | |||
129 | Request(const char *cap_name, const LLSD& sd_payload, LLMediaDataClientObject *obj, LLMediaDataClient *mdc); | ||
130 | const char *getCapName() const { return mCapName; } | ||
131 | const LLSD &getPayload() const { return mPayload; } | ||
132 | LLMediaDataClientObject *getObject() const { return mObject; } | ||
133 | |||
134 | U32 getNum() const { return mNum; } | ||
135 | |||
136 | U32 getRetryCount() const { return mRetryCount; } | ||
137 | void incRetryCount() { mRetryCount++; } | ||
138 | |||
139 | // Note: may return empty string! | ||
140 | std::string getCapability() const; | ||
141 | |||
142 | Type getType() const; | ||
143 | const char *getTypeAsString() const; | ||
144 | |||
145 | // Re-enqueue thyself | ||
146 | void reEnqueue() const; | ||
147 | |||
148 | F32 getRetryTimerDelay() const; | ||
149 | U32 getMaxNumRetries() const; | ||
150 | |||
151 | bool isNew() const { return mObject.notNull() ? mObject->isNew() : false; } | ||
152 | void markSent(bool flag); | ||
153 | bool isMarkedSent() const { return mMarkedSent; } | ||
154 | void updateScore(); | ||
155 | F64 getScore() const { return mScore; } | ||
156 | |||
157 | public: | ||
158 | friend std::ostream& operator<<(std::ostream &s, const Request &q); | ||
159 | |||
160 | protected: | ||
161 | virtual ~Request(); // use unref(); | ||
162 | |||
163 | private: | ||
164 | const char *mCapName; | ||
165 | LLSD mPayload; | ||
166 | LLMediaDataClientObject::ptr_t mObject; | ||
167 | // Simple tracking | ||
168 | U32 mNum; | ||
169 | static U32 sNum; | ||
170 | U32 mRetryCount; | ||
171 | F64 mScore; | ||
172 | bool mMarkedSent; | ||
173 | |||
174 | // Back pointer to the MDC...not a ref! | ||
175 | LLMediaDataClient *mMDC; | ||
176 | }; | ||
177 | typedef LLPointer<Request> request_ptr_t; | ||
178 | |||
179 | // Responder | ||
180 | class Responder : public LLHTTPClient::Responder | ||
181 | { | ||
182 | public: | ||
183 | Responder(const request_ptr_t &request); | ||
184 | //If we get back an error (not found, etc...), handle it here | ||
185 | virtual void error(U32 status, const std::string& reason); | ||
186 | //If we get back a normal response, handle it here. Default just logs it. | ||
187 | virtual void result(const LLSD& content); | ||
188 | |||
189 | const request_ptr_t &getRequest() const { return mRequest; } | ||
190 | |||
191 | protected: | ||
192 | virtual ~Responder(); | ||
193 | |||
194 | private: | ||
195 | |||
196 | class RetryTimer : public LLEventTimer | ||
197 | { | ||
198 | public: | ||
199 | RetryTimer(F32 time, Responder *); | ||
200 | virtual ~RetryTimer(); | ||
201 | virtual BOOL tick(); | ||
202 | private: | ||
203 | // back-pointer | ||
204 | boost::intrusive_ptr<Responder> mResponder; | ||
205 | }; | ||
206 | |||
207 | request_ptr_t mRequest; | ||
208 | }; | ||
209 | |||
210 | protected: | ||
211 | |||
212 | // Subclasses must override this factory method to return a new responder | ||
213 | virtual Responder *createResponder(const request_ptr_t &request) const = 0; | ||
214 | |||
215 | // Subclasses must override to return a cap name | ||
216 | virtual const char *getCapabilityName() const = 0; | ||
217 | |||
218 | virtual void sortQueue(); | ||
219 | virtual void serviceQueue(); | ||
220 | |||
221 | private: | ||
222 | typedef std::list<request_ptr_t> request_queue_t; | ||
223 | |||
224 | void enqueue(const Request*); | ||
225 | |||
226 | // Return whether the given object is/was in the queue | ||
227 | static LLMediaDataClient::request_ptr_t findOrRemove(request_queue_t &queue, const LLMediaDataClientObject::ptr_t &obj, bool remove, Request::Type type); | ||
228 | |||
229 | // Comparator for sorting | ||
230 | static bool compareRequests(const request_ptr_t &o1, const request_ptr_t &o2); | ||
231 | static F64 getObjectScore(const LLMediaDataClientObject::ptr_t &obj); | ||
232 | |||
233 | friend std::ostream& operator<<(std::ostream &s, const Request &q); | ||
234 | friend std::ostream& operator<<(std::ostream &s, const request_queue_t &q); | ||
235 | |||
236 | class QueueTimer : public LLEventTimer | ||
237 | { | ||
238 | public: | ||
239 | QueueTimer(F32 time, LLMediaDataClient *mdc); | ||
240 | virtual BOOL tick(); | ||
241 | protected: | ||
242 | virtual ~QueueTimer(); | ||
243 | private: | ||
244 | // back-pointer | ||
245 | LLPointer<LLMediaDataClient> mMDC; | ||
246 | }; | ||
247 | |||
248 | void startQueueTimer(); | ||
249 | void stopQueueTimer(); | ||
250 | void setIsRunning(bool val) { mQueueTimerIsRunning = val; } | ||
251 | |||
252 | void swapCurrentQueue(); | ||
253 | request_queue_t *getCurrentQueue(); | ||
254 | |||
255 | const F32 mQueueTimerDelay; | ||
256 | const F32 mRetryTimerDelay; | ||
257 | const U32 mMaxNumRetries; | ||
258 | const U32 mMaxSortedQueueSize; | ||
259 | const U32 mMaxRoundRobinQueueSize; | ||
260 | |||
261 | bool mQueueTimerIsRunning; | ||
262 | |||
263 | request_queue_t mSortedQueue; | ||
264 | request_queue_t mRoundRobinQueue; | ||
265 | bool mCurrentQueueIsTheSortedQueue; | ||
266 | }; | ||
267 | |||
268 | |||
269 | // MediaDataClient specific for the ObjectMedia cap | ||
270 | class LLObjectMediaDataClient : public LLMediaDataClient | ||
271 | { | ||
272 | public: | ||
273 | LLObjectMediaDataClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY, | ||
274 | F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY, | ||
275 | U32 max_retries = MAX_RETRIES, | ||
276 | U32 max_sorted_queue_size = MAX_SORTED_QUEUE_SIZE, | ||
277 | U32 max_round_robin_queue_size = MAX_ROUND_ROBIN_QUEUE_SIZE) | ||
278 | : LLMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries) | ||
279 | {} | ||
280 | virtual ~LLObjectMediaDataClient() {} | ||
281 | |||
282 | void fetchMedia(LLMediaDataClientObject *object); | ||
283 | void updateMedia(LLMediaDataClientObject *object); | ||
284 | |||
285 | protected: | ||
286 | // Subclasses must override this factory method to return a new responder | ||
287 | virtual Responder *createResponder(const request_ptr_t &request) const; | ||
288 | |||
289 | // Subclasses must override to return a cap name | ||
290 | virtual const char *getCapabilityName() const; | ||
291 | |||
292 | class Responder : public LLMediaDataClient::Responder | ||
293 | { | ||
294 | public: | ||
295 | Responder(const request_ptr_t &request) | ||
296 | : LLMediaDataClient::Responder(request) {} | ||
297 | virtual void result(const LLSD &content); | ||
298 | }; | ||
299 | }; | ||
300 | |||
301 | |||
302 | // MediaDataClient specific for the ObjectMediaNavigate cap | ||
303 | class LLObjectMediaNavigateClient : public LLMediaDataClient | ||
304 | { | ||
305 | public: | ||
306 | // NOTE: from llmediaservice.h | ||
307 | static const int ERROR_PERMISSION_DENIED_CODE = 8002; | ||
308 | |||
309 | LLObjectMediaNavigateClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY, | ||
310 | F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY, | ||
311 | U32 max_retries = MAX_RETRIES, | ||
312 | U32 max_sorted_queue_size = MAX_SORTED_QUEUE_SIZE, | ||
313 | U32 max_round_robin_queue_size = MAX_ROUND_ROBIN_QUEUE_SIZE) | ||
314 | : LLMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries) | ||
315 | {} | ||
316 | virtual ~LLObjectMediaNavigateClient() {} | ||
317 | |||
318 | void navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url); | ||
319 | |||
320 | protected: | ||
321 | // Subclasses must override this factory method to return a new responder | ||
322 | virtual Responder *createResponder(const request_ptr_t &request) const; | ||
323 | |||
324 | // Subclasses must override to return a cap name | ||
325 | virtual const char *getCapabilityName() const; | ||
326 | |||
327 | class Responder : public LLMediaDataClient::Responder | ||
328 | { | ||
329 | public: | ||
330 | Responder(const request_ptr_t &request) | ||
331 | : LLMediaDataClient::Responder(request) {} | ||
332 | virtual void error(U32 status, const std::string& reason); | ||
333 | virtual void result(const LLSD &content); | ||
334 | private: | ||
335 | void mediaNavigateBounceBack(); | ||
336 | }; | ||
337 | |||
338 | }; | ||
339 | |||
340 | |||
341 | #endif // LL_LLMEDIADATACLIENT_H | ||
diff --git a/linden/indra/newview/rcmoapradar.cpp b/linden/indra/newview/rcmoapradar.cpp new file mode 100644 index 0000000..f0593ef --- /dev/null +++ b/linden/indra/newview/rcmoapradar.cpp | |||
@@ -0,0 +1,440 @@ | |||
1 | // MOAP Radar | ||
2 | // Robin Cornelius | ||
3 | |||
4 | |||
5 | #include "llviewerprecompiledheaders.h" | ||
6 | |||
7 | #include "llavatarconstants.h" | ||
8 | #include "llfloateravatarlist.h" | ||
9 | |||
10 | #include "lluictrlfactory.h" | ||
11 | #include "llviewerwindow.h" | ||
12 | #include "llscrolllistctrl.h" | ||
13 | #include "llradiogroup.h" | ||
14 | #include "llviewercontrol.h" | ||
15 | #include "llbutton.h" | ||
16 | |||
17 | #include "llvoavatar.h" | ||
18 | #include "llimview.h" | ||
19 | #include "rcmoapradar.h" | ||
20 | #include "llregionflags.h" | ||
21 | #include "llfloaterreporter.h" | ||
22 | #include "llagent.h" | ||
23 | #include "llviewerregion.h" | ||
24 | #include "lltracker.h" | ||
25 | #include "llviewerstats.h" | ||
26 | #include "llerror.h" | ||
27 | #include "llchat.h" | ||
28 | #include "llfloaterchat.h" | ||
29 | #include "llviewermessage.h" | ||
30 | #include "llweb.h" | ||
31 | #include "llviewerobjectlist.h" | ||
32 | #include "llmutelist.h" | ||
33 | #include "llcallbacklist.h" | ||
34 | #include "llmediaentry.h" | ||
35 | |||
36 | #include <time.h> | ||
37 | #include <string.h> | ||
38 | |||
39 | #include <map> | ||
40 | |||
41 | #include "llworld.h" | ||
42 | |||
43 | #include "llsdutil.h" | ||
44 | |||
45 | LLFloaterMOAPRadar* LLFloaterMOAPRadar::sInstance = NULL; | ||
46 | |||
47 | LLFloaterMOAPRadar::LLFloaterMOAPRadar() : LLFloater(std::string("MOAPradar")) | ||
48 | { | ||
49 | llassert_always(sInstance == NULL); | ||
50 | sInstance = this; | ||
51 | mUpdateRate = gSavedSettings.getU32("MOAPRadarUpdateRate") * 3 + 3; | ||
52 | mTrackingRunning=false; | ||
53 | } | ||
54 | |||
55 | LLFloaterMOAPRadar::~LLFloaterMOAPRadar() | ||
56 | { | ||
57 | gIdleCallbacks.deleteFunction(LLFloaterMOAPRadar::callbackIdle); | ||
58 | sInstance = NULL; | ||
59 | } | ||
60 | |||
61 | //static | ||
62 | void LLFloaterMOAPRadar::toggle(void*) | ||
63 | { | ||
64 | if (sInstance) | ||
65 | { | ||
66 | if (sInstance->getVisible()) | ||
67 | { | ||
68 | sInstance->close(false); | ||
69 | } | ||
70 | else | ||
71 | { | ||
72 | sInstance->open(); | ||
73 | } | ||
74 | } | ||
75 | else | ||
76 | { | ||
77 | showInstance(); | ||
78 | } | ||
79 | } | ||
80 | |||
81 | //static | ||
82 | void LLFloaterMOAPRadar::showInstance() | ||
83 | { | ||
84 | if (sInstance) | ||
85 | { | ||
86 | if (!sInstance->getVisible()) | ||
87 | { | ||
88 | sInstance->open(); | ||
89 | } | ||
90 | } | ||
91 | else | ||
92 | { | ||
93 | sInstance = new LLFloaterMOAPRadar(); | ||
94 | LLUICtrlFactory::getInstance()->buildFloater(sInstance, "floater_moap_radar.xml"); | ||
95 | } | ||
96 | } | ||
97 | |||
98 | void LLFloaterMOAPRadar::draw() | ||
99 | { | ||
100 | LLFloater::draw(); | ||
101 | } | ||
102 | |||
103 | void LLFloaterMOAPRadar::onOpen() | ||
104 | { | ||
105 | gSavedSettings.setBOOL("ShowMOAPRadar", TRUE); | ||
106 | sInstance->setVisible(TRUE); | ||
107 | } | ||
108 | |||
109 | void LLFloaterMOAPRadar::onClose(bool app_quitting) | ||
110 | { | ||
111 | sInstance->setVisible(FALSE); | ||
112 | if (!app_quitting) | ||
113 | { | ||
114 | gSavedSettings.setBOOL("ShowMOAPRadar", FALSE); | ||
115 | } | ||
116 | if (!gSavedSettings.getBOOL("MOAPRadarKeepOpen") || app_quitting) | ||
117 | { | ||
118 | destroy(); | ||
119 | } | ||
120 | } | ||
121 | |||
122 | |||
123 | BOOL LLFloaterMOAPRadar::postBuild() | ||
124 | { | ||
125 | |||
126 | mMOAPList = getChild<LLScrollListCtrl>("moap_list"); | ||
127 | mMOAPList->sortByColumn("distance", TRUE); | ||
128 | |||
129 | mTrackBtn = getChild<LLButton>("track_btn"); | ||
130 | mTrackBtn->setLabel(LLStringExplicit("Track")); | ||
131 | |||
132 | childSetAction("open_btn", onClickOpen, this); | ||
133 | childSetAction("track_btn", onClickTrack, this); | ||
134 | childSetAction("copy_btn", onClickCopy, this); | ||
135 | |||
136 | mMOAPList->setCommitOnSelectionChange(TRUE); | ||
137 | childSetCommitCallback("moap_list", onSelectMOAP, this); | ||
138 | |||
139 | gIdleCallbacks.addFunction(LLFloaterMOAPRadar::callbackIdle); | ||
140 | |||
141 | return TRUE; | ||
142 | } | ||
143 | |||
144 | //static | ||
145 | void LLFloaterMOAPRadar::callbackIdle(void *userdata) { | ||
146 | if (LLFloaterMOAPRadar::sInstance != NULL) | ||
147 | { | ||
148 | // Do not update at every frame: this would be insane ! | ||
149 | if (gFrameCount % LLFloaterMOAPRadar::sInstance->mUpdateRate == 0) | ||
150 | { | ||
151 | LLFloaterMOAPRadar::sInstance->updateMOAPList(); | ||
152 | } | ||
153 | } | ||
154 | } | ||
155 | |||
156 | |||
157 | void LLFloaterMOAPRadar::updateMOAPList() | ||
158 | { | ||
159 | if (sInstance != this) return; | ||
160 | |||
161 | mMOAPList->deleteAllItems(); | ||
162 | |||
163 | S32 obj_count=gObjectList.getNumObjects(); | ||
164 | |||
165 | int count=0; | ||
166 | |||
167 | for(int objnum=0;objnum<obj_count;objnum++) | ||
168 | { | ||
169 | LLPointer<LLViewerObject> obj = gObjectList.getObject(objnum); | ||
170 | |||
171 | if(obj.isNull()) | ||
172 | continue; | ||
173 | |||
174 | if(obj->isDead() || obj->isOrphaned()) | ||
175 | continue; | ||
176 | |||
177 | if(obj->getMediaType() == LLViewerObject::MEDIA_TYPE_NONE) | ||
178 | continue; | ||
179 | |||
180 | LLSD element; | ||
181 | |||
182 | element["id"] = obj->getID(); | ||
183 | |||
184 | for(int face=0;face<obj->getNumTEs();face++) | ||
185 | { | ||
186 | const LLTextureEntry * te=obj->getTE(face); | ||
187 | if(te==NULL) | ||
188 | continue; | ||
189 | |||
190 | if(te->hasMedia()) | ||
191 | { | ||
192 | LLMediaEntry* media=te->getMediaData(); | ||
193 | |||
194 | if(media==NULL) | ||
195 | continue; | ||
196 | |||
197 | LLSD media_details; | ||
198 | media->asLLSD(media_details); | ||
199 | |||
200 | element["columns"][LIST_URL]["column"] = "URL"; | ||
201 | element["columns"][LIST_URL]["type"] = "text"; | ||
202 | std::string URL=media->getCurrentURL(); | ||
203 | if(URL=="") | ||
204 | URL="(Waiting....)"; | ||
205 | |||
206 | element["columns"][LIST_URL]["value"] = URL; | ||
207 | |||
208 | if(mTrackingRunning && mTrackedID==obj->getID() && mTrackedFace==face) | ||
209 | { | ||
210 | element["columns"][LIST_URL]["font-style"] = "BOLD"; | ||
211 | } | ||
212 | else | ||
213 | { | ||
214 | element["columns"][LIST_URL]["font-style"] = "NORMAL"; | ||
215 | } | ||
216 | |||
217 | element["columns"][LIST_FACE]["column"] = "face"; | ||
218 | element["columns"][LIST_FACE]["type"] = "text"; | ||
219 | std::stringstream face_buf; | ||
220 | face_buf << face; | ||
221 | element["columns"][LIST_FACE]["value"] = face_buf.str(); | ||
222 | |||
223 | LLVector3d offset = gAgent.getPositionGlobal()-obj->getPositionGlobal(); | ||
224 | S32 dist= offset.length(); | ||
225 | |||
226 | element["columns"][LIST_DISTANCE]["column"] = "distance"; | ||
227 | element["columns"][LIST_DISTANCE]["type"] = "text"; | ||
228 | std::stringstream dist_buf; | ||
229 | dist_buf << std::fixed << std::setprecision(2) << dist<<"m"; | ||
230 | element["columns"][LIST_DISTANCE]["value"] = dist_buf.str(); | ||
231 | |||
232 | if(dist<25) | ||
233 | element["columns"][LIST_DISTANCE]["color"] = LLColor4::green.getValue(); | ||
234 | else if(dist<50) | ||
235 | element["columns"][LIST_DISTANCE]["color"] = LLColor4::blue1.getValue(); | ||
236 | else if(dist<100) | ||
237 | element["columns"][LIST_DISTANCE]["color"] = LLColor4::black.getValue(); | ||
238 | else if(dist<256) | ||
239 | element["columns"][LIST_DISTANCE]["color"] = LLColor4::yellow.getValue(); | ||
240 | else | ||
241 | element["columns"][LIST_DISTANCE]["color"] = LLColor4::red.getValue(); | ||
242 | |||
243 | |||
244 | element["columns"][LIST_POSITION]["column"] = "position"; | ||
245 | element["columns"][LIST_POSITION]["type"] = "text"; | ||
246 | element["columns"][LIST_POSITION]["value"] = ""; | ||
247 | |||
248 | element["columns"][LIST_ALTITUDE]["column"] = "altitude"; | ||
249 | element["columns"][LIST_ALTITUDE]["type"] = "text"; | ||
250 | std::stringstream alt_buf; | ||
251 | alt_buf << std::fixed << std::setprecision(2) << obj->getPositionGlobal().mdV[2]<<"m"; | ||
252 | element["columns"][LIST_ALTITUDE]["value"] = alt_buf.str(); | ||
253 | |||
254 | element["columns"][LIST_POSITION]["column"]="position"; | ||
255 | element["columns"][LIST_POSITION]["type"] = "text"; | ||
256 | std::stringstream pos_buf; | ||
257 | |||
258 | S32 moap_x = (S32)offset.mdV[VX]; | ||
259 | S32 moap_y = (S32)offset.mdV[VY]; | ||
260 | if (moap_x >= -256 && moap_x <= 256 && moap_y >= -256 && moap_y <= 256) | ||
261 | { | ||
262 | pos_buf<< std::fixed << std::setprecision(2)<<obj->getPositionRegion().mV[VX]<<","<<obj->getPositionRegion().mV[VY]; | ||
263 | } | ||
264 | else | ||
265 | { | ||
266 | if (moap_y < 0) | ||
267 | pos_buf<<"S"; | ||
268 | else if (moap_y > 256) | ||
269 | pos_buf<<"N"; | ||
270 | |||
271 | if (moap_x < 0) | ||
272 | pos_buf<<"W"; | ||
273 | else if (moap_x > 256) | ||
274 | pos_buf<<"E"; | ||
275 | } | ||
276 | |||
277 | |||
278 | element["columns"][LIST_POSITION]["value"] = pos_buf.str(); | ||
279 | |||
280 | mMOAPList->addElement(element, ADD_BOTTOM); | ||
281 | |||
282 | if(mSelectedObjID==obj->getID() && mSelectedFace==face) | ||
283 | { | ||
284 | mMOAPList->selectNthItem(count); | ||
285 | } | ||
286 | |||
287 | count++; | ||
288 | } | ||
289 | } | ||
290 | } | ||
291 | |||
292 | updatetrackbtn(); | ||
293 | } | ||
294 | |||
295 | // static | ||
296 | void LLFloaterMOAPRadar::onClickOpen(void* userdata) | ||
297 | { | ||
298 | LLFloaterMOAPRadar *self = (LLFloaterMOAPRadar*)userdata; | ||
299 | |||
300 | LLDynamicArray<LLUUID> ids = self->mMOAPList->getSelectedIDs(); | ||
301 | |||
302 | if(ids.empty()) | ||
303 | { | ||
304 | return; | ||
305 | } | ||
306 | |||
307 | LLUUID id=ids.front(); | ||
308 | |||
309 | LLPointer<LLViewerObject> obj = gObjectList.findObject(id); | ||
310 | if(obj.notNull()) | ||
311 | { | ||
312 | |||
313 | const LLTextureEntry * te=obj->getTE(self->mSelectedFace); | ||
314 | if(te==NULL) | ||
315 | return; | ||
316 | |||
317 | if(te->hasMedia()) | ||
318 | { | ||
319 | LLMediaEntry* media=te->getMediaData(); | ||
320 | if(media) | ||
321 | { | ||
322 | //gViewerWindow->mWindow->copyTextToClipboard(utf8str_to_wstring(media->getCurrentURL())); | ||
323 | LLWeb::loadURL(media->getCurrentURL()); | ||
324 | } | ||
325 | } | ||
326 | } | ||
327 | |||
328 | } | ||
329 | |||
330 | // static | ||
331 | void LLFloaterMOAPRadar::onClickTrack(void* userdata) | ||
332 | { | ||
333 | LLFloaterMOAPRadar *self = (LLFloaterMOAPRadar*)userdata; | ||
334 | |||
335 | LLDynamicArray<LLUUID> ids = self->mMOAPList->getSelectedIDs(); | ||
336 | |||
337 | if(self->mTrackingRunning) | ||
338 | { | ||
339 | LLTracker::stopTracking(NULL); | ||
340 | self->mTrackingRunning=false; | ||
341 | } | ||
342 | |||
343 | if(ids.empty()) | ||
344 | { | ||
345 | self->mTrackedID=LLUUID::null; | ||
346 | self->mTrackingRunning=false; | ||
347 | return; | ||
348 | } | ||
349 | |||
350 | LLUUID id=ids.front(); | ||
351 | |||
352 | if(id==self->mTrackedID && self->mSelectedFace==self->mTrackedFace) | ||
353 | { | ||
354 | self->mTrackedID=LLUUID::null; | ||
355 | self->mTrackingRunning=false; | ||
356 | return; | ||
357 | } | ||
358 | |||
359 | self->mTrackedID=LLUUID::null; | ||
360 | |||
361 | LLPointer<LLViewerObject> obj = gObjectList.findObject(id); | ||
362 | if(obj.notNull()) | ||
363 | { | ||
364 | LLTracker::trackLocation(obj->getPositionGlobal(),"MOAP Tracking","",LLTracker::LOCATION_ITEM); | ||
365 | self->mTrackingRunning=true; | ||
366 | self->mTrackedID=id; | ||
367 | self->mTrackedFace=self->mSelectedFace; | ||
368 | } | ||
369 | } | ||
370 | |||
371 | // static | ||
372 | void LLFloaterMOAPRadar::onClickCopy(void* userdata) | ||
373 | { | ||
374 | LLFloaterMOAPRadar *self = (LLFloaterMOAPRadar*)userdata; | ||
375 | |||
376 | LLDynamicArray<LLUUID> ids = self->mMOAPList->getSelectedIDs(); | ||
377 | |||
378 | if(ids.empty()) | ||
379 | { | ||
380 | LLTracker::stopTracking(NULL); | ||
381 | return; | ||
382 | } | ||
383 | |||
384 | LLUUID id=ids.front(); | ||
385 | |||
386 | LLPointer<LLViewerObject> obj = gObjectList.findObject(id); | ||
387 | |||
388 | if(obj.notNull()) | ||
389 | { | ||
390 | |||
391 | const LLTextureEntry * te=obj->getTE(self->mSelectedFace); | ||
392 | if(te==NULL) | ||
393 | return; | ||
394 | |||
395 | if(te->hasMedia()) | ||
396 | { | ||
397 | LLMediaEntry* media=te->getMediaData(); | ||
398 | if(media) | ||
399 | { | ||
400 | gViewerWindow->mWindow->copyTextToClipboard(utf8str_to_wstring(media->getCurrentURL())); | ||
401 | } | ||
402 | } | ||
403 | } | ||
404 | |||
405 | } | ||
406 | |||
407 | //static | ||
408 | void LLFloaterMOAPRadar::onSelectMOAP(LLUICtrl*, void* userdata) | ||
409 | { | ||
410 | LLFloaterMOAPRadar *self = (LLFloaterMOAPRadar*)userdata; | ||
411 | |||
412 | LLScrollListItem *item = self->mMOAPList->getFirstSelected(); | ||
413 | if (item) | ||
414 | { | ||
415 | self->mSelectedObjID = item->getUUID(); | ||
416 | self->mSelectedFace = item->getColumn(LIST_FACE)->getValue().asInteger(); | ||
417 | } | ||
418 | |||
419 | self->updatetrackbtn(); | ||
420 | } | ||
421 | |||
422 | void LLFloaterMOAPRadar::updatetrackbtn() | ||
423 | { | ||
424 | if( mTrackingRunning) | ||
425 | { | ||
426 | if( mTrackedID==mSelectedObjID && mTrackedFace==mSelectedFace) | ||
427 | { | ||
428 | mTrackBtn->setLabel(LLStringExplicit("Untrack")); | ||
429 | } | ||
430 | else | ||
431 | { | ||
432 | mTrackBtn->setLabel(LLStringExplicit("New Track")); | ||
433 | } | ||
434 | } | ||
435 | else | ||
436 | { | ||
437 | mTrackBtn->setLabel(LLStringExplicit("Track")); | ||
438 | } | ||
439 | |||
440 | } \ No newline at end of file | ||
diff --git a/linden/indra/newview/rcmoapradar.h b/linden/indra/newview/rcmoapradar.h new file mode 100644 index 0000000..1d01546 --- /dev/null +++ b/linden/indra/newview/rcmoapradar.h | |||
@@ -0,0 +1,58 @@ | |||
1 | |||
2 | |||
3 | class LLFloaterMOAPRadar : public LLFloater | ||
4 | { | ||
5 | |||
6 | private: | ||
7 | LLFloaterMOAPRadar(); | ||
8 | public: | ||
9 | ~LLFloaterMOAPRadar(); | ||
10 | |||
11 | enum AVATARS_COLUMN_ORDER | ||
12 | { | ||
13 | LIST_URL, | ||
14 | LIST_FACE, | ||
15 | LIST_DISTANCE, | ||
16 | LIST_POSITION, | ||
17 | LIST_ALTITUDE | ||
18 | }; | ||
19 | |||
20 | /*virtual*/ void onClose(bool app_quitting); | ||
21 | /*virtual*/ void onOpen(); | ||
22 | /*virtual*/ BOOL postBuild(); | ||
23 | /*virtual*/ void draw(); | ||
24 | |||
25 | static void toggle(void*); | ||
26 | |||
27 | static void showInstance(); | ||
28 | |||
29 | static void callbackIdle(void *userdata); | ||
30 | |||
31 | void updateMOAPList(); | ||
32 | |||
33 | static void onClickOpen(void* userdata); | ||
34 | static void onClickCopy(void* userdata); | ||
35 | static void onClickTrack(void* userdata); | ||
36 | static void onSelectMOAP(LLUICtrl*, void* userdata); | ||
37 | |||
38 | |||
39 | private: | ||
40 | static LLFloaterMOAPRadar* sInstance; | ||
41 | LLScrollListCtrl* mMOAPList; | ||
42 | LLButton * mTrackBtn; | ||
43 | |||
44 | U32 mUpdateRate; | ||
45 | |||
46 | LLUUID mSelectedObjID; | ||
47 | U8 mSelectedFace; | ||
48 | bool mTrackingRunning; | ||
49 | |||
50 | LLUUID mTrackedID; | ||
51 | U8 mTrackedFace; | ||
52 | |||
53 | void updatetrackbtn(); | ||
54 | |||
55 | public: | ||
56 | static LLFloaterMOAPRadar* getInstance() { return sInstance; } | ||
57 | |||
58 | }; \ No newline at end of file | ||
diff --git a/linden/indra/newview/skins/default/xui/en-us/floater_moap_radar.xml b/linden/indra/newview/skins/default/xui/en-us/floater_moap_radar.xml new file mode 100644 index 0000000..b9e3f99 --- /dev/null +++ b/linden/indra/newview/skins/default/xui/en-us/floater_moap_radar.xml | |||
@@ -0,0 +1,54 @@ | |||
1 | <?xml version="1.0" encoding="utf-8" standalone="yes"?> | ||
2 | <floater name="MOAP" title="MOAP Radar" | ||
3 | can_resize="true" can_minimize="true" can_close="true" can_drag_on_left="false" | ||
4 | rect_control="FloaterRadarRect" min_width="700" min_height="150"> | ||
5 | |||
6 | <scroll_list name="moap_list" | ||
7 | left="10" right="-10" top="-20" bottom="40" can_resize="true" | ||
8 | column_padding="0" follows="left|top|bottom|right" | ||
9 | draw_heading="true" multi_select="true" search_column="1" | ||
10 | tool_tip="MOAP List"> | ||
11 | <column name="URL" label="URL" width="450" tool_tip="URL of MOAP"/> | ||
12 | <column name="face" label="Face" width="48" tool_tip="URL of MOAP"/> | ||
13 | <column name="distance" label="Dist." width="48" tool_tip="Distance from your avatar"/> | ||
14 | <column name="position" label="Pos." width="90" tool_tip="Position (X, Y) within this sim, or general direction (cardinal point) for outside sims"/> | ||
15 | <column name="altitude" label="Alt." width="48" tool_tip="Altitude"/> | ||
16 | </scroll_list> | ||
17 | |||
18 | |||
19 | <button | ||
20 | name="open_btn" | ||
21 | label="Open" | ||
22 | tool_tip="Open Current URL" | ||
23 | left="10" | ||
24 | bottom_delta="-30" | ||
25 | width="80" | ||
26 | height="20" | ||
27 | font="SansSerifSmall" | ||
28 | follows="bottom|left" | ||
29 | /> | ||
30 | <button | ||
31 | name="track_btn" | ||
32 | label="(Un)Track" | ||
33 | tool_tip="Toggle tracking of this MOAPs position" | ||
34 | left_delta="90" | ||
35 | bottom_delta="0" | ||
36 | width="80" | ||
37 | height="20" | ||
38 | font="SansSerifSmall" | ||
39 | follows="bottom|left" | ||
40 | /> | ||
41 | <button | ||
42 | name="copy_btn" | ||
43 | label="Get URL" | ||
44 | tool_tip="Copies URL to the clipboard" | ||
45 | left_delta="90" | ||
46 | bottom_delta="0" | ||
47 | width="80" | ||
48 | height="20" | ||
49 | font="SansSerifSmall" | ||
50 | follows="bottom|left" | ||
51 | /> | ||
52 | <!-- upper middle row --> | ||
53 | |||
54 | </floater> | ||