diff options
Diffstat (limited to 'indra/newview/llinventorymodel.cpp')
-rw-r--r-- | indra/newview/llinventorymodel.cpp | 2544 |
1 files changed, 925 insertions, 1619 deletions
diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 17a8b84472..53835f0166 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -2,116 +2,67 @@ * @file llinventorymodel.cpp * @brief Implementation of the inventory model used to track agent inventory. * - * $LicenseInfo:firstyear=2002&license=viewergpl$ - * - * Copyright (c) 2002-2007, Linden Research, Inc. - * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 + * Copyright (C) 2010, Linden Research, Inc. * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" - #include "llinventorymodel.h" -#include "llassetstorage.h" -#include "llcrc.h" -#include "lldir.h" -#include "llsys.h" -#include "llxfermanager.h" -#include "message.h" - #include "llagent.h" -#include "llfloater.h" -#include "llfocusmgr.h" -#include "llinventoryview.h" -#include "llviewerinventory.h" +#include "llagentwearables.h" +#include "llappearancemgr.h" +#include "llinventorypanel.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llinventoryobserver.h" +#include "llinventorypanel.h" +#include "llnotificationsutil.h" +#include "llwindow.h" +#include "llviewercontrol.h" +#include "llpreview.h" #include "llviewermessage.h" +#include "llviewerfoldertype.h" #include "llviewerwindow.h" -#include "llviewerregion.h" #include "llappviewer.h" -#include "lldbstrings.h" -#include "llviewerstats.h" -#include "llmutelist.h" -#include "llnotify.h" +#include "llviewerregion.h" #include "llcallbacklist.h" -#include "llpreview.h" -#include "llviewercontrol.h" -#include "llsdutil.h" -#include <deque> +#include "llvoavatarself.h" //#define DIFF_INVENTORY_FILES #ifdef DIFF_INVENTORY_FILES #include "process.h" #endif -BOOL LLInventoryModel::sBackgroundFetchActive = FALSE; -BOOL LLInventoryModel::sAllFoldersFetched = FALSE; -BOOL LLInventoryModel::sFullFetchStarted = FALSE; -S32 LLInventoryModel::sNumFetchRetries = 0; -F32 LLInventoryModel::sMinTimeBetweenFetches = 0.3f; -F32 LLInventoryModel::sMaxTimeBetweenFetches = 10.f; -BOOL LLInventoryModel::sTimelyFetchPending = FALSE; -LLFrameTimer LLInventoryModel::sFetchTimer; -LLInventoryModel::cat_map_t LLInventoryModel::sBulkFetchMap; -S16 LLInventoryModel::sBulkFetchCount = 0; - -// RN: for some reason, using std::queue in the header file confuses the compiler which things it's an xmlrpc_queue -static std::deque<LLUUID> sFetchQueue; +// Increment this if the inventory contents change in a non-backwards-compatible way. +// For viewer 2, the addition of link items makes a pre-viewer-2 cache incorrect. +const S32 LLInventoryModel::sCurrentInvCacheVersion = 2; +BOOL LLInventoryModel::sFirstTimeInViewer2 = TRUE; ///---------------------------------------------------------------------------- /// Local function declarations, constants, enums, and typedefs ///---------------------------------------------------------------------------- //BOOL decompress_file(const char* src_filename, const char* dst_filename); -const F32 MAX_TIME_FOR_SINGLE_FETCH = 10.f; -const S32 MAX_FETCH_RETRIES = 5; const char CACHE_FORMAT_STRING[] = "%s.inv"; -const char* NEW_CATEGORY_NAME = "New Folder"; -const char* NEW_CATEGORY_NAMES[LLAssetType::AT_COUNT] = -{ - "Textures", // AT_TEXTURE - "Sounds", // AT_SOUND - "Calling Cards", // AT_CALLINGCARD - "Landmarks", // AT_LANDMARK - "Scripts", // AT_SCRIPT (deprecated?) - "Clothing", // AT_CLOTHING - "Objects", // AT_OBJECT - "Notecards", // AT_NOTECARD - "New Folder", // AT_CATEGORY - "Inventory", // AT_ROOT_CATEGORY - "Scripts", // AT_LSL_TEXT - "Scripts", // AT_LSL_BYTECODE - "Uncompressed Images", // AT_TEXTURE_TGA - "Body Parts", // AT_BODYPART - "Trash", // AT_TRASH - "Photo Album", // AT_SNAPSHOT_CATEGORY - "Lost And Found", // AT_LOST_AND_FOUND - "Uncompressed Sounds", // AT_SOUND_WAV - "Uncompressed Images", // AT_IMAGE_TGA - "Uncompressed Images", // AT_IMAGE_JPEG - "Animations", // AT_ANIMATION - "Gestures", // AT_GESTURE -}; struct InventoryIDPtrLess { @@ -178,9 +129,21 @@ bool LLCanCache::operator()(LLInventoryCategory* cat, LLInventoryItem* item) LLInventoryModel gInventory; // Default constructor -LLInventoryModel::LLInventoryModel() : - mModifyMask(LLInventoryObserver::ALL), +LLInventoryModel::LLInventoryModel() +: mModifyMask(LLInventoryObserver::ALL), + mChangedItemIDs(), + mCategoryMap(), + mItemMap(), + mCategoryLock(), + mItemLock(), mLastItem(NULL), + mParentChildCategoryTree(), + mParentChildItemTree(), + mObservers(), + mRootFolderID(), + mLibraryRootFolderID(), + mLibraryOwnerID(), + mIsNotifyObservers(FALSE), mIsAgentInvUsable(false) { } @@ -188,20 +151,31 @@ LLInventoryModel::LLInventoryModel() : // Destroys the object LLInventoryModel::~LLInventoryModel() { + cleanupInventory(); +} + +void LLInventoryModel::cleanupInventory() +{ empty(); - for (observer_list_t::iterator iter = mObservers.begin(); - iter != mObservers.end(); ++iter) + // Deleting one observer might erase others from the list, so always pop off the front + while (!mObservers.empty()) { - delete *iter; + observer_list_t::iterator iter = mObservers.begin(); + LLInventoryObserver* observer = *iter; + mObservers.erase(iter); + delete observer; } + mObservers.clear(); } // This is a convenience function to check if one object has a parent // chain up to the category specified by UUID. BOOL LLInventoryModel::isObjectDescendentOf(const LLUUID& obj_id, - const LLUUID& cat_id) + const LLUUID& cat_id) const { - LLInventoryObject* obj = getObject(obj_id); + if (obj_id == cat_id) return TRUE; + + const LLInventoryObject* obj = getObject(obj_id); while(obj) { const LLUUID& parent_id = obj->getParentUUID(); @@ -220,6 +194,29 @@ BOOL LLInventoryModel::isObjectDescendentOf(const LLUUID& obj_id, return FALSE; } +const LLViewerInventoryCategory *LLInventoryModel::getFirstNondefaultParent(const LLUUID& obj_id) const +{ + const LLInventoryObject* obj = getObject(obj_id); + + // Search up the parent chain until we get to root or an acceptable folder. + // This assumes there are no cycles in the tree else we'll get a hang. + LLUUID parent_id = obj->getParentUUID(); + while (!parent_id.isNull()) + { + const LLViewerInventoryCategory *cat = getCategory(parent_id); + if (!cat) break; + const LLFolderType::EType folder_type = cat->getPreferredType(); + if (folder_type != LLFolderType::FT_NONE && + folder_type != LLFolderType::FT_ROOT_INVENTORY && + !LLFolderType::lookupIsEnsembleType(folder_type)) + { + return cat; + } + parent_id = cat->getParentUUID(); + } + return NULL; +} + // Get the object by id. Returns NULL if not found. LLInventoryObject* LLInventoryModel::getObject(const LLUUID& id) const { @@ -291,34 +288,68 @@ void LLInventoryModel::getDirectDescendentsOf(const LLUUID& cat_id, items = get_ptr_in_map(mParentChildItemTree, cat_id); } -// findCategoryUUIDForType() returns the uuid of the category that -// specifies 'type' as what it defaults to containing. The category is -// not necessarily only for that type. *NOTE: This will create a new -// inventory category on the fly if one does not exist. -LLUUID LLInventoryModel::findCategoryUUIDForType(LLAssetType::EType t, bool create_folder) +LLMD5 LLInventoryModel::hashDirectDescendentNames(const LLUUID& cat_id) const { - LLUUID rv = findCatUUID(t); - if(rv.isNull() && isInventoryUsable() && create_folder) + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + getDirectDescendentsOf(cat_id,cat_array,item_array); + LLMD5 item_name_hash; + if (!item_array) { - LLUUID root_id = gAgent.getInventoryRootID(); - if(root_id.notNull()) - { - rv = createNewCategory(root_id, t, NULL); - } + item_name_hash.finalize(); + return item_name_hash; } - return rv; + for (LLInventoryModel::item_array_t::const_iterator iter = item_array->begin(); + iter != item_array->end(); + iter++) + { + const LLViewerInventoryItem *item = (*iter); + if (!item) + continue; + item_name_hash.update(item->getName()); + } + item_name_hash.finalize(); + return item_name_hash; } -// Internal method which looks for a category with the specified -// preferred type. Returns LLUUID::null if not found. -LLUUID LLInventoryModel::findCatUUID(LLAssetType::EType preferred_type) +// SJB: Added version to lock the arrays to catch potential logic bugs +void LLInventoryModel::lockDirectDescendentArrays(const LLUUID& cat_id, + cat_array_t*& categories, + item_array_t*& items) { - LLUUID root_id = gAgent.getInventoryRootID(); - if(LLAssetType::AT_CATEGORY == preferred_type) + getDirectDescendentsOf(cat_id, categories, items); + if (categories) { - return root_id; + mCategoryLock[cat_id] = true; } - if(root_id.notNull()) + if (items) + { + mItemLock[cat_id] = true; + } +} + +void LLInventoryModel::unlockDirectDescendentArrays(const LLUUID& cat_id) +{ + mCategoryLock[cat_id] = false; + mItemLock[cat_id] = false; +} + +// findCategoryUUIDForType() returns the uuid of the category that +// specifies 'type' as what it defaults to containing. The category is +// not necessarily only for that type. *NOTE: This will create a new +// inventory category on the fly if one does not exist. +const LLUUID LLInventoryModel::findCategoryUUIDForType(LLFolderType::EType preferred_type, + bool create_folder, + bool find_in_library) +{ + LLUUID rv = LLUUID::null; + + const LLUUID &root_id = (find_in_library) ? gInventory.getLibraryRootFolderID() : gInventory.getRootFolderID(); + if(LLFolderType::FT_ROOT_INVENTORY == preferred_type) + { + rv = root_id; + } + else if (root_id.notNull()) { cat_array_t* cats = NULL; cats = get_ptr_in_map(mParentChildCategoryTree, root_id); @@ -329,12 +360,21 @@ LLUUID LLInventoryModel::findCatUUID(LLAssetType::EType preferred_type) { if(cats->get(i)->getPreferredType() == preferred_type) { - return cats->get(i)->getUUID(); + rv = cats->get(i)->getUUID(); + break; } } } } - return LLUUID::null; + + if(rv.isNull() && isInventoryUsable() && (create_folder && !find_in_library)) + { + if(root_id.notNull()) + { + return createNewCategory(root_id, preferred_type, LLStringUtil::null); + } + } + return rv; } // Convenience function to create a new category. You could call @@ -342,8 +382,8 @@ LLUUID LLInventoryModel::findCatUUID(LLAssetType::EType preferred_type) // version will take care of details like what the name should be // based on preferred type. Returns the UUID of the new category. LLUUID LLInventoryModel::createNewCategory(const LLUUID& parent_id, - LLAssetType::EType preferred_type, - const LLString& pname) + LLFolderType::EType preferred_type, + const std::string& pname) { LLUUID id; if(!isInventoryUsable()) @@ -352,20 +392,21 @@ LLUUID LLInventoryModel::createNewCategory(const LLUUID& parent_id, return id; } + if(LLFolderType::lookup(preferred_type) == LLFolderType::badLookup()) + { + lldebugs << "Attempt to create undefined category." << llendl; + return id; + } + id.generate(); - LLString name = pname; + std::string name = pname; if(!pname.empty()) { name.assign(pname); } - else if((preferred_type >= LLAssetType::AT_TEXTURE) && - (preferred_type < LLAssetType::AT_COUNT)) - { - name.assign(NEW_CATEGORY_NAMES[preferred_type]); - } else { - name.assign(NEW_CATEGORY_NAME); + name.assign(LLViewerFolderType::lookupNewCategoryName(preferred_type)); } // Add the category to the internal representation @@ -423,12 +464,13 @@ void LLInventoryModel::collectDescendentsIf(const LLUUID& id, cat_array_t& cats, item_array_t& items, BOOL include_trash, - LLInventoryCollectFunctor& add) + LLInventoryCollectFunctor& add, + BOOL follow_folder_links) { // Start with categories if(!include_trash) { - LLUUID trash_id(findCatUUID(LLAssetType::AT_TRASH)); + const LLUUID trash_id = findCategoryUUIDForType(LLFolderType::FT_TRASH); if(trash_id.notNull() && (trash_id == id)) return; } @@ -447,9 +489,40 @@ void LLInventoryModel::collectDescendentsIf(const LLUUID& id, } } - // Move onto items LLViewerInventoryItem* item = NULL; item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, id); + + // Follow folder links recursively. Currently never goes more + // than one level deep (for current outfit support) + // Note: if making it fully recursive, need more checking against infinite loops. + if (follow_folder_links && item_array) + { + S32 count = item_array->count(); + for(S32 i = 0; i < count; ++i) + { + item = item_array->get(i); + if (item && item->getActualType() == LLAssetType::AT_LINK_FOLDER) + { + LLViewerInventoryCategory *linked_cat = item->getLinkedCategory(); + if (linked_cat && linked_cat->getPreferredType() != LLFolderType::FT_OUTFIT) + // BAP - was + // LLAssetType::lookupIsEnsembleCategoryType(linked_cat->getPreferredType())) + // Change back once ensemble typing is in place. + { + if(add(linked_cat,NULL)) + { + // BAP should this be added here? May not + // matter if it's only being used in current + // outfit traversal. + cats.put(LLPointer<LLViewerInventoryCategory>(linked_cat)); + } + collectDescendentsIf(linked_cat->getUUID(), cats, items, include_trash, add, FALSE); + } + } + } + } + + // Move onto items if(item_array) { S32 count = item_array->count(); @@ -464,31 +537,77 @@ void LLInventoryModel::collectDescendentsIf(const LLUUID& id, } } -// Generates a string containing the path to the item specified by -// item_id. -void LLInventoryModel::appendPath(const LLUUID& id, LLString& path) +void LLInventoryModel::addChangedMaskForLinks(const LLUUID& object_id, U32 mask) { - LLString temp; - LLInventoryObject* obj = getObject(id); - LLUUID parent_id; - if(obj) parent_id = obj->getParentUUID(); - LLString forward_slash("/"); - while(obj) + const LLInventoryObject *obj = getObject(object_id); + if (!obj || obj->getIsLinkType()) + return; + + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + LLLinkedItemIDMatches is_linked_item_match(object_id); + collectDescendentsIf(gInventory.getRootFolderID(), + cat_array, + item_array, + LLInventoryModel::INCLUDE_TRASH, + is_linked_item_match); + if (cat_array.empty() && item_array.empty()) { - obj = getCategory(parent_id); - if(obj) - { - temp.assign(forward_slash + obj->getName() + temp); - parent_id = obj->getParentUUID(); - } + return; } - path.append(temp); + for (LLInventoryModel::cat_array_t::iterator cat_iter = cat_array.begin(); + cat_iter != cat_array.end(); + cat_iter++) + { + LLViewerInventoryCategory *linked_cat = (*cat_iter); + addChangedMask(mask, linked_cat->getUUID()); + }; + + for (LLInventoryModel::item_array_t::iterator iter = item_array.begin(); + iter != item_array.end(); + iter++) + { + LLViewerInventoryItem *linked_item = (*iter); + addChangedMask(mask, linked_item->getUUID()); + }; } -bool LLInventoryModel::isInventoryUsable() +const LLUUID& LLInventoryModel::getLinkedItemID(const LLUUID& object_id) const +{ + const LLInventoryItem *item = gInventory.getItem(object_id); + if (!item) + { + return object_id; + } + + // Find the base item in case this a link (if it's not a link, + // this will just be inv_item_id) + return item->getLinkedUUID(); +} + +LLViewerInventoryItem* LLInventoryModel::getLinkedItem(const LLUUID& object_id) const +{ + return object_id.notNull() ? getItem(getLinkedItemID(object_id)) : NULL; +} + +LLInventoryModel::item_array_t LLInventoryModel::collectLinkedItems(const LLUUID& id, + const LLUUID& start_folder_id) +{ + item_array_t items; + LLInventoryModel::cat_array_t cat_array; + LLLinkedItemIDMatches is_linked_item_match(id); + collectDescendentsIf((start_folder_id == LLUUID::null ? gInventory.getRootFolderID() : start_folder_id), + cat_array, + items, + LLInventoryModel::INCLUDE_TRASH, + is_linked_item_match); + return items; +} + +bool LLInventoryModel::isInventoryUsable() const { bool result = false; - if(gAgent.getInventoryRootID().notNull() && mIsAgentInvUsable) + if(gInventory.getRootFolderID().notNull() && mIsAgentInvUsable) { result = true; } @@ -513,11 +632,14 @@ U32 LLInventoryModel::updateItem(const LLViewerInventoryItem* item) } LLViewerInventoryItem* old_item = getItem(item->getUUID()); + LLPointer<LLViewerInventoryItem> new_item; if(old_item) { - // We already have an old item, modify it's values + // We already have an old item, modify its values + new_item = old_item; LLUUID old_parent_id = old_item->getParentUUID(); LLUUID new_parent_id = item->getParentUUID(); + if(old_parent_id != new_parent_id) { // need to update the parent-child tree @@ -538,18 +660,18 @@ U32 LLInventoryModel::updateItem(const LLViewerInventoryItem* item) { mask |= LLInventoryObserver::LABEL; } - old_item->copy(item); + old_item->copyViewerItem(item); mask |= LLInventoryObserver::INTERNAL; } else { // Simply add this item - LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item); + new_item = new LLViewerInventoryItem(item); addItem(new_item); if(item->getParentUUID().isNull()) { - LLUUID category_id = findCategoryUUIDForType(new_item->getType()); + const LLUUID category_id = findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(new_item->getType())); new_item->setParent(category_id); item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, category_id); if( item_array ) @@ -573,7 +695,7 @@ U32 LLInventoryModel::updateItem(const LLViewerInventoryItem* item) LLUUID parent_id = item->getParentUUID(); if(parent_id == CATEGORIZE_LOST_AND_FOUND_ID) { - parent_id = findCategoryUUIDForType(LLAssetType::AT_LOST_AND_FOUND); + parent_id = findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); new_item->setParent(parent_id); } item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, parent_id); @@ -586,7 +708,7 @@ U32 LLInventoryModel::updateItem(const LLViewerInventoryItem* item) // Whoops! No such parent, make one. llinfos << "Lost item: " << new_item->getUUID() << " - " << new_item->getName() << llendl; - parent_id = findCategoryUUIDForType(LLAssetType::AT_LOST_AND_FOUND); + parent_id = findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); new_item->setParent(parent_id); item_array = get_ptr_in_map(mParentChildItemTree, parent_id); if(item_array) @@ -604,14 +726,63 @@ U32 LLInventoryModel::updateItem(const LLViewerInventoryItem* item) } mask |= LLInventoryObserver::ADD; } - if(item->getType() == LLAssetType::AT_CALLINGCARD) + if(new_item->getType() == LLAssetType::AT_CALLINGCARD) { mask |= LLInventoryObserver::CALLING_CARD; + // Handle user created calling cards. + // Target ID is stored in the description field of the card. + LLUUID id; + std::string desc = new_item->getDescription(); + BOOL isId = desc.empty() ? FALSE : id.set(desc, FALSE); + if (isId) + { + // Valid UUID; set the item UUID and rename it + new_item->setCreator(id); + std::string avatar_name; + + if (gCacheName->getFullName(id, avatar_name)) + { + new_item->rename(avatar_name); + mask |= LLInventoryObserver::LABEL; + } + else + { + // Fetch the current name + gCacheName->get(id, FALSE, + boost::bind(&LLViewerInventoryItem::onCallingCardNameLookup, new_item.get(), + _1, _2, _3)); + } + + } } - addChangedMask(mask, item->getUUID()); + else if (new_item->getType() == LLAssetType::AT_GESTURE) + { + mask |= LLInventoryObserver::GESTURE; + } + addChangedMask(mask, new_item->getUUID()); return mask; } +LLInventoryModel::cat_array_t* LLInventoryModel::getUnlockedCatArray(const LLUUID& id) +{ + cat_array_t* cat_array = get_ptr_in_map(mParentChildCategoryTree, id); + if (cat_array) + { + llassert_always(mCategoryLock[id] == false); + } + return cat_array; +} + +LLInventoryModel::item_array_t* LLInventoryModel::getUnlockedItemArray(const LLUUID& id) +{ + item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, id); + if (item_array) + { + llassert_always(mItemLock[id] == false); + } + return item_array; +} + // Calling this method with an inventory category will either change // an existing item with the matching id, or it will add the category. void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat) @@ -638,12 +809,12 @@ void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat) { // need to update the parent-child tree cat_array_t* cat_array; - cat_array = get_ptr_in_map(mParentChildCategoryTree, old_parent_id); + cat_array = getUnlockedCatArray(old_parent_id); if(cat_array) { cat_array->removeObj(old_cat); } - cat_array = get_ptr_in_map(mParentChildCategoryTree, new_parent_id); + cat_array = getUnlockedCatArray(new_parent_id); if(cat_array) { cat_array->put(old_cat); @@ -654,25 +825,27 @@ void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat) { mask |= LLInventoryObserver::LABEL; } - old_cat->copy(cat); + old_cat->copyViewerCategory(cat); addChangedMask(mask, cat->getUUID()); } else { // add this category LLPointer<LLViewerInventoryCategory> new_cat = new LLViewerInventoryCategory(cat->getParentUUID()); - new_cat->copy(cat); + new_cat->copyViewerCategory(cat); addCategory(new_cat); // make sure this category is correctly referenced by it's parent. cat_array_t* cat_array; - cat_array = get_ptr_in_map(mParentChildCategoryTree, cat->getParentUUID()); + cat_array = getUnlockedCatArray(cat->getParentUUID()); if(cat_array) { cat_array->put(new_cat); } // make space in the tree for this category's children. + llassert_always(mCategoryLock[new_cat->getUUID()] == false); + llassert_always(mItemLock[new_cat->getUUID()] == false); cat_array_t* catsp = new cat_array_t; item_array_t* itemsp = new item_array_t; mParentChildCategoryTree[new_cat->getUUID()] = catsp; @@ -700,9 +873,9 @@ void LLInventoryModel::moveObject(const LLUUID& object_id, const LLUUID& cat_id) if(cat && (cat->getParentUUID() != cat_id)) { cat_array_t* cat_array; - cat_array = get_ptr_in_map(mParentChildCategoryTree, cat->getParentUUID()); + cat_array = getUnlockedCatArray(cat->getParentUUID()); if(cat_array) cat_array->removeObj(cat); - cat_array = get_ptr_in_map(mParentChildCategoryTree, cat_id); + cat_array = getUnlockedCatArray(cat_id); cat->setParent(cat_id); if(cat_array) cat_array->put(cat); addChangedMask(LLInventoryObserver::STRUCTURE, object_id); @@ -712,9 +885,9 @@ void LLInventoryModel::moveObject(const LLUUID& object_id, const LLUUID& cat_id) if(item && (item->getParentUUID() != cat_id)) { item_array_t* item_array; - item_array = get_ptr_in_map(mParentChildItemTree, item->getParentUUID()); + item_array = getUnlockedItemArray(item->getParentUUID()); if(item_array) item_array->removeObj(item); - item_array = get_ptr_in_map(mParentChildItemTree, cat_id); + item_array = getUnlockedItemArray(cat_id); item->setParent(cat_id); if(item_array) item_array->put(item); addChangedMask(LLInventoryObserver::STRUCTURE, object_id); @@ -727,41 +900,78 @@ void LLInventoryModel::deleteObject(const LLUUID& id) { lldebugs << "LLInventoryModel::deleteObject()" << llendl; LLPointer<LLInventoryObject> obj = getObject(id); + if (!obj) + { + llwarns << "Deleting non-existent object [ id: " << id << " ] " << llendl; + return; + } + + lldebugs << "Deleting inventory object " << id << llendl; + mLastItem = NULL; + LLUUID parent_id = obj->getParentUUID(); + mCategoryMap.erase(id); + mItemMap.erase(id); + //mInventory.erase(id); + item_array_t* item_list = getUnlockedItemArray(parent_id); + if(item_list) + { + LLViewerInventoryItem* item = (LLViewerInventoryItem*)((LLInventoryObject*)obj); + item_list->removeObj(item); + } + cat_array_t* cat_list = getUnlockedCatArray(parent_id); + if(cat_list) + { + LLViewerInventoryCategory* cat = (LLViewerInventoryCategory*)((LLInventoryObject*)obj); + cat_list->removeObj(cat); + } + item_list = getUnlockedItemArray(id); + if(item_list) + { + delete item_list; + mParentChildItemTree.erase(id); + } + cat_list = getUnlockedCatArray(id); + if(cat_list) + { + delete cat_list; + mParentChildCategoryTree.erase(id); + } + addChangedMask(LLInventoryObserver::REMOVE, id); + obj = NULL; // delete obj + updateLinkedObjectsFromPurge(id); + gInventory.notifyObservers(); +} + +// Delete a particular inventory item by ID, and remove it from the server. +void LLInventoryModel::purgeObject(const LLUUID &id) +{ + lldebugs << "LLInventoryModel::purgeObject() [ id: " << id << " ] " << llendl; + LLPointer<LLInventoryObject> obj = getObject(id); if(obj) { - lldebugs << "Deleting inventory object " << id << llendl; - mLastItem = NULL; - LLUUID parent_id = obj->getParentUUID(); - mCategoryMap.erase(id); - mItemMap.erase(id); - //mInventory.erase(id); - item_array_t* item_list = get_ptr_in_map(mParentChildItemTree, parent_id); - if(item_list) - { - LLViewerInventoryItem* item = (LLViewerInventoryItem*)((LLInventoryObject*)obj); - item_list->removeObj(item); - } - cat_array_t* cat_list = get_ptr_in_map(mParentChildCategoryTree, parent_id); - if(cat_list) - { - LLViewerInventoryCategory* cat = (LLViewerInventoryCategory*)((LLInventoryObject*)obj); - cat_list->removeObj(cat); - } - item_list = get_ptr_in_map(mParentChildItemTree, id); - if(item_list) - { - delete item_list; - mParentChildItemTree.erase(id); - } - cat_list = get_ptr_in_map(mParentChildCategoryTree, id); - if(cat_list) - { - delete cat_list; - mParentChildCategoryTree.erase(id); - } - addChangedMask(LLInventoryObserver::REMOVE, id); - obj = NULL; // delete obj + obj->removeFromServer(); + LLPreview::hide(id); + deleteObject(id); + } +} + +void LLInventoryModel::updateLinkedObjectsFromPurge(const LLUUID &baseobj_id) +{ + LLInventoryModel::item_array_t item_array = collectLinkedItems(baseobj_id); + + // REBUILD is expensive, so clear the current change list first else + // everything else on the changelist will also get rebuilt. + gInventory.notifyObservers(); + for (LLInventoryModel::item_array_t::const_iterator iter = item_array.begin(); + iter != item_array.end(); + iter++) + { + const LLViewerInventoryItem *linked_item = (*iter); + const LLUUID &item_id = linked_item->getUUID(); + if (item_id == baseobj_id) continue; + addChangedMask(LLInventoryObserver::REBUILD, item_id); } + gInventory.notifyObservers(); } // This is a method which collects the descendents of the id @@ -826,98 +1036,6 @@ void LLInventoryModel::purgeDescendentsOf(const LLUUID& id) } } -void LLInventoryModel::deleteFromServer(LLDynamicArray<LLUUID>& category_ids, - LLDynamicArray<LLUUID>& item_ids) -{ - // Store off tre UUIDS of parents which are being deleted (thus no - // need to increment) and the parents which are being modified. We - // have to increment the version of the parent with each message - // sent upstream since the dataserver will increment each unique - // parent per update message. - std::set<LLUUID> ignore_parents; - update_map_t inc_parents; - - S32 i; - S32 count = category_ids.count(); - BOOL start_new_message = TRUE; - LLMessageSystem* msg = gMessageSystem; - LLPointer<LLViewerInventoryCategory> cat; - for(i = 0; i < count; i++) - { - if(start_new_message) - { - start_new_message = FALSE; - msg->newMessageFast(_PREHASH_RemoveInventoryObjects); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - } - LLUUID cat_id = category_ids.get(i); - - msg->nextBlockFast(_PREHASH_FolderData); - msg->addUUIDFast(_PREHASH_FolderID, cat_id); - cat = getCategory(cat_id); - ignore_parents.insert(cat_id); - addChangedMask(LLInventoryObserver::REMOVE | LLInventoryObserver::STRUCTURE, cat_id); - if(cat.notNull() && (ignore_parents.find(cat->getParentUUID())==ignore_parents.end())) - { - --inc_parents[cat->getParentUUID()]; - } - if(msg->isSendFullFast(_PREHASH_FolderData)) - { - start_new_message = TRUE; - msg->nextBlockFast(_PREHASH_ItemData); - msg->addUUIDFast(_PREHASH_ItemID, LLUUID::null); - gAgent.sendReliableMessage(); - accountForUpdate(inc_parents); - inc_parents.clear(); - } - } - - count = item_ids.count(); - std::set<LLUUID>::iterator not_ignored = ignore_parents.end(); - LLPointer<LLViewerInventoryItem> item; - if((0 == count) && (!start_new_message)) - { - msg->nextBlockFast(_PREHASH_ItemData); - msg->addUUIDFast(_PREHASH_ItemID, LLUUID::null); - } - for(i = 0; i < count; i++) - { - if(start_new_message) - { - start_new_message = FALSE; - msg->newMessageFast(_PREHASH_RemoveInventoryObjects); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_FolderData); - msg->addUUIDFast(_PREHASH_FolderID, LLUUID::null); - } - LLUUID item_id = item_ids.get(i); - msg->nextBlockFast(_PREHASH_ItemData); - msg->addUUIDFast(_PREHASH_ItemID, item_id); - item = getItem(item_id); - addChangedMask(LLInventoryObserver::REMOVE | LLInventoryObserver::STRUCTURE, item_id); - if(item.notNull() && (ignore_parents.find(item->getParentUUID()) == not_ignored)) - { - --inc_parents[item->getParentUUID()]; - } - if(msg->isSendFullFast(_PREHASH_ItemData)) - { - start_new_message = TRUE; - gAgent.sendReliableMessage(); - accountForUpdate(inc_parents); - inc_parents.clear(); - } - } - if(!start_new_message) - { - gAgent.sendReliableMessage(); - accountForUpdate(inc_parents); - } -} - // Add/remove an observer. If the observer is destroyed, be sure to // remove it. void LLInventoryModel::addObserver(LLInventoryObserver* observer) @@ -930,545 +1048,183 @@ void LLInventoryModel::removeObserver(LLInventoryObserver* observer) mObservers.erase(observer); } -// Call this method when it's time to update everyone on a new state, -// by default, the inventory model will not update observers -// automatically. -void LLInventoryModel::notifyObservers() +BOOL LLInventoryModel::containsObserver(LLInventoryObserver* observer) const { - for (observer_list_t::iterator iter = mObservers.begin(); - iter != mObservers.end(); ) - { - LLInventoryObserver* observer = *iter; - observer->changed(mModifyMask); - // safe way to incrament since changed may delete entries! (@!##%@!@&*!) - iter = mObservers.upper_bound(observer); - } - - mModifyMask = LLInventoryObserver::NONE; - mChangedItemIDs.clear(); -} - -// store flag for change -// and id of object change applies to -void LLInventoryModel::addChangedMask(U32 mask, const LLUUID& referent) -{ - mModifyMask |= mask; - if (referent.notNull()) - { - mChangedItemIDs.insert(referent); - } + return mObservers.find(observer) != mObservers.end(); } -// This method to prepares a set of mock inventory which provides -// minimal functionality before the actual arrival of inventory. -/* -void LLInventoryModel::mock(const LLUUID& root_id) +void LLInventoryModel::idleNotifyObservers() { - llinfos << "LLInventoryModel::mock() " << root_id << llendl; - if(root_id.isNull()) + if (mModifyMask == LLInventoryObserver::NONE && (mChangedItemIDs.size() == 0)) { - llwarns << "Not a valid root id" << llendl; return; } - LLPointer<LLViewerInventoryCategory> cat = new LLViewerInventoryCategory( - root_id, - LLUUID::null, - LLAssetType::AT_CATEGORY, - NEW_CATEGORY_NAMES[LLAssetType::AT_ROOT_CATEGORY], - gAgent.getID()); - addCategory(cat); - gInventory.buildParentChildMap(); + notifyObservers(""); } -*/ -void LLInventoryModel::fetchDescendentsOf(const LLUUID& folder_id) +// Call this method when it's time to update everyone on a new state. +// The optional argument 'service_name' is used by Agent Inventory Service [DEV-20328] +void LLInventoryModel::notifyObservers(const std::string service_name) { - LLViewerInventoryCategory* cat = getCategory(folder_id); - if(!cat) + if (mIsNotifyObservers) { - llwarns << "Asked to fetch descendents of non-existent folder: " - << folder_id << llendl; + // Within notifyObservers, something called notifyObservers + // again. This type of recursion is unsafe because it causes items to be + // processed twice, and this can easily lead to infinite loops. + llwarns << "Call was made to notifyObservers within notifyObservers!" << llendl; return; } - //S32 known_descendents = 0; - ///cat_array_t* categories = get_ptr_in_map(mParentChildCategoryTree, folder_id); - //item_array_t* items = get_ptr_in_map(mParentChildItemTree, folder_id); - //if(categories) - //{ - // known_descendents += categories->count(); - //} - //if(items) - //{ - // known_descendents += items->count(); - //} - if(!cat->fetchDescendents()) - { - //llinfos << "Not fetching descendents" << llendl; - } -} -//Initialize statics. -LLAlertDialog* LLInventoryModel::fetchDescendentsResponder::sRetryDialog=NULL; -LLSD LLInventoryModel::fetchDescendentsResponder::sRetrySD; - -bool LLInventoryModel::isBulkFetchProcessingComplete() -{ - return ( (sFetchQueue.empty() - && sBulkFetchMap.empty() - && sBulkFetchCount==0) ? TRUE : FALSE ) ; -} - -//If we get back a normal response, handle it here -void LLInventoryModel::fetchDescendentsResponder::result(const LLSD& content) -{ - if (content.has("folders")) + mIsNotifyObservers = TRUE; + for (observer_list_t::iterator iter = mObservers.begin(); + iter != mObservers.end(); ) { - for(LLSD::array_const_iterator folder_it = content["folders"].beginArray(); - folder_it != content["folders"].endArray(); - ++folder_it) - { - LLSD folder_sd = *folder_it; - - - LLUUID agent_id = folder_sd["agent-id"]; - - if(agent_id != gAgent.getID()) //This should never happen. - { - llwarns << "Got a UpdateInventoryItem for the wrong agent." - << llendl; - break; - } - LLUUID parent_id = folder_sd["folder-id"]; - LLUUID owner_id = folder_sd["owner-id"]; - S32 version = (S32)folder_sd["version"].asInteger(); - S32 descendents = (S32)folder_sd["descendents"].asInteger(); - LLPointer<LLViewerInventoryCategory> tcategory = new LLViewerInventoryCategory(owner_id); - for(LLSD::array_const_iterator category_it = folder_sd["categories"].beginArray(); - category_it != folder_sd["categories"].endArray(); - ++category_it) - { - LLSD category = *category_it; - tcategory->fromLLSD(category); - - if (sFullFetchStarted) - { - sFetchQueue.push_back(tcategory->getUUID()); - } - else if ( !gInventory.isCategoryComplete(tcategory->getUUID()) ) - { - gInventory.updateCategory(tcategory); - } - - } - LLPointer<LLViewerInventoryItem> titem = new LLViewerInventoryItem; - for(LLSD::array_const_iterator item_it = folder_sd["items"].beginArray(); - item_it != folder_sd["items"].endArray(); - ++item_it) - { - LLSD item = *item_it; - titem->unpackMessage(item); - - gInventory.updateItem(titem); - } - - // set version and descendentcount according to message. - LLViewerInventoryCategory* cat = gInventory.getCategory(parent_id); - if(cat) - { - cat->setVersion(version); - cat->setDescendentCount(descendents); - } - - } - } + LLInventoryObserver* observer = *iter; - if (content.has("bad-folders")) - { - for(LLSD::array_const_iterator folder_it = content["bad-folders"].beginArray(); - folder_it != content["bad-folders"].endArray(); - ++folder_it) - { - LLSD folder_sd = *folder_it; - - //These folders failed on the dataserver. We probably don't want to retry them. - llinfos << "Folder " << folder_sd["folder-id"].asString() - << "Error: " << folder_sd["error"].asString() << llendl; + if (service_name.empty()) + { + observer->changed(mModifyMask); } - } - - LLInventoryModel::incrBulkFetch(-1); - - if (isBulkFetchProcessingComplete()) - { - llinfos << "Inventory fetch completed" << llendl; - if (sFullFetchStarted) + else { - sAllFoldersFetched = TRUE; + observer->mMessageName = service_name; + observer->changed(mModifyMask); } - stopBackgroundFetch(); + + // safe way to increment since changed may delete entries! (@!##%@!@&*!) + iter = mObservers.upper_bound(observer); } - - gInventory.notifyObservers(); -} -//If we get back an error (not found, etc...), handle it here -void LLInventoryModel::fetchDescendentsResponder::error(U32 status, const std::string& reason) -{ - llinfos << "fetchDescendentsResponder::error " - << status << ": " << reason << llendl; - - LLInventoryModel::incrBulkFetch(-1); + mModifyMask = LLInventoryObserver::NONE; + mChangedItemIDs.clear(); + mIsNotifyObservers = FALSE; +} - if (status==499) //timed out. Let's be awesome! - { - for(LLSD::array_const_iterator folder_it = mRequestSD["folders"].beginArray(); - folder_it != mRequestSD["folders"].endArray(); - ++folder_it) - { - LLSD folder_sd = *folder_it; - sRetrySD["folders"].append(folder_sd); - } - sMinTimeBetweenFetches = 10.0f; //Add 10 seconds for every time out in this sequence. - - if (!sRetryDialog) //The dialog isn't up. Prompt the resident. - { - sRetryDialog = gViewerWindow->alertXml("RetryFetchInventoryDescendents", onClickRetry, this); - } - } - else +// store flag for change +// and id of object change applies to +void LLInventoryModel::addChangedMask(U32 mask, const LLUUID& referent) +{ + if (mIsNotifyObservers) { - if (isBulkFetchProcessingComplete()) - { - if (sFullFetchStarted) - { - sAllFoldersFetched = TRUE; - } - stopBackgroundFetch(); - } + // Something marked an item for change within a call to notifyObservers + // (which is in the process of processing the list of items marked for change). + // This means the change may fail to be processed. + llwarns << "Adding changed mask within notify observers! Change will likely be lost." << llendl; } - gInventory.notifyObservers(); -} - -void LLInventoryModel::fetchDescendentsResponder::onClickRetry(S32 option, void* userdata) -{ - if (option == 0) + + mModifyMask |= mask; + if (referent.notNull()) { - std::string url = gAgent.getRegion()->getCapability("FetchInventoryDescendents"); - - if (!url.empty()) //Capability found. Build up LLSD and use it. - { - LLSD body = sRetrySD; - LLInventoryModel::incrBulkFetch(1); - LLHTTPClient::post(url, body, new LLInventoryModel::fetchDescendentsResponder(body),300); - } + mChangedItemIDs.insert(referent); } - else + + // Update all linked items. Starting with just LABEL because I'm + // not sure what else might need to be accounted for this. + if (mModifyMask & LLInventoryObserver::LABEL) { - if (isBulkFetchProcessingComplete()) - { - if (sFullFetchStarted) - { - sAllFoldersFetched = TRUE; - } - stopBackgroundFetch(); - } + addChangedMaskForLinks(referent, LLInventoryObserver::LABEL); } - sRetryDialog=NULL; - sRetrySD.clear(); } -//static Bundle up a bunch of requests to send all at once. -void LLInventoryModel::bulkFetch(std::string url) -{ - //Background fetch is called from gIdleCallbacks in a loop until background fetch is stopped. - //If there are items in sFetchQueue, we want to check the time since the last bulkFetch was - //sent. If it exceeds our retry time, go ahead and fire off another batch. - //Stopbackgroundfetch will be run from the Responder instead of here. +// If we get back a normal response, handle it here +void LLInventoryModel::fetchInventoryResponder::result(const LLSD& content) +{ + start_new_inventory_observer(); - S16 max_concurrent_fetches=8; - F32 new_min_time = 0.5f; //HACK! Clean this up when old code goes away entirely. - if (sMinTimeBetweenFetches <= new_min_time) sMinTimeBetweenFetches=new_min_time; //HACK! See above. - - if(gDisconnected - || sBulkFetchCount > max_concurrent_fetches - || sFetchTimer.getElapsedTimeF32() < sMinTimeBetweenFetches) + /*LLUUID agent_id; + agent_id = content["agent_id"].asUUID(); + if(agent_id != gAgent.getID()) { - return; // just bail if we are disconnected. - } - - //HACK. This is inelegant. We're shuffling a dequeue to a map to get rid of - //redundant requests. When we get rid of the old code entirely, we can change - //the dequeue to a map. In the new model, there is no benefit to queue order. - U32 folder_count=0; - U32 max_batch_size=10; - while( !(sFetchQueue.empty() ) ) + llwarns << "Got a inventory update for the wrong agent: " << agent_id + << llendl; + return; + }*/ + item_array_t items; + update_map_t update; + S32 count = content["items"].size(); + bool all_one_folder = true; + LLUUID folder_id; + // Does this loop ever execute more than once? + for(S32 i = 0; i < count; ++i) { - LLViewerInventoryCategory* cat = gInventory.getCategory(sFetchQueue.front()); + LLPointer<LLViewerInventoryItem> titem = new LLViewerInventoryItem; + titem->unpackMessage(content["items"][i]); - if (cat) + lldebugs << "LLInventoryModel::messageUpdateCore() item id:" + << titem->getUUID() << llendl; + items.push_back(titem); + // examine update for changes. + LLViewerInventoryItem* itemp = gInventory.getItem(titem->getUUID()); + if(itemp) { - if ( !gInventory.isCategoryComplete(cat->getUUID()) ) //grab this folder. + if(titem->getParentUUID() == itemp->getParentUUID()) { - sBulkFetchMap[(cat->getUUID())] = cat; - } - else if (sFullFetchStarted) - { //Already have this folder but append child folders to list. - // add all children to queue - parent_cat_map_t::iterator cat_it = gInventory.mParentChildCategoryTree.find(cat->getUUID()); - if (cat_it != gInventory.mParentChildCategoryTree.end()) - { - cat_array_t* child_categories = cat_it->second; - - for (S32 child_num = 0; child_num < child_categories->count(); child_num++) - { - sFetchQueue.push_back(child_categories->get(child_num)->getUUID()); - } - } - + update[titem->getParentUUID()]; } - } - sFetchQueue.pop_front(); - } - - - if (!sBulkFetchMap.empty()) //There's stuff to fetch. - { - U32 sort_order = gSavedSettings.getU32("InventorySortOrder") & 0x1; - - LLSD body; - - cat_map_t::iterator iter=sBulkFetchMap.begin(); - while( iter!=sBulkFetchMap.end() && (folder_count < max_batch_size) ) - { - LLViewerInventoryCategory* cat = iter->second; - - if (cat && !gInventory.isCategoryComplete(cat->getUUID()) ) //Category exists + else { - BOOL fetchItems=TRUE; - if ( sFullFetchStarted - && gInventory.isCategoryComplete(cat->getUUID()) ) - { - fetchItems=FALSE; - } - - LLSD folder_sd; - folder_sd["folder-id"] = cat->getUUID(); - folder_sd["owner-id"] = cat->getOwnerID(); - folder_sd["sort-order"] = (LLSD::Integer)sort_order; - folder_sd["fetch-folders"] = (LLSD::Boolean)sFullFetchStarted; - folder_sd["fetch-items"] = (LLSD::Boolean)fetchItems; - body["folders"].append(folder_sd); - - folder_count++; + ++update[titem->getParentUUID()]; + --update[itemp->getParentUUID()]; } - sBulkFetchMap.erase(iter); - iter=sBulkFetchMap.begin(); } - - if (iter == sBulkFetchMap.end()) sBulkFetchMap.clear(); - - if (folder_count > 0) - { - sBulkFetchCount++; - - LLHTTPClient::post(url, body, new LLInventoryModel::fetchDescendentsResponder(body)); - sFetchTimer.reset(); - } - - } - - if (isBulkFetchProcessingComplete()) - { - if (sFullFetchStarted) + else { - sAllFoldersFetched = TRUE; + ++update[titem->getParentUUID()]; } - stopBackgroundFetch(); - } -} - -// static -bool LLInventoryModel::isEverythingFetched() -{ - return (sAllFoldersFetched ? true : false); -} - -//static -BOOL LLInventoryModel::backgroundFetchActive() -{ - return sBackgroundFetchActive; -} - -//static -void LLInventoryModel::startBackgroundFetch(const LLUUID& cat_id) -{ - if (!sAllFoldersFetched) - { - sBackgroundFetchActive = TRUE; - if (cat_id.isNull()) + if (folder_id.isNull()) { - if (!sFullFetchStarted) - { - sFullFetchStarted = TRUE; - sFetchQueue.push_back(gInventoryLibraryRoot); - sFetchQueue.push_back(gAgent.getInventoryRootID()); - gIdleCallbacks.addFunction(&LLInventoryModel::backgroundFetch, NULL); - } + folder_id = titem->getParentUUID(); } else { - // specific folder requests go to front of queue - if (sFetchQueue.empty() || sFetchQueue.front() != cat_id) - { - sFetchQueue.push_front(cat_id); - gIdleCallbacks.addFunction(&LLInventoryModel::backgroundFetch, NULL); - } + all_one_folder = false; } } -} -//static -void LLInventoryModel::stopBackgroundFetch() -{ - if (sBackgroundFetchActive) + U32 changes = 0x0; + //as above, this loop never seems to loop more than once per call + for (item_array_t::iterator it = items.begin(); it != items.end(); ++it) { - sBackgroundFetchActive = FALSE; - gIdleCallbacks.deleteFunction(&LLInventoryModel::backgroundFetch, NULL); - sBulkFetchCount=0; - sMinTimeBetweenFetches=0.0f; -// sFullFetchStarted=FALSE; + changes |= gInventory.updateItem(*it); } + gInventory.notifyObservers("fetchinventory"); + gViewerWindow->getWindow()->decBusyCount(); } -//static -void LLInventoryModel::backgroundFetch(void*) +//If we get back an error (not found, etc...), handle it here +void LLInventoryModel::fetchInventoryResponder::error(U32 status, const std::string& reason) { - if (sBackgroundFetchActive) - { - //If we'll be using the capability, we'll be sending batches and the background thing isn't as important. - std::string url = gAgent.getRegion()->getCapability("FetchInventoryDescendents"); - if (!url.empty()) - { - bulkFetch(url); - return; - } - - //DEPRECATED OLD CODE FOLLOWS. - // no more categories to fetch, stop fetch process - if (sFetchQueue.empty()) - { - llinfos << "Inventory fetch completed" << llendl; - if (sFullFetchStarted) - { - sAllFoldersFetched = TRUE; - } - stopBackgroundFetch(); - return; - } - - F32 fast_fetch_time = lerp(sMinTimeBetweenFetches, sMaxTimeBetweenFetches, 0.1f); - F32 slow_fetch_time = lerp(sMinTimeBetweenFetches, sMaxTimeBetweenFetches, 0.5f); - if (sTimelyFetchPending && sFetchTimer.getElapsedTimeF32() > slow_fetch_time) - { - // double timeouts on failure - sMinTimeBetweenFetches = llmin(sMinTimeBetweenFetches * 2.f, 10.f); - sMaxTimeBetweenFetches = llmin(sMaxTimeBetweenFetches * 2.f, 120.f); - llinfos << "Inventory fetch times grown to (" << sMinTimeBetweenFetches << ", " << sMaxTimeBetweenFetches << ")" << llendl; - // fetch is no longer considered "timely" although we will wait for full time-out - sTimelyFetchPending = FALSE; - } - - while(1) - { - if (sFetchQueue.empty()) - { - break; - } - - if(gDisconnected) - { - // just bail if we are disconnected. - break; - } - - LLViewerInventoryCategory* cat = gInventory.getCategory(sFetchQueue.front()); - - // category has been deleted, remove from queue. - if (!cat) - { - sFetchQueue.pop_front(); - continue; - } - - if (sFetchTimer.getElapsedTimeF32() > sMinTimeBetweenFetches && - LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion()) - { - // category exists but has no children yet, fetch the descendants - // for now, just request every time and rely on retry timer to throttle - if (cat->fetchDescendents()) - { - sFetchTimer.reset(); - sTimelyFetchPending = TRUE; - } - else - { - // The catagory also tracks if it has expired and here it says it hasn't - // yet. Get out of here because nothing is going to happen until we - // update the timers. - break; - } - } - // do I have all my children? - else if (gInventory.isCategoryComplete(sFetchQueue.front())) - { - // finished with this category, remove from queue - sFetchQueue.pop_front(); - - // add all children to queue - parent_cat_map_t::iterator cat_it = gInventory.mParentChildCategoryTree.find(cat->getUUID()); - if (cat_it != gInventory.mParentChildCategoryTree.end()) - { - cat_array_t* child_categories = cat_it->second; - - for (S32 child_num = 0; child_num < child_categories->count(); child_num++) - { - sFetchQueue.push_back(child_categories->get(child_num)->getUUID()); - } - } - - // we received a response in less than the fast time - if (sTimelyFetchPending && sFetchTimer.getElapsedTimeF32() < fast_fetch_time) - { - // shrink timeouts based on success - sMinTimeBetweenFetches = llmax(sMinTimeBetweenFetches * 0.8f, 0.3f); - sMaxTimeBetweenFetches = llmax(sMaxTimeBetweenFetches * 0.8f, 10.f); - //llinfos << "Inventory fetch times shrunk to (" << sMinTimeBetweenFetches << ", " << sMaxTimeBetweenFetches << ")" << llendl; - } - - sTimelyFetchPending = FALSE; - continue; - } - else if (sFetchTimer.getElapsedTimeF32() > sMaxTimeBetweenFetches) - { - // received first packet, but our num descendants does not match db's num descendants - // so try again later - LLUUID fetch_id = sFetchQueue.front(); - sFetchQueue.pop_front(); - - if (sNumFetchRetries++ < MAX_FETCH_RETRIES) - { - // push on back of queue - sFetchQueue.push_back(fetch_id); - } - sTimelyFetchPending = FALSE; - sFetchTimer.reset(); - break; - } + llinfos << "fetchInventory::error " + << status << ": " << reason << llendl; + gInventory.notifyObservers("fetchinventory"); +} - // not enough time has elapsed to do a new fetch - break; - } +bool LLInventoryModel::fetchDescendentsOf(const LLUUID& folder_id) const +{ + if(folder_id.isNull()) + { + llwarns << "Calling fetch descendents on NULL folder id!" << llendl; + return false; + } + LLViewerInventoryCategory* cat = getCategory(folder_id); + if(!cat) + { + llwarns << "Asked to fetch descendents of non-existent folder: " + << folder_id << llendl; + return false; } + //S32 known_descendents = 0; + ///cat_array_t* categories = get_ptr_in_map(mParentChildCategoryTree, folder_id); + //item_array_t* items = get_ptr_in_map(mParentChildItemTree, folder_id); + //if(categories) + //{ + // known_descendents += categories->count(); + //} + //if(items) + //{ + // known_descendents += items->count(); + //} + return cat->fetch(); } void LLInventoryModel::cache( @@ -1491,19 +1247,15 @@ void LLInventoryModel::cache( items, INCLUDE_TRASH, can_cache); - char agent_id_str[UUID_STR_LENGTH]; /*Flawfinder: ignore*/ - char inventory_filename[LL_MAX_PATH]; /*Flawfinder: ignore*/ + std::string agent_id_str; + std::string inventory_filename; agent_id.toString(agent_id_str); std::string path(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, agent_id_str)); - snprintf( /* Flawfinder: ignore */ - inventory_filename, - LL_MAX_PATH, - CACHE_FORMAT_STRING, - path.c_str()); + inventory_filename = llformat(CACHE_FORMAT_STRING, path.c_str()); saveToFile(inventory_filename, categories, items); std::string gzip_filename(inventory_filename); gzip_filename.append(".gz"); - if(gzip_file(inventory_filename, gzip_filename.c_str())) + if(gzip_file(inventory_filename, gzip_filename)) { lldebugs << "Successfully compressed " << inventory_filename << llendl; LLFile::remove(inventory_filename); @@ -1520,6 +1272,9 @@ void LLInventoryModel::addCategory(LLViewerInventoryCategory* category) //llinfos << "LLInventoryModel::addCategory()" << llendl; if(category) { + // try to localize default names first. See EXT-8319, EXT-7051. + category->localizeName(); + // Insert category uniquely into the map mCategoryMap[category->getUUID()] = category; // LLPointer will deref and delete the old one //mInventory[category->getUUID()] = category; @@ -1528,11 +1283,27 @@ void LLInventoryModel::addCategory(LLViewerInventoryCategory* category) void LLInventoryModel::addItem(LLViewerInventoryItem* item) { - //llinfos << "LLInventoryModel::addItem()" << llendl; + llassert(item); if(item) { + // This can happen if assettype enums from llassettype.h ever change. + // For example, there is a known backwards compatibility issue in some viewer prototypes prior to when + // the AT_LINK enum changed from 23 to 24. + if ((item->getType() == LLAssetType::AT_NONE) + || LLAssetType::lookup(item->getType()) == LLAssetType::badLookup()) + { + llwarns << "Got bad asset type for item [ name: " << item->getName() << " type: " << item->getType() << " inv-type: " << item->getInventoryType() << " ], ignoring." << llendl; + return; + } + + // This condition means that we tried to add a link without the baseobj being in memory. + // The item will show up as a broken link. + if (item->getIsBrokenLink()) + { + llinfos << "Adding broken link [ name: " << item->getName() << " itemID: " << item->getUUID() << " assetID: " << item->getAssetUUID() << " ) parent: " << item->getParentUUID() << llendl; + } + mItemMap[item->getUUID()] = item; - //mInventory[item->getUUID()] = item; } } @@ -1556,7 +1327,7 @@ void LLInventoryModel::empty() //mInventory.clear(); } -void LLInventoryModel::accountForUpdate(const LLCategoryUpdate& update) +void LLInventoryModel::accountForUpdate(const LLCategoryUpdate& update) const { LLViewerInventoryCategory* cat = getCategory(update.mCategoryID); if(cat) @@ -1580,21 +1351,23 @@ void LLInventoryModel::accountForUpdate(const LLCategoryUpdate& update) descendents_actual += update.mDescendentDelta; cat->setDescendentCount(descendents_actual); cat->setVersion(++version); - llinfos << "accounted: '" << cat->getName() << "' " - << version << " with " << descendents_actual - << " descendents." << llendl; + lldebugs << "accounted: '" << cat->getName() << "' " + << version << " with " << descendents_actual + << " descendents." << llendl; } } if(!accounted) { - lldebugs << "No accounting for: '" << cat->getName() << "' " + // Error condition, this means that the category did not register that + // it got new descendents (perhaps because it is still being loaded) + // which means its descendent count will be wrong. + llwarns << "Accounting failed for '" << cat->getName() << "' version:" << version << llendl; } } else { - llwarns << "No category found for update " << update.mCategoryID - << llendl; + llwarns << "No category found for update " << update.mCategoryID << llendl; } } @@ -1721,84 +1494,74 @@ bool LLInventoryModel::isCategoryComplete(const LLUUID& cat_id) const } bool LLInventoryModel::loadSkeleton( - const LLInventoryModel::options_t& options, + const LLSD& options, const LLUUID& owner_id) { lldebugs << "importing inventory skeleton for " << owner_id << llendl; typedef std::set<LLPointer<LLViewerInventoryCategory>, InventoryIDPtrLess> cat_set_t; cat_set_t temp_cats; - - update_map_t child_counts; - - LLUUID id; - LLAssetType::EType preferred_type; bool rv = true; - for(options_t::const_iterator it = options.begin(); it < options.end(); ++it) - { - LLPointer<LLViewerInventoryCategory> cat = new LLViewerInventoryCategory(owner_id); - response_t::const_iterator no_response = (*it).end(); - response_t::const_iterator skel; - skel = (*it).find("name"); - if(skel == no_response) goto clean_cat; - cat->rename(LLString((*skel).second.c_str())); - skel = (*it).find("folder_id"); - if(skel == no_response) goto clean_cat; - id.set((*skel).second.c_str()); - // if an id is null, it locks the viewer. - if(id.isNull()) goto clean_cat; - cat->setUUID(id); - skel = (*it).find("parent_id"); - if(skel == no_response) goto clean_cat; - id.set((*skel).second.c_str()); - cat->setParent(id); - skel = (*it).find("type_default"); - if(skel == no_response) - { - preferred_type = LLAssetType::AT_NONE; + + for(LLSD::array_const_iterator it = options.beginArray(), + end = options.endArray(); it != end; ++it) + { + LLSD name = (*it)["name"]; + LLSD folder_id = (*it)["folder_id"]; + LLSD parent_id = (*it)["parent_id"]; + LLSD version = (*it)["version"]; + if(name.isDefined() + && folder_id.isDefined() + && parent_id.isDefined() + && version.isDefined() + && folder_id.asUUID().notNull() // if an id is null, it locks the viewer. + ) + { + LLPointer<LLViewerInventoryCategory> cat = new LLViewerInventoryCategory(owner_id); + cat->rename(name.asString()); + cat->setUUID(folder_id.asUUID()); + cat->setParent(parent_id.asUUID()); + + LLFolderType::EType preferred_type = LLFolderType::FT_NONE; + LLSD type_default = (*it)["type_default"]; + if(type_default.isDefined()) + { + preferred_type = (LLFolderType::EType)type_default.asInteger(); + } + cat->setPreferredType(preferred_type); + cat->setVersion(version.asInteger()); + temp_cats.insert(cat); } else { - S32 t = atoi((*skel).second.c_str()); - preferred_type = (LLAssetType::EType)t; + llwarns << "Unable to import near " << name.asString() << llendl; + rv = false; } - cat->setPreferredType(preferred_type); - skel = (*it).find("version"); - if(skel == no_response) goto clean_cat; - cat->setVersion(atoi((*skel).second.c_str())); - temp_cats.insert(cat); - continue; - clean_cat: - llwarns << "Unable to import near " << cat->getName() << llendl; - rv = false; - //delete cat; // automatic when cat is reasigned or destroyed } S32 cached_category_count = 0; S32 cached_item_count = 0; if(!temp_cats.empty()) { + update_map_t child_counts; cat_array_t categories; item_array_t items; - char owner_id_str[UUID_STR_LENGTH]; /*Flawfinder: ignore*/ + cat_set_t invalid_categories; // Used to mark categories that weren't successfully loaded. + std::string owner_id_str; owner_id.toString(owner_id_str); std::string path(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, owner_id_str)); - char inventory_filename[LL_MAX_PATH]; /*Flawfinder: ignore*/ - snprintf( /* Flawfinder: ignore */ - inventory_filename, - LL_MAX_PATH, - CACHE_FORMAT_STRING, - path.c_str()); + std::string inventory_filename; + inventory_filename = llformat(CACHE_FORMAT_STRING, path.c_str()); const S32 NO_VERSION = LLViewerInventoryCategory::VERSION_UNKNOWN; std::string gzip_filename(inventory_filename); gzip_filename.append(".gz"); - FILE* fp = LLFile::fopen(gzip_filename.c_str(), "rb"); /*Flawfinder: ignore*/ + LLFILE* fp = LLFile::fopen(gzip_filename, "rb"); bool remove_inventory_file = false; if(fp) { fclose(fp); fp = NULL; - if(gunzip_file(gzip_filename.c_str(), inventory_filename)) + if(gunzip_file(gzip_filename, inventory_filename)) { // we only want to remove the inventory file if it was // gzipped before we loaded, and we successfully @@ -1810,7 +1573,8 @@ bool LLInventoryModel::loadSkeleton( llinfos << "Unable to gunzip " << gzip_filename << llendl; } } - if(loadFromFile(inventory_filename, categories, items)) + bool is_cache_obsolete = false; + if(loadFromFile(inventory_filename, categories, items, is_cache_obsolete)) { // We were able to find a cache of files. So, use what we // found to generate a set of categories we should add. We @@ -1849,7 +1613,7 @@ bool LLInventoryModel::loadSkeleton( } // go ahead and add the cats returned during the download - std::set<LLUUID>::iterator not_cached_id = cached_ids.end(); + std::set<LLUUID>::const_iterator not_cached_id = cached_ids.end(); cached_category_count = cached_ids.size(); for(cat_set_t::iterator it = temp_cats.begin(); it != temp_cats.end(); ++it) { @@ -1867,23 +1631,43 @@ bool LLInventoryModel::loadSkeleton( // Add all the items loaded which are parented to a // category with a correctly cached parent - count = items.count(); + S32 bad_link_count = 0; cat_map_t::iterator unparented = mCategoryMap.end(); - for(int i = 0; i < count; ++i) + for(item_array_t::const_iterator item_iter = items.begin(); + item_iter != items.end(); + ++item_iter) { - cat_map_t::iterator cit = mCategoryMap.find(items[i]->getParentUUID()); + LLViewerInventoryItem *item = (*item_iter).get(); + const cat_map_t::iterator cit = mCategoryMap.find(item->getParentUUID()); if(cit != unparented) { - LLViewerInventoryCategory* cat = cit->second; + const LLViewerInventoryCategory* cat = cit->second.get(); if(cat->getVersion() != NO_VERSION) { - addItem(items[i]); + // This can happen if the linked object's baseobj is removed from the cache but the linked object is still in the cache. + if (item->getIsBrokenLink()) + { + bad_link_count++; + lldebugs << "Attempted to add cached link item without baseobj present ( name: " + << item->getName() << " itemID: " << item->getUUID() + << " assetID: " << item->getAssetUUID() + << " ). Ignoring and invalidating " << cat->getName() << " . " << llendl; + invalid_categories.insert(cit->second); + continue; + } + addItem(item); cached_item_count += 1; ++child_counts[cat->getUUID()]; } } } + if (bad_link_count > 0) + { + llinfos << "Attempted to add " << bad_link_count + << " cached link items without baseobj present. " + << "The corresponding categories were invalidated." << llendl; + } } else { @@ -1897,20 +1681,31 @@ bool LLInventoryModel::loadSkeleton( } } + // Invalidate all categories that failed fetching descendents for whatever + // reason (e.g. one of the descendents was a broken link). + for (cat_set_t::iterator invalid_cat_it = invalid_categories.begin(); + invalid_cat_it != invalid_categories.end(); + invalid_cat_it++) + { + LLViewerInventoryCategory* cat = (*invalid_cat_it).get(); + cat->setVersion(NO_VERSION); + llinfos << "Invalidating category name: " << cat->getName() << " UUID: " << cat->getUUID() << " due to invalid descendents cache" << llendl; + } + // At this point, we need to set the known descendents for each // category which successfully cached so that we do not // needlessly fetch descendents for categories which we have. - update_map_t::iterator no_child_counts = child_counts.end(); - update_map_t::iterator the_count; + update_map_t::const_iterator no_child_counts = child_counts.end(); for(cat_set_t::iterator it = temp_cats.begin(); it != temp_cats.end(); ++it) { - LLViewerInventoryCategory* cat = (*it); + LLViewerInventoryCategory* cat = (*it).get(); if(cat->getVersion() != NO_VERSION) { - the_count = child_counts.find(cat->getUUID()); + update_map_t::const_iterator the_count = child_counts.find(cat->getUUID()); if(the_count != no_child_counts) { - cat->setDescendentCount((*the_count).second.mValue); + const S32 num_descendents = (*the_count).second.mValue; + cat->setDescendentCount(num_descendents); } else { @@ -1924,6 +1719,12 @@ bool LLInventoryModel::loadSkeleton( // clean up the gunzipped file. LLFile::remove(inventory_filename); } + if(is_cache_obsolete) + { + // If out of date, remove the gzipped file too. + llwarns << "Inv cache out of date, removing" << llendl; + LLFile::remove(gzip_filename); + } categories.clear(); // will unref and delete entries } @@ -1934,89 +1735,6 @@ bool LLInventoryModel::loadSkeleton( return rv; } -bool LLInventoryModel::loadMeat( - const LLInventoryModel::options_t& options, const LLUUID& owner_id) -{ - llinfos << "importing inventory for " << owner_id << llendl; - LLPermissions default_perm; - default_perm.init(LLUUID::null, owner_id, LLUUID::null, LLUUID::null); - LLPointer<LLViewerInventoryItem> item; - LLUUID id; - LLAssetType::EType type; - LLInventoryType::EType inv_type; - bool rv = true; - for(options_t::const_iterator it = options.begin(); it < options.end(); ++it) - { - item = new LLViewerInventoryItem; - response_t::const_iterator no_response = (*it).end(); - response_t::const_iterator meat; - meat = (*it).find("name"); - if(meat == no_response) goto clean_item; - item->rename(LLString((*meat).second.c_str())); - meat = (*it).find("item_id"); - if(meat == no_response) goto clean_item; - id.set((*meat).second.c_str()); - item->setUUID(id); - meat = (*it).find("parent_id"); - if(meat == no_response) goto clean_item; - id.set((*meat).second.c_str()); - item->setParent(id); - meat = (*it).find("type"); - if(meat == no_response) goto clean_item; - type = (LLAssetType::EType)atoi((*meat).second.c_str()); - item->setType(type); - meat = (*it).find("inv_type"); - if(meat != no_response) - { - inv_type = (LLInventoryType::EType)atoi((*meat).second.c_str()); - item->setInventoryType(inv_type); - } - meat = (*it).find("data_id"); - if(meat == no_response) goto clean_item; - id.set((*meat).second.c_str()); - if(LLAssetType::AT_CALLINGCARD == type) - { - LLPermissions perm; - perm.init(id, owner_id, LLUUID::null, LLUUID::null); - item->setPermissions(perm); - } - else - { - meat = (*it).find("perm_mask"); - if(meat != no_response) - { - PermissionMask perm_mask = atoi((*meat).second.c_str()); - default_perm.initMasks( - perm_mask, perm_mask, perm_mask, perm_mask, perm_mask); - } - else - { - default_perm.initMasks( - PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE); - } - item->setPermissions(default_perm); - item->setAssetUUID(id); - } - meat = (*it).find("flags"); - if(meat != no_response) - { - item->setFlags(strtoul((*meat).second.c_str(), NULL, 0)); - } - meat = (*it).find("time"); - if(meat != no_response) - { - item->setCreationDate(atoi((*meat).second.c_str())); - } - addItem(item); - continue; - clean_item: - llwarns << "Unable to import near " << item->getName() << llendl; - rv = false; - //delete item; // automatic when item is reassigned or destroyed - } - return rv; -} - // This is a brute force method to rebuild the entire parent-child // relations. The overall operation has O(NlogN) performance, which // should be sufficient for our needs. @@ -2042,11 +1760,13 @@ void LLInventoryModel::buildParentChildMap() cats.put(cat); if (mParentChildCategoryTree.count(cat->getUUID()) == 0) { + llassert_always(mCategoryLock[cat->getUUID()] == false); catsp = new cat_array_t; mParentChildCategoryTree[cat->getUUID()] = catsp; } if (mParentChildItemTree.count(cat->getUUID()) == 0) { + llassert_always(mItemLock[cat->getUUID()] == false); itemsp = new item_array_t; mParentChildItemTree[cat->getUUID()] = itemsp; } @@ -2071,7 +1791,7 @@ void LLInventoryModel::buildParentChildMap() for(i = 0; i < count; ++i) { LLViewerInventoryCategory* cat = cats.get(i); - catsp = get_ptr_in_map(mParentChildCategoryTree, cat->getParentUUID()); + catsp = getUnlockedCatArray(cat->getParentUUID()); if(catsp) { catsp->put(cat); @@ -2088,12 +1808,12 @@ void LLInventoryModel::buildParentChildMap() << cat->getName() << llendl; ++lost; // plop it into the lost & found. - LLAssetType::EType pref = cat->getPreferredType(); - if(LLAssetType::AT_NONE == pref) + LLFolderType::EType pref = cat->getPreferredType(); + if(LLFolderType::FT_NONE == pref) { - cat->setParent(findCategoryUUIDForType(LLAssetType::AT_LOST_AND_FOUND)); + cat->setParent(findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND)); } - else if(LLAssetType::AT_CATEGORY == pref) + else if(LLFolderType::FT_ROOT_INVENTORY == pref) { // it's the root cat->setParent(LLUUID::null); @@ -2101,10 +1821,10 @@ void LLInventoryModel::buildParentChildMap() else { // it's a protected folder. - cat->setParent(gAgent.getInventoryRootID()); + cat->setParent(gInventory.getRootFolderID()); } cat->updateServer(TRUE); - catsp = get_ptr_in_map(mParentChildCategoryTree, cat->getParentUUID()); + catsp = getUnlockedCatArray(cat->getParentUUID()); if(catsp) { catsp->put(cat); @@ -2120,6 +1840,10 @@ void LLInventoryModel::buildParentChildMap() llwarns << "Found " << lost << " lost categories." << llendl; } + const BOOL COF_exists = (findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT, FALSE) != LLUUID::null); + sFirstTimeInViewer2 = !COF_exists || gAgent.isFirstLogin(); + + // Now the items. We allocated in the last step, so now all we // have to do is iterate over the items and put them in the right // place. @@ -2135,12 +1859,12 @@ void LLInventoryModel::buildParentChildMap() } count = items.count(); lost = 0; - std::vector<LLUUID> lost_item_ids; + uuid_vec_t lost_item_ids; for(i = 0; i < count; ++i) { LLPointer<LLViewerInventoryItem> item; item = items.get(i); - itemsp = get_ptr_in_map(mParentChildItemTree, item->getParentUUID()); + itemsp = getUnlockedItemArray(item->getParentUUID()); if(itemsp) { itemsp->put(item); @@ -2152,12 +1876,12 @@ void LLInventoryModel::buildParentChildMap() ++lost; // plop it into the lost & found. // - item->setParent(findCategoryUUIDForType(LLAssetType::AT_LOST_AND_FOUND)); + item->setParent(findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND)); // move it later using a special message to move items. If // we update server here, the client might crash. //item->updateServer(); lost_item_ids.push_back(item->getUUID()); - itemsp = get_ptr_in_map(mParentChildItemTree, item->getParentUUID()); + itemsp = getUnlockedItemArray(item->getParentUUID()); if(itemsp) { itemsp->put(item); @@ -2173,8 +1897,8 @@ void LLInventoryModel::buildParentChildMap() llwarns << "Found " << lost << " lost items." << llendl; LLMessageSystem* msg = gMessageSystem; BOOL start_new_message = TRUE; - LLUUID lnf = findCategoryUUIDForType(LLAssetType::AT_LOST_AND_FOUND); - for(std::vector<LLUUID>::iterator it = lost_item_ids.begin() ; it < lost_item_ids.end(); ++it) + const LLUUID lnf = findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); + for(uuid_vec_t::iterator it = lost_item_ids.begin() ; it < lost_item_ids.end(); ++it) { if(start_new_message) { @@ -2201,16 +1925,47 @@ void LLInventoryModel::buildParentChildMap() } } - const LLUUID& agent_inv_root_id = gAgent.getInventoryRootID(); + const LLUUID &agent_inv_root_id = gInventory.getRootFolderID(); if (agent_inv_root_id.notNull()) { cat_array_t* catsp = get_ptr_in_map(mParentChildCategoryTree, agent_inv_root_id); if(catsp) { + // *HACK - fix root inventory folder + // some accounts has pbroken inventory root folders + + std::string name = "My Inventory"; + LLUUID prev_root_id = mRootFolderID; + for (parent_cat_map_t::const_iterator it = mParentChildCategoryTree.begin(), + it_end = mParentChildCategoryTree.end(); it != it_end; ++it) + { + cat_array_t* cat_array = it->second; + for (cat_array_t::const_iterator cat_it = cat_array->begin(), + cat_it_end = cat_array->end(); cat_it != cat_it_end; ++cat_it) + { + LLPointer<LLViewerInventoryCategory> category = *cat_it; + + if(category && category->getPreferredType() != LLFolderType::FT_ROOT_INVENTORY) + continue; + if ( category && 0 == LLStringUtil::compareInsensitive(name, category->getName()) ) + { + if(category->getUUID()!=mRootFolderID) + { + LLUUID& new_inv_root_folder_id = const_cast<LLUUID&>(mRootFolderID); + new_inv_root_folder_id = category->getUUID(); + } + } + } + } + // 'My Inventory', // root of the agent's inv found. // The inv tree is built. mIsAgentInvUsable = true; + + llinfos << "Inventory initialized, notifying observers" << llendl; + addChangedMask(LLInventoryObserver::ALL, LLUUID::null); + notifyObservers(); } } } @@ -2218,16 +1973,16 @@ void LLInventoryModel::buildParentChildMap() struct LLUUIDAndName { LLUUIDAndName() {} - LLUUIDAndName(const LLUUID& id, const LLString& name); + LLUUIDAndName(const LLUUID& id, const std::string& name); bool operator==(const LLUUIDAndName& rhs) const; bool operator<(const LLUUIDAndName& rhs) const; bool operator>(const LLUUIDAndName& rhs) const; LLUUID mID; - LLString mName; + std::string mName; }; -LLUUIDAndName::LLUUIDAndName(const LLUUID& id, const LLString& name) : +LLUUIDAndName::LLUUIDAndName(const LLUUID& id, const std::string& name) : mID(id), mName(name) { } @@ -2247,86 +2002,19 @@ bool LLUUIDAndName::operator>(const LLUUIDAndName& rhs) const return (mID > rhs.mID); } -// Given the current state of the inventory items, figure out the -// clone information. *FIX: This is sub-optimal, since we can insert -// this information snurgically, but this makes sure the implementation -// works before we worry about optimization. -//void LLInventoryModel::recalculateCloneInformation() -//{ -// //dumpInventory(); -// -// // This implements a 'multi-map' like structure to keep track of -// // how many clones we find. -// typedef LLDynamicArray<LLViewerInventoryItem*> viewer_item_array_t; -// typedef std::map<LLUUIDAndName, viewer_item_array_t*> clone_map_t; -// clone_map_t clone_map; -// LLUUIDAndName id_and_name; -// viewer_item_array_t* clones = NULL; -// LLViewerInventoryItem* item = NULL; -// for(item = (LLViewerInventoryItem*)mItemMap.getFirstData(); -// item != NULL; -// item = (LLViewerInventoryItem*)mItemMap.getNextData()) -// { -// if(item->getType() == LLAssetType::AT_CALLINGCARD) -// { -// // if it's a calling card, we key off of the creator id, not -// // the asset id. -// id_and_name.mID = item->getCreatorUUID(); -// } -// else -// { -// // if it's not a calling card, we key clones from the -// // asset id. -// id_and_name.mID = item->getAssetUUID(); -// } -// if(id_and_name.mID == LLUUID::null) -// { -// continue; -// } -// id_and_name.mName = item->getName(); -// if(clone_map.checkData(id_and_name)) -// { -// clones = clone_map.getData(id_and_name); -// } -// else -// { -// clones = new viewer_item_array_t; -// clone_map.addData(id_and_name, clones); -// } -// clones->put(item); -// } -// -// S32 count = 0; -// for(clones = clone_map.getFirstData(); -// clones != NULL; -// clones = clone_map.getNextData()) -// { -// count = clones->count(); -// for(S32 i = 0; i < count; i++) -// { -// item = clones->get(i); -// item->setCloneCount(count - 1); -// //clones[i] = NULL; -// } -// delete clones; -// } -// clone_map.removeAllData(); -// //dumpInventory(); -//} - // static -bool LLInventoryModel::loadFromFile( - const char* filename, - LLInventoryModel::cat_array_t& categories, - LLInventoryModel::item_array_t& items) +bool LLInventoryModel::loadFromFile(const std::string& filename, + LLInventoryModel::cat_array_t& categories, + LLInventoryModel::item_array_t& items, + bool &is_cache_obsolete) { - if(!filename) + if(filename.empty()) { llerrs << "Filename is Null!" << llendl; return false; } llinfos << "LLInventoryModel::loadFromFile(" << filename << ")" << llendl; - FILE* file = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ + LLFILE* file = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ if(!file) { llinfos << "unable to load inventory from: " << filename << llendl; @@ -2335,11 +2023,32 @@ bool LLInventoryModel::loadFromFile( // *NOTE: This buffer size is hard coded into scanf() below. char buffer[MAX_STRING]; /*Flawfinder: ignore*/ char keyword[MAX_STRING]; /*Flawfinder: ignore*/ + char value[MAX_STRING]; /*Flawfinder: ignore*/ + is_cache_obsolete = true; // Obsolete until proven current while(!feof(file) && fgets(buffer, MAX_STRING, file)) { - sscanf(buffer, " %254s", keyword); /* Flawfinder: ignore */ - if(0 == strcmp("inv_category", keyword)) + sscanf(buffer, " %126s %126s", keyword, value); /* Flawfinder: ignore */ + if(0 == strcmp("inv_cache_version", keyword)) + { + S32 version; + int succ = sscanf(value,"%d",&version); + if ((1 == succ) && (version == sCurrentInvCacheVersion)) + { + // Cache is up to date + is_cache_obsolete = false; + continue; + } + else + { + // Cache is out of date + break; + } + } + else if(0 == strcmp("inv_category", keyword)) { + if (is_cache_obsolete) + break; + LLPointer<LLViewerInventoryCategory> inv_cat = new LLViewerInventoryCategory(LLUUID::null); if(inv_cat->importFileLocal(file)) { @@ -2353,6 +2062,9 @@ bool LLInventoryModel::loadFromFile( } else if(0 == strcmp("inv_item", keyword)) { + if (is_cache_obsolete) + break; + LLPointer<LLViewerInventoryItem> inv_item = new LLViewerInventoryItem; if( inv_item->importFileLocal(file) ) { @@ -2384,28 +2096,30 @@ bool LLInventoryModel::loadFromFile( } } fclose(file); + if (is_cache_obsolete) + return false; return true; } // static -bool LLInventoryModel::saveToFile( - const char* filename, - const cat_array_t& categories, - const item_array_t& items) +bool LLInventoryModel::saveToFile(const std::string& filename, + const cat_array_t& categories, + const item_array_t& items) { - if(!filename) + if(filename.empty()) { llerrs << "Filename is Null!" << llendl; return false; } llinfos << "LLInventoryModel::saveToFile(" << filename << ")" << llendl; - FILE* file = LLFile::fopen(filename, "wb"); /*Flawfinder: ignore*/ + LLFILE* file = LLFile::fopen(filename, "wb"); /*Flawfinder: ignore*/ if(!file) { llwarns << "unable to save inventory to: " << filename << llendl; return false; } + fprintf(file, "\tinv_cache_version\t%d\n",sCurrentInvCacheVersion); S32 count = categories.count(); S32 i; for(i = 0; i < count; ++i) @@ -2496,7 +2210,7 @@ void LLInventoryModel::processFetchInventoryReply(LLMessageSystem* msg, void**) bool LLInventoryModel::messageUpdateCore(LLMessageSystem* msg, bool account) { - //make sure our added inventory observer is active -Gigs + //make sure our added inventory observer is active start_new_inventory_observer(); LLUUID agent_id; @@ -2512,7 +2226,7 @@ bool LLInventoryModel::messageUpdateCore(LLMessageSystem* msg, bool account) S32 count = msg->getNumberOfBlocksFast(_PREHASH_InventoryData); bool all_one_folder = true; LLUUID folder_id; - // Does this loop ever execute more than once? -Gigs + // Does this loop ever execute more than once? for(S32 i = 0; i < count; ++i) { LLPointer<LLViewerInventoryItem> titem = new LLViewerInventoryItem; @@ -2577,7 +2291,7 @@ void LLInventoryModel::processRemoveInventoryItem(LLMessageSystem* msg, void**) return; } S32 count = msg->getNumberOfBlocksFast(_PREHASH_InventoryData); - std::vector<LLUUID> item_ids; + uuid_vec_t item_ids; update_map_t update; for(S32 i = 0; i < count; ++i) { @@ -2593,7 +2307,7 @@ void LLInventoryModel::processRemoveInventoryItem(LLMessageSystem* msg, void**) } } gInventory.accountForUpdate(update); - for(std::vector<LLUUID>::iterator it = item_ids.begin(); it != item_ids.end(); ++it) + for(uuid_vec_t::iterator it = item_ids.begin(); it != item_ids.end(); ++it) { gInventory.deleteObject(*it); } @@ -2624,7 +2338,7 @@ void LLInventoryModel::processUpdateInventoryFolder(LLMessageSystem* msg, lastfolder = tfolder; tfolder->unpackMessage(msg, _PREHASH_FolderData, i); // make sure it's not a protected folder - tfolder->setPreferredType(LLAssetType::AT_NONE); + tfolder->setPreferredType(LLFolderType::FT_NONE); folders.push_back(tfolder); // examine update for changes. LLViewerInventoryCategory* folderp = gInventory.getCategory(tfolder->getUUID()); @@ -2653,10 +2367,10 @@ void LLInventoryModel::processUpdateInventoryFolder(LLMessageSystem* msg, gInventory.notifyObservers(); // *HACK: Do the 'show' logic for a new item in the inventory. - LLInventoryView* view = LLInventoryView::getActiveInventory(); - if(view) + LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); + if (active_panel) { - view->getPanel()->setSelection(lastfolder->getUUID(), TAKE_FOCUS_NO); + active_panel->setSelection(lastfolder->getUUID(), TAKE_FOCUS_NO); } } @@ -2673,7 +2387,7 @@ void LLInventoryModel::processRemoveInventoryFolder(LLMessageSystem* msg, << llendl; return; } - std::vector<LLUUID> folder_ids; + uuid_vec_t folder_ids; update_map_t update; S32 count = msg->getNumberOfBlocksFast(_PREHASH_FolderData); for(S32 i = 0; i < count; ++i) @@ -2687,7 +2401,7 @@ void LLInventoryModel::processRemoveInventoryFolder(LLMessageSystem* msg, } } gInventory.accountForUpdate(update); - for(std::vector<LLUUID>::iterator it = folder_ids.begin(); it != folder_ids.end(); ++it) + for(uuid_vec_t::iterator it = folder_ids.begin(); it != folder_ids.end(); ++it) { gInventory.deleteObject(*it); } @@ -2754,7 +2468,9 @@ void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) } LLUUID tid; msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_TransactionID, tid); +#ifndef LL_RELEASE_FOR_DOWNLOAD llinfos << "Bulk inventory: " << tid << llendl; +#endif update_map_t update; cat_array_t folders; @@ -2800,7 +2516,7 @@ void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) count = msg->getNumberOfBlocksFast(_PREHASH_ItemData); - std::vector<LLUUID> wearable_ids; + uuid_vec_t wearable_ids; item_array_t items; std::list<InventoryCallbackInfo> cblist; for(i = 0; i < count; ++i) @@ -2862,20 +2578,20 @@ void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) // The incoming inventory could span more than one BulkInventoryUpdate packet, // so record the transaction ID for this purchase, then wear all clothing // that comes in as part of that transaction ID. JC - if (LLInventoryView::sWearNewClothing) + if (LLInventoryState::sWearNewClothing) { - LLInventoryView::sWearNewClothingTransactionID = tid; - LLInventoryView::sWearNewClothing = FALSE; + LLInventoryState::sWearNewClothingTransactionID = tid; + LLInventoryState::sWearNewClothing = FALSE; } - if (tid == LLInventoryView::sWearNewClothingTransactionID) + if (tid == LLInventoryState::sWearNewClothingTransactionID) { count = wearable_ids.size(); for (i = 0; i < count; ++i) { LLViewerInventoryItem* wearable_item; wearable_item = gInventory.getItem(wearable_ids[i]); - wear_inventory_item_on_avatar(wearable_item); + LLAppearanceMgr::instance().wearItemOnAvatar(wearable_item->getUUID(), true, true); } } @@ -2886,7 +2602,7 @@ void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) gInventoryCallbacks.fire(cbinfo.mCallback, cbinfo.mInvID); } // Don't show the inventory. We used to call showAgentInventory here. - //LLInventoryView* view = LLInventoryView::getActiveInventory(); + //LLFloaterInventory* view = LLFloaterInventory::getActiveInventory(); //if(view) //{ // const BOOL take_keyboard_focus = FALSE; @@ -2896,10 +2612,10 @@ void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) // // HACK to open inventory offers that are accepted. This information // // really needs to flow through the instant messages and inventory // // transfer/update messages. - // if (LLInventoryView::sOpenNextNewItem) + // if (LLFloaterInventory::sOpenNextNewItem) // { // view->openSelected(); - // LLInventoryView::sOpenNextNewItem = FALSE; + // LLFloaterInventory::sOpenNextNewItem = FALSE; // } // // // restore keyboard focus @@ -2914,8 +2630,7 @@ void LLInventoryModel::processInventoryDescendents(LLMessageSystem* msg,void**) msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); if(agent_id != gAgent.getID()) { - llwarns << "Got a UpdateInventoryItem for the wrong agent." - << llendl; + llwarns << "Got a UpdateInventoryItem for the wrong agent." << llendl; return; } LLUUID parent_id; @@ -2926,6 +2641,7 @@ void LLInventoryModel::processInventoryDescendents(LLMessageSystem* msg,void**) msg->getS32("AgentData", "Version", version); S32 descendents; msg->getS32("AgentData", "Descendents", descendents); + S32 i; S32 count = msg->getNumberOfBlocksFast(_PREHASH_FolderData); LLPointer<LLViewerInventoryCategory> tcategory = new LLViewerInventoryCategory(owner_id); @@ -2940,6 +2656,12 @@ void LLInventoryModel::processInventoryDescendents(LLMessageSystem* msg,void**) for(i = 0; i < count; ++i) { titem->unpackMessage(msg, _PREHASH_ItemData, i); + // If the item has already been added (e.g. from link prefetch), then it doesn't need to be re-added. + if (gInventory.getItem(titem->getUUID())) + { + lldebugs << "Skipping prefetched item [ Name: " << titem->getName() << " | Type: " << titem->getActualType() << " | ItemUUID: " << titem->getUUID() << " ] " << llendl; + continue; + } gInventory.updateItem(titem); } @@ -2949,6 +2671,9 @@ void LLInventoryModel::processInventoryDescendents(LLMessageSystem* msg,void**) { cat->setVersion(version); cat->setDescendentCount(descendents); + // Get this UUID on the changed list so that whatever's listening for it + // will get triggered. + gInventory.addChangedMask(LLInventoryObserver::INTERNAL, cat->getUUID()); } gInventory.notifyObservers(); } @@ -2968,7 +2693,7 @@ void LLInventoryModel::processMoveInventoryItem(LLMessageSystem* msg, void**) LLUUID item_id; LLUUID folder_id; - char new_name[MAX_STRING]; /*Flawfinder: ignore*/ + std::string new_name; bool anything_changed = false; S32 count = msg->getNumberOfBlocksFast(_PREHASH_InventoryData); for(S32 i = 0; i < count; ++i) @@ -2979,7 +2704,7 @@ void LLInventoryModel::processMoveInventoryItem(LLMessageSystem* msg, void**) { LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item); msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_FolderID, folder_id, i); - msg->getString("InventoryData", "NewName", MAX_STRING, new_name, i); + msg->getString("InventoryData", "NewName", new_name, i); lldebugs << "moving item " << item_id << " to folder " << folder_id << llendl; @@ -2991,7 +2716,7 @@ void LLInventoryModel::processMoveInventoryItem(LLMessageSystem* msg, void**) gInventory.accountForUpdate(update); new_item->setParent(folder_id); - if(strlen(new_name) > 0) /*Flawfinder: ignore*/ + if (new_name.length() > 0) { new_item->rename(new_name); } @@ -3009,660 +2734,241 @@ void LLInventoryModel::processMoveInventoryItem(LLMessageSystem* msg, void**) } } -// *NOTE: DEBUG functionality -void LLInventoryModel::dumpInventory() -{ - llinfos << "\nBegin Inventory Dump\n**********************:" << llendl; - llinfos << "mCategroy[] contains " << mCategoryMap.size() << " items." << llendl; - for(cat_map_t::iterator cit = mCategoryMap.begin(); cit != mCategoryMap.end(); ++cit) - { - LLViewerInventoryCategory* cat = cit->second; - if(cat) - { - llinfos << " " << cat->getUUID() << " '" << cat->getName() << "' " - << cat->getVersion() << " " << cat->getDescendentCount() - << llendl; - } - else - { - llinfos << " NULL!" << llendl; - } - } - llinfos << "mItemMap[] contains " << mItemMap.size() << " items." << llendl; - for(item_map_t::iterator iit = mItemMap.begin(); iit != mItemMap.end(); ++iit) - { - LLViewerInventoryItem* item = iit->second; - if(item) - { - llinfos << " " << item->getUUID() << " " - << item->getName() << llendl; - } - else - { - llinfos << " NULL!" << llendl; - } - } - llinfos << "\n**********************\nEnd Inventory Dump" << llendl; -} +//---------------------------------------------------------------------------- -///---------------------------------------------------------------------------- -/// LLInventoryCollectFunctor implementations -///---------------------------------------------------------------------------- +// Trash: LLFolderType::FT_TRASH, "ConfirmEmptyTrash" +// Lost&Found: LLFolderType::FT_LOST_AND_FOUND, "ConfirmEmptyLostAndFound" -bool LLIsType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +bool LLInventoryModel::callbackEmptyFolderType(const LLSD& notification, const LLSD& response, LLFolderType::EType preferred_type) { - if(mType == LLAssetType::AT_CATEGORY) + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES { - if(cat) return TRUE; + const LLUUID folder_id = findCategoryUUIDForType(preferred_type); + purgeDescendentsOf(folder_id); + notifyObservers(); } - if(item) - { - if(item->getType() == mType) return TRUE; - } - return FALSE; + return false; } -bool LLIsNotType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +void LLInventoryModel::emptyFolderType(const std::string notification, LLFolderType::EType preferred_type) { - if(mType == LLAssetType::AT_CATEGORY) + if (!notification.empty()) { - if(cat) return FALSE; + LLNotificationsUtil::add(notification, LLSD(), LLSD(), + boost::bind(&LLInventoryModel::callbackEmptyFolderType, this, _1, _2, preferred_type)); } - if(item) + else { - if(item->getType() == mType) return FALSE; - else return TRUE; + const LLUUID folder_id = findCategoryUUIDForType(preferred_type); + purgeDescendentsOf(folder_id); + notifyObservers(); } - return TRUE; } -bool LLIsTypeWithPermissions::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +//---------------------------------------------------------------------------- + +void LLInventoryModel::removeItem(const LLUUID& item_id) { - if(mType == LLAssetType::AT_CATEGORY) + LLViewerInventoryItem* item = getItem(item_id); + const LLUUID new_parent = findCategoryUUIDForType(LLFolderType::FT_TRASH); + if (item && item->getParentUUID() != new_parent) { - if(cat) - { - return TRUE; - } - } - if(item) - { - if(item->getType() == mType) - { - LLPermissions perm = item->getPermissions(); - if ((perm.getMaskBase() & mPerm) == mPerm) - { - return TRUE; - } - } + LLInventoryModel::update_list_t update; + LLInventoryModel::LLCategoryUpdate old_folder(item->getParentUUID(),-1); + update.push_back(old_folder); + LLInventoryModel::LLCategoryUpdate new_folder(new_parent, 1); + update.push_back(new_folder); + accountForUpdate(update); + + LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item); + new_item->setParent(new_parent); + new_item->updateParentOnServer(TRUE); + updateItem(new_item); + notifyObservers(); } - return FALSE; } - -//bool LLIsClone::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -//{ -// if(cat) return FALSE; -// if(item) -// { -// if(mItemMap->getType() == LLAssetType::AT_CALLINGCARD) -// { -// if((item->getType() == LLAssetType::AT_CALLINGCARD) -// && !(item->getCreatorUUID().isNull()) -// && (item->getCreatorUUID() == mItemMap->getCreatorUUID())) -// { -// return TRUE; -// } -// } -// else -// { -// if((item->getType() == mItemMap->getType()) -// && !(item->getAssetUUID().isNull()) -// && (item->getAssetUUID() == mItemMap->getAssetUUID()) -// && (item->getName() == mItemMap->getName())) -// { -// return TRUE; -// } -// } -// } -// return FALSE; -//} - -bool LLBuddyCollector::operator()(LLInventoryCategory* cat, - LLInventoryItem* item) +const LLUUID &LLInventoryModel::getRootFolderID() const { - if(item) - { - if((LLAssetType::AT_CALLINGCARD == item->getType()) - && (!item->getCreatorUUID().isNull()) - && (item->getCreatorUUID() != gAgent.getID())) - { - return true; - } - } - return false; + return mRootFolderID; } - -bool LLUniqueBuddyCollector::operator()(LLInventoryCategory* cat, - LLInventoryItem* item) +void LLInventoryModel::setRootFolderID(const LLUUID& val) { - if(item) - { - if((LLAssetType::AT_CALLINGCARD == item->getType()) - && (item->getCreatorUUID().notNull()) - && (item->getCreatorUUID() != gAgent.getID())) - { - mSeen.insert(item->getCreatorUUID()); - return true; - } - } - return false; + mRootFolderID = val; } - -bool LLParticularBuddyCollector::operator()(LLInventoryCategory* cat, - LLInventoryItem* item) +const LLUUID &LLInventoryModel::getLibraryRootFolderID() const { - if(item) - { - if((LLAssetType::AT_CALLINGCARD == item->getType()) - && (item->getCreatorUUID() == mBuddyID)) - { - return TRUE; - } - } - return FALSE; + return mLibraryRootFolderID; } - -bool LLNameCategoryCollector::operator()( - LLInventoryCategory* cat, LLInventoryItem* item) +void LLInventoryModel::setLibraryRootFolderID(const LLUUID& val) { - if(cat) - { - if (!LLString::compareInsensitive(mName.c_str(), cat->getName().c_str())) - { - return true; - } - } - return false; + mLibraryRootFolderID = val; } - - -///---------------------------------------------------------------------------- -/// Observers -///---------------------------------------------------------------------------- - -void LLInventoryCompletionObserver::changed(U32 mask) +const LLUUID &LLInventoryModel::getLibraryOwnerID() const { - // scan through the incomplete items and move or erase them as - // appropriate. - if(!mIncomplete.empty()) - { - for(item_ref_t::iterator it = mIncomplete.begin(); it < mIncomplete.end(); ) - { - LLViewerInventoryItem* item = gInventory.getItem(*it); - if(!item) - { - it = mIncomplete.erase(it); - continue; - } - if(item->isComplete()) - { - mComplete.push_back(*it); - it = mIncomplete.erase(it); - continue; - } - ++it; - } - if(mIncomplete.empty()) - { - done(); - } - } + return mLibraryOwnerID; } -void LLInventoryCompletionObserver::watchItem(const LLUUID& id) +void LLInventoryModel::setLibraryOwnerID(const LLUUID& val) { - if(id.notNull()) - { - mIncomplete.push_back(id); - } + mLibraryOwnerID = val; } - -void LLInventoryFetchObserver::changed(U32 mask) +// static +BOOL LLInventoryModel::getIsFirstTimeInViewer2() { - // scan through the incomplete items and move or erase them as - // appropriate. - if(!mIncomplete.empty()) + // Do not call this before parentchild map is built. + if (!gInventory.mIsAgentInvUsable) { - for(item_ref_t::iterator it = mIncomplete.begin(); it < mIncomplete.end(); ) - { - LLViewerInventoryItem* item = gInventory.getItem(*it); - if(!item) - { - // BUG: This can cause done() to get called prematurely below. - // This happens with the LLGestureInventoryFetchObserver that - // loads gestures at startup. JC - it = mIncomplete.erase(it); - continue; - } - if(item->isComplete()) - { - mComplete.push_back(*it); - it = mIncomplete.erase(it); - continue; - } - ++it; - } - if(mIncomplete.empty()) - { - done(); - } + llwarns << "Parent Child Map not yet built; guessing as first time in viewer2." << llendl; + return TRUE; } - //llinfos << "LLInventoryFetchObserver::changed() mComplete size " << mComplete.size() << llendl; - //llinfos << "LLInventoryFetchObserver::changed() mIncomplete size " << mIncomplete.size() << llendl; -} -bool LLInventoryFetchObserver::isEverythingComplete() const -{ - return mIncomplete.empty(); + return sFirstTimeInViewer2; } -void LLInventoryFetchObserver::fetchItems( - const LLInventoryFetchObserver::item_ref_t& ids) +static LLInventoryModel::item_array_t::iterator find_item_iter_by_uuid(LLInventoryModel::item_array_t& items, const LLUUID& id) { - LLMessageSystem* msg = gMessageSystem; - BOOL start_new_message = TRUE; - LLUUID owner_id; - for(item_ref_t::const_iterator it = ids.begin(); it < ids.end(); ++it) - { - LLViewerInventoryItem* item = gInventory.getItem(*it); - if(item) - { - if(item->isComplete()) - { - // It's complete, so put it on the complete container. - mComplete.push_back(*it); - continue; - } - else - { - owner_id = item->getPermissions().getOwner(); - } - } - else - { - // assume it's agent inventory. - owner_id = gAgent.getID(); - } + LLInventoryModel::item_array_t::iterator result = items.end(); - // It's incomplete, so put it on the incomplete container, and - // pack this on the message. - mIncomplete.push_back(*it); - if(start_new_message) - { - start_new_message = FALSE; - msg->newMessageFast(_PREHASH_FetchInventory); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - } - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addUUIDFast(_PREHASH_OwnerID, owner_id); - msg->addUUIDFast(_PREHASH_ItemID, (*it)); - if(msg->isSendFull(NULL)) + for (LLInventoryModel::item_array_t::iterator i = items.begin(); i != items.end(); ++i) + { + if ((*i)->getUUID() == id) { - start_new_message = TRUE; - gAgent.sendReliableMessage(); + result = i; + break; } } - if(!start_new_message) - { - gAgent.sendReliableMessage(); - } + + return result; } -// virtual -void LLInventoryFetchDescendentsObserver::changed(U32 mask) +// static +// * @param[in, out] items - vector with items to be updated. It should be sorted in a right way +// * before calling this method. +// * @param src_item_id - LLUUID of inventory item to be moved in new position +// * @param dest_item_id - LLUUID of inventory item before which source item should be placed. +void LLInventoryModel::updateItemsOrder(LLInventoryModel::item_array_t& items, const LLUUID& src_item_id, const LLUUID& dest_item_id) { - for(folder_ref_t::iterator it = mIncompleteFolders.begin(); it < mIncompleteFolders.end();) - { - LLViewerInventoryCategory* cat = gInventory.getCategory(*it); - if(!cat) - { - it = mIncompleteFolders.erase(it); - continue; - } - if(isComplete(cat)) - { - mCompleteFolders.push_back(*it); - it = mIncompleteFolders.erase(it); - continue; - } - ++it; - } - if(mIncompleteFolders.empty()) - { - done(); - } + LLInventoryModel::item_array_t::iterator it_src = find_item_iter_by_uuid(items, src_item_id); + LLInventoryModel::item_array_t::iterator it_dest = find_item_iter_by_uuid(items, dest_item_id); + + if (it_src == items.end() || it_dest == items.end()) return; + + LLViewerInventoryItem* src_item = *it_src; + items.erase(it_src); + + // target iterator can not be valid because the container was changed, so update it. + it_dest = find_item_iter_by_uuid(items, dest_item_id); + items.insert(it_dest, src_item); } -void LLInventoryFetchDescendentsObserver::fetchDescendents( - const folder_ref_t& ids) +//* @param[in] items vector of items in order to be saved. +void LLInventoryModel::saveItemsOrder(const LLInventoryModel::item_array_t& items) { - for(folder_ref_t::const_iterator it = ids.begin(); it != ids.end(); ++it) + int sortField = 0; + + // current order is saved by setting incremental values (1, 2, 3, ...) for the sort field + for (item_array_t::const_iterator i = items.begin(); i != items.end(); ++i) { - LLViewerInventoryCategory* cat = gInventory.getCategory(*it); - if(!cat) continue; - if(!isComplete(cat)) - { - cat->fetchDescendents(); //blindly fetch it without seeing if anything else is fetching it. - mIncompleteFolders.push_back(*it); //Add to list of things being downloaded for this observer. - } - else - { - mCompleteFolders.push_back(*it); - } + LLViewerInventoryItem* item = *i; + + item->setSortField(++sortField); + item->setComplete(TRUE); + item->updateServer(FALSE); + + updateItem(item); + + // Tell the parent folder to refresh its sort order. + addChangedMask(LLInventoryObserver::SORT, item->getParentUUID()); } -} -bool LLInventoryFetchDescendentsObserver::isEverythingComplete() const -{ - return mIncompleteFolders.empty(); + notifyObservers(); } -bool LLInventoryFetchDescendentsObserver::isComplete(LLViewerInventoryCategory* cat) +// See also LLInventorySort where landmarks in the Favorites folder are sorted. +class LLViewerInventoryItemSort { - S32 version = cat->getVersion(); - S32 descendents = cat->getDescendentCount(); - if((LLViewerInventoryCategory::VERSION_UNKNOWN == version) - || (LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN == descendents)) - { - return false; - } - // it might be complete - check known descendents against - // currently available. - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(cat->getUUID(), cats, items); - if(!cats || !items) - { - // bit of a hack - pretend we're done if they are gone or - // incomplete. should never know, but it would suck if this - // kept tight looping because of a corrupt memory state. - return true; - } - S32 known = cats->count() + items->count(); - if(descendents == known) +public: + bool operator()(const LLPointer<LLViewerInventoryItem>& a, const LLPointer<LLViewerInventoryItem>& b) { - // hey - we're done. - return true; + return a->getSortField() < b->getSortField(); } - return false; -} +}; -void LLInventoryFetchComboObserver::changed(U32 mask) +/** + * Sorts passed items by LLViewerInventoryItem sort field. + * + * @param[in, out] items - array of items, not sorted. + */ +static void rearrange_item_order_by_sort_field(LLInventoryModel::item_array_t& items) { - if(!mIncompleteItems.empty()) - { - for(item_ref_t::iterator it = mIncompleteItems.begin(); it < mIncompleteItems.end(); ) - { - LLViewerInventoryItem* item = gInventory.getItem(*it); - if(!item) - { - it = mIncompleteItems.erase(it); - continue; - } - if(item->isComplete()) - { - mCompleteItems.push_back(*it); - it = mIncompleteItems.erase(it); - continue; - } - ++it; - } - } - if(!mIncompleteFolders.empty()) - { - for(folder_ref_t::iterator it = mIncompleteFolders.begin(); it < mIncompleteFolders.end();) - { - LLViewerInventoryCategory* cat = gInventory.getCategory(*it); - if(!cat) - { - it = mIncompleteFolders.erase(it); - continue; - } - if(gInventory.isCategoryComplete(*it)) - { - mCompleteFolders.push_back(*it); - it = mIncompleteFolders.erase(it); - continue; - } - ++it; - } - } - if(!mDone && mIncompleteItems.empty() && mIncompleteFolders.empty()) - { - mDone = true; - done(); - } + static LLViewerInventoryItemSort sort_functor; + std::sort(items.begin(), items.end(), sort_functor); } -void LLInventoryFetchComboObserver::fetch( - const folder_ref_t& folder_ids, - const item_ref_t& item_ids) +// * @param source_item_id - LLUUID of the source item to be moved into new position +// * @param target_item_id - LLUUID of the target item before which source item should be placed. +void LLInventoryModel::rearrangeFavoriteLandmarks(const LLUUID& source_item_id, const LLUUID& target_item_id) { - lldebugs << "LLInventoryFetchComboObserver::fetch()" << llendl; - for(folder_ref_t::const_iterator fit = folder_ids.begin(); fit != folder_ids.end(); ++fit) - { - LLViewerInventoryCategory* cat = gInventory.getCategory(*fit); - if(!cat) continue; - if(!gInventory.isCategoryComplete(*fit)) - { - cat->fetchDescendents(); - lldebugs << "fetching folder " << *fit <<llendl; - mIncompleteFolders.push_back(*fit); - } - else - { - mCompleteFolders.push_back(*fit); - lldebugs << "completing folder " << *fit <<llendl; - } - } + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLIsType is_type(LLAssetType::AT_LANDMARK); + LLUUID favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + gInventory.collectDescendentsIf(favorites_id, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); - // Now for the items - we fetch everything which is not a direct - // descendent of an incomplete folder because the item will show - // up in an inventory descendents message soon enough so we do not - // have to fetch it individually. - LLUUID owner_id; - LLMessageSystem* msg = gMessageSystem; - bool start_new_message = true; - for(item_ref_t::const_iterator iit = item_ids.begin(); iit != item_ids.end(); ++iit) - { - LLViewerInventoryItem* item = gInventory.getItem(*iit); - if(!item) - { - lldebugs << "uanble to find item " << *iit << llendl; - continue; - } - if(item->isComplete()) - { - // It's complete, so put it on the complete container. - mCompleteItems.push_back(*iit); - lldebugs << "completing item " << *iit << llendl; - continue; - } - else - { - mIncompleteItems.push_back(*iit); - owner_id = item->getPermissions().getOwner(); - } - if(std::find(mIncompleteFolders.begin(), mIncompleteFolders.end(), item->getParentUUID()) == mIncompleteFolders.end()) - { - lldebugs << "fetching item " << *iit << llendl; - if(start_new_message) - { - start_new_message = false; - msg->newMessageFast(_PREHASH_FetchInventory); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - } - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addUUIDFast(_PREHASH_OwnerID, owner_id); - msg->addUUIDFast(_PREHASH_ItemID, (*iit)); - if(msg->isSendFullFast(_PREHASH_InventoryData)) - { - start_new_message = true; - gAgent.sendReliableMessage(); - } - } - else - { - lldebugs << "not worrying about " << *iit << llendl; - } - } - if(!start_new_message) - { - gAgent.sendReliableMessage(); - } -} + // ensure items are sorted properly before changing order. EXT-3498 + rearrange_item_order_by_sort_field(items); -void LLInventoryExistenceObserver::watchItem(const LLUUID& id) -{ - if(id.notNull()) - { - mMIA.push_back(id); - } + // update order + updateItemsOrder(items, source_item_id, target_item_id); + + saveItemsOrder(items); } -void LLInventoryExistenceObserver::changed(U32 mask) +//---------------------------------------------------------------------------- + +// *NOTE: DEBUG functionality +void LLInventoryModel::dumpInventory() const { - // scan through the incomplete items and move or erase them as - // appropriate. - if(!mMIA.empty()) + llinfos << "\nBegin Inventory Dump\n**********************:" << llendl; + llinfos << "mCategory[] contains " << mCategoryMap.size() << " items." << llendl; + for(cat_map_t::const_iterator cit = mCategoryMap.begin(); cit != mCategoryMap.end(); ++cit) { - for(item_ref_t::iterator it = mMIA.begin(); it < mMIA.end(); ) + const LLViewerInventoryCategory* cat = cit->second; + if(cat) { - LLViewerInventoryItem* item = gInventory.getItem(*it); - if(!item) - { - ++it; - continue; - } - mExist.push_back(*it); - it = mMIA.erase(it); + llinfos << " " << cat->getUUID() << " '" << cat->getName() << "' " + << cat->getVersion() << " " << cat->getDescendentCount() + << llendl; } - if(mMIA.empty()) + else { - done(); + llinfos << " NULL!" << llendl; } - } -} - -void LLInventoryAddedObserver::changed(U32 mask) -{ - if(!(mask & LLInventoryObserver::ADD)) - { - return; - } - - // *HACK: If this was in response to a packet off - // the network, figure out which item was updated. - // Code from Gigs Taggert, sin allowed by JC. - LLMessageSystem* msg = gMessageSystem; - const char* msg_name = msg->getMessageName(); - if (!msg_name) return; - - // We only want newly created inventory items. JC - if ( strcmp(msg_name, "UpdateCreateInventoryItem") ) - { - return; - } - - LLPointer<LLViewerInventoryItem> titem = new LLViewerInventoryItem; - S32 num_blocks = msg->getNumberOfBlocksFast(_PREHASH_InventoryData); - for(S32 i = 0; i < num_blocks; ++i) + } + llinfos << "mItemMap[] contains " << mItemMap.size() << " items." << llendl; + for(item_map_t::const_iterator iit = mItemMap.begin(); iit != mItemMap.end(); ++iit) { - titem->unpackMessage(msg, _PREHASH_InventoryData, i); - if (!(titem->getUUID().isNull())) + const LLViewerInventoryItem* item = iit->second; + if(item) { - //we don't do anything with null keys - mAdded.push_back(titem->getUUID()); + llinfos << " " << item->getUUID() << " " + << item->getName() << llendl; } - } - if (!mAdded.empty()) - { - done(); - } -} - -LLInventoryTransactionObserver::LLInventoryTransactionObserver( - const LLTransactionID& transaction_id) : - mTransactionID(transaction_id) -{ -} - -void LLInventoryTransactionObserver::changed(U32 mask) -{ - if(mask & LLInventoryObserver::ADD) - { - // This could be it - see if we are processing a bulk update - LLMessageSystem* msg = gMessageSystem; - if(msg->getMessageName() - && (0 == strcmp(msg->getMessageName(), "BulkUpdateInventory"))) - { - // we have a match for the message - now check the - // transaction id. - LLUUID id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_TransactionID, id); - if(id == mTransactionID) - { - // woo hoo, we found it - folder_ref_t folders; - item_ref_t items; - S32 count; - count = msg->getNumberOfBlocksFast(_PREHASH_FolderData); - S32 i; - for(i = 0; i < count; ++i) - { - msg->getUUIDFast(_PREHASH_FolderData, _PREHASH_FolderID, id, i); - if(id.notNull()) - { - folders.push_back(id); - } - } - count = msg->getNumberOfBlocksFast(_PREHASH_ItemData); - for(i = 0; i < count; ++i) - { - msg->getUUIDFast(_PREHASH_ItemData, _PREHASH_ItemID, id, i); - if(id.notNull()) - { - items.push_back(id); - } - } - - // call the derived class the implements this method. - done(folders, items); - } + else + { + llinfos << " NULL!" << llendl; } } + llinfos << "\n**********************\nEnd Inventory Dump" << llendl; } - -///---------------------------------------------------------------------------- -/// LLAssetIDMatches -///---------------------------------------------------------------------------- -bool LLAssetIDMatches ::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - return (item && item->getAssetUUID() == mAssetID); -} - - ///---------------------------------------------------------------------------- /// Local function definitions ///---------------------------------------------------------------------------- @@ -3674,7 +2980,7 @@ BOOL decompress_file(const char* src_filename, const char* dst_filename) BOOL rv = FALSE; gzFile src = NULL; U8* buffer = NULL; - FILE* dst = NULL; + LLFILE* dst = NULL; S32 bytes = 0; const S32 DECOMPRESS_BUFFER_SIZE = 32000; |