/indra/newview/llinventorymodelbackgroundfetch.cpp

https://bitbucket.org/lindenlab/viewer-beta/ · C++ · 634 lines · 480 code · 83 blank · 71 comment · 91 complexity · 3646f5b463d824fb696defd97218bef4 MD5 · raw file

  1. /**
  2. * @file llinventorymodel.cpp
  3. * @brief Implementation of the inventory model used to track agent inventory.
  4. *
  5. * $LicenseInfo:firstyear=2002&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. #include "llviewerprecompiledheaders.h"
  27. #include "llinventorymodelbackgroundfetch.h"
  28. #include "llagent.h"
  29. #include "llappviewer.h"
  30. #include "llcallbacklist.h"
  31. #include "llinventorypanel.h"
  32. #include "llinventorymodel.h"
  33. #include "llviewercontrol.h"
  34. #include "llviewerinventory.h"
  35. #include "llviewermessage.h"
  36. #include "llviewerregion.h"
  37. #include "llviewerwindow.h"
  38. const F32 MAX_TIME_FOR_SINGLE_FETCH = 10.f;
  39. const S32 MAX_FETCH_RETRIES = 10;
  40. LLInventoryModelBackgroundFetch::LLInventoryModelBackgroundFetch() :
  41. mBackgroundFetchActive(FALSE),
  42. mAllFoldersFetched(FALSE),
  43. mRecursiveInventoryFetchStarted(FALSE),
  44. mRecursiveLibraryFetchStarted(FALSE),
  45. mNumFetchRetries(0),
  46. mMinTimeBetweenFetches(0.3f),
  47. mMaxTimeBetweenFetches(10.f),
  48. mTimelyFetchPending(FALSE),
  49. mBulkFetchCount(0)
  50. {
  51. }
  52. LLInventoryModelBackgroundFetch::~LLInventoryModelBackgroundFetch()
  53. {
  54. }
  55. bool LLInventoryModelBackgroundFetch::isBulkFetchProcessingComplete() const
  56. {
  57. return mFetchQueue.empty() && mBulkFetchCount<=0;
  58. }
  59. bool LLInventoryModelBackgroundFetch::libraryFetchStarted() const
  60. {
  61. return mRecursiveLibraryFetchStarted;
  62. }
  63. bool LLInventoryModelBackgroundFetch::libraryFetchCompleted() const
  64. {
  65. return libraryFetchStarted() && fetchQueueContainsNoDescendentsOf(gInventory.getLibraryRootFolderID());
  66. }
  67. bool LLInventoryModelBackgroundFetch::libraryFetchInProgress() const
  68. {
  69. return libraryFetchStarted() && !libraryFetchCompleted();
  70. }
  71. bool LLInventoryModelBackgroundFetch::inventoryFetchStarted() const
  72. {
  73. return mRecursiveInventoryFetchStarted;
  74. }
  75. bool LLInventoryModelBackgroundFetch::inventoryFetchCompleted() const
  76. {
  77. return inventoryFetchStarted() && fetchQueueContainsNoDescendentsOf(gInventory.getRootFolderID());
  78. }
  79. bool LLInventoryModelBackgroundFetch::inventoryFetchInProgress() const
  80. {
  81. return inventoryFetchStarted() && !inventoryFetchCompleted();
  82. }
  83. bool LLInventoryModelBackgroundFetch::isEverythingFetched() const
  84. {
  85. return mAllFoldersFetched;
  86. }
  87. BOOL LLInventoryModelBackgroundFetch::backgroundFetchActive() const
  88. {
  89. return mBackgroundFetchActive;
  90. }
  91. void LLInventoryModelBackgroundFetch::start(const LLUUID& cat_id, BOOL recursive)
  92. {
  93. if (!mAllFoldersFetched || cat_id.notNull())
  94. {
  95. LL_DEBUGS("InventoryFetch") << "Start fetching category: " << cat_id << ", recursive: " << recursive << LL_ENDL;
  96. mBackgroundFetchActive = TRUE;
  97. if (cat_id.isNull())
  98. {
  99. if (!mRecursiveInventoryFetchStarted)
  100. {
  101. mRecursiveInventoryFetchStarted |= recursive;
  102. mFetchQueue.push_back(FetchQueueInfo(gInventory.getRootFolderID(), recursive));
  103. gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL);
  104. }
  105. if (!mRecursiveLibraryFetchStarted)
  106. {
  107. mRecursiveLibraryFetchStarted |= recursive;
  108. mFetchQueue.push_back(FetchQueueInfo(gInventory.getLibraryRootFolderID(), recursive));
  109. gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL);
  110. }
  111. }
  112. else
  113. {
  114. // Specific folder requests go to front of queue.
  115. if (mFetchQueue.empty() || mFetchQueue.front().mCatUUID != cat_id)
  116. {
  117. mFetchQueue.push_front(FetchQueueInfo(cat_id, recursive));
  118. gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL);
  119. }
  120. if (cat_id == gInventory.getLibraryRootFolderID())
  121. {
  122. mRecursiveLibraryFetchStarted |= recursive;
  123. }
  124. if (cat_id == gInventory.getRootFolderID())
  125. {
  126. mRecursiveInventoryFetchStarted |= recursive;
  127. }
  128. }
  129. }
  130. }
  131. void LLInventoryModelBackgroundFetch::findLostItems()
  132. {
  133. mBackgroundFetchActive = TRUE;
  134. mFetchQueue.push_back(FetchQueueInfo(LLUUID::null, TRUE));
  135. gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL);
  136. }
  137. void LLInventoryModelBackgroundFetch::stopBackgroundFetch()
  138. {
  139. if (mBackgroundFetchActive)
  140. {
  141. mBackgroundFetchActive = FALSE;
  142. gIdleCallbacks.deleteFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL);
  143. mBulkFetchCount=0;
  144. mMinTimeBetweenFetches=0.0f;
  145. }
  146. }
  147. void LLInventoryModelBackgroundFetch::setAllFoldersFetched()
  148. {
  149. if (mRecursiveInventoryFetchStarted &&
  150. mRecursiveLibraryFetchStarted)
  151. {
  152. mAllFoldersFetched = TRUE;
  153. }
  154. stopBackgroundFetch();
  155. }
  156. void LLInventoryModelBackgroundFetch::backgroundFetchCB(void *)
  157. {
  158. LLInventoryModelBackgroundFetch::instance().backgroundFetch();
  159. }
  160. void LLInventoryModelBackgroundFetch::backgroundFetch()
  161. {
  162. if (mBackgroundFetchActive && gAgent.getRegion())
  163. {
  164. // If we'll be using the capability, we'll be sending batches and the background thing isn't as important.
  165. std::string url = gAgent.getRegion()->getCapability("FetchInventoryDescendents2");
  166. if (gSavedSettings.getBOOL("UseHTTPInventory") && !url.empty())
  167. {
  168. bulkFetch(url);
  169. return;
  170. }
  171. #if 1
  172. //--------------------------------------------------------------------------------
  173. // DEPRECATED OLD CODE
  174. //
  175. // No more categories to fetch, stop fetch process.
  176. if (mFetchQueue.empty())
  177. {
  178. llinfos << "Inventory fetch completed" << llendl;
  179. setAllFoldersFetched();
  180. return;
  181. }
  182. F32 fast_fetch_time = lerp(mMinTimeBetweenFetches, mMaxTimeBetweenFetches, 0.1f);
  183. F32 slow_fetch_time = lerp(mMinTimeBetweenFetches, mMaxTimeBetweenFetches, 0.5f);
  184. if (mTimelyFetchPending && mFetchTimer.getElapsedTimeF32() > slow_fetch_time)
  185. {
  186. // Double timeouts on failure.
  187. mMinTimeBetweenFetches = llmin(mMinTimeBetweenFetches * 2.f, 10.f);
  188. mMaxTimeBetweenFetches = llmin(mMaxTimeBetweenFetches * 2.f, 120.f);
  189. lldebugs << "Inventory fetch times grown to (" << mMinTimeBetweenFetches << ", " << mMaxTimeBetweenFetches << ")" << llendl;
  190. // fetch is no longer considered "timely" although we will wait for full time-out.
  191. mTimelyFetchPending = FALSE;
  192. }
  193. while(1)
  194. {
  195. if (mFetchQueue.empty())
  196. {
  197. break;
  198. }
  199. if(gDisconnected)
  200. {
  201. // Just bail if we are disconnected.
  202. break;
  203. }
  204. const FetchQueueInfo info = mFetchQueue.front();
  205. LLViewerInventoryCategory* cat = gInventory.getCategory(info.mCatUUID);
  206. // Category has been deleted, remove from queue.
  207. if (!cat)
  208. {
  209. mFetchQueue.pop_front();
  210. continue;
  211. }
  212. if (mFetchTimer.getElapsedTimeF32() > mMinTimeBetweenFetches &&
  213. LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion())
  214. {
  215. // Category exists but has no children yet, fetch the descendants
  216. // for now, just request every time and rely on retry timer to throttle.
  217. if (cat->fetch())
  218. {
  219. mFetchTimer.reset();
  220. mTimelyFetchPending = TRUE;
  221. }
  222. else
  223. {
  224. // The catagory also tracks if it has expired and here it says it hasn't
  225. // yet. Get out of here because nothing is going to happen until we
  226. // update the timers.
  227. break;
  228. }
  229. }
  230. // Do I have all my children?
  231. else if (gInventory.isCategoryComplete(info.mCatUUID))
  232. {
  233. // Finished with this category, remove from queue.
  234. mFetchQueue.pop_front();
  235. // Add all children to queue.
  236. LLInventoryModel::cat_array_t* categories;
  237. LLInventoryModel::item_array_t* items;
  238. gInventory.getDirectDescendentsOf(cat->getUUID(), categories, items);
  239. for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin();
  240. it != categories->end();
  241. ++it)
  242. {
  243. mFetchQueue.push_back(FetchQueueInfo((*it)->getUUID(),info.mRecursive));
  244. }
  245. // We received a response in less than the fast time.
  246. if (mTimelyFetchPending && mFetchTimer.getElapsedTimeF32() < fast_fetch_time)
  247. {
  248. // Shrink timeouts based on success.
  249. mMinTimeBetweenFetches = llmax(mMinTimeBetweenFetches * 0.8f, 0.3f);
  250. mMaxTimeBetweenFetches = llmax(mMaxTimeBetweenFetches * 0.8f, 10.f);
  251. lldebugs << "Inventory fetch times shrunk to (" << mMinTimeBetweenFetches << ", " << mMaxTimeBetweenFetches << ")" << llendl;
  252. }
  253. mTimelyFetchPending = FALSE;
  254. continue;
  255. }
  256. else if (mFetchTimer.getElapsedTimeF32() > mMaxTimeBetweenFetches)
  257. {
  258. // Received first packet, but our num descendants does not match db's num descendants
  259. // so try again later.
  260. mFetchQueue.pop_front();
  261. if (mNumFetchRetries++ < MAX_FETCH_RETRIES)
  262. {
  263. // push on back of queue
  264. mFetchQueue.push_back(info);
  265. }
  266. mTimelyFetchPending = FALSE;
  267. mFetchTimer.reset();
  268. break;
  269. }
  270. // Not enough time has elapsed to do a new fetch
  271. break;
  272. }
  273. //
  274. // DEPRECATED OLD CODE
  275. //--------------------------------------------------------------------------------
  276. #endif
  277. }
  278. }
  279. void LLInventoryModelBackgroundFetch::incrBulkFetch(S16 fetching)
  280. {
  281. mBulkFetchCount += fetching;
  282. if (mBulkFetchCount < 0)
  283. {
  284. mBulkFetchCount = 0;
  285. }
  286. }
  287. class LLInventoryModelFetchDescendentsResponder: public LLHTTPClient::Responder
  288. {
  289. public:
  290. LLInventoryModelFetchDescendentsResponder(const LLSD& request_sd, uuid_vec_t recursive_cats) :
  291. mRequestSD(request_sd),
  292. mRecursiveCatUUIDs(recursive_cats)
  293. {};
  294. //LLInventoryModelFetchDescendentsResponder() {};
  295. void result(const LLSD& content);
  296. void error(U32 status, const std::string& reason);
  297. protected:
  298. BOOL getIsRecursive(const LLUUID& cat_id) const;
  299. private:
  300. LLSD mRequestSD;
  301. uuid_vec_t mRecursiveCatUUIDs; // hack for storing away which cat fetches are recursive
  302. };
  303. // If we get back a normal response, handle it here.
  304. void LLInventoryModelFetchDescendentsResponder::result(const LLSD& content)
  305. {
  306. LLInventoryModelBackgroundFetch *fetcher = LLInventoryModelBackgroundFetch::getInstance();
  307. if (content.has("folders"))
  308. {
  309. for(LLSD::array_const_iterator folder_it = content["folders"].beginArray();
  310. folder_it != content["folders"].endArray();
  311. ++folder_it)
  312. {
  313. LLSD folder_sd = *folder_it;
  314. //LLUUID agent_id = folder_sd["agent_id"];
  315. //if(agent_id != gAgent.getID()) //This should never happen.
  316. //{
  317. // llwarns << "Got a UpdateInventoryItem for the wrong agent."
  318. // << llendl;
  319. // break;
  320. //}
  321. LLUUID parent_id = folder_sd["folder_id"];
  322. LLUUID owner_id = folder_sd["owner_id"];
  323. S32 version = (S32)folder_sd["version"].asInteger();
  324. S32 descendents = (S32)folder_sd["descendents"].asInteger();
  325. LLPointer<LLViewerInventoryCategory> tcategory = new LLViewerInventoryCategory(owner_id);
  326. if (parent_id.isNull())
  327. {
  328. LLPointer<LLViewerInventoryItem> titem = new LLViewerInventoryItem;
  329. for(LLSD::array_const_iterator item_it = folder_sd["items"].beginArray();
  330. item_it != folder_sd["items"].endArray();
  331. ++item_it)
  332. {
  333. const LLUUID lost_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND);
  334. if (lost_uuid.notNull())
  335. {
  336. LLSD item = *item_it;
  337. titem->unpackMessage(item);
  338. LLInventoryModel::update_list_t update;
  339. LLInventoryModel::LLCategoryUpdate new_folder(lost_uuid, 1);
  340. update.push_back(new_folder);
  341. gInventory.accountForUpdate(update);
  342. titem->setParent(lost_uuid);
  343. titem->updateParentOnServer(FALSE);
  344. gInventory.updateItem(titem);
  345. gInventory.notifyObservers();
  346. }
  347. }
  348. }
  349. LLViewerInventoryCategory* pcat = gInventory.getCategory(parent_id);
  350. if (!pcat)
  351. {
  352. continue;
  353. }
  354. for(LLSD::array_const_iterator category_it = folder_sd["categories"].beginArray();
  355. category_it != folder_sd["categories"].endArray();
  356. ++category_it)
  357. {
  358. LLSD category = *category_it;
  359. tcategory->fromLLSD(category);
  360. const BOOL recursive = getIsRecursive(tcategory->getUUID());
  361. if (recursive)
  362. {
  363. fetcher->mFetchQueue.push_back(LLInventoryModelBackgroundFetch::FetchQueueInfo(tcategory->getUUID(), recursive));
  364. }
  365. else if ( !gInventory.isCategoryComplete(tcategory->getUUID()) )
  366. {
  367. gInventory.updateCategory(tcategory);
  368. }
  369. }
  370. LLPointer<LLViewerInventoryItem> titem = new LLViewerInventoryItem;
  371. for(LLSD::array_const_iterator item_it = folder_sd["items"].beginArray();
  372. item_it != folder_sd["items"].endArray();
  373. ++item_it)
  374. {
  375. LLSD item = *item_it;
  376. titem->unpackMessage(item);
  377. gInventory.updateItem(titem);
  378. }
  379. // Set version and descendentcount according to message.
  380. LLViewerInventoryCategory* cat = gInventory.getCategory(parent_id);
  381. if(cat)
  382. {
  383. cat->setVersion(version);
  384. cat->setDescendentCount(descendents);
  385. cat->determineFolderType();
  386. }
  387. }
  388. }
  389. if (content.has("bad_folders"))
  390. {
  391. for(LLSD::array_const_iterator folder_it = content["bad_folders"].beginArray();
  392. folder_it != content["bad_folders"].endArray();
  393. ++folder_it)
  394. {
  395. LLSD folder_sd = *folder_it;
  396. // These folders failed on the dataserver. We probably don't want to retry them.
  397. llinfos << "Folder " << folder_sd["folder_id"].asString()
  398. << "Error: " << folder_sd["error"].asString() << llendl;
  399. }
  400. }
  401. fetcher->incrBulkFetch(-1);
  402. if (fetcher->isBulkFetchProcessingComplete())
  403. {
  404. llinfos << "Inventory fetch completed" << llendl;
  405. fetcher->setAllFoldersFetched();
  406. }
  407. gInventory.notifyObservers();
  408. }
  409. // If we get back an error (not found, etc...), handle it here.
  410. void LLInventoryModelFetchDescendentsResponder::error(U32 status, const std::string& reason)
  411. {
  412. LLInventoryModelBackgroundFetch *fetcher = LLInventoryModelBackgroundFetch::getInstance();
  413. llinfos << "LLInventoryModelFetchDescendentsResponder::error "
  414. << status << ": " << reason << llendl;
  415. fetcher->incrBulkFetch(-1);
  416. if (status==499) // timed out
  417. {
  418. for(LLSD::array_const_iterator folder_it = mRequestSD["folders"].beginArray();
  419. folder_it != mRequestSD["folders"].endArray();
  420. ++folder_it)
  421. {
  422. LLSD folder_sd = *folder_it;
  423. LLUUID folder_id = folder_sd["folder_id"];
  424. const BOOL recursive = getIsRecursive(folder_id);
  425. fetcher->mFetchQueue.push_front(LLInventoryModelBackgroundFetch::FetchQueueInfo(folder_id, recursive));
  426. }
  427. }
  428. else
  429. {
  430. if (fetcher->isBulkFetchProcessingComplete())
  431. {
  432. fetcher->setAllFoldersFetched();
  433. }
  434. }
  435. gInventory.notifyObservers();
  436. }
  437. BOOL LLInventoryModelFetchDescendentsResponder::getIsRecursive(const LLUUID& cat_id) const
  438. {
  439. return (std::find(mRecursiveCatUUIDs.begin(),mRecursiveCatUUIDs.end(), cat_id) != mRecursiveCatUUIDs.end());
  440. }
  441. // Bundle up a bunch of requests to send all at once.
  442. // static
  443. void LLInventoryModelBackgroundFetch::bulkFetch(std::string url)
  444. {
  445. //Background fetch is called from gIdleCallbacks in a loop until background fetch is stopped.
  446. //If there are items in mFetchQueue, we want to check the time since the last bulkFetch was
  447. //sent. If it exceeds our retry time, go ahead and fire off another batch.
  448. //Stopbackgroundfetch will be run from the Responder instead of here.
  449. S16 max_concurrent_fetches=8;
  450. F32 new_min_time = 0.5f; //HACK! Clean this up when old code goes away entirely.
  451. if (mMinTimeBetweenFetches < new_min_time)
  452. {
  453. mMinTimeBetweenFetches=new_min_time; //HACK! See above.
  454. }
  455. if (gDisconnected ||
  456. (mBulkFetchCount > max_concurrent_fetches) ||
  457. (mFetchTimer.getElapsedTimeF32() < mMinTimeBetweenFetches))
  458. {
  459. return; // just bail if we are disconnected
  460. }
  461. U32 folder_count=0;
  462. U32 max_batch_size=5;
  463. U32 sort_order = gSavedSettings.getU32(LLInventoryPanel::DEFAULT_SORT_ORDER) & 0x1;
  464. uuid_vec_t recursive_cats;
  465. LLSD body;
  466. LLSD body_lib;
  467. while (!(mFetchQueue.empty()) && (folder_count < max_batch_size))
  468. {
  469. const FetchQueueInfo& fetch_info = mFetchQueue.front();
  470. const LLUUID &cat_id = fetch_info.mCatUUID;
  471. if (cat_id.isNull()) //DEV-17797
  472. {
  473. LLSD folder_sd;
  474. folder_sd["folder_id"] = LLUUID::null.asString();
  475. folder_sd["owner_id"] = gAgent.getID();
  476. folder_sd["sort_order"] = (LLSD::Integer)sort_order;
  477. folder_sd["fetch_folders"] = (LLSD::Boolean)FALSE;
  478. folder_sd["fetch_items"] = (LLSD::Boolean)TRUE;
  479. body["folders"].append(folder_sd);
  480. folder_count++;
  481. }
  482. else
  483. {
  484. const LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
  485. if (cat)
  486. {
  487. if (LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion())
  488. {
  489. LLSD folder_sd;
  490. folder_sd["folder_id"] = cat->getUUID();
  491. folder_sd["owner_id"] = cat->getOwnerID();
  492. folder_sd["sort_order"] = (LLSD::Integer)sort_order;
  493. folder_sd["fetch_folders"] = TRUE; //(LLSD::Boolean)sFullFetchStarted;
  494. folder_sd["fetch_items"] = (LLSD::Boolean)TRUE;
  495. if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID())
  496. body_lib["folders"].append(folder_sd);
  497. else
  498. body["folders"].append(folder_sd);
  499. folder_count++;
  500. }
  501. // May already have this folder, but append child folders to list.
  502. if (fetch_info.mRecursive)
  503. {
  504. LLInventoryModel::cat_array_t* categories;
  505. LLInventoryModel::item_array_t* items;
  506. gInventory.getDirectDescendentsOf(cat->getUUID(), categories, items);
  507. for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin();
  508. it != categories->end();
  509. ++it)
  510. {
  511. mFetchQueue.push_back(FetchQueueInfo((*it)->getUUID(), fetch_info.mRecursive));
  512. }
  513. }
  514. }
  515. }
  516. if (fetch_info.mRecursive)
  517. recursive_cats.push_back(cat_id);
  518. mFetchQueue.pop_front();
  519. }
  520. if (folder_count > 0)
  521. {
  522. mBulkFetchCount++;
  523. if (body["folders"].size())
  524. {
  525. LLInventoryModelFetchDescendentsResponder *fetcher = new LLInventoryModelFetchDescendentsResponder(body, recursive_cats);
  526. LLHTTPClient::post(url, body, fetcher, 300.0);
  527. }
  528. if (body_lib["folders"].size())
  529. {
  530. std::string url_lib = gAgent.getRegion()->getCapability("FetchLibDescendents2");
  531. LLInventoryModelFetchDescendentsResponder *fetcher = new LLInventoryModelFetchDescendentsResponder(body_lib, recursive_cats);
  532. LLHTTPClient::post(url_lib, body_lib, fetcher, 300.0);
  533. }
  534. mFetchTimer.reset();
  535. }
  536. else if (isBulkFetchProcessingComplete())
  537. {
  538. setAllFoldersFetched();
  539. }
  540. }
  541. bool LLInventoryModelBackgroundFetch::fetchQueueContainsNoDescendentsOf(const LLUUID& cat_id) const
  542. {
  543. for (fetch_queue_t::const_iterator it = mFetchQueue.begin();
  544. it != mFetchQueue.end(); ++it)
  545. {
  546. const LLUUID& fetch_id = (*it).mCatUUID;
  547. if (gInventory.isObjectDescendentOf(fetch_id, cat_id))
  548. return false;
  549. }
  550. return true;
  551. }