diff --git a/indra/llinventory/llinventory.cpp b/indra/llinventory/llinventory.cpp index 05e38299af..10f7927202 100644 --- a/indra/llinventory/llinventory.cpp +++ b/indra/llinventory/llinventory.cpp @@ -1198,11 +1198,7 @@ LLSD LLInventoryCategory::asAISLLSD() const S8 type = static_cast(mPreferredType); sd[INV_ASSET_TYPE_LABEL_WS] = type; sd[INV_NAME_LABEL] = mName; - - if (mThumbnailUUID.notNull()) - { - sd[INV_THUMBNAIL_LABEL] = LLSD().with(INV_ASSET_ID_LABEL, mThumbnailUUID); - } + sd[INV_THUMBNAIL_LABEL] = LLSD().with(INV_ASSET_ID_LABEL, mThumbnailUUID); return sd; } @@ -1427,16 +1423,16 @@ bool LLInventoryCategory::importLLSD(const LLSD& cat_data) { setPreferredType(LLFolderType::lookup(cat_data[INV_PREFERRED_TYPE_LABEL].asString())); } - LLUUID thumbnail_uuid; if (cat_data.has(INV_THUMBNAIL_LABEL)) { + LLUUID thumbnail_uuid; const LLSD &thumbnail_data = cat_data[INV_THUMBNAIL_LABEL]; if (thumbnail_data.has(INV_ASSET_ID_LABEL)) { thumbnail_uuid = thumbnail_data[INV_ASSET_ID_LABEL].asUUID(); } + setThumbnailUUID(thumbnail_uuid); } - setThumbnailUUID(thumbnail_uuid); if (cat_data.has(INV_NAME_LABEL)) { mName = cat_data[INV_NAME_LABEL].asString(); diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 9414fcbe27..bb5dae77c5 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -19051,6 +19051,17 @@ Change of this parameter will affect the layout of buttons in notification toast Backup 0 + BatchSizeAIS3 + + Comment + Amount of folder ais packs into category subset request + Persist + 1 + Type + S32 + Value + 20 + PoolSizeAIS Comment diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp index 0a9e67505e..b702387912 100644 --- a/indra/newview/llagentwearables.cpp +++ b/indra/newview/llagentwearables.cpp @@ -1443,8 +1443,9 @@ void LLAgentWearables::findAttachmentsAddRemoveInfo(LLInventoryModel::item_array } // Build up list of objects to be removed and items currently attached. - for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); - iter != gAgentAvatarp->mAttachmentPoints.end();) + LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); + LLVOAvatar::attachment_map_t::iterator end = gAgentAvatarp->mAttachmentPoints.end(); + while (iter != end) { LLVOAvatar::attachment_map_t::iterator curiter = iter++; LLViewerJointAttachment* attachment = curiter->second; diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp index 98e6aafbfc..6828498f52 100644 --- a/indra/newview/llaisapi.cpp +++ b/indra/newview/llaisapi.cpp @@ -48,11 +48,16 @@ //========================================================================= const std::string AISAPI::INVENTORY_CAP_NAME("InventoryAPIv3"); const std::string AISAPI::LIBRARY_CAP_NAME("LibraryAPIv3"); +const S32 AISAPI::HTTP_TIMEOUT = 180; std::list AISAPI::sPostponedQuery; const S32 MAX_SIMULTANEOUS_COROUTINES = 2048; +// AIS3 allows '*' requests, but in reality those will be cut at some point +// Specify own depth to be able to anticipate it and mark folders as incomplete +const S32 MAX_FOLDER_DEPTH_REQUEST = 50; + //------------------------------------------------------------------------- /*static*/ bool AISAPI::isAvailable() @@ -463,11 +468,11 @@ void AISAPI::FetchCategoryChildren(const LLUUID &catId, ITEM_TYPE type, bool rec { // can specify depth=*, but server side is going to cap requests // and reject everything 'over the top',. - depth = 50; + depth = MAX_FOLDER_DEPTH_REQUEST; } else { - depth = llmax(depth, 50); + depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); } url += "?depth=" + std::to_string(depth); @@ -516,11 +521,11 @@ void AISAPI::FetchCategoryChildren(const std::string &identifier, bool recursive { // can specify depth=*, but server side is going to cap requests // and reject everything 'over the top',. - depth = 50; + depth = MAX_FOLDER_DEPTH_REQUEST; } else { - depth = llmax(depth, 50); + depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); } url += "?depth=" + std::to_string(depth); @@ -567,11 +572,11 @@ void AISAPI::FetchCategoryCategories(const LLUUID &catId, ITEM_TYPE type, bool r { // can specify depth=*, but server side is going to cap requests // and reject everything 'over the top',. - depth = 50; + depth = MAX_FOLDER_DEPTH_REQUEST; } else { - depth = llmax(depth, 50); + depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); } url += "?depth=" + std::to_string(depth); @@ -597,6 +602,117 @@ void AISAPI::FetchCategoryCategories(const LLUUID &catId, ITEM_TYPE type, bool r EnqueueAISCommand("FetchCategoryCategories", proc); } +void AISAPI::FetchCategorySubset(const LLUUID& catId, + const uuid_vec_t specificChildren, + ITEM_TYPE type, + bool recursive, + completion_t callback, + S32 depth) +{ + std::string cap = (type == INVENTORY) ? getInvCap() : getLibCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + if (specificChildren.empty()) + { + LL_WARNS("Inventory") << "Empty request!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + // category/any_folder_id/children?depth=*&children=child_id1,child_id2,child_id3 + std::string url = cap + std::string("/category/") + catId.asString() + "/children"; + + if (recursive) + { + depth = MAX_FOLDER_DEPTH_REQUEST; + } + else + { + depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); + } + + uuid_vec_t::const_iterator iter = specificChildren.begin(); + uuid_vec_t::const_iterator end = specificChildren.end(); + + url += "?depth=" + std::to_string(depth) + "&children=" + iter->asString(); + iter++; + + while (iter != end) + { + url += "," + iter->asString(); + iter++; + } + + const S32 MAX_URL_LENGH = 2000; // RFC documentation specifies a maximum length of 2048 + if (url.length() > MAX_URL_LENGH) + { + LL_WARNS("Inventory") << "Request url is too long, url: " << url << LL_ENDL; + } + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); + + // get doesn't use body, can pass additional data + LLSD body; + body["depth"] = depth; + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, getFn, url, catId, body, callback, FETCHCATEGORYSUBSET)); + + EnqueueAISCommand("FetchCategorySubset", proc); +} + +/*static*/ +// Will get COF folder, links in it and items those links point to +void AISAPI::FetchCOF(completion_t callback) +{ + std::string cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/category/current/links"); + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); + + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, getFn, url, LLUUID::null, LLSD(), callback, FETCHCOF)); + + EnqueueAISCommand("FetchCOF", proc); +} + /*static*/ void AISAPI::FetchOrphans(completion_t callback) { @@ -681,7 +797,7 @@ void AISAPI::onIdle(void *userdata) } /*static*/ -void AISAPI::onUpdateReceived(const std::string& context, const LLSD& update, COMMAND_TYPE type, const LLSD& request_body) +void AISAPI::onUpdateReceived(const LLSD& update, COMMAND_TYPE type, const LLSD& request_body) { LLTimer timer; if ( (type == UPDATECATEGORY || type == UPDATEITEM) @@ -689,17 +805,8 @@ void AISAPI::onUpdateReceived(const std::string& context, const LLSD& update, CO { dump_sequential_xml(gAgentAvatarp->getFullname() + "_ais_update", update); } - bool is_fetch = (type == FETCHITEM) - || (type == FETCHCATEGORYCHILDREN) - || (type == FETCHCATEGORYCATEGORIES) - || (type == FETCHORPHANS); - // parse update llsd into stuff to do or parse received items. - S32 depth = 0; - if (is_fetch && request_body.has("depth")) - { - depth = request_body["depth"].asInteger(); - } - AISUpdate ais_update(update, is_fetch, depth); + + AISUpdate ais_update(update, type, request_body); ais_update.doUpdate(); // execute the updates in the appropriate order. LL_DEBUGS("Inventory", "AIS3") << "Elapsed processing: " << timer.getElapsedTimeF32() << LL_ENDL; } @@ -722,7 +829,7 @@ void AISAPI::InvokeAISCommandCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t ht LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest()); LLCore::HttpHeaders::ptr_t httpHeaders; - httpOptions->setTimeout(180); + httpOptions->setTimeout(HTTP_TIMEOUT); LL_DEBUGS("Inventory") << "Request url: " << url << LL_ENDL; @@ -741,7 +848,7 @@ void AISAPI::InvokeAISCommandCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t ht LL_DEBUGS("Inventory") << "Request type: " << (S32)type << " \nRequest target: " << targetId - << " \nElapsed time ince request: " << elapsed_time + << " \nElapsed time since request: " << elapsed_time << " \nstatus: " << status.toULong() << LL_ENDL; } else @@ -803,7 +910,7 @@ void AISAPI::InvokeAISCommandCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t ht } LL_DEBUGS("Inventory", "AIS3") << "Result: " << result << LL_ENDL; - onUpdateReceived("AISCommand", result, type, body); + onUpdateReceived(result, type, body); if (callback && !callback.empty()) { @@ -814,6 +921,8 @@ void AISAPI::InvokeAISCommandCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t ht case COPYLIBRARYCATEGORY: case FETCHCATEGORYCATEGORIES: case FETCHCATEGORYCHILDREN: + case FETCHCATEGORYSUBSET: + case FETCHCOF: if (result.has("category_id")) { ids.emplace(result["category_id"]); @@ -865,6 +974,8 @@ void AISAPI::InvokeAISCommandCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t ht //case COPYLIBRARYCATEGORY: //case FETCHCATEGORYCATEGORIES: //case FETCHCATEGORYCHILDREN: + //case FETCHCATEGORYSUBSET: + //case FETCHCOF: // if (result.has("category_id")) // { // id = result["category_id"]; @@ -920,10 +1031,24 @@ void AISAPI::InvokeAISCommandCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t ht } //------------------------------------------------------------------------- -AISUpdate::AISUpdate(const LLSD& update, bool fetch, S32 depth) -: mFetch(fetch) -, mFetchDepth(depth) +AISUpdate::AISUpdate(const LLSD& update, AISAPI::COMMAND_TYPE type, const LLSD& request_body) +: mType(type) { + mFetch = (type == AISAPI::FETCHITEM) + || (type == AISAPI::FETCHCATEGORYCHILDREN) + || (type == AISAPI::FETCHCATEGORYCATEGORIES) + || (type == AISAPI::FETCHCATEGORYSUBSET) + || (type == AISAPI::FETCHCOF) + || (type == AISAPI::FETCHORPHANS); + // parse update llsd into stuff to do or parse received items. + mFetchDepth = MAX_FOLDER_DEPTH_REQUEST; + if (mFetch && request_body.has("depth")) + { + mFetchDepth = request_body["depth"].asInteger(); + } + + mTimer.setTimerExpirySec(debugLoggingEnabled("Inventory") ? EXPIRY_SECONDS_DEBUG : EXPIRY_SECONDS_LIVE); + mTimer.start(); parseUpdate(update); } @@ -942,6 +1067,16 @@ void AISUpdate::clearParseResults() mCategoryIds.clear(); } +void AISUpdate::checkTimeout() +{ + if (mTimer.hasExpired()) + { + llcoro::suspend(); + LLCoros::checkStop(); + mTimer.setTimerExpirySec(debugLoggingEnabled("Inventory") ? EXPIRY_SECONDS_DEBUG : EXPIRY_SECONDS_LIVE); + } +} + void AISUpdate::parseUpdate(const LLSD& update) { clearParseResults(); @@ -1031,17 +1166,26 @@ void AISUpdate::parseContent(const LLSD& update) { if (update.has("linked_id")) { - parseLink(update); + parseLink(update, mFetchDepth); } else if (update.has("item_id")) { parseItem(update); } - if (update.has("category_id")) - { - parseCategory(update, mFetchDepth); - } + if (mType == AISAPI::FETCHCATEGORYSUBSET) + { + // initial category is incomplete, don't process it, + // go for content instead + if (update.has("_embedded")) + { + parseEmbedded(update["_embedded"], mFetchDepth - 1); + } + } + else if (update.has("category_id")) + { + parseCategory(update, mFetchDepth); + } else { if (update.has("_embedded")) @@ -1097,7 +1241,7 @@ void AISUpdate::parseItem(const LLSD& item_map) } } -void AISUpdate::parseLink(const LLSD& link_map) +void AISUpdate::parseLink(const LLSD& link_map, S32 depth) { LLUUID item_id = link_map["item_id"].asUUID(); LLPointer new_link(new LLViewerInventoryItem); @@ -1150,6 +1294,11 @@ void AISUpdate::parseLink(const LLSD& link_map) mCatDescendentDeltas[parent_id]++; new_link->setComplete(true); } + + if (link_map.has("_embedded")) + { + parseEmbedded(link_map["_embedded"], depth); + } } else { @@ -1294,9 +1443,11 @@ void AISUpdate::parseDescendentCount(const LLUUID& category_id, const LLSD& embe void AISUpdate::parseEmbedded(const LLSD& embedded, S32 depth) { + checkTimeout(); + if (embedded.has("links")) // _embedded in a category { - parseEmbeddedLinks(embedded["links"]); + parseEmbeddedLinks(embedded["links"], depth); } if (embedded.has("items")) // _embedded in a category { @@ -1329,7 +1480,7 @@ void AISUpdate::parseUUIDArray(const LLSD& content, const std::string& name, uui } } -void AISUpdate::parseEmbeddedLinks(const LLSD& links) +void AISUpdate::parseEmbeddedLinks(const LLSD& links, S32 depth) { for(LLSD::map_const_iterator linkit = links.beginMap(), linkend = links.endMap(); @@ -1343,7 +1494,7 @@ void AISUpdate::parseEmbeddedLinks(const LLSD& links) } else { - parseLink(link_map); + parseLink(link_map, depth); } } } @@ -1414,6 +1565,8 @@ void AISUpdate::parseEmbeddedCategories(const LLSD& categories, S32 depth) void AISUpdate::doUpdate() { + checkTimeout(); + // Do version/descendant accounting. for (std::map::const_iterator catit = mCatDescendentDeltas.begin(); catit != mCatDescendentDeltas.end(); ++catit) @@ -1455,6 +1608,7 @@ void AISUpdate::doUpdate() } // CREATE CATEGORIES + const S32 MAX_UPDATE_BACKLOG = 50; // stall prevention for (deferred_category_map_t::const_iterator create_it = mCategoriesCreated.begin(); create_it != mCategoriesCreated.end(); ++create_it) { @@ -1463,6 +1617,13 @@ void AISUpdate::doUpdate() gInventory.updateCategory(new_category, LLInventoryObserver::CREATE); LL_DEBUGS("Inventory") << "created category " << category_id << LL_ENDL; + + // fetching can receive massive amount of items and fodlers + if (gInventory.getChangedIDs().size() > MAX_UPDATE_BACKLOG) + { + gInventory.notifyObservers(); + checkTimeout(); + } } // UPDATE CATEGORIES @@ -1517,6 +1678,13 @@ void AISUpdate::doUpdate() // case this is create. LL_DEBUGS("Inventory") << "created item " << item_id << LL_ENDL; gInventory.updateItem(new_item, LLInventoryObserver::CREATE); + + // fetching can receive massive amount of items and fodlers + if (gInventory.getChangedIDs().size() > MAX_UPDATE_BACKLOG) + { + gInventory.notifyObservers(); + checkTimeout(); + } } // UPDATE ITEMS @@ -1577,6 +1745,8 @@ void AISUpdate::doUpdate() } } + checkTimeout(); + gInventory.notifyObservers(); } diff --git a/indra/newview/llaisapi.h b/indra/newview/llaisapi.h index e175b13031..b3c09ed8fa 100644 --- a/indra/newview/llaisapi.h +++ b/indra/newview/llaisapi.h @@ -38,6 +38,7 @@ class AISAPI { public: + static const S32 HTTP_TIMEOUT; typedef enum { INVENTORY, LIBRARY @@ -59,10 +60,11 @@ public: static void FetchCategoryChildren(const LLUUID &catId, ITEM_TYPE type = AISAPI::ITEM_TYPE::INVENTORY, bool recursive = false, completion_t callback = completion_t(), S32 depth = 0); static void FetchCategoryChildren(const std::string &identifier, bool recursive = false, completion_t callback = completion_t(), S32 depth = 0); static void FetchCategoryCategories(const LLUUID &catId, ITEM_TYPE type = AISAPI::ITEM_TYPE::INVENTORY, bool recursive = false, completion_t callback = completion_t(), S32 depth = 0); + static void FetchCategorySubset(const LLUUID& catId, const uuid_vec_t specificChildren, ITEM_TYPE type = AISAPI::ITEM_TYPE::INVENTORY, bool recursive = false, completion_t callback = completion_t(), S32 depth = 0); + static void FetchCOF(completion_t callback = completion_t()); static void FetchOrphans(completion_t callback = completion_t() ); static void CopyLibraryCategory(const LLUUID& sourceId, const LLUUID& destId, bool copySubfolders, completion_t callback = completion_t()); -private: typedef enum { COPYINVENTORY, SLAMFOLDER, @@ -76,9 +78,12 @@ private: FETCHITEM, FETCHCATEGORYCHILDREN, FETCHCATEGORYCATEGORIES, + FETCHCATEGORYSUBSET, + FETCHCOF, FETCHORPHANS, } COMMAND_TYPE; +private: static const std::string INVENTORY_CAP_NAME; static const std::string LIBRARY_CAP_NAME; @@ -87,7 +92,7 @@ private: static void EnqueueAISCommand(const std::string &procName, LLCoprocedureManager::CoProcedure_t proc); static void onIdle(void *userdata); // launches postponed AIS commands - static void onUpdateReceived(const std::string& context, const LLSD& update, COMMAND_TYPE type, const LLSD& request_body); + static void onUpdateReceived(const LLSD& update, COMMAND_TYPE type, const LLSD& request_body); static std::string getInvCap(); static std::string getLibCap(); @@ -103,7 +108,7 @@ private: class AISUpdate { public: - AISUpdate(const LLSD& update, bool fetch, S32 depth); + AISUpdate(const LLSD& update, AISAPI::COMMAND_TYPE type, const LLSD& request_body); void parseUpdate(const LLSD& update); void parseMeta(const LLSD& update); void parseContent(const LLSD& update); @@ -111,12 +116,12 @@ public: static void parseUUIDArray(const LLSD& content, const std::string& name, uuid_list_t& ids); // [/SL:KB] // void parseUUIDArray(const LLSD& content, const std::string& name, uuid_list_t& ids); - void parseLink(const LLSD& link_map); + void parseLink(const LLSD& link_map, S32 depth); void parseItem(const LLSD& link_map); void parseCategory(const LLSD& link_map, S32 depth); void parseDescendentCount(const LLUUID& category_id, const LLSD& embedded); void parseEmbedded(const LLSD& embedded, S32 depth); - void parseEmbeddedLinks(const LLSD& links); + void parseEmbeddedLinks(const LLSD& links, S32 depth); void parseEmbeddedItems(const LLSD& items); void parseEmbeddedCategories(const LLSD& categories, S32 depth); void parseEmbeddedItem(const LLSD& item); @@ -124,6 +129,12 @@ public: void doUpdate(); private: void clearParseResults(); + void checkTimeout(); + + // Debug is very log-heavy, give it more time or it will take forever to process + // Todo: find a way to make throttle static isntead of per-request + const F32 EXPIRY_SECONDS_DEBUG = 1.f; + const F32 EXPIRY_SECONDS_LIVE = 0.008f; typedef std::map uuid_int_map_t; uuid_int_map_t mCatDescendentDeltas; @@ -145,6 +156,8 @@ private: uuid_list_t mCategoryIds; bool mFetch; S32 mFetchDepth; + LLTimer mTimer; + AISAPI::COMMAND_TYPE mType; }; #endif diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index d5c1bc0502..d62fa16002 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -5131,18 +5131,45 @@ protected: nullary_func_t mCallable; }; +void callAfterCOFFetch(nullary_func_t cb) +{ + LLUUID cat_id = LLAppearanceMgr::instance().getCOF(); + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN && AISAPI::isAvailable()) + { + // Assume that we have no relevant cache. Fetch cof, and items cof's links point to. + AISAPI::FetchCOF([cb](const LLUUID& id) + { + cb(); + LLUUID cat_id = LLAppearanceMgr::instance().getCOF(); + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + if (cat) + { + cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); + } + }); + // Mark it so that background fetch won't request it if it didn't already + cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); + } + else + { + // Assume that cache is present. Process like a normal folder. + callAfterCategoryFetch(cat_id, cb); + } +} + void callAfterCategoryFetch(const LLUUID& cat_id, nullary_func_t cb) { - CallAfterCategoryFetchStage1 *stage1 = new CallAfterCategoryFetchStage1(cat_id, cb); - stage1->startFetch(); - if (stage1->isFinished()) - { - stage1->done(); - } - else - { - gInventory.addObserver(stage1); - } + CallAfterCategoryFetchStage1* stage1 = new CallAfterCategoryFetchStage1(cat_id, cb); + stage1->startFetch(); + if (stage1->isFinished()) + { + stage1->done(); + } + else + { + gInventory.addObserver(stage1); + } } void add_wearable_type_counts(const uuid_vec_t& ids, diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index 81d8e5b1ca..faf6b68a91 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -367,6 +367,7 @@ public: LLUUID findDescendentCategoryIDByName(const LLUUID& parent_id,const std::string& name); // Invoke a given callable after category contents are fully fetched. +void callAfterCOFFetch(nullary_func_t cb); void callAfterCategoryFetch(const LLUUID& cat_id, nullary_func_t cb); // Wear all items in a uuid vector. diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp index 68ed138ef7..7b147cb73b 100644 --- a/indra/newview/llinventoryfilter.cpp +++ b/indra/newview/llinventoryfilter.cpp @@ -221,7 +221,7 @@ bool LLInventoryFilter::checkFolder(const LLUUID& folder_id) const && !LLInventoryModelBackgroundFetch::instance().inventoryFetchInProgress()) { LLViewerInventoryCategory* cat = gInventory.getCategory(folder_id); - if (!cat || (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN)) + if ((!cat && folder_id.notNull()) || (cat && cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN)) { // At the moment background fetch only cares about VERSION_UNKNOWN, // so do not check isCategoryComplete that compares descendant count @@ -229,6 +229,11 @@ bool LLInventoryFilter::checkFolder(const LLUUID& folder_id) const } } + if (!checkAgainstFilterThumbnails(folder_id)) + { + return false; + } + // Marketplace folder filtering const U32 filterTypes = mFilterOps.mFilterTypes; const U32 marketplace_filter = FILTERTYPE_MARKETPLACE_ACTIVE | FILTERTYPE_MARKETPLACE_INACTIVE | diff --git a/indra/newview/llinventorygallery.h b/indra/newview/llinventorygallery.h index 1760535524..2cf038b7d8 100644 --- a/indra/newview/llinventorygallery.h +++ b/indra/newview/llinventorygallery.h @@ -128,6 +128,7 @@ public: bool hasDescendents(const LLUUID& cat_id); bool hasVisibleItems(); void handleModifiedFilter(); + LLScrollContainer* getScrollableContainer() { return mScrollPanel; } protected: diff --git a/indra/newview/llinventorygallerymenu.cpp b/indra/newview/llinventorygallerymenu.cpp index 84b18c1517..43ddf4da63 100644 --- a/indra/newview/llinventorygallerymenu.cpp +++ b/indra/newview/llinventorygallerymenu.cpp @@ -334,6 +334,10 @@ void LLInventoryGalleryContextMenu::doToSelected(const LLSD& userdata, const LLU gInventory.updateItem(item); gInventory.notifyObservers(); } + else if ("replace_links" == action) + { + LLFloaterReg::showInstance("linkreplace", LLSD(selected_id)); + } } void LLInventoryGalleryContextMenu::onRename(const LLSD& notification, const LLSD& response) @@ -451,8 +455,7 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men if(!is_link) { items.push_back(std::string("thumbnail")); - LLViewerInventoryItem* inv_item = gInventory.getItem(selected_id); - if (inv_item && !inv_item->getPermissions().allowOperationBy(PERM_MODIFY, gAgent.getID())) + if (!is_agent_inventory) { disabled_items.push_back(std::string("thumbnail")); } @@ -466,6 +469,13 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men items.push_back(std::string("open_in_new_window")); items.push_back(std::string("Open Folder Separator")); } + else + { + if (is_agent_inventory && (obj->getType() != LLAssetType::AT_LINK_FOLDER)) + { + items.push_back(std::string("Replace Links")); + } + } if(is_trash) { @@ -490,7 +500,7 @@ void LLInventoryGalleryContextMenu::updateMenuItemsVisibility(LLContextMenu* men } } items.push_back(std::string("Purge Item")); - if (!get_is_category_removable(&gInventory, selected_id)) + if (is_folder && !get_is_category_removable(&gInventory, selected_id)) { disabled_items.push_back(std::string("Purge Item")); } diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index f0f3fd53e2..33dc7f393f 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -2878,12 +2878,6 @@ bool LLInventoryModel::loadSkeleton( cat->setUUID(folder_id.asUUID()); cat->setParent(parent_id.asUUID()); - LLSD thumbnail = (*it)["thumbnail"]; - if (thumbnail.isMap()) - { - cat->setThumbnailUUID(thumbnail["asset_id"].asUUID()); - } - LLFolderType::EType preferred_type = LLFolderType::FT_NONE; LLSD type_default = (*it)["type_default"]; if(type_default.isDefined()) @@ -2975,6 +2969,10 @@ bool LLInventoryModel::loadSkeleton( else { cached_ids.insert(tcat->getUUID()); + + // At the moment download does not provide a thumbnail + // uuid, use the one from cache + tcat->setThumbnailUUID(cat->getThumbnailUUID()); } } @@ -5257,7 +5255,6 @@ LLPointer LLInventoryModel::validate() const } else if (count_under_root > 1) { - // LL_WARNS("Inventory") << "Fatal inventory corruption: system folder type has excess copies under root, type " << ft << " count " << count_under_root << LL_ENDL; // FIRE-31634 [OPENSIM] Better inventory validation logging validation_info->mDuplicateRequiredSystemFolders.insert(folder_type); if (!is_automatic && folder_type != LLFolderType::FT_SETTINGS) { @@ -5273,6 +5270,8 @@ LLPointer LLInventoryModel::validate() const // outfits, trash and other non-automatic folders. validation_info->mFatalSystemDuplicate++; fatal_errs++; + // FIRE-31634 [OPENSIM] Better inventory validation logging + //LL_WARNS("Inventory") << "Fatal inventory corruption: system folder type has excess copies under root, type " << ft << " count " << count_under_root << LL_ENDL; } else { @@ -5282,6 +5281,8 @@ LLPointer LLInventoryModel::validate() const // Exception: FT_SETTINGS is not automatic, but only deserves a warning. validation_info->mWarnings["non_fatal_system_duplicate_under_root"]++; warning_count++; + // FIRE-31634 [OPENSIM] Better inventory validation logging + //LL_WARNS("Inventory") << "System folder type has excess copies under root, type " << ft << " count " << count_under_root << LL_ENDL; } } if (count_elsewhere > 0) diff --git a/indra/newview/llinventorymodelbackgroundfetch.cpp b/indra/newview/llinventorymodelbackgroundfetch.cpp index f3e192373c..cd5a02d943 100644 --- a/indra/newview/llinventorymodelbackgroundfetch.cpp +++ b/indra/newview/llinventorymodelbackgroundfetch.cpp @@ -303,7 +303,7 @@ void LLInventoryModelBackgroundFetch::start(const LLUUID& id, bool recursive) // Not only root folder can be massive, but // most system folders will be requested independently // so request root folder and content separately - mFetchFolderQueue.push_front(FetchQueueInfo(gInventory.getRootFolderID(), FT_CONTENT_RECURSIVE)); + mFetchFolderQueue.push_front(FetchQueueInfo(gInventory.getRootFolderID(), FT_FOLDER_AND_CONTENT)); } else { @@ -358,17 +358,7 @@ void LLInventoryModelBackgroundFetch::start(const LLUUID& id, bool recursive) void LLInventoryModelBackgroundFetch::scheduleFolderFetch(const LLUUID& cat_id, bool forced) { - if (AISAPI::isAvailable()) - { - if (mFetchFolderQueue.empty() || mFetchFolderQueue.back().mUUID != cat_id) - { - // On AIS make sure root goes to the top and follow up recursive - // fetches, not individual requests - mFetchFolderQueue.push_back(FetchQueueInfo(cat_id, forced ? FT_FORCED : FT_DEFAULT)); - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); - } - } - else if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != cat_id) + if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != cat_id) { // Specific folder requests go to front of queue. mFetchFolderQueue.push_front(FetchQueueInfo(cat_id, forced ? FT_FORCED : FT_DEFAULT)); @@ -630,10 +620,67 @@ void ais_simple_item_callback(const LLUUID& inv_id) LLInventoryModelBackgroundFetch::instance().incrFetchCount(-1); } -void LLInventoryModelBackgroundFetch::onAISFolderCalback(const LLUUID &request_id, const LLUUID &response_id, EFetchType recursion) +void LLInventoryModelBackgroundFetch::onAISContentCalback( + const LLUUID& request_id, + const uuid_vec_t& content_ids, + const LLUUID& response_id, + EFetchType fetch_type) { + // Don't push_front on failure - there is a chance it was fired from inside bulkFetchViaAis incrFetchFolderCount(-1); - std::list::const_iterator found = std::find(mExpectedFolderIds.begin() , mExpectedFolderIds.end(), request_id); + + uuid_vec_t::const_iterator folder_iter = content_ids.begin(); + uuid_vec_t::const_iterator folder_end = content_ids.end(); + while (folder_iter != folder_end) + { + std::list::const_iterator found = std::find(mExpectedFolderIds.begin(), mExpectedFolderIds.end(), *folder_iter); + if (found != mExpectedFolderIds.end()) + { + mExpectedFolderIds.erase(found); + } + + LLViewerInventoryCategory* cat(gInventory.getCategory(*folder_iter)); + if (cat) + { + cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); + } + if (response_id.isNull()) + { + // Failed to fetch, get it individually + mFetchFolderQueue.push_back(FetchQueueInfo(*folder_iter, FT_RECURSIVE)); + } + else + { + // push descendant back to verify they are fetched fully (ex: didn't encounter depth limit) + LLInventoryModel::cat_array_t* categories(NULL); + LLInventoryModel::item_array_t* items(NULL); + gInventory.getDirectDescendentsOf(*folder_iter, categories, items); + if (categories) + { + for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); + it != categories->end(); + ++it) + { + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); + } + } + } + + folder_iter++; + } + + if (!mFetchFolderQueue.empty()) + { + mBackgroundFetchActive = true; + mFolderFetchActive = true; + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } +} +void LLInventoryModelBackgroundFetch::onAISFolderCalback(const LLUUID &request_id, const LLUUID &response_id, EFetchType fetch_type) +{ + // Don't push_front on failure - there is a chance it was fired from inside bulkFetchViaAis + incrFetchFolderCount(-1); + std::list::const_iterator found = std::find(mExpectedFolderIds.begin(), mExpectedFolderIds.end(), request_id); if (found != mExpectedFolderIds.end()) { mExpectedFolderIds.erase(found); @@ -655,32 +702,34 @@ void LLInventoryModelBackgroundFetch::onAISFolderCalback(const LLUUID &request_i if (response_id.isNull()) // Failure { LL_DEBUGS(LOG_INV , "AIS3") << "Failure response for folder " << request_id << LL_ENDL; - if (recursion == FT_RECURSIVE) + if (fetch_type == FT_RECURSIVE) { // A full recursive request failed. // Try requesting folder and nested content separately - mBackgroundFetchActive = true; - mFolderFetchActive = true; - mFetchFolderQueue.push_front(FetchQueueInfo(request_id, FT_CONTENT_RECURSIVE)); - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + mFetchFolderQueue.push_back(FetchQueueInfo(request_id, FT_FOLDER_AND_CONTENT)); } - else if (recursion == FT_CONTENT_RECURSIVE) + else if (fetch_type == FT_FOLDER_AND_CONTENT) { LL_WARNS() << "Failed to download folder: " << request_id << " Requesting known content separately" << LL_ENDL; - request_descendants = true; + mFetchFolderQueue.push_back(FetchQueueInfo(request_id, FT_CONTENT_RECURSIVE)); } } else { - if (recursion == FT_CONTENT_RECURSIVE || recursion == FT_RECURSIVE) + if (fetch_type == FT_RECURSIVE) { - // Got the folder, now recursively request content + // Got the folder and content, now verify content // Request content even for FT_RECURSIVE in case of changes, failures // or if depth limit gets imlemented. // This shouldn't redownload folders if they already have version request_descendants = true; LL_DEBUGS(LOG_INV, "AIS3") << "Got folder " << request_id << ". Requesting content" << LL_ENDL; } + else if (fetch_type == FT_FOLDER_AND_CONTENT) + { + // readd folder for content request + mFetchFolderQueue.push_front(FetchQueueInfo(request_id, FT_CONTENT_RECURSIVE)); + } else { LL_DEBUGS(LOG_INV, "AIS3") << "Got folder " << request_id << "." << LL_ENDL; @@ -699,17 +748,18 @@ void LLInventoryModelBackgroundFetch::onAISFolderCalback(const LLUUID &request_i it != categories->end(); ++it) { - mFetchFolderQueue.push_front(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); - } - if (!mFetchFolderQueue.empty()) - { - mBackgroundFetchActive = true; - mFolderFetchActive = true; - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); } } } + if (!mFetchFolderQueue.empty()) + { + mBackgroundFetchActive = true; + mFolderFetchActive = true; + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } + // done LLViewerInventoryCategory * cat(gInventory.getCategory(request_id)); if (cat) @@ -730,7 +780,9 @@ void LLInventoryModelBackgroundFetch::bulkFetchViaAis() } static LLCachedControl ais_pool(gSavedSettings, "PoolSizeAIS", 20); - const U32 max_concurrent_fetches = llmax(1, ais_pool - 1); + // Don't have too many requests at once, AIS throttles + // Reserve one request for actions outside of fetch (like renames) + const U32 max_concurrent_fetches = llclamp(ais_pool - 1, 1, 50); if (mFetchCount >= max_concurrent_fetches) { @@ -833,10 +885,111 @@ void LLInventoryModelBackgroundFetch::bulkFetchViaAis(const FetchQueueInfo& fetc LLViewerInventoryCategory * cat(gInventory.getCategory(cat_id)); if (cat) { - if (LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion() || fetch_info.mFetchType == FT_FORCED) + if (fetch_info.mFetchType == FT_CONTENT_RECURSIVE) + { + // fetch content only, ignore cat itself + uuid_vec_t children; + LLInventoryModel::cat_array_t* categories(NULL); + LLInventoryModel::item_array_t* items(NULL); + gInventory.getDirectDescendentsOf(cat_id, categories, items); + + LLViewerInventoryCategory::EFetchType target_state = LLViewerInventoryCategory::FETCH_RECURSIVE; + bool content_done = true; + + // Top limit is 'as many as you can put into url' + static LLCachedControl ais_batch(gSavedSettings, "BatchSizeAIS3", 20); + S32 batch_limit = llclamp(ais_batch(), 1, 40); + + for (LLInventoryModel::cat_array_t::iterator it = categories->begin(); + it != categories->end(); + ++it) + { + LLViewerInventoryCategory* child_cat = (*it); + if (LLViewerInventoryCategory::VERSION_UNKNOWN != child_cat->getVersion() + || child_cat->getFetching() >= target_state) + { + continue; + } + + if (child_cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_LISTINGS) + { + // special case + content_done = false; + if (children.empty()) + { + // fetch marketplace alone + // Should it actually be fetched as FT_FOLDER_AND_CONTENT? + children.push_back(child_cat->getUUID()); + mExpectedFolderIds.push_back(child_cat->getUUID()); + child_cat->setFetching(target_state); + break; + } + else + { + // fetch marketplace alone next run + continue; + } + } + + children.push_back(child_cat->getUUID()); + mExpectedFolderIds.push_back(child_cat->getUUID()); + child_cat->setFetching(target_state); + + if (children.size() >= batch_limit) + { + content_done = false; + break; + } + } + + if (!children.empty()) + { + // increment before call in case of immediate callback + incrFetchFolderCount(1); + + EFetchType type = fetch_info.mFetchType; + LLUUID cat_id = cat->getUUID(); // need a copy for lambda + AISAPI::completion_t cb = [cat_id, children, type](const LLUUID& response_id) + { + LLInventoryModelBackgroundFetch::instance().onAISContentCalback(cat_id, children, response_id, type); + }; + + AISAPI::ITEM_TYPE item_type = AISAPI::INVENTORY; + if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) + { + item_type = AISAPI::LIBRARY; + } + + AISAPI::FetchCategorySubset(cat_id, children, item_type, true, cb, 0); + } + + if (content_done) + { + // This will have a bit of overlap with onAISContentCalback, + // but something else might have dowloaded folders, so verify + // every child that is complete has it's children done as well + for (LLInventoryModel::cat_array_t::iterator it = categories->begin(); + it != categories->end(); + ++it) + { + LLViewerInventoryCategory* child_cat = (*it); + if (LLViewerInventoryCategory::VERSION_UNKNOWN != child_cat->getVersion()) + { + mFetchFolderQueue.push_back(FetchQueueInfo(child_cat->getUUID(), FT_RECURSIVE)); + } + } + } + else + { + // send it back to get the rest + mFetchFolderQueue.push_back(FetchQueueInfo(cat_id, FT_CONTENT_RECURSIVE)); + } + } + else if (LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion() + || fetch_info.mFetchType == FT_FORCED) { LLViewerInventoryCategory::EFetchType target_state = - fetch_info.mFetchType >= FT_CONTENT_RECURSIVE + fetch_info.mFetchType > FT_CONTENT_RECURSIVE ? LLViewerInventoryCategory::FETCH_RECURSIVE : LLViewerInventoryCategory::FETCH_NORMAL; // start again if we did a non-recursive fetch before @@ -867,7 +1020,7 @@ void LLInventoryModelBackgroundFetch::bulkFetchViaAis(const FetchQueueInfo& fetc else { // Already fetched, check if anything inside needs fetching - if (fetch_info.mFetchType >= FT_CONTENT_RECURSIVE) + if (fetch_info.mFetchType == FT_RECURSIVE) { LLInventoryModel::cat_array_t * categories(NULL); LLInventoryModel::item_array_t * items(NULL); @@ -881,7 +1034,7 @@ void LLInventoryModelBackgroundFetch::bulkFetchViaAis(const FetchQueueInfo& fetc } } } - } // else? + } // else try to fetch folder either way? } } else diff --git a/indra/newview/llinventorymodelbackgroundfetch.h b/indra/newview/llinventorymodelbackgroundfetch.h index 781a458b66..bc159f4727 100644 --- a/indra/newview/llinventorymodelbackgroundfetch.h +++ b/indra/newview/llinventorymodelbackgroundfetch.h @@ -64,8 +64,8 @@ public: bool inventoryFetchCompleted() const; bool inventoryFetchInProgress() const; - void findLostItems(); - void incrFetchCount(S32 fetching); + void findLostItems(); + void incrFetchCount(S32 fetching); void incrFetchFolderCount(S32 fetching); bool isBulkFetchProcessingComplete() const; @@ -82,8 +82,9 @@ protected: typedef enum { FT_DEFAULT = 0, - FT_FORCED, // request even if already loaded + FT_FORCED, // request non-recursively even if already loaded FT_CONTENT_RECURSIVE, // request content recursively + FT_FOLDER_AND_CONTENT, // request folder, then content recursively FT_RECURSIVE, // request everything recursively } EFetchType; struct FetchQueueInfo @@ -100,7 +101,8 @@ protected: }; typedef std::deque fetch_queue_t; - void onAISFolderCalback(const LLUUID &request_id, const LLUUID &response_id, EFetchType recursion); + void onAISContentCalback(const LLUUID& request_id, const uuid_vec_t &content_ids, const LLUUID& response_id, EFetchType fetch_type); + void onAISFolderCalback(const LLUUID &request_id, const LLUUID &response_id, EFetchType fetch_type); void bulkFetchViaAis(); void bulkFetchViaAis(const FetchQueueInfo& fetch_info); void bulkFetch(); diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index 6d7363261e..ad9c261e3e 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -979,7 +979,10 @@ void LLInventoryPanel::initializeViews(F64 max_time) gIdleCallbacks.addFunction(idle, this); - openStartFolderOrMyInventory(); + if(mParams.open_first_folder) + { + openStartFolderOrMyInventory(); + } // Special case for new user login if (gAgent.isFirstLogin()) @@ -2349,10 +2352,11 @@ void LLInventorySingleFolderPanel::setSelectCallback(const boost::function folder_view; Optional folder; Optional item; + Optional open_first_folder; // All item and folder views will be initialized on init if true (default) // Will initialize on visibility change otherwise. @@ -126,6 +127,7 @@ public: show_root_folder("show_root_folder", false), allow_drop_on_root("allow_drop_on_root", true), use_marketplace_folders("use_marketplace_folders", false), + open_first_folder("open_first_folder", true), scroll("scroll"), accepts_drag_and_drop("accepts_drag_and_drop"), folder_view("folder_view"), diff --git a/indra/newview/llpanelmaininventory.cpp b/indra/newview/llpanelmaininventory.cpp index ed633b3369..2fb777e768 100644 --- a/indra/newview/llpanelmaininventory.cpp +++ b/indra/newview/llpanelmaininventory.cpp @@ -134,6 +134,7 @@ LLPanelMainInventory::LLPanelMainInventory(const LLPanel::Params& p) mSingleFolderMode(false), mForceShowInvLayout(false), mViewMode(MODE_COMBINATION), + mCombinationShapeDirty(true), mListViewRootUpdatedConnection(), mGalleryRootUpdatedConnection(), mViewMenuButton(nullptr), // Keep better inventory layout @@ -374,6 +375,8 @@ BOOL LLPanelMainInventory::postBuild() mInventoryGalleryPanel = getChild("gallery_view_inv"); mGalleryRootUpdatedConnection = mInventoryGalleryPanel->setRootChangedCallback(boost::bind(&LLPanelMainInventory::updateTitle, this)); + mCombinationScrollPanel = getChild("combination_view_inventory"); + mCombinationInventoryPanel = getChild("comb_single_folder_inv"); LLInventoryFilter& comb_inv_filter = mCombinationInventoryPanel->getFilter(); comb_inv_filter.setFilterThumbnails(LLInventoryFilter::FILTER_EXCLUDE_THUMBNAILS); @@ -387,6 +390,8 @@ BOOL LLPanelMainInventory::postBuild() comb_gallery_filter.markDefault(); mCombinationGalleryPanel->setRootChangedCallback(boost::bind(&LLPanelMainInventory::onCombinationRootChanged, this, true)); + mCombinationScroller = getChild("combination_scroller"); + initListCommandsHandlers(); const std::string texture_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost()); const std::string sound_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getSoundUploadCost()); @@ -402,6 +407,7 @@ BOOL LLPanelMainInventory::postBuild() // Trigger callback for focus received so we can deselect items in inbox/outbox LLFocusableElement::setFocusReceivedCallback(boost::bind(&LLPanelMainInventory::onFocusReceived, this)); + mCombinationShapeDirty = true; return TRUE; } @@ -590,7 +596,7 @@ void LLPanelMainInventory::newFolderWindow(LLUUID folder_id, LLUUID item_to_sele { LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); if (main_inventory && main_inventory->isSingleFolderMode() - && (main_inventory->getSingleFolderViewRoot() == folder_id)) + && (main_inventory->getCurrentSFVRoot() == folder_id)) { main_inventory->setFocus(true); if(item_to_select.notNull()) @@ -1251,9 +1257,18 @@ BOOL LLPanelMainInventory::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, } // virtual -void LLPanelMainInventory::changed(U32) +void LLPanelMainInventory::changed(U32 mask) { updateItemcountText(); + if ((mask & LLInventoryObserver::REBUILD) + || (mask & LLInventoryObserver::STRUCTURE) + || (mask & LLInventoryObserver::REMOVE) + || (mask & LLInventoryObserver::ADD) + || (mask & LLInventoryObserver::LABEL)) + { + // todo: can be limited to just observed folder + mCombinationShapeDirty = true; + } } void LLPanelMainInventory::setFocusFilterEditor() @@ -1264,6 +1279,13 @@ void LLPanelMainInventory::setFocusFilterEditor() } } + +void LLPanelMainInventory::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + mCombinationShapeDirty = true; + LLPanel::reshape(width, height, called_from_parent); +} + // virtual void LLPanelMainInventory::draw() { @@ -1293,6 +1315,7 @@ void LLPanelMainInventory::draw() mActivePanel->setSortOrder(order); mResortActivePanel = false; } + LLPanel::draw(); updateItemcountText(); updateCombinationVisibility(); @@ -1982,9 +2005,9 @@ void LLPanelMainInventory::toggleViewMode() getChild("default_inventory_panel")->setVisible(!mSingleFolderMode); getChild("single_folder_inventory")->setVisible(mSingleFolderMode && isListViewMode()); getChild("gallery_view_inventory")->setVisible(mSingleFolderMode && isGalleryViewMode()); - getChild("combination_view_inventory")->setVisible(mSingleFolderMode && isCombinationViewMode()); getChild("nav_buttons")->setVisible(mSingleFolderMode); getChild("view_mode_btn")->setImageOverlay(mSingleFolderMode ? getString("default_mode_btn") : getString("single_folder_mode_btn")); + mCombinationScrollPanel->setVisible(mSingleFolderMode && isCombinationViewMode()); // Disable Expand/Collapse buttons in single folder mode getChild("collapse_expand_buttons")->setVisible(!mSingleFolderMode); @@ -2446,6 +2469,7 @@ void LLPanelMainInventory::onVisibilityChange( BOOL new_visibility ) } // } + mCombinationShapeDirty = true; } bool LLPanelMainInventory::isSaveTextureEnabled(const LLSD& userdata) @@ -2911,27 +2935,57 @@ void LLPanelMainInventory::onCombinationRootChanged(bool gallery_clicked) } mForceShowInvLayout = false; updateTitle(); + + //force update scroll container + mCombinationShapeDirty = true; } void LLPanelMainInventory::updateCombinationVisibility() { - if(mSingleFolderMode && isCombinationViewMode()) + if(mSingleFolderMode && isCombinationViewMode() && mCombinationShapeDirty) { - bool is_gallery_empty = !mCombinationGalleryPanel->hasVisibleItems(); - bool show_inv_pane = mCombinationInventoryPanel->hasVisibleItems() || is_gallery_empty || mForceShowInvLayout; - getChild("comb_gallery_layout")->setVisible(!is_gallery_empty); - getChild("comb_inventory_layout")->setVisible(show_inv_pane); - mCombinationInventoryPanel->getRootFolder()->setForceArrange(!show_inv_pane); - if(mCombinationInventoryPanel->hasVisibleItems()) - { - mForceShowInvLayout = false; - } - if(is_gallery_empty) + mCombinationShapeDirty = false; + mCombinationInventoryPanel->reshape(1,1); // HACK: force reduce visible area + if (!mCombinationGalleryPanel->hasVisibleItems()) { mCombinationGalleryPanel->handleModifiedFilter(); } - - getActivePanel()->getRootFolder(); + LLRect inv_rect = mCombinationInventoryPanel->getRect(); + LLRect inv_inner_rect = mCombinationInventoryPanel->getScrollableContainer()->getScrolledViewRect(); + LLRect galery_rect = mCombinationGalleryPanel->getRect(); + LLRect inner_galery_rect = mCombinationGalleryPanel->getScrollableContainer()->getScrolledViewRect(); + LLScrollContainer* scroll = static_cast(mCombinationScrollPanel); + LLRect scroller_window_rect = scroll->getContentWindowRect(); + S32 desired_width = llmax(inv_inner_rect.getWidth(), scroller_window_rect.getWidth()); + const S32 BORDER_PAD = 2; // two sides + + inv_rect.mBottom = 0; + inv_rect.mRight = inv_rect.mLeft + desired_width; + if (!mCombinationGalleryPanel->hasVisibleItems() || mCombinationInventoryPanel->hasVisibleItems()) + { + inv_rect.mTop = inv_rect.mBottom + inv_inner_rect.getHeight() + BORDER_PAD; + } + else + { + inv_rect.mTop = inv_rect.mBottom; + } + + galery_rect.mBottom = inv_rect.mTop; + galery_rect.mRight = galery_rect.mLeft + scroller_window_rect.getWidth(); + if (mCombinationGalleryPanel->hasVisibleItems()) + { + mCombinationGalleryPanel->setVisible(true); + galery_rect.mTop = galery_rect.mBottom + inner_galery_rect.getHeight() + BORDER_PAD; + } + else + { + mCombinationGalleryPanel->setVisible(false); + galery_rect.mTop = galery_rect.mBottom; + } + + mCombinationScroller->reshape(desired_width, inv_rect.getHeight() + galery_rect.getHeight(), true); + mCombinationGalleryPanel->setShape(galery_rect, false); + mCombinationInventoryPanel->setShape(inv_rect, false); } } @@ -2996,7 +3050,7 @@ void LLPanelMainInventory::setViewMode(EViewModeType mode) getChild("single_folder_inventory")->setVisible(mSingleFolderMode && isListViewMode()); getChild("gallery_view_inventory")->setVisible(mSingleFolderMode && isGalleryViewMode()); - getChild("combination_view_inventory")->setVisible(mSingleFolderMode && isCombinationViewMode()); + mCombinationScrollPanel->setVisible(mSingleFolderMode && isCombinationViewMode()); if(isListViewMode()) { diff --git a/indra/newview/llpanelmaininventory.h b/indra/newview/llpanelmaininventory.h index fb3ab02586..7b52d6a237 100644 --- a/indra/newview/llpanelmaininventory.h +++ b/indra/newview/llpanelmaininventory.h @@ -85,8 +85,9 @@ public: EAcceptance* accept, std::string& tooltip_msg); /*virtual*/ void changed(U32); + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); /*virtual*/ void draw(); - /*virtual*/ void onVisibilityChange ( BOOL new_visibility ); + /*virtual*/ void onVisibilityChange ( BOOL new_visibility ); // CTRL-F focusses local search editor /*virtual*/ bool hasAccelerators() const { return true; } @@ -160,8 +161,6 @@ protected: static BOOL filtersVisible(void* user_data); void onClearSearch(); - static void onFoldersByName(void *user_data); - static BOOL checkFoldersByName(void *user_data); static BOOL incrementalFind(LLFolderViewItem* first_item, const char *find_text, BOOL backward); void onFilterSelected(); @@ -220,8 +219,10 @@ private: LLInventorySingleFolderPanel* mSingleFolderPanelInventory; LLInventoryGallery* mInventoryGalleryPanel; + LLUICtrl* mCombinationScrollPanel; LLInventorySingleFolderPanel* mCombinationInventoryPanel; LLInventoryGallery* mCombinationGalleryPanel; + LLView* mCombinationScroller; // Filter dropdown LLComboBox* mFilterComboBox; @@ -295,6 +296,7 @@ private: bool mNeedUploadCost; bool mForceShowInvLayout; + bool mCombinationShapeDirty; // List Commands // //////////////////////////////////////////////////////////////////////////////// }; diff --git a/indra/newview/llpanelmarketplaceinboxinventory.cpp b/indra/newview/llpanelmarketplaceinboxinventory.cpp index bdc38e0233..990e0abb69 100644 --- a/indra/newview/llpanelmarketplaceinboxinventory.cpp +++ b/indra/newview/llpanelmarketplaceinboxinventory.cpp @@ -64,10 +64,13 @@ LLInboxInventoryPanel::LLInboxInventoryPanel(const LLInboxInventoryPanel::Params : LLInventoryPanel(p) { LLInboxNewItemsStorage::getInstance()->load(); + LLInboxNewItemsStorage::getInstance()->addInboxPanel(this); } LLInboxInventoryPanel::~LLInboxInventoryPanel() -{} +{ + LLInboxNewItemsStorage::getInstance()->removeInboxPanel(this); +} void LLInboxInventoryPanel::initFromParams(const LLInventoryPanel::Params& params) { @@ -128,6 +131,21 @@ LLFolderViewItem * LLInboxInventoryPanel::createFolderViewItem(LLInvFVBridge * b return LLUICtrlFactory::create(params); } +void LLInboxInventoryPanel::onRemoveItemFreshness(const LLUUID& item_id) +{ + LLInboxFolderViewFolder* inbox_folder_view = dynamic_cast(getFolderByID(item_id)); + if(inbox_folder_view) + { + inbox_folder_view->setFresh(false); + } + + LLInboxFolderViewItem* inbox_item_view = dynamic_cast(getItemByID(item_id)); + if(inbox_item_view) + { + inbox_item_view->setFresh(false); + } +} + // // LLInboxFolderViewFolder Implementation // @@ -364,4 +382,18 @@ void LLInboxNewItemsStorage::load() } } } + +void LLInboxNewItemsStorage::removeItem(const LLUUID& id) +{ + mNewItemsIDs.erase(id); + + //notify inbox panels + for (auto inbox : mInboxPanels) + { + if(inbox) + { + inbox->onRemoveItemFreshness(id); + } + } +} // eof diff --git a/indra/newview/llpanelmarketplaceinboxinventory.h b/indra/newview/llpanelmarketplaceinboxinventory.h index 3e508e801b..9eef5f209c 100644 --- a/indra/newview/llpanelmarketplaceinboxinventory.h +++ b/indra/newview/llpanelmarketplaceinboxinventory.h @@ -49,6 +49,8 @@ public: void initFromParams(const LLInventoryPanel::Params&); LLFolderViewFolder* createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop); LLFolderViewItem * createFolderViewItem(LLInvFVBridge * bridge); + + void onRemoveItemFreshness(const LLUUID& item_id); }; @@ -77,6 +79,7 @@ public: void deFreshify(); bool isFresh() const { return mFresh; } + void setFresh(bool is_fresh) { mFresh = is_fresh; } protected: bool mFresh; @@ -108,6 +111,7 @@ public: void deFreshify(); bool isFresh() const { return mFresh; } + void setFresh(bool is_fresh) { mFresh = is_fresh; } protected: bool mFresh; @@ -125,11 +129,16 @@ public: void load(); void addFreshItem(const LLUUID& id) { mNewItemsIDs.insert(id); } - void removeItem(const LLUUID& id) { mNewItemsIDs.erase(id); } + void removeItem(const LLUUID& id); bool isItemFresh(const LLUUID& id) { return (mNewItemsIDs.find(id) != mNewItemsIDs.end()); } + void addInboxPanel(LLInboxInventoryPanel* inbox) { mInboxPanels.insert(inbox); } + void removeInboxPanel(LLInboxInventoryPanel* inbox) { mInboxPanels.erase(inbox); } + private: std::set mNewItemsIDs; + + std::set mInboxPanels; }; #endif //LL_INBOXINVENTORYPANEL_H diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 6dd577b4fe..136bfc89f4 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -2985,7 +2985,7 @@ bool idle_startup() gAgentWearables.sendDummyAgentWearablesUpdate(); } // [Legacy Bake] - callAfterCategoryFetch(LLAppearanceMgr::instance().getCOF(), set_flags_and_update_appearance); + callAfterCOFFetch(set_flags_and_update_appearance); } display_startup(); diff --git a/indra/newview/lltoolbarview.cpp b/indra/newview/lltoolbarview.cpp index bbeab246b6..0222ca2e03 100644 --- a/indra/newview/lltoolbarview.cpp +++ b/indra/newview/lltoolbarview.cpp @@ -385,9 +385,9 @@ bool LLToolBarView::loadToolbars(bool force_default) } // SL-18581: Don't show the starter avatar toolbar button for NUX users - LLViewerInventoryCategory* my_outfits_cat = gInventory.getCategory(gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS)); if (gAgent.isFirstLogin()) { + LLViewerInventoryCategory* my_outfits_cat = gInventory.getCategory(gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS)); LL_WARNS() << "First login: checking for NUX user." << LL_ENDL; if (my_outfits_cat != NULL && my_outfits_cat->getDescendentCount() > 0) { diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 960dfd57af..0088a3db29 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -780,9 +780,16 @@ void LLViewerInventoryCategory::setFetching(LLViewerInventoryCategory::EFetchTyp { if (mDescendentsRequested.hasExpired() || (mFetching == FETCH_NONE)) { - const F32 FETCH_TIMER_EXPIRY = 30.0f; mDescendentsRequested.reset(); - mDescendentsRequested.setTimerExpirySec(FETCH_TIMER_EXPIRY); + if (AISAPI::isAvailable()) + { + mDescendentsRequested.setTimerExpirySec(AISAPI::HTTP_TIMEOUT); + } + else + { + const F32 FETCH_TIMER_EXPIRY = 30.0f; + mDescendentsRequested.setTimerExpirySec(FETCH_TIMER_EXPIRY); + } } mFetching = fetching; } @@ -1600,19 +1607,18 @@ void update_inventory_category( return; } - LLPointer new_cat = new LLViewerInventoryCategory(obj); - new_cat->fromLLSD(updates); // [UDP-Msg] if (AISAPI::isAvailable()) { // [UDP-Msg] - LLSD new_llsd = new_cat->asLLSD(); AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); - AISAPI::UpdateCategory(cat_id, new_llsd, cr); + AISAPI::UpdateCategory(cat_id, updates, cr); // [UDP-Msg] } else { + LLPointer new_cat = new LLViewerInventoryCategory(obj); + new_cat->fromLLSD(updates); LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_UpdateInventoryFolder); msg->nextBlockFast(_PREHASH_AgentData); diff --git a/indra/newview/skins/ansastorm/xui/en/panel_main_inventory.xml b/indra/newview/skins/ansastorm/xui/en/panel_main_inventory.xml index 6f90d20fa5..4e7b358821 100644 --- a/indra/newview/skins/ansastorm/xui/en/panel_main_inventory.xml +++ b/indra/newview/skins/ansastorm/xui/en/panel_main_inventory.xml @@ -900,7 +900,7 @@ follows="all" layout="topleft" /> - - - - + + + + + + + - - - - - - - - - + - - - - + + + + + + + - - - - - - - - - +