Added a new statemachine AIFetchInventoryFolder, which can be used to fetch the contents of a folder by name or UUID. Also added AIEvent (and one event, AIEvent::LLInventoryModel_mIsAgentInvUsable_true, which is needed for AIFetchInventoryFolder). Fixed LLInventoryModel::sBackgroundFetchActive to correctly reflect whether or not LLInventoryModel::backgroundFetch is added to gIdleCallbacks. Avoid duplicated entries in sFetchQueue. Reset sFullFetchStarted in LLInventoryModel::stopBackgroundFetch to allow for a renewed full fetch when some single-folder fetch stops it. Added AIStateMachine::mQueued to make calling 'cont()' more robust: calling cont() / idle() / cont() on a row would otherwise add a statemachine twice to the active list, which would cause a crash when it's killed.
4617 lines
129 KiB
C++
4617 lines
129 KiB
C++
/**
|
|
* @file llinventorymodel.cpp
|
|
* @brief Implementation of the inventory model used to track agent inventory.
|
|
*
|
|
* $LicenseInfo:firstyear=2002&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2002-2009, Linden Research, Inc.
|
|
*
|
|
* 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://secondlifegrid.net/programs/open_source/licensing/gplv2
|
|
*
|
|
* 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://secondlifegrid.net/programs/open_source/licensing/flossexception
|
|
*
|
|
* 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.
|
|
*
|
|
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
|
|
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
|
|
* COMPLETENESS OR PERFORMANCE.
|
|
* $/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 "llviewermessage.h"
|
|
#include "llviewerwindow.h"
|
|
#include "llviewerregion.h"
|
|
#include "llappviewer.h"
|
|
#include "lldbstrings.h"
|
|
#include "llviewerstats.h"
|
|
#include "llmutelist.h"
|
|
#include "llnotify.h"
|
|
#include "llcallbacklist.h"
|
|
#include "llpreview.h"
|
|
#include "llviewercontrol.h"
|
|
#include "llvoavatar.h"
|
|
#include "llsdutil.h"
|
|
#include "statemachine/aievent.h"
|
|
// <edit>
|
|
#include "llappviewer.h" // gLostItemsRoot
|
|
// </edit>
|
|
#include <deque>
|
|
|
|
// [RLVa:KB]
|
|
#include "rlvhandler.h"
|
|
// [/RLVa:KB]
|
|
|
|
//#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;
|
|
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 viewers with link items support, former caches are incorrect.
|
|
const S32 LLInventoryModel::sCurrentInvCacheVersion = 2;
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// 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 = 10;
|
|
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
|
|
"New Folder", // AT_SIMSTATE
|
|
"Favorites",
|
|
"New Folder",
|
|
"New Folder",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"New Ensemble",
|
|
"Current Outfit",
|
|
"New Outfit",
|
|
"My Outfits"
|
|
};
|
|
|
|
struct InventoryIDPtrLess
|
|
{
|
|
bool operator()(const LLViewerInventoryCategory* i1, const LLViewerInventoryCategory* i2) const
|
|
{
|
|
return (i1->getUUID() < i2->getUUID());
|
|
}
|
|
};
|
|
|
|
class LLCanCache : public LLInventoryCollectFunctor
|
|
{
|
|
public:
|
|
LLCanCache(LLInventoryModel* model) : mModel(model) {}
|
|
virtual ~LLCanCache() {}
|
|
virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item);
|
|
protected:
|
|
LLInventoryModel* mModel;
|
|
std::set<LLUUID> mCachedCatIDs;
|
|
};
|
|
|
|
bool LLCanCache::operator()(LLInventoryCategory* cat, LLInventoryItem* item)
|
|
{
|
|
bool rv = false;
|
|
if(item)
|
|
{
|
|
if(mCachedCatIDs.find(item->getParentUUID()) != mCachedCatIDs.end())
|
|
{
|
|
rv = true;
|
|
}
|
|
}
|
|
else if(cat)
|
|
{
|
|
// HACK: downcast
|
|
LLViewerInventoryCategory* c = (LLViewerInventoryCategory*)cat;
|
|
if(c->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN)
|
|
{
|
|
S32 descendents_server = c->getDescendentCount();
|
|
LLInventoryModel::cat_array_t* cats;
|
|
LLInventoryModel::item_array_t* items;
|
|
mModel->getDirectDescendentsOf(
|
|
c->getUUID(),
|
|
cats,
|
|
items);
|
|
S32 descendents_actual = 0;
|
|
if(cats && items)
|
|
{
|
|
descendents_actual = cats->count() + items->count();
|
|
}
|
|
if(descendents_server == descendents_actual)
|
|
{
|
|
mCachedCatIDs.insert(c->getUUID());
|
|
rv = true;
|
|
}
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// Class LLInventoryModel
|
|
///----------------------------------------------------------------------------
|
|
|
|
// global for the agent inventory.
|
|
LLInventoryModel gInventory;
|
|
|
|
// Default constructor
|
|
LLInventoryModel::LLInventoryModel() :
|
|
mModifyMask(LLInventoryObserver::ALL),
|
|
mLastItem(NULL),
|
|
mIsAgentInvUsable(false)
|
|
{
|
|
}
|
|
|
|
// Destroys the object
|
|
LLInventoryModel::~LLInventoryModel()
|
|
{
|
|
empty();
|
|
for (observer_list_t::iterator iter = mObservers.begin();
|
|
iter != mObservers.end(); ++iter)
|
|
{
|
|
delete *iter;
|
|
}
|
|
}
|
|
|
|
// 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 BOOL break_on_recursion) const
|
|
{
|
|
LLInventoryObject* obj = getObject(obj_id);
|
|
int depthCounter = 0;
|
|
while(obj)
|
|
{
|
|
const LLUUID& parent_id = obj->getParentUUID();
|
|
// <edit>
|
|
if(break_on_recursion)
|
|
{
|
|
if(depthCounter++ >= 100)
|
|
{
|
|
llwarns << "In way too damn deep, possibly recursive parenting" << llendl;
|
|
llinfos << obj->getName() << " : " << obj->getUUID() << " : " << parent_id << llendl;
|
|
return FALSE;
|
|
}
|
|
if(parent_id == obj->getUUID())
|
|
{
|
|
// infinite loop... same thing as having no parent, hopefully.
|
|
llwarns << "This shit has itself as parent! " << parent_id.asString() << ", " << obj->getName() << llendl;
|
|
return FALSE;
|
|
}
|
|
}
|
|
// </edit>
|
|
if( parent_id.isNull() )
|
|
{
|
|
return FALSE;
|
|
}
|
|
if(parent_id == cat_id)
|
|
{
|
|
return TRUE;
|
|
}
|
|
// Since we're scanning up the parents, we only need to check
|
|
// in the category list.
|
|
obj = getCategory(parent_id);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// Get the object by id. Returns NULL if not found.
|
|
LLInventoryObject* LLInventoryModel::getObject(const LLUUID& id) const
|
|
{
|
|
LLViewerInventoryCategory* cat = getCategory(id);
|
|
if (cat)
|
|
{
|
|
return cat;
|
|
}
|
|
LLViewerInventoryItem* item = getItem(id);
|
|
if (item)
|
|
{
|
|
return item;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Get the item by id. Returns NULL if not found.
|
|
LLViewerInventoryItem* LLInventoryModel::getItem(const LLUUID& id) const
|
|
{
|
|
LLViewerInventoryItem* item = NULL;
|
|
if(mLastItem.notNull() && mLastItem->getUUID() == id)
|
|
{
|
|
item = mLastItem;
|
|
}
|
|
else
|
|
{
|
|
item_map_t::const_iterator iter = mItemMap.find(id);
|
|
if (iter != mItemMap.end())
|
|
{
|
|
item = iter->second;
|
|
mLastItem = item;
|
|
}
|
|
}
|
|
return item;
|
|
}
|
|
|
|
// Get the category by id. Returns NULL if not found
|
|
LLViewerInventoryCategory* LLInventoryModel::getCategory(const LLUUID& id) const
|
|
{
|
|
LLViewerInventoryCategory* category = NULL;
|
|
cat_map_t::const_iterator iter = mCategoryMap.find(id);
|
|
if (iter != mCategoryMap.end())
|
|
{
|
|
category = iter->second;
|
|
}
|
|
return category;
|
|
}
|
|
|
|
S32 LLInventoryModel::getItemCount() const
|
|
{
|
|
return mItemMap.size();
|
|
}
|
|
|
|
S32 LLInventoryModel::getCategoryCount() const
|
|
{
|
|
return mCategoryMap.size();
|
|
}
|
|
|
|
// Return the direct descendents of the id provided. The array
|
|
// provided points straight into the guts of this object, and
|
|
// should only be used for read operations, since modifications
|
|
// may invalidate the internal state of the inventory. Set passed
|
|
// in values to NULL if the call fails.
|
|
void LLInventoryModel::getDirectDescendentsOf(const LLUUID& cat_id,
|
|
cat_array_t*& categories,
|
|
item_array_t*& items) const
|
|
{
|
|
categories = get_ptr_in_map(mParentChildCategoryTree, cat_id);
|
|
items = get_ptr_in_map(mParentChildItemTree, cat_id);
|
|
}
|
|
|
|
// Same but just categories.
|
|
void LLInventoryModel::getDirectDescendentsOf(const LLUUID& cat_id,
|
|
cat_array_t*& categories) const
|
|
{
|
|
categories = get_ptr_in_map(mParentChildCategoryTree, cat_id);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
getDirectDescendentsOf(cat_id, categories, items);
|
|
if (categories)
|
|
{
|
|
mCategoryLock[cat_id] = true;
|
|
}
|
|
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(LLAssetType::EType t, bool create_folder)
|
|
{
|
|
LLUUID rv = findCatUUID(t);
|
|
if(rv.isNull() && isInventoryUsable() && create_folder)
|
|
{
|
|
LLUUID root_id = gAgent.getInventoryRootID();
|
|
if(root_id.notNull())
|
|
{
|
|
rv = createNewCategory(root_id, t, LLStringUtil::null);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
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 LLAssetType::EType folder_type = cat->getPreferredType();
|
|
if (folder_type != LLAssetType::AT_NONE && folder_type != LLAssetType::AT_ROOT_CATEGORY)
|
|
{
|
|
return cat;
|
|
}
|
|
parent_id = cat->getParentUUID();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
LLUUID root_id = gAgent.getInventoryRootID();
|
|
if(LLAssetType::AT_CATEGORY == preferred_type)
|
|
{
|
|
return root_id;
|
|
}
|
|
if(root_id.notNull())
|
|
{
|
|
cat_array_t* cats = NULL;
|
|
cats = get_ptr_in_map(mParentChildCategoryTree, root_id);
|
|
if(cats)
|
|
{
|
|
S32 count = cats->count();
|
|
for(S32 i = 0; i < count; ++i)
|
|
{
|
|
if(cats->get(i)->getPreferredType() == preferred_type)
|
|
{
|
|
return cats->get(i)->getUUID();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return LLUUID::null;
|
|
}
|
|
|
|
LLUUID LLInventoryModel::findCategoryByName(std::string name)
|
|
{
|
|
LLUUID root_id = gAgent.getInventoryRootID();
|
|
if(root_id.notNull())
|
|
{
|
|
cat_array_t* cats = NULL;
|
|
cats = get_ptr_in_map(mParentChildCategoryTree, root_id);
|
|
if(cats)
|
|
{
|
|
S32 count = cats->count();
|
|
for(S32 i = 0; i < count; ++i)
|
|
{
|
|
if(cats->get(i)->getName() == name)
|
|
{
|
|
return cats->get(i)->getUUID();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return LLUUID::null;
|
|
}
|
|
|
|
// Convenience function to create a new category. You could call
|
|
// updateCategory() with a newly generated UUID category, but this
|
|
// 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 std::string& pname)
|
|
{
|
|
LLUUID id;
|
|
if(!isInventoryUsable())
|
|
{
|
|
llwarns << "Inventory is broken." << llendl;
|
|
return id;
|
|
}
|
|
|
|
if(preferred_type == LLAssetType::AT_SIMSTATE)
|
|
{
|
|
lldebugs << "Attempt to create simstate category." << llendl;
|
|
return id;
|
|
}
|
|
else if(preferred_type == LLAssetType::AT_SOUND_WAV)
|
|
{
|
|
lldebugs << "Attempt to create (wave) uncompressed sound category." << llendl;
|
|
return id;
|
|
}
|
|
else if(preferred_type == LLAssetType::AT_IMAGE_TGA)
|
|
{
|
|
lldebugs << "Attempt to create a AT_IMAGE_TGA uncompresssed images category." << llendl;
|
|
return id;
|
|
}
|
|
else if(preferred_type == LLAssetType::AT_TEXTURE_TGA)
|
|
{
|
|
lldebugs << "Attempt to create a AT_TEXTURE_TGA uncompresssed images category." << llendl;
|
|
return id;
|
|
}
|
|
|
|
else if(preferred_type == LLAssetType::AT_IMAGE_JPEG)
|
|
{
|
|
lldebugs << "Attempt to create a AT_IMAGE_JPEG uncompresssed images category." << llendl;
|
|
return id;
|
|
}else if(preferred_type == LLAssetType::AT_SCRIPT)
|
|
{
|
|
lldebugs << "Attempt to create a AT_Script scripts category." << llendl;
|
|
return id;
|
|
}else if(preferred_type == LLAssetType::AT_LSL_BYTECODE)
|
|
{
|
|
lldebugs << "Attempt to create a AT_LSL_BYTECODE scripts category." << llendl;
|
|
return id;
|
|
}
|
|
|
|
id.generate();
|
|
std::string name = pname;
|
|
if(!pname.empty())
|
|
{
|
|
name.assign(pname);
|
|
}
|
|
else if((preferred_type >= LLAssetType::AT_TEXTURE) &&
|
|
(preferred_type <= LLAssetType::AT_MY_OUTFITS))
|
|
{
|
|
name.assign(NEW_CATEGORY_NAMES[preferred_type]);
|
|
}
|
|
else
|
|
{
|
|
name.assign(NEW_CATEGORY_NAME);
|
|
}
|
|
|
|
// Add the category to the internal representation
|
|
LLPointer<LLViewerInventoryCategory> cat =
|
|
new LLViewerInventoryCategory(id, parent_id, preferred_type, name, gAgent.getID());
|
|
cat->setVersion(LLViewerInventoryCategory::VERSION_INITIAL);
|
|
cat->setDescendentCount(0);
|
|
LLCategoryUpdate update(cat->getParentUUID(), 1);
|
|
accountForUpdate(update);
|
|
updateCategory(cat);
|
|
|
|
// Create the category on the server. We do this to prevent people
|
|
// from munging their protected folders.
|
|
LLMessageSystem* msg = gMessageSystem;
|
|
msg->newMessage("CreateInventoryFolder");
|
|
msg->nextBlock("AgentData");
|
|
msg->addUUID("AgentID", gAgent.getID());
|
|
msg->addUUID(_PREHASH_SessionID, gAgent.getSessionID());
|
|
msg->nextBlock("FolderData");
|
|
cat->packMessage(msg);
|
|
gAgent.sendReliableMessage();
|
|
|
|
// return the folder id of the newly created folder
|
|
return id;
|
|
}
|
|
|
|
// Starting with the object specified, add it's descendents to the
|
|
// array provided, but do not add the inventory object specified by
|
|
// id. There is no guaranteed order. Neither array will be erased
|
|
// before adding objects to it. Do not store a copy of the pointers
|
|
// collected - use them, and collect them again later if you need to
|
|
// reference the same objects.
|
|
|
|
class LLAlwaysCollect : public LLInventoryCollectFunctor
|
|
{
|
|
public:
|
|
virtual ~LLAlwaysCollect() {}
|
|
virtual bool operator()(LLInventoryCategory* cat,
|
|
LLInventoryItem* item)
|
|
{
|
|
return TRUE;
|
|
}
|
|
};
|
|
|
|
void LLInventoryModel::collectDescendents(const LLUUID& id,
|
|
cat_array_t& cats,
|
|
item_array_t& items,
|
|
BOOL include_trash)
|
|
{
|
|
LLAlwaysCollect always;
|
|
collectDescendentsIf(id, cats, items, include_trash, always);
|
|
}
|
|
|
|
void LLInventoryModel::collectDescendentsIf(const LLUUID& id,
|
|
cat_array_t& cats,
|
|
item_array_t& items,
|
|
BOOL include_trash,
|
|
LLInventoryCollectFunctor& add,
|
|
BOOL follow_folder_links)
|
|
{
|
|
// Start with categories
|
|
if(!include_trash)
|
|
{
|
|
const LLUUID trash_id = findCategoryUUIDForType(LLAssetType::AT_TRASH);
|
|
if(trash_id.notNull() && (trash_id == id))
|
|
return;
|
|
}
|
|
cat_array_t* cat_array = get_ptr_in_map(mParentChildCategoryTree, id);
|
|
if(cat_array)
|
|
{
|
|
S32 count = cat_array->count();
|
|
for(S32 i = 0; i < count; ++i)
|
|
{
|
|
LLViewerInventoryCategory* cat = cat_array->get(i);
|
|
if(add(cat,NULL))
|
|
{
|
|
cats.put(cat);
|
|
}
|
|
collectDescendentsIf(cat->getUUID(), cats, items, include_trash, add);
|
|
}
|
|
}
|
|
|
|
LLViewerInventoryItem* item = NULL;
|
|
item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, id);
|
|
|
|
// Move onto items
|
|
if(item_array)
|
|
{
|
|
S32 count = item_array->count();
|
|
for(S32 i = 0; i < count; ++i)
|
|
{
|
|
item = item_array->get(i);
|
|
if(add(NULL, item))
|
|
{
|
|
items.put(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
// [RLVa:KB] - Checked: 2010-09-30 (RLVa-1.2.1d) | Added: RLVa-1.2.1d
|
|
// The problem is that we want some way for the functor to know that it's being asked to decide on a folder link
|
|
// but it won't know that until after it has encountered the folder link item (which doesn't happen until *after*
|
|
// it has already collected all items from it the way the code was originally laid out)
|
|
// This breaks the "finish collecting all folders before collecting items (top to bottom and then bottom to top)"
|
|
// assumption but no functor is (currently) relying on it (and likely never should since it's an implementation detail?)
|
|
// [Only LLAppearanceMgr actually ever passes in 'follow_folder_links == TRUE']
|
|
// [/RLVa:KB]
|
|
// 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() != LLAssetType::AT_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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLInventoryModel::addChangedMaskForLinks(const LLUUID& object_id, U32 mask)
|
|
{
|
|
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(gAgent.getInventoryRootID(),
|
|
cat_array,
|
|
item_array,
|
|
LLInventoryModel::INCLUDE_TRASH,
|
|
is_linked_item_match);
|
|
if (cat_array.empty() && item_array.empty())
|
|
{
|
|
return;
|
|
}
|
|
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());
|
|
};
|
|
}
|
|
|
|
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 ? gAgent.getInventoryRootID() : start_folder_id),
|
|
cat_array,
|
|
items,
|
|
LLInventoryModel::INCLUDE_TRASH,
|
|
is_linked_item_match);
|
|
return items;
|
|
}
|
|
|
|
// Generates a string containing the path to the item specified by
|
|
// item_id.
|
|
void LLInventoryModel::appendPath(const LLUUID& id, std::string& path)
|
|
{
|
|
std::string temp;
|
|
LLInventoryObject* obj = getObject(id);
|
|
LLUUID parent_id;
|
|
if(obj) parent_id = obj->getParentUUID();
|
|
std::string forward_slash("/");
|
|
while(obj)
|
|
{
|
|
obj = getCategory(parent_id);
|
|
if(obj)
|
|
{
|
|
temp.assign(forward_slash + obj->getName() + temp);
|
|
parent_id = obj->getParentUUID();
|
|
}
|
|
}
|
|
path.append(temp);
|
|
}
|
|
|
|
bool LLInventoryModel::isInventoryUsable() const
|
|
{
|
|
bool result = false;
|
|
if(gAgent.getInventoryRootID().notNull() && mIsAgentInvUsable)
|
|
{
|
|
result = true;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Calling this method with an inventory item will either change an
|
|
// existing item with a matching item_id, or will add the item to the
|
|
// current inventory.
|
|
U32 LLInventoryModel::updateItem(const LLViewerInventoryItem* item)
|
|
{
|
|
U32 mask = LLInventoryObserver::NONE;
|
|
if(item->getUUID().isNull())
|
|
{
|
|
return mask;
|
|
}
|
|
|
|
if(!isInventoryUsable())
|
|
{
|
|
llwarns << "Inventory is broken." << llendl;
|
|
return mask;
|
|
}
|
|
|
|
LLViewerInventoryItem* old_item = getItem(item->getUUID());
|
|
if(old_item)
|
|
{
|
|
// We already have an old item, modify it's values
|
|
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
|
|
item_array_t* item_array;
|
|
item_array = get_ptr_in_map(mParentChildItemTree, old_parent_id);
|
|
if(item_array)
|
|
{
|
|
item_array->removeObj(old_item);
|
|
}
|
|
item_array = get_ptr_in_map(mParentChildItemTree, new_parent_id);
|
|
if(item_array)
|
|
{
|
|
item_array->put(old_item);
|
|
}
|
|
mask |= LLInventoryObserver::STRUCTURE;
|
|
}
|
|
if(old_item->getName() != item->getName())
|
|
{
|
|
mask |= LLInventoryObserver::LABEL;
|
|
}
|
|
old_item->copyViewerItem(item);
|
|
mask |= LLInventoryObserver::INTERNAL;
|
|
}
|
|
else
|
|
{
|
|
// Simply add this item
|
|
LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item);
|
|
addItem(new_item);
|
|
|
|
if(item->getParentUUID().isNull())
|
|
{
|
|
LLUUID category_id = findCategoryUUIDForType(new_item->getType());
|
|
new_item->setParent(category_id);
|
|
item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, category_id);
|
|
if( item_array )
|
|
{
|
|
// *FIX: bit of a hack to call update server from here...
|
|
new_item->updateServer(TRUE);
|
|
item_array->put(new_item);
|
|
}
|
|
else
|
|
{
|
|
llwarns << "Couldn't find parent-child item tree for " << new_item->getName() << llendl;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// *NOTE: The general scheme is that if every byte of the
|
|
// uuid is 0, except for the last one or two,the use the
|
|
// last two bytes of the parent id, and match that up
|
|
// against the type. For now, we're only worried about
|
|
// lost & found.
|
|
LLUUID parent_id = item->getParentUUID();
|
|
if(parent_id == CATEGORIZE_LOST_AND_FOUND_ID)
|
|
{
|
|
parent_id = findCategoryUUIDForType(LLAssetType::AT_LOST_AND_FOUND);
|
|
new_item->setParent(parent_id);
|
|
}
|
|
item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, parent_id);
|
|
if(item_array)
|
|
{
|
|
item_array->put(new_item);
|
|
}
|
|
else
|
|
{
|
|
// Whoops! No such parent, make one.
|
|
llinfos << "Lost item: " << new_item->getUUID() << " - "
|
|
<< new_item->getName() << llendl;
|
|
parent_id = findCategoryUUIDForType(LLAssetType::AT_LOST_AND_FOUND);
|
|
new_item->setParent(parent_id);
|
|
item_array = get_ptr_in_map(mParentChildItemTree, parent_id);
|
|
if(item_array)
|
|
{
|
|
// *FIX: bit of a hack to call update server from
|
|
// here...
|
|
new_item->updateServer(TRUE);
|
|
item_array->put(new_item);
|
|
}
|
|
else
|
|
{
|
|
llwarns << "Lost and found Not there!!" << llendl;
|
|
}
|
|
}
|
|
}
|
|
mask |= LLInventoryObserver::ADD;
|
|
}
|
|
if(item->getType() == LLAssetType::AT_CALLINGCARD)
|
|
{
|
|
mask |= LLInventoryObserver::CALLING_CARD;
|
|
}
|
|
addChangedMask(mask, 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)
|
|
{
|
|
if(cat->getUUID().isNull())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(!isInventoryUsable())
|
|
{
|
|
llwarns << "Inventory is broken." << llendl;
|
|
return;
|
|
}
|
|
|
|
LLViewerInventoryCategory* old_cat = getCategory(cat->getUUID());
|
|
if(old_cat)
|
|
{
|
|
// We already have an old category, modify it's values
|
|
U32 mask = LLInventoryObserver::NONE;
|
|
LLUUID old_parent_id = old_cat->getParentUUID();
|
|
LLUUID new_parent_id = cat->getParentUUID();
|
|
if(old_parent_id != new_parent_id)
|
|
{
|
|
// need to update the parent-child tree
|
|
cat_array_t* cat_array;
|
|
cat_array = getUnlockedCatArray(old_parent_id);
|
|
if(cat_array)
|
|
{
|
|
cat_array->removeObj(old_cat);
|
|
}
|
|
cat_array = getUnlockedCatArray(new_parent_id);
|
|
if(cat_array)
|
|
{
|
|
cat_array->put(old_cat);
|
|
}
|
|
mask |= LLInventoryObserver::STRUCTURE;
|
|
}
|
|
if(old_cat->getName() != cat->getName())
|
|
{
|
|
mask |= LLInventoryObserver::LABEL;
|
|
}
|
|
old_cat->copyViewerCategory(cat);
|
|
addChangedMask(mask, cat->getUUID());
|
|
}
|
|
else
|
|
{
|
|
// add this category
|
|
LLPointer<LLViewerInventoryCategory> new_cat = new LLViewerInventoryCategory(cat->getParentUUID());
|
|
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 = 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;
|
|
mParentChildItemTree[new_cat->getUUID()] = itemsp;
|
|
addChangedMask(LLInventoryObserver::ADD, cat->getUUID());
|
|
}
|
|
}
|
|
|
|
void LLInventoryModel::moveObject(const LLUUID& object_id, const LLUUID& cat_id)
|
|
{
|
|
lldebugs << "LLInventoryModel::moveObject()" << llendl;
|
|
if(!isInventoryUsable())
|
|
{
|
|
llwarns << "Inventory is broken." << llendl;
|
|
return;
|
|
}
|
|
|
|
if((object_id == cat_id) || !is_in_map(mCategoryMap, cat_id))
|
|
{
|
|
llwarns << "Could not move inventory object " << object_id << " to "
|
|
<< cat_id << llendl;
|
|
return;
|
|
}
|
|
LLViewerInventoryCategory* cat = getCategory(object_id);
|
|
if(cat && (cat->getParentUUID() != cat_id))
|
|
{
|
|
cat_array_t* cat_array;
|
|
cat_array = getUnlockedCatArray(cat->getParentUUID());
|
|
if(cat_array) cat_array->removeObj(cat);
|
|
cat_array = getUnlockedCatArray(cat_id);
|
|
cat->setParent(cat_id);
|
|
if(cat_array) cat_array->put(cat);
|
|
addChangedMask(LLInventoryObserver::STRUCTURE, object_id);
|
|
return;
|
|
}
|
|
LLViewerInventoryItem* item = getItem(object_id);
|
|
if(item && (item->getParentUUID() != cat_id))
|
|
{
|
|
item_array_t* item_array;
|
|
item_array = getUnlockedItemArray(item->getParentUUID());
|
|
if(item_array) item_array->removeObj(item);
|
|
item_array = getUnlockedItemArray(cat_id);
|
|
item->setParent(cat_id);
|
|
if(item_array) item_array->put(item);
|
|
addChangedMask(LLInventoryObserver::STRUCTURE, object_id);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Delete a particular inventory object by ID.
|
|
void LLInventoryModel::deleteObject(const LLUUID& id)
|
|
{
|
|
lldebugs << "LLInventoryModel::deleteObject()" << 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 = 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
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
obj->removeFromServer();
|
|
LLPreview::hide(id);
|
|
deleteObject(id);
|
|
}
|
|
}
|
|
|
|
// This is a method which collects the descendents of the id
|
|
// provided. If the category is not found, no action is
|
|
// taken. This method goes through the long winded process of
|
|
// cancelling any calling cards, removing server representation of
|
|
// folders, items, etc in a fairly efficient manner.
|
|
void LLInventoryModel::purgeDescendentsOf(const LLUUID& id)
|
|
{
|
|
EHasChildren children = categoryHasChildren(id);
|
|
if(children == CHILDREN_NO)
|
|
{
|
|
llinfos << "Not purging descendents of " << id << llendl;
|
|
return;
|
|
}
|
|
LLPointer<LLViewerInventoryCategory> cat = getCategory(id);
|
|
if(cat.notNull())
|
|
{
|
|
// do the cache accounting
|
|
llinfos << "LLInventoryModel::purgeDescendentsOf " << cat->getName()
|
|
<< llendl;
|
|
S32 descendents = cat->getDescendentCount();
|
|
if(descendents > 0)
|
|
{
|
|
LLCategoryUpdate up(id, -descendents);
|
|
accountForUpdate(up);
|
|
}
|
|
|
|
// we know that descendent count is 0, aide since the
|
|
// accounting may actually not do an update, we should force
|
|
// it here.
|
|
cat->setDescendentCount(0);
|
|
|
|
// send it upstream
|
|
LLMessageSystem* msg = gMessageSystem;
|
|
msg->newMessage("PurgeInventoryDescendents");
|
|
msg->nextBlock("AgentData");
|
|
msg->addUUID("AgentID", gAgent.getID());
|
|
msg->addUUID("SessionID", gAgent.getSessionID());
|
|
msg->nextBlock("InventoryData");
|
|
msg->addUUID("FolderID", id);
|
|
gAgent.sendReliableMessage();
|
|
|
|
// unceremoniously remove anything we have locally stored.
|
|
cat_array_t categories;
|
|
item_array_t items;
|
|
collectDescendents(id,
|
|
categories,
|
|
items,
|
|
INCLUDE_TRASH);
|
|
S32 count = items.count();
|
|
S32 i;
|
|
for(i = 0; i < count; ++i)
|
|
{
|
|
deleteObject(items.get(i)->getUUID());
|
|
}
|
|
count = categories.count();
|
|
for(i = 0; i < count; ++i)
|
|
{
|
|
deleteObject(categories.get(i)->getUUID());
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
mObservers.insert(observer);
|
|
}
|
|
|
|
void LLInventoryModel::removeObserver(LLInventoryObserver* observer)
|
|
{
|
|
mObservers.erase(observer);
|
|
}
|
|
|
|
BOOL LLInventoryModel::containsObserver(LLInventoryObserver* observer) const
|
|
{
|
|
return mObservers.find(observer) != mObservers.end();
|
|
}
|
|
|
|
// Call this method when it's time to update everyone on a new state,
|
|
// by default, the inventory model will not update observers
|
|
// automatically.
|
|
// The optional argument 'service_name' is used by Agent Inventory Service [DEV-20328]
|
|
void LLInventoryModel::notifyObservers(const std::string service_name)
|
|
{
|
|
for (observer_list_t::iterator iter = mObservers.begin();
|
|
iter != mObservers.end(); )
|
|
{
|
|
LLInventoryObserver* observer = *iter;
|
|
|
|
if (service_name.empty())
|
|
{
|
|
observer->changed(mModifyMask);
|
|
}
|
|
else
|
|
{
|
|
observer->mMessageName = service_name;
|
|
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);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
addChangedMaskForLinks(referent, LLInventoryObserver::LABEL);
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
llinfos << "LLInventoryModel::mock() " << root_id << llendl;
|
|
if(root_id.isNull())
|
|
{
|
|
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();
|
|
}
|
|
*/
|
|
|
|
//If we get back a normal response, handle it here
|
|
// Note: this is the responder used in "fetchInventory" cap,
|
|
// this is not responder for "WebFetchInventoryDescendents" or "agent/inventory" cap
|
|
|
|
void LLInventoryModel::fetchInventoryResponder::result(const LLSD& content)
|
|
{
|
|
LL_DEBUGS("Inventory") << " fetch http got " << ll_pretty_print_sd(content) << LL_ENDL; // OGPX
|
|
start_new_inventory_observer();
|
|
|
|
/*LLUUID agent_id;
|
|
agent_id = content["agent_id"].asUUID();
|
|
if(agent_id != gAgent.getID())
|
|
{
|
|
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)
|
|
{
|
|
LLPointer<LLViewerInventoryItem> titem = new LLViewerInventoryItem;
|
|
titem->unpackMessage(content["items"][i]);
|
|
|
|
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(titem->getParentUUID() == itemp->getParentUUID())
|
|
{
|
|
update[titem->getParentUUID()];
|
|
}
|
|
else
|
|
{
|
|
++update[titem->getParentUUID()];
|
|
--update[itemp->getParentUUID()];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++update[titem->getParentUUID()];
|
|
}
|
|
if (folder_id.isNull())
|
|
{
|
|
folder_id = titem->getParentUUID();
|
|
}
|
|
else
|
|
{
|
|
all_one_folder = false;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
changes |= gInventory.updateItem(*it);
|
|
}
|
|
gInventory.notifyObservers("fetchinventory");
|
|
gViewerWindow->getWindow()->decBusyCount();
|
|
}
|
|
|
|
//If we get back an error (not found, etc...), handle it here
|
|
void LLInventoryModel::fetchInventoryResponder::error(U32 status, const std::string& reason)
|
|
{
|
|
LL_INFOS("Inventory") << "fetchInventory::error "
|
|
<< status << ": " << reason << LL_ENDL;
|
|
gInventory.notifyObservers("fetchinventory");
|
|
}
|
|
|
|
bool LLInventoryModel::fetchDescendentsOf(const LLUUID& folder_id) const
|
|
{
|
|
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->fetchDescendents();
|
|
}
|
|
|
|
//Initialize statics.
|
|
bool LLInventoryModel::isBulkFetchProcessingComplete()
|
|
{
|
|
return ( (sFetchQueue.empty()
|
|
&& sBulkFetchCount<=0) ? TRUE : FALSE ) ;
|
|
}
|
|
|
|
class fetchDescendentsResponder: public LLHTTPClient::Responder
|
|
{
|
|
public:
|
|
fetchDescendentsResponder(const LLSD& request_sd) : mRequestSD(request_sd) {};
|
|
//fetchDescendentsResponder() {};
|
|
void result(const LLSD& content);
|
|
void error(U32 status, const std::string& reason);
|
|
public:
|
|
typedef std::vector<LLViewerInventoryCategory*> folder_ref_t;
|
|
protected:
|
|
LLSD mRequestSD;
|
|
};
|
|
|
|
//If we get back a normal response, handle it here
|
|
// Note: this is the handler for WebFetchInventoryDescendents and agent/inventory caps
|
|
void fetchDescendentsResponder::result(const LLSD& content)
|
|
{
|
|
LL_DEBUGS("Inventory") << " fetch descendents got " << ll_pretty_print_sd(content) << LL_ENDL; // OGPX
|
|
if (content.has("folders"))
|
|
{
|
|
|
|
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);
|
|
|
|
if (parent_id.isNull())
|
|
{
|
|
LLPointer<LLViewerInventoryItem> titem = new LLViewerInventoryItem;
|
|
for(LLSD::array_const_iterator item_it = folder_sd["items"].beginArray();
|
|
item_it != folder_sd["items"].endArray();
|
|
++item_it)
|
|
{
|
|
LLUUID lost_uuid = gInventory.findCategoryUUIDForType(LLAssetType::AT_LOST_AND_FOUND);
|
|
if (lost_uuid.notNull())
|
|
{
|
|
LLSD item = *item_it;
|
|
titem->unpackMessage(item);
|
|
|
|
LLInventoryModel::update_list_t update;
|
|
LLInventoryModel::LLCategoryUpdate new_folder(lost_uuid, 1);
|
|
update.push_back(new_folder);
|
|
gInventory.accountForUpdate(update);
|
|
|
|
titem->setParent(lost_uuid);
|
|
titem->updateParentOnServer(FALSE);
|
|
gInventory.updateItem(titem);
|
|
gInventory.notifyObservers("fetchDescendents");
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
LLViewerInventoryCategory* pcat = gInventory.getCategory(parent_id);
|
|
if (!pcat)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
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 (LLInventoryModel::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);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
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.
|
|
LL_INFOS("Inventory") << "Folder " << folder_sd["folder_id"].asString()
|
|
<< "Error: " << folder_sd["error"].asString() << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
LLInventoryModel::incrBulkFetch(-1);
|
|
|
|
if (LLInventoryModel::isBulkFetchProcessingComplete())
|
|
{
|
|
LL_DEBUGS("Inventory") << "Inventory fetch completed" << LL_ENDL;
|
|
if (LLInventoryModel::sFullFetchStarted)
|
|
{
|
|
LLInventoryModel::sAllFoldersFetched = TRUE;
|
|
}
|
|
LLInventoryModel::stopBackgroundFetch();
|
|
}
|
|
|
|
gInventory.notifyObservers("fetchDescendents");
|
|
}
|
|
|
|
//If we get back an error (not found, etc...), handle it here
|
|
void fetchDescendentsResponder::error(U32 status, const std::string& reason)
|
|
{
|
|
LL_INFOS("Inventory") << "fetchDescendentsResponder::error "
|
|
<< status << ": " << reason << LL_ENDL;
|
|
|
|
LLInventoryModel::incrBulkFetch(-1);
|
|
|
|
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;
|
|
LLUUID folder_id = folder_sd["folder_id"];
|
|
sFetchQueue.push_front(folder_id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (LLInventoryModel::isBulkFetchProcessingComplete())
|
|
{
|
|
if (LLInventoryModel::sFullFetchStarted)
|
|
{
|
|
LLInventoryModel::sAllFoldersFetched = TRUE;
|
|
}
|
|
LLInventoryModel::stopBackgroundFetch();
|
|
}
|
|
}
|
|
gInventory.notifyObservers("fetchDescendents");
|
|
}
|
|
|
|
//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.
|
|
|
|
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)
|
|
{
|
|
return; // just bail if we are disconnected.
|
|
}
|
|
|
|
U32 folder_count=0;
|
|
U32 max_batch_size=5;
|
|
|
|
U32 sort_order = gSavedSettings.getU32("InventorySortOrder") & 0x1;
|
|
|
|
LLSD body;
|
|
LLSD body_lib;
|
|
while( !(sFetchQueue.empty() ) && (folder_count < max_batch_size) )
|
|
{
|
|
if (sFetchQueue.front().isNull()) //DEV-17797
|
|
{
|
|
LLSD folder_sd;
|
|
folder_sd["folder_id"] = LLUUID::null.asString();
|
|
folder_sd["owner_id"] = gAgent.getID();
|
|
folder_sd["sort_order"] = (LLSD::Integer)sort_order;
|
|
folder_sd["fetch_folders"] = (LLSD::Boolean)FALSE;
|
|
folder_sd["fetch_items"] = (LLSD::Boolean)TRUE;
|
|
body["folders"].append(folder_sd);
|
|
folder_count++;
|
|
}
|
|
else
|
|
{
|
|
|
|
|
|
LLViewerInventoryCategory* cat = gInventory.getCategory(sFetchQueue.front());
|
|
|
|
if (cat)
|
|
{
|
|
// <edit> Pre-emptive strike
|
|
//if(!(gInventory.isObjectDescendentOf(cat->getUUID(), gSystemFolderRoot)))
|
|
if(true)
|
|
{
|
|
// </edit>
|
|
if ( LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion())
|
|
{
|
|
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"] = TRUE; //(LLSD::Boolean)sFullFetchStarted;
|
|
folder_sd["fetch_items"] = (LLSD::Boolean)TRUE;
|
|
|
|
LL_DEBUGS("Inventory") << " fetching "<<cat->getUUID()<<" with cat owner "<<cat->getOwnerID()<<" and agent" << gAgent.getID() << LL_ENDL;
|
|
//OGPX if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID())
|
|
// for OGP it really doesnt make sense to have the decision about whether to fetch
|
|
// from the library or user cap be determined by a hard coded UUID.
|
|
// if it isnt an item that belongs to the agent, then fetch from the library
|
|
if (gAgent.getID() != cat->getOwnerID()) //if i am not the owner, it must be in the library
|
|
body_lib["folders"].append(folder_sd);
|
|
else
|
|
body["folders"].append(folder_sd);
|
|
folder_count++;
|
|
}
|
|
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());
|
|
}
|
|
}
|
|
|
|
}
|
|
// <edit>
|
|
}
|
|
// </edit>
|
|
}
|
|
}
|
|
sFetchQueue.pop_front();
|
|
}
|
|
|
|
if (folder_count > 0)
|
|
{
|
|
sBulkFetchCount++;
|
|
if (body["folders"].size())
|
|
{
|
|
LL_DEBUGS("Inventory") << " fetch descendents post to " << url << ": " << ll_pretty_print_sd(body) << LL_ENDL; // OGPX
|
|
LLHTTPClient::post(url, body, new fetchDescendentsResponder(body),300.0);
|
|
}
|
|
if (body_lib["folders"].size())
|
|
{
|
|
std::string url_lib;
|
|
url_lib = gAgent.getRegion()->getCapability("FetchLibDescendents");
|
|
LL_DEBUGS("Inventory") << " fetch descendents lib post: " << ll_pretty_print_sd(body_lib) << LL_ENDL; // OGPX
|
|
LLHTTPClient::post(url_lib, body_lib, new fetchDescendentsResponder(body_lib),300.0);
|
|
}
|
|
sFetchTimer.reset();
|
|
}
|
|
else if (isBulkFetchProcessingComplete())
|
|
{
|
|
if (sFullFetchStarted)
|
|
{
|
|
sAllFoldersFetched = TRUE;
|
|
}
|
|
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)
|
|
{
|
|
if (cat_id.isNull())
|
|
{
|
|
if (!sFullFetchStarted)
|
|
{
|
|
sFullFetchStarted = TRUE;
|
|
sFetchQueue.push_back(gInventoryLibraryRoot);
|
|
sFetchQueue.push_back(gAgent.getInventoryRootID());
|
|
if (!sBackgroundFetchActive)
|
|
{
|
|
sBackgroundFetchActive = TRUE;
|
|
gIdleCallbacks.addFunction(&LLInventoryModel::backgroundFetch, NULL);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// specific folder requests go to front of queue
|
|
// Remove it from the queue first, to avoid getting it twice.
|
|
if (!sFetchQueue.empty() && sFetchQueue.front() != cat_id)
|
|
{
|
|
std::deque<LLUUID>::iterator old_entry = std::find(sFetchQueue.begin(), sFetchQueue.end(), cat_id);
|
|
if (old_entry != sFetchQueue.end())
|
|
{
|
|
sFetchQueue.erase(old_entry);
|
|
}
|
|
}
|
|
sFetchQueue.push_front(cat_id);
|
|
if (!sBackgroundFetchActive)
|
|
{
|
|
sBackgroundFetchActive = TRUE;
|
|
gIdleCallbacks.addFunction(&LLInventoryModel::backgroundFetch, NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//static
|
|
void LLInventoryModel::findLostItems()
|
|
{
|
|
sFetchQueue.push_back(LLUUID::null);
|
|
if (!sBackgroundFetchActive)
|
|
{
|
|
sBackgroundFetchActive = TRUE;
|
|
gIdleCallbacks.addFunction(&LLInventoryModel::backgroundFetch, NULL);
|
|
}
|
|
}
|
|
|
|
//static
|
|
void LLInventoryModel::stopBackgroundFetch()
|
|
{
|
|
if (sBackgroundFetchActive)
|
|
{
|
|
sBackgroundFetchActive = FALSE;
|
|
gIdleCallbacks.deleteFunction(&LLInventoryModel::backgroundFetch, NULL);
|
|
sBulkFetchCount=0;
|
|
sMinTimeBetweenFetches=0.0f;
|
|
if (!sAllFoldersFetched)
|
|
{
|
|
// We didn't finish this, so set it to FALSE in order to be able to start it again.
|
|
sFullFetchStarted=FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
//static
|
|
void LLInventoryModel::backgroundFetch(void*)
|
|
{
|
|
if (sBackgroundFetchActive && gAgent.getRegion())
|
|
{
|
|
// OGPX : agent/inventory is on gAgent for OGPX. I check both, and give
|
|
// ...preference to the one on gAgent. That way the existing non OGP path will remain the same.
|
|
// Q: How *should* I handle deciding which cap to use? Maybe I don't want to trust fetching inventory from the region
|
|
// OGPX TODO: manage getting caps from more than one service securely.
|
|
std::string url = gAgent.getCapability("agent/inventory"); // was WebFetchInventoryDescendents
|
|
if (url.empty())
|
|
{
|
|
// OGPX : if we didn't get an agent/inventory cap from the Agent Domain, check to see if there was one on the region
|
|
// If we'll be using the capability, we'll be sending batches and the background thing isn't as important.
|
|
// OGPX TODO: this should change when Capabilities are refactored.
|
|
// ... this is a trust/security issue. if we have an agent/inventory from the Agent Domain,
|
|
// maybe we shouldn't trust WFID from region.
|
|
LL_DEBUGS("Inventory") << " no agent/inventory not on AD, checking fallback to region " << LL_ENDL;
|
|
url = gAgent.getRegion()->getCapability("WebFetchInventoryDescendents");
|
|
}
|
|
if (!url.empty())
|
|
{
|
|
bulkFetch(url);
|
|
return;
|
|
}
|
|
|
|
// If there was no HTTP cap to fetch with, then do the UDP fetch
|
|
//DEPRECATED OLD CODE FOLLOWS.
|
|
// no more categories to fetch, stop fetch process
|
|
if (sFetchQueue.empty())
|
|
{
|
|
LL_DEBUGS("Inventory") << "Inventory fetch completed" << LL_ENDL;
|
|
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);
|
|
LL_DEBUGS("Inventory") << "Inventory fetch times grown to (" << sMinTimeBetweenFetches << ", " << sMaxTimeBetweenFetches << ")" << LL_ENDL;
|
|
// 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;
|
|
}
|
|
|
|
// not enough time has elapsed to do a new fetch
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLInventoryModel::cache(
|
|
const LLUUID& parent_folder_id,
|
|
const LLUUID& agent_id)
|
|
{
|
|
lldebugs << "Caching " << parent_folder_id << " for " << agent_id
|
|
<< llendl;
|
|
LLViewerInventoryCategory* root_cat = getCategory(parent_folder_id);
|
|
if(!root_cat) return;
|
|
cat_array_t categories;
|
|
categories.put(root_cat);
|
|
item_array_t items;
|
|
|
|
LLCanCache can_cache(this);
|
|
can_cache(root_cat, NULL);
|
|
collectDescendentsIf(
|
|
parent_folder_id,
|
|
categories,
|
|
items,
|
|
INCLUDE_TRASH,
|
|
can_cache);
|
|
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));
|
|
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))
|
|
{
|
|
lldebugs << "Successfully compressed " << inventory_filename << llendl;
|
|
LLFile::remove(inventory_filename);
|
|
}
|
|
else
|
|
{
|
|
llwarns << "Unable to compress " << inventory_filename << llendl;
|
|
}
|
|
}
|
|
|
|
|
|
void LLInventoryModel::addCategory(LLViewerInventoryCategory* category)
|
|
{
|
|
//llinfos << "LLInventoryModel::addCategory()" << llendl;
|
|
if(category)
|
|
{
|
|
// Insert category uniquely into the map
|
|
mCategoryMap[category->getUUID()] = category; // LLPointer will deref and delete the old one
|
|
//mInventory[category->getUUID()] = category;
|
|
}
|
|
}
|
|
|
|
void LLInventoryModel::addItem(LLViewerInventoryItem* item)
|
|
{
|
|
//llinfos << "LLInventoryModel::addItem()" << llendl;
|
|
if(item)
|
|
{
|
|
mItemMap[item->getUUID()] = item;
|
|
}
|
|
}
|
|
|
|
// Empty the entire contents
|
|
void LLInventoryModel::empty()
|
|
{
|
|
// llinfos << "LLInventoryModel::empty()" << llendl;
|
|
std::for_each(
|
|
mParentChildCategoryTree.begin(),
|
|
mParentChildCategoryTree.end(),
|
|
DeletePairedPointer());
|
|
mParentChildCategoryTree.clear();
|
|
std::for_each(
|
|
mParentChildItemTree.begin(),
|
|
mParentChildItemTree.end(),
|
|
DeletePairedPointer());
|
|
mParentChildItemTree.clear();
|
|
mCategoryMap.clear(); // remove all references (should delete entries)
|
|
mItemMap.clear(); // remove all references (should delete entries)
|
|
mLastItem = NULL;
|
|
//mInventory.clear();
|
|
}
|
|
|
|
void LLInventoryModel::accountForUpdate(const LLCategoryUpdate& update) const
|
|
{
|
|
LLViewerInventoryCategory* cat = getCategory(update.mCategoryID);
|
|
if(cat)
|
|
{
|
|
bool accounted = false;
|
|
S32 version = cat->getVersion();
|
|
if(version != LLViewerInventoryCategory::VERSION_UNKNOWN)
|
|
{
|
|
S32 descendents_server = cat->getDescendentCount();
|
|
LLInventoryModel::cat_array_t* cats;
|
|
LLInventoryModel::item_array_t* items;
|
|
getDirectDescendentsOf(update.mCategoryID, cats, items);
|
|
S32 descendents_actual = 0;
|
|
if(cats && items)
|
|
{
|
|
descendents_actual = cats->count() + items->count();
|
|
}
|
|
if(descendents_server == descendents_actual)
|
|
{
|
|
accounted = true;
|
|
descendents_actual += update.mDescendentDelta;
|
|
cat->setDescendentCount(descendents_actual);
|
|
cat->setVersion(++version);
|
|
llinfos << "accounted: '" << cat->getName() << "' "
|
|
<< version << " with " << descendents_actual
|
|
<< " descendents." << llendl;
|
|
}
|
|
}
|
|
if(!accounted)
|
|
{
|
|
lldebugs << "No accounting for: '" << cat->getName() << "' "
|
|
<< version << llendl;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
llwarns << "No category found for update " << update.mCategoryID
|
|
<< llendl;
|
|
}
|
|
}
|
|
|
|
void LLInventoryModel::accountForUpdate(
|
|
const LLInventoryModel::update_list_t& update)
|
|
{
|
|
update_list_t::const_iterator it = update.begin();
|
|
update_list_t::const_iterator end = update.end();
|
|
for(; it != end; ++it)
|
|
{
|
|
accountForUpdate(*it);
|
|
}
|
|
}
|
|
|
|
void LLInventoryModel::accountForUpdate(
|
|
const LLInventoryModel::update_map_t& update)
|
|
{
|
|
LLCategoryUpdate up;
|
|
update_map_t::const_iterator it = update.begin();
|
|
update_map_t::const_iterator end = update.end();
|
|
for(; it != end; ++it)
|
|
{
|
|
up.mCategoryID = (*it).first;
|
|
up.mDescendentDelta = (*it).second.mValue;
|
|
accountForUpdate(up);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
void LLInventoryModel::incrementCategoryVersion(const LLUUID& category_id)
|
|
{
|
|
LLViewerInventoryCategory* cat = getCategory(category_id);
|
|
if(cat)
|
|
{
|
|
S32 version = cat->getVersion();
|
|
if(LLViewerInventoryCategory::VERSION_UNKNOWN != version)
|
|
{
|
|
cat->setVersion(version + 1);
|
|
llinfos << "IncrementVersion: " << cat->getName() << " "
|
|
<< cat->getVersion() << llendl;
|
|
}
|
|
else
|
|
{
|
|
llinfos << "Attempt to increment version when unknown: "
|
|
<< category_id << llendl;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
llinfos << "Attempt to increment category: " << category_id << llendl;
|
|
}
|
|
}
|
|
void LLInventoryModel::incrementCategorySetVersion(
|
|
const std::set<LLUUID>& categories)
|
|
{
|
|
if(!categories.empty())
|
|
{
|
|
std::set<LLUUID>::const_iterator it = categories.begin();
|
|
std::set<LLUUID>::const_iterator end = categories.end();
|
|
for(; it != end; ++it)
|
|
{
|
|
incrementCategoryVersion(*it);
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
|
|
LLInventoryModel::EHasChildren LLInventoryModel::categoryHasChildren(
|
|
const LLUUID& cat_id) const
|
|
{
|
|
LLViewerInventoryCategory* cat = getCategory(cat_id);
|
|
if(!cat) return CHILDREN_NO;
|
|
if(cat->getDescendentCount() > 0)
|
|
{
|
|
return CHILDREN_YES;
|
|
}
|
|
if(cat->getDescendentCount() == 0)
|
|
{
|
|
return CHILDREN_NO;
|
|
}
|
|
if((cat->getDescendentCount() == LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN)
|
|
|| (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN))
|
|
{
|
|
return CHILDREN_MAYBE;
|
|
}
|
|
|
|
// Shouldn't have to run this, but who knows.
|
|
parent_cat_map_t::const_iterator cat_it = mParentChildCategoryTree.find(cat->getUUID());
|
|
if (cat_it != mParentChildCategoryTree.end() && cat_it->second->count() > 0)
|
|
{
|
|
return CHILDREN_YES;
|
|
}
|
|
parent_item_map_t::const_iterator item_it = mParentChildItemTree.find(cat->getUUID());
|
|
if (item_it != mParentChildItemTree.end() && item_it->second->count() > 0)
|
|
{
|
|
return CHILDREN_YES;
|
|
}
|
|
|
|
return CHILDREN_NO;
|
|
}
|
|
|
|
bool LLInventoryModel::isCategoryComplete(const LLUUID& cat_id) const
|
|
{
|
|
LLViewerInventoryCategory* cat = getCategory(cat_id);
|
|
if(cat && (cat->getVersion()!=LLViewerInventoryCategory::VERSION_UNKNOWN))
|
|
{
|
|
S32 descendents_server = cat->getDescendentCount();
|
|
LLInventoryModel::cat_array_t* cats;
|
|
LLInventoryModel::item_array_t* items;
|
|
getDirectDescendentsOf(cat_id, cats, items);
|
|
S32 descendents_actual = 0;
|
|
if(cats && items)
|
|
{
|
|
descendents_actual = cats->count() + items->count();
|
|
}
|
|
if(descendents_server == descendents_actual)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// <edit>
|
|
if((cat_id == gSystemFolderRoot) || gInventory.isObjectDescendentOf(cat_id, gSystemFolderRoot)) return true;
|
|
// </edit>
|
|
|
|
return false;
|
|
}
|
|
|
|
// OGPX : This loadSkeleton() takes LLSD formated data, other loadSkeleton is for XML-RPC data.
|
|
// There are major differences in what is returned from the XML-RPC authenticate call versus
|
|
// the style and sequence of OGP/llsd style authentication.
|
|
// In OGP, authentication returns a cap to the skeleton on agent domain. An HTTP GET then occurs on
|
|
// this "agent/inventory-skeleton" cap. This loadSkeleton()
|
|
// routine is called after that HTTP GET of "agent/inventory-skeleton" cap occurs.
|
|
bool LLInventoryModel::loadSkeleton(
|
|
const LLSD& options,
|
|
const LLUUID& owner_id)
|
|
{
|
|
LL_INFOS("OGPX") << "importing inventory skeleton for " << owner_id << LL_ENDL;
|
|
LL_DEBUGS("Inventory") << " skeleton is " << ll_pretty_print_sd(options) << LL_ENDL;
|
|
|
|
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 (LLSD::array_const_iterator it = options.beginArray(); it < options.endArray(); ++it)
|
|
{
|
|
LLPointer<LLViewerInventoryCategory> cat = new LLViewerInventoryCategory(owner_id);
|
|
|
|
LL_DEBUGS("Inventory") << "cat name, folder, parent, type " << (*it)["name"].asString() << " " << (*it)["folder_id"].asUUID() << " " << (*it)["parent_id"].asUUID() << " " << (*it)["type_default"].asString() << " " << LL_ENDL; // OGPX
|
|
if ((*it)["name"].asString().empty()) goto clean_cat;
|
|
cat->rename((*it)["name"].asString().c_str());
|
|
if ((*it)["folder_id"].asUUID().isNull()) goto clean_cat;
|
|
id = (*it)["folder_id"].asUUID();
|
|
// if an id is null, it locks the viewer.
|
|
if (id.isNull()) goto clean_cat;
|
|
cat->setUUID(id);
|
|
// OGPX : slight change in snowglobe non OGP handling of things with null parents vs OGP9 SVN branch
|
|
// OGPX : so commented this line out for OGPX as well. if((*it)["parent_id"].asUUID().isNull()) goto clean_cat;
|
|
id = (*it)["parent_id"].asUUID();
|
|
cat->setParent(id);
|
|
if ((*it)["type_default"].asString().empty())
|
|
{
|
|
preferred_type = LLAssetType::AT_NONE;
|
|
}
|
|
else
|
|
{
|
|
S32 t = (*it)["type_default"].asInteger();
|
|
preferred_type = (LLAssetType::EType)t;
|
|
}
|
|
cat->setPreferredType(preferred_type);
|
|
if ((*it)["version"].asString().empty()) goto clean_cat;
|
|
cat->setVersion((*it)["version"].asInteger());
|
|
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())
|
|
{
|
|
cat_array_t categories;
|
|
item_array_t items;
|
|
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));
|
|
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");
|
|
LLFILE* fp = LLFile::fopen(gzip_filename, "rb");
|
|
bool remove_inventory_file = false;
|
|
if (fp)
|
|
{
|
|
fclose(fp);
|
|
fp = NULL;
|
|
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
|
|
// gunziped it.
|
|
remove_inventory_file = true;
|
|
}
|
|
else
|
|
{
|
|
llinfos << "Unable to gunzip " << gzip_filename << llendl;
|
|
}
|
|
}
|
|
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
|
|
// will go through each category loaded and if the version
|
|
// does not match, invalidate the version.
|
|
S32 count = categories.count();
|
|
cat_set_t::iterator not_cached = temp_cats.end();
|
|
std::set<LLUUID> cached_ids;
|
|
for (S32 i = 0; i < count; ++i)
|
|
{
|
|
LLViewerInventoryCategory* cat = categories[i];
|
|
cat_set_t::iterator cit = temp_cats.find(cat);
|
|
if (cit == temp_cats.end())
|
|
{
|
|
continue; // cache corruption?? not sure why this happens -SJB
|
|
}
|
|
LLViewerInventoryCategory* tcat = *cit;
|
|
|
|
// we can safely ignore anything loaded from file, but
|
|
// not sent down in the skeleton.
|
|
if (cit == not_cached)
|
|
{
|
|
continue;
|
|
}
|
|
if (cat->getVersion() != tcat->getVersion())
|
|
{
|
|
// if the cached version does not match the server version,
|
|
// throw away the version we have so we can fetch the
|
|
// correct contents the next time the viewer opens the folder.
|
|
tcat->setVersion(NO_VERSION);
|
|
}
|
|
else
|
|
{
|
|
cached_ids.insert(tcat->getUUID());
|
|
}
|
|
}
|
|
|
|
// go ahead and add the cats returned during the download
|
|
std::set<LLUUID>::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)
|
|
{
|
|
if (cached_ids.find((*it)->getUUID()) == not_cached_id)
|
|
{
|
|
// this check is performed so that we do not
|
|
// mark new folders in the skeleton (and not in cache)
|
|
// as being cached.
|
|
LLViewerInventoryCategory *llvic = (*it);
|
|
llvic->setVersion(NO_VERSION);
|
|
}
|
|
addCategory(*it);
|
|
++child_counts[(*it)->getParentUUID()];
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
cat_map_t::iterator cit = mCategoryMap.find(items[i]->getParentUUID());
|
|
|
|
if (cit != unparented)
|
|
{
|
|
LLViewerInventoryCategory* cat = cit->second;
|
|
if (cat->getVersion() != NO_VERSION)
|
|
{
|
|
// This can happen if the linked object's baseobj is removed from the cache but the linked object is still in the cache.
|
|
if (items[i]->getIsBrokenLink())
|
|
{
|
|
bad_link_count++;
|
|
lldebugs << "Attempted to add cached link item without baseobj present ( name: "
|
|
<< items[i]->getName() << " itemID: " << items[i]->getUUID()
|
|
<< " assetID: " << items[i]->getAssetUUID()
|
|
<< " ). Ignoring and invalidating " << cat->getName() << " . " << llendl;
|
|
invalid_categories.insert(cit->second);
|
|
continue;
|
|
}
|
|
addItem(items[i]);
|
|
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
|
|
{
|
|
// go ahead and add everything after stripping the version
|
|
// information.
|
|
for (cat_set_t::iterator it = temp_cats.begin(); it != temp_cats.end(); ++it)
|
|
{
|
|
LLViewerInventoryCategory *llvic = (*it);
|
|
llvic->setVersion(NO_VERSION);
|
|
addCategory(*it);
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
for (cat_set_t::iterator it = temp_cats.begin(); it != temp_cats.end(); ++it)
|
|
{
|
|
LLViewerInventoryCategory* cat = (*it);
|
|
if (cat->getVersion() != NO_VERSION)
|
|
{
|
|
the_count = child_counts.find(cat->getUUID());
|
|
if (the_count != no_child_counts)
|
|
{
|
|
cat->setDescendentCount((*the_count).second.mValue);
|
|
}
|
|
else
|
|
{
|
|
cat->setDescendentCount(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (remove_inventory_file)
|
|
{
|
|
// 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
|
|
}
|
|
|
|
LL_DEBUGS("Inventory") << "Successfully loaded " << cached_category_count
|
|
<< " categories and " << cached_item_count << " items from cache."
|
|
<< LL_ENDL;
|
|
|
|
return rv;
|
|
}
|
|
|
|
bool LLInventoryModel::loadSkeleton(
|
|
const LLInventoryModel::options_t& 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(std::string((*skel).second));
|
|
skel = (*it).find("folder_id");
|
|
if(skel == no_response) goto clean_cat;
|
|
id.set((*skel).second);
|
|
// 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);
|
|
cat->setParent(id);
|
|
skel = (*it).find("type_default");
|
|
if(skel == no_response)
|
|
{
|
|
preferred_type = LLAssetType::AT_NONE;
|
|
}
|
|
else
|
|
{
|
|
S32 t = atoi((*skel).second.c_str());
|
|
preferred_type = (LLAssetType::EType)t;
|
|
}
|
|
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())
|
|
{
|
|
cat_array_t categories;
|
|
item_array_t items;
|
|
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));
|
|
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");
|
|
LLFILE* fp = LLFile::fopen(gzip_filename, "rb");
|
|
bool remove_inventory_file = false;
|
|
if(fp)
|
|
{
|
|
fclose(fp);
|
|
fp = NULL;
|
|
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
|
|
// gunziped it.
|
|
remove_inventory_file = true;
|
|
}
|
|
else
|
|
{
|
|
llinfos << "Unable to gunzip " << gzip_filename << llendl;
|
|
}
|
|
}
|
|
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
|
|
// will go through each category loaded and if the version
|
|
// does not match, invalidate the version.
|
|
S32 count = categories.count();
|
|
cat_set_t::iterator not_cached = temp_cats.end();
|
|
std::set<LLUUID> cached_ids;
|
|
for(S32 i = 0; i < count; ++i)
|
|
{
|
|
LLViewerInventoryCategory* cat = categories[i];
|
|
cat_set_t::iterator cit = temp_cats.find(cat);
|
|
if (cit == temp_cats.end())
|
|
{
|
|
continue; // cache corruption?? not sure why this happens -SJB
|
|
}
|
|
LLViewerInventoryCategory* tcat = *cit;
|
|
|
|
// we can safely ignore anything loaded from file, but
|
|
// not sent down in the skeleton.
|
|
if(cit == not_cached)
|
|
{
|
|
continue;
|
|
}
|
|
if(cat->getVersion() != tcat->getVersion())
|
|
{
|
|
// if the cached version does not match the server version,
|
|
// throw away the version we have so we can fetch the
|
|
// correct contents the next time the viewer opens the folder.
|
|
tcat->setVersion(NO_VERSION);
|
|
}
|
|
else
|
|
{
|
|
cached_ids.insert(tcat->getUUID());
|
|
}
|
|
}
|
|
|
|
// go ahead and add the cats returned during the download
|
|
std::set<LLUUID>::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)
|
|
{
|
|
if(cached_ids.find((*it)->getUUID()) == not_cached_id)
|
|
{
|
|
// this check is performed so that we do not
|
|
// mark new folders in the skeleton (and not in cache)
|
|
// as being cached.
|
|
LLViewerInventoryCategory *llvic = (*it);
|
|
llvic->setVersion(NO_VERSION);
|
|
}
|
|
addCategory(*it);
|
|
++child_counts[(*it)->getParentUUID()];
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
cat_map_t::iterator cit = mCategoryMap.find(items[i]->getParentUUID());
|
|
|
|
if(cit != unparented)
|
|
{
|
|
LLViewerInventoryCategory* cat = cit->second;
|
|
if(cat->getVersion() != NO_VERSION)
|
|
{
|
|
// This can happen if the linked object's baseobj is removed from the cache but the linked object is still in the cache.
|
|
if (items[i]->getIsBrokenLink())
|
|
{
|
|
bad_link_count++;
|
|
lldebugs << "Attempted to add cached link item without baseobj present ( name: "
|
|
<< items[i]->getName() << " itemID: " << items[i]->getUUID()
|
|
<< " assetID: " << items[i]->getAssetUUID()
|
|
<< " ). Ignoring and invalidating " << cat->getName() << " . " << llendl;
|
|
invalid_categories.insert(cit->second);
|
|
continue;
|
|
}
|
|
addItem(items[i]);
|
|
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
|
|
{
|
|
// go ahead and add everything after stripping the version
|
|
// information.
|
|
for(cat_set_t::iterator it = temp_cats.begin(); it != temp_cats.end(); ++it)
|
|
{
|
|
LLViewerInventoryCategory *llvic = (*it);
|
|
llvic->setVersion(NO_VERSION);
|
|
addCategory(*it);
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
for(cat_set_t::iterator it = temp_cats.begin(); it != temp_cats.end(); ++it)
|
|
{
|
|
LLViewerInventoryCategory* cat = (*it);
|
|
if(cat->getVersion() != NO_VERSION)
|
|
{
|
|
the_count = child_counts.find(cat->getUUID());
|
|
if(the_count != no_child_counts)
|
|
{
|
|
cat->setDescendentCount((*the_count).second.mValue);
|
|
}
|
|
else
|
|
{
|
|
cat->setDescendentCount(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(remove_inventory_file)
|
|
{
|
|
// 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
|
|
}
|
|
|
|
LL_DEBUGS("Inventory") << "Successfully loaded " << cached_category_count
|
|
<< " categories and " << cached_item_count << " items from cache."
|
|
<< LL_ENDL;
|
|
|
|
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(std::string((*meat).second));
|
|
meat = (*it).find("item_id");
|
|
if(meat == no_response) goto clean_item;
|
|
id.set((*meat).second);
|
|
item->setUUID(id);
|
|
meat = (*it).find("parent_id");
|
|
if(meat == no_response) goto clean_item;
|
|
id.set((*meat).second);
|
|
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);
|
|
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.
|
|
void LLInventoryModel::buildParentChildMap()
|
|
{
|
|
llinfos << "LLInventoryModel::buildParentChildMap()" << llendl;
|
|
|
|
// *NOTE: I am skipping the logic around folder version
|
|
// synchronization here because it seems if a folder is lost, we
|
|
// might actually want to invalidate it at that point - not
|
|
// attempt to cache. More time & thought is necessary.
|
|
|
|
// First the categories. We'll copy all of the categories into a
|
|
// temporary container to iterate over (oh for real iterators.)
|
|
// While we're at it, we'll allocate the arrays in the trees.
|
|
cat_array_t cats;
|
|
cat_array_t* catsp;
|
|
item_array_t* itemsp;
|
|
|
|
for(cat_map_t::iterator cit = mCategoryMap.begin(); cit != mCategoryMap.end(); ++cit)
|
|
{
|
|
LLViewerInventoryCategory* cat = cit->second;
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Insert a special parent for the root - so that lookups on
|
|
// LLUUID::null as the parent work correctly. This is kind of a
|
|
// blatent wastes of space since we allocate a block of memory for
|
|
// the array, but whatever - it's not that much space.
|
|
if (mParentChildCategoryTree.count(LLUUID::null) == 0)
|
|
{
|
|
catsp = new cat_array_t;
|
|
mParentChildCategoryTree[LLUUID::null] = catsp;
|
|
}
|
|
|
|
// Now we have a structure with all of the categories that we can
|
|
// iterate over and insert into the correct place in the child
|
|
// category tree.
|
|
S32 count = cats.count();
|
|
S32 i;
|
|
S32 lost = 0;
|
|
for(i = 0; i < count; ++i)
|
|
{
|
|
LLViewerInventoryCategory* cat = cats.get(i);
|
|
catsp = getUnlockedCatArray(cat->getParentUUID());
|
|
if(catsp)
|
|
{
|
|
catsp->put(cat);
|
|
}
|
|
else
|
|
{
|
|
// *NOTE: This process could be a lot more efficient if we
|
|
// used the new MoveInventoryFolder message, but we would
|
|
// have to continue to do the update & build here. So, to
|
|
// implement it, we would need a set or map of uuid pairs
|
|
// which would be (folder_id, new_parent_id) to be sent up
|
|
// to the server.
|
|
llinfos << "Lost categroy: " << cat->getUUID() << " - "
|
|
<< cat->getName() << " with parent:" << cat->getParentUUID() << llendl;
|
|
++lost;
|
|
// plop it into the lost & found.
|
|
LLAssetType::EType pref = cat->getPreferredType();
|
|
if(LLAssetType::AT_NONE == pref)
|
|
{
|
|
cat->setParent(findCategoryUUIDForType(LLAssetType::AT_LOST_AND_FOUND));
|
|
}
|
|
else if(LLAssetType::AT_CATEGORY == pref)
|
|
{
|
|
// it's the root
|
|
cat->setParent(LLUUID::null);
|
|
}
|
|
else
|
|
{
|
|
// it's a protected folder.
|
|
cat->setParent(gAgent.getInventoryRootID());
|
|
}
|
|
cat->updateServer(TRUE);
|
|
catsp = getUnlockedCatArray(cat->getParentUUID());
|
|
if(catsp)
|
|
{
|
|
catsp->put(cat);
|
|
}
|
|
else
|
|
{
|
|
llwarns << "Lost and found Not there!!" << llendl;
|
|
}
|
|
}
|
|
}
|
|
if(lost)
|
|
{
|
|
llwarns << "Found " << lost << " lost categories." << llendl;
|
|
}
|
|
|
|
// 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.
|
|
item_array_t items;
|
|
if(!mItemMap.empty())
|
|
{
|
|
LLPointer<LLViewerInventoryItem> item;
|
|
for(item_map_t::iterator iit = mItemMap.begin(); iit != mItemMap.end(); ++iit)
|
|
{
|
|
item = (*iit).second;
|
|
items.put(item);
|
|
}
|
|
}
|
|
count = items.count();
|
|
lost = 0;
|
|
std::vector<LLUUID> lost_item_ids;
|
|
for(i = 0; i < count; ++i)
|
|
{
|
|
LLPointer<LLViewerInventoryItem> item;
|
|
item = items.get(i);
|
|
itemsp = getUnlockedItemArray(item->getParentUUID());
|
|
if(itemsp)
|
|
{
|
|
itemsp->put(item);
|
|
}
|
|
else
|
|
{
|
|
llinfos << "Lost item: " << item->getUUID() << " - "
|
|
<< item->getName() << llendl;
|
|
++lost;
|
|
// plop it into the lost & found.
|
|
//
|
|
item->setParent(findCategoryUUIDForType(LLAssetType::AT_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 = getUnlockedItemArray(item->getParentUUID());
|
|
if(itemsp)
|
|
{
|
|
itemsp->put(item);
|
|
}
|
|
else
|
|
{
|
|
llwarns << "Lost and found Not there!!" << llendl;
|
|
}
|
|
}
|
|
}
|
|
if(lost)
|
|
{
|
|
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)
|
|
{
|
|
if(start_new_message)
|
|
{
|
|
start_new_message = FALSE;
|
|
msg->newMessageFast(_PREHASH_MoveInventoryItem);
|
|
msg->nextBlockFast(_PREHASH_AgentData);
|
|
msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
|
|
msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
|
|
msg->addBOOLFast(_PREHASH_Stamp, FALSE);
|
|
}
|
|
msg->nextBlockFast(_PREHASH_InventoryData);
|
|
msg->addUUIDFast(_PREHASH_ItemID, (*it));
|
|
msg->addUUIDFast(_PREHASH_FolderID, lnf);
|
|
msg->addString("NewName", NULL);
|
|
if(msg->isSendFull(NULL))
|
|
{
|
|
start_new_message = TRUE;
|
|
gAgent.sendReliableMessage();
|
|
}
|
|
}
|
|
if(!start_new_message)
|
|
{
|
|
gAgent.sendReliableMessage();
|
|
}
|
|
}
|
|
|
|
const LLUUID& agent_inv_root_id = gAgent.getInventoryRootID();
|
|
if (agent_inv_root_id.notNull())
|
|
{
|
|
cat_array_t* catsp = get_ptr_in_map(mParentChildCategoryTree, agent_inv_root_id);
|
|
if(catsp)
|
|
{
|
|
// 'My Inventory',
|
|
// root of the agent's inv found.
|
|
// The inv tree is built.
|
|
mIsAgentInvUsable = true;
|
|
AIEvent::trigger(AIEvent::LLInventoryModel_mIsAgentInvUsable_true);
|
|
}
|
|
}
|
|
llinfos << " finished buildParentChildMap " << llendl;
|
|
// dumpInventory(); // enable this if debugging inventory or appearance issues OGPX
|
|
}
|
|
|
|
struct LLUUIDAndName
|
|
{
|
|
LLUUIDAndName() {}
|
|
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;
|
|
std::string mName;
|
|
};
|
|
|
|
LLUUIDAndName::LLUUIDAndName(const LLUUID& id, const std::string& name) :
|
|
mID(id), mName(name)
|
|
{
|
|
}
|
|
|
|
bool LLUUIDAndName::operator==(const LLUUIDAndName& rhs) const
|
|
{
|
|
return ((mID == rhs.mID) && (mName == rhs.mName));
|
|
}
|
|
|
|
bool LLUUIDAndName::operator<(const LLUUIDAndName& rhs) const
|
|
{
|
|
return (mID < rhs.mID);
|
|
}
|
|
|
|
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 std::string& filename,
|
|
LLInventoryModel::cat_array_t& categories,
|
|
LLInventoryModel::item_array_t& items,
|
|
bool &is_cache_obsolete)
|
|
{
|
|
if(filename.empty())
|
|
{
|
|
llerrs << "Filename is Null!" << llendl;
|
|
return false;
|
|
}
|
|
llinfos << "LLInventoryModel::loadFromFile(" << filename << ")" << llendl;
|
|
LLFILE* file = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/
|
|
if(!file)
|
|
{
|
|
llinfos << "unable to load inventory from: " << filename << llendl;
|
|
return false;
|
|
}
|
|
// *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, " %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))
|
|
{
|
|
categories.put(inv_cat);
|
|
}
|
|
else
|
|
{
|
|
llwarns << "loadInventoryFromFile(). Ignoring invalid inventory category: " << inv_cat->getName() << llendl;
|
|
//delete inv_cat; // automatic when inv_cat is reassigned or destroyed
|
|
}
|
|
}
|
|
else if(0 == strcmp("inv_item", keyword))
|
|
{
|
|
if (is_cache_obsolete)
|
|
break;
|
|
|
|
LLPointer<LLViewerInventoryItem> inv_item = new LLViewerInventoryItem;
|
|
if( inv_item->importFileLocal(file) )
|
|
{
|
|
// *FIX: Need a better solution, this prevents the
|
|
// application from freezing, but breaks inventory
|
|
// caching.
|
|
if(inv_item->getUUID().isNull())
|
|
{
|
|
//delete inv_item; // automatic when inv_cat is reassigned or destroyed
|
|
llwarns << "Ignoring inventory with null item id: "
|
|
<< inv_item->getName() << llendl;
|
|
|
|
}
|
|
else
|
|
{
|
|
items.put(inv_item);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
llwarns << "loadInventoryFromFile(). Ignoring invalid inventory item: " << inv_item->getName() << llendl;
|
|
//delete inv_item; // automatic when inv_cat is reassigned or destroyed
|
|
}
|
|
}
|
|
else
|
|
{
|
|
llwarns << "Unknown token in inventory file '" << keyword << "'"
|
|
<< llendl;
|
|
}
|
|
}
|
|
fclose(file);
|
|
if (is_cache_obsolete)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
bool LLInventoryModel::saveToFile(const std::string& filename,
|
|
const cat_array_t& categories,
|
|
const item_array_t& items)
|
|
{
|
|
if(filename.empty())
|
|
{
|
|
llerrs << "Filename is Null!" << llendl;
|
|
return false;
|
|
}
|
|
llinfos << "LLInventoryModel::saveToFile(" << filename << ")" << llendl;
|
|
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)
|
|
{
|
|
LLViewerInventoryCategory* cat = categories[i];
|
|
if(cat->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN)
|
|
{
|
|
cat->exportFileLocal(file);
|
|
}
|
|
}
|
|
|
|
count = items.count();
|
|
for(i = 0; i < count; ++i)
|
|
{
|
|
items[i]->exportFile(file);
|
|
}
|
|
|
|
fclose(file);
|
|
return true;
|
|
}
|
|
|
|
// message handling functionality
|
|
// static
|
|
void LLInventoryModel::registerCallbacks(LLMessageSystem* msg)
|
|
{
|
|
//msg->setHandlerFuncFast(_PREHASH_InventoryUpdate,
|
|
// processInventoryUpdate,
|
|
// NULL);
|
|
//msg->setHandlerFuncFast(_PREHASH_UseCachedInventory,
|
|
// processUseCachedInventory,
|
|
// NULL);
|
|
msg->setHandlerFuncFast(_PREHASH_UpdateCreateInventoryItem,
|
|
processUpdateCreateInventoryItem,
|
|
NULL);
|
|
msg->setHandlerFuncFast(_PREHASH_RemoveInventoryItem,
|
|
processRemoveInventoryItem,
|
|
NULL);
|
|
msg->setHandlerFuncFast(_PREHASH_UpdateInventoryFolder,
|
|
processUpdateInventoryFolder,
|
|
NULL);
|
|
msg->setHandlerFuncFast(_PREHASH_RemoveInventoryFolder,
|
|
processRemoveInventoryFolder,
|
|
NULL);
|
|
//msg->setHandlerFuncFast(_PREHASH_ExchangeCallingCard,
|
|
// processExchangeCallingcard,
|
|
// NULL);
|
|
//msg->setHandlerFuncFast(_PREHASH_AddCallingCard,
|
|
// processAddCallingcard,
|
|
// NULL);
|
|
//msg->setHandlerFuncFast(_PREHASH_DeclineCallingCard,
|
|
// processDeclineCallingcard,
|
|
// NULL);
|
|
msg->setHandlerFuncFast(_PREHASH_SaveAssetIntoInventory,
|
|
processSaveAssetIntoInventory,
|
|
NULL);
|
|
msg->setHandlerFuncFast(_PREHASH_BulkUpdateInventory,
|
|
processBulkUpdateInventory,
|
|
NULL);
|
|
msg->setHandlerFunc("InventoryDescendents", processInventoryDescendents);
|
|
msg->setHandlerFunc("MoveInventoryItem", processMoveInventoryItem);
|
|
msg->setHandlerFunc("FetchInventoryReply", processFetchInventoryReply);
|
|
}
|
|
|
|
|
|
// static
|
|
void LLInventoryModel::processUpdateCreateInventoryItem(LLMessageSystem* msg, void**)
|
|
{
|
|
// do accounting and highlight new items if they arrive
|
|
if (gInventory.messageUpdateCore(msg, true))
|
|
{
|
|
U32 callback_id;
|
|
LLUUID item_id;
|
|
msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_ItemID, item_id);
|
|
msg->getU32Fast(_PREHASH_InventoryData, _PREHASH_CallbackID, callback_id);
|
|
|
|
gInventoryCallbacks.fire(callback_id, item_id);
|
|
}
|
|
|
|
}
|
|
|
|
// static
|
|
void LLInventoryModel::processFetchInventoryReply(LLMessageSystem* msg, void**)
|
|
{
|
|
// no accounting
|
|
gInventory.messageUpdateCore(msg, false);
|
|
}
|
|
|
|
|
|
bool LLInventoryModel::messageUpdateCore(LLMessageSystem* msg, bool account)
|
|
{
|
|
//make sure our added inventory observer is active
|
|
start_new_inventory_observer();
|
|
|
|
LLUUID agent_id;
|
|
msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id);
|
|
if(agent_id != gAgent.getID())
|
|
{
|
|
llwarns << "Got a inventory update for the wrong agent: " << agent_id
|
|
<< llendl;
|
|
return false;
|
|
}
|
|
item_array_t items;
|
|
update_map_t update;
|
|
S32 count = msg->getNumberOfBlocksFast(_PREHASH_InventoryData);
|
|
bool all_one_folder = true;
|
|
LLUUID folder_id;
|
|
// Does this loop ever execute more than once?
|
|
for(S32 i = 0; i < count; ++i)
|
|
{
|
|
LLPointer<LLViewerInventoryItem> titem = new LLViewerInventoryItem;
|
|
titem->unpackMessage(msg, _PREHASH_InventoryData, i);
|
|
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(titem->getParentUUID() == itemp->getParentUUID())
|
|
{
|
|
update[titem->getParentUUID()];
|
|
}
|
|
else
|
|
{
|
|
++update[titem->getParentUUID()];
|
|
--update[itemp->getParentUUID()];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++update[titem->getParentUUID()];
|
|
}
|
|
if (folder_id.isNull())
|
|
{
|
|
folder_id = titem->getParentUUID();
|
|
}
|
|
else
|
|
{
|
|
all_one_folder = false;
|
|
}
|
|
}
|
|
if(account)
|
|
{
|
|
gInventory.accountForUpdate(update);
|
|
}
|
|
|
|
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)
|
|
{
|
|
changes |= gInventory.updateItem(*it);
|
|
}
|
|
gInventory.notifyObservers();
|
|
gViewerWindow->getWindow()->decBusyCount();
|
|
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
void LLInventoryModel::processRemoveInventoryItem(LLMessageSystem* msg, void**)
|
|
{
|
|
lldebugs << "LLInventoryModel::processRemoveInventoryItem()" << llendl;
|
|
LLUUID agent_id, item_id;
|
|
msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id);
|
|
if(agent_id != gAgent.getID())
|
|
{
|
|
llwarns << "Got a RemoveInventoryItem for the wrong agent."
|
|
<< llendl;
|
|
return;
|
|
}
|
|
S32 count = msg->getNumberOfBlocksFast(_PREHASH_InventoryData);
|
|
std::vector<LLUUID> item_ids;
|
|
update_map_t update;
|
|
for(S32 i = 0; i < count; ++i)
|
|
{
|
|
msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_ItemID, item_id, i);
|
|
LLViewerInventoryItem* itemp = gInventory.getItem(item_id);
|
|
if(itemp)
|
|
{
|
|
// we only bother with the delete and account if we found
|
|
// the item - this is usually a back-up for permissions,
|
|
// so frequently the item will already be gone.
|
|
--update[itemp->getParentUUID()];
|
|
item_ids.push_back(item_id);
|
|
}
|
|
}
|
|
gInventory.accountForUpdate(update);
|
|
for(std::vector<LLUUID>::iterator it = item_ids.begin(); it != item_ids.end(); ++it)
|
|
{
|
|
gInventory.deleteObject(*it);
|
|
}
|
|
gInventory.notifyObservers();
|
|
}
|
|
|
|
// static
|
|
void LLInventoryModel::processUpdateInventoryFolder(LLMessageSystem* msg,
|
|
void**)
|
|
{
|
|
lldebugs << "LLInventoryModel::processUpdateInventoryFolder()" << llendl;
|
|
LLUUID agent_id, folder_id, parent_id;
|
|
//char name[DB_INV_ITEM_NAME_BUF_SIZE];
|
|
msg->getUUIDFast(_PREHASH_FolderData, _PREHASH_AgentID, agent_id);
|
|
if(agent_id != gAgent.getID())
|
|
{
|
|
llwarns << "Got an UpdateInventoryFolder for the wrong agent."
|
|
<< llendl;
|
|
return;
|
|
}
|
|
LLPointer<LLViewerInventoryCategory> lastfolder; // hack
|
|
cat_array_t folders;
|
|
update_map_t update;
|
|
S32 count = msg->getNumberOfBlocksFast(_PREHASH_FolderData);
|
|
for(S32 i = 0; i < count; ++i)
|
|
{
|
|
LLPointer<LLViewerInventoryCategory> tfolder = new LLViewerInventoryCategory(gAgent.getID());
|
|
lastfolder = tfolder;
|
|
tfolder->unpackMessage(msg, _PREHASH_FolderData, i);
|
|
// make sure it's not a protected folder
|
|
tfolder->setPreferredType(LLAssetType::AT_NONE);
|
|
folders.push_back(tfolder);
|
|
// examine update for changes.
|
|
LLViewerInventoryCategory* folderp = gInventory.getCategory(tfolder->getUUID());
|
|
if(folderp)
|
|
{
|
|
if(tfolder->getParentUUID() == folderp->getParentUUID())
|
|
{
|
|
update[tfolder->getParentUUID()];
|
|
}
|
|
else
|
|
{
|
|
++update[tfolder->getParentUUID()];
|
|
--update[folderp->getParentUUID()];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
++update[tfolder->getParentUUID()];
|
|
}
|
|
}
|
|
gInventory.accountForUpdate(update);
|
|
for (cat_array_t::iterator it = folders.begin(); it != folders.end(); ++it)
|
|
{
|
|
gInventory.updateCategory(*it);
|
|
}
|
|
gInventory.notifyObservers();
|
|
|
|
// *HACK: Do the 'show' logic for a new item in the inventory.
|
|
LLInventoryView* view = LLInventoryView::getActiveInventory();
|
|
if(view)
|
|
{
|
|
view->getPanel()->setSelection(lastfolder->getUUID(), TAKE_FOCUS_NO);
|
|
}
|
|
}
|
|
|
|
// static
|
|
void LLInventoryModel::processRemoveInventoryFolder(LLMessageSystem* msg,
|
|
void**)
|
|
{
|
|
lldebugs << "LLInventoryModel::processRemoveInventoryFolder()" << llendl;
|
|
LLUUID agent_id, folder_id;
|
|
msg->getUUIDFast(_PREHASH_FolderData, _PREHASH_AgentID, agent_id);
|
|
if(agent_id != gAgent.getID())
|
|
{
|
|
llwarns << "Got a RemoveInventoryFolder for the wrong agent."
|
|
<< llendl;
|
|
return;
|
|
}
|
|
std::vector<LLUUID> folder_ids;
|
|
update_map_t update;
|
|
S32 count = msg->getNumberOfBlocksFast(_PREHASH_FolderData);
|
|
for(S32 i = 0; i < count; ++i)
|
|
{
|
|
msg->getUUIDFast(_PREHASH_FolderData, _PREHASH_FolderID, folder_id, i);
|
|
LLViewerInventoryCategory* folderp = gInventory.getCategory(folder_id);
|
|
if(folderp)
|
|
{
|
|
--update[folderp->getParentUUID()];
|
|
folder_ids.push_back(folder_id);
|
|
}
|
|
}
|
|
gInventory.accountForUpdate(update);
|
|
for(std::vector<LLUUID>::iterator it = folder_ids.begin(); it != folder_ids.end(); ++it)
|
|
{
|
|
gInventory.deleteObject(*it);
|
|
}
|
|
gInventory.notifyObservers();
|
|
}
|
|
|
|
// static
|
|
void LLInventoryModel::processSaveAssetIntoInventory(LLMessageSystem* msg,
|
|
void**)
|
|
{
|
|
LLUUID agent_id;
|
|
msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id);
|
|
if(agent_id != gAgent.getID())
|
|
{
|
|
llwarns << "Got a SaveAssetIntoInventory message for the wrong agent."
|
|
<< llendl;
|
|
return;
|
|
}
|
|
|
|
LLUUID item_id;
|
|
msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_ItemID, item_id);
|
|
|
|
// The viewer ignores the asset id because this message is only
|
|
// used for attachments/objects, so the asset id is not used in
|
|
// the viewer anyway.
|
|
lldebugs << "LLInventoryModel::processSaveAssetIntoInventory itemID="
|
|
<< item_id << llendl;
|
|
LLViewerInventoryItem* item = gInventory.getItem( item_id );
|
|
if( item )
|
|
{
|
|
LLCategoryUpdate up(item->getParentUUID(), 0);
|
|
gInventory.accountForUpdate(up);
|
|
gInventory.addChangedMask( LLInventoryObserver::INTERNAL, item_id);
|
|
gInventory.notifyObservers();
|
|
}
|
|
else
|
|
{
|
|
llinfos << "LLInventoryModel::processSaveAssetIntoInventory item"
|
|
" not found: " << item_id << llendl;
|
|
}
|
|
|
|
// [RLVa:KB] - Checked: 2010-03-05 (RLVa-1.2.0a) | Added: RLVa-0.2.0e
|
|
if (rlv_handler_t::isEnabled())
|
|
{
|
|
RlvAttachmentLockWatchdog::instance().onSavedAssetIntoInventory(item_id);
|
|
}
|
|
// [/RLVa:KB]
|
|
|
|
if(gViewerWindow)
|
|
{
|
|
gViewerWindow->getWindow()->decBusyCount();
|
|
}
|
|
}
|
|
|
|
struct InventoryCallbackInfo
|
|
{
|
|
InventoryCallbackInfo(U32 callback, const LLUUID& inv_id) :
|
|
mCallback(callback), mInvID(inv_id) {}
|
|
U32 mCallback;
|
|
LLUUID mInvID;
|
|
};
|
|
|
|
// static
|
|
void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**)
|
|
{
|
|
LLUUID agent_id;
|
|
msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id);
|
|
if(agent_id != gAgent.getID())
|
|
{
|
|
llwarns << "Got a BulkUpdateInventory for the wrong agent." << llendl;
|
|
return;
|
|
}
|
|
LLUUID tid;
|
|
msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_TransactionID, tid);
|
|
llinfos << "Bulk inventory: " << tid << llendl;
|
|
|
|
update_map_t update;
|
|
cat_array_t folders;
|
|
S32 count;
|
|
S32 i;
|
|
count = msg->getNumberOfBlocksFast(_PREHASH_FolderData);
|
|
for(i = 0; i < count; ++i)
|
|
{
|
|
LLPointer<LLViewerInventoryCategory> tfolder = new LLViewerInventoryCategory(gAgent.getID());
|
|
tfolder->unpackMessage(msg, _PREHASH_FolderData, i);
|
|
//llinfos << "unpaked folder '" << tfolder->getName() << "' ("
|
|
// << tfolder->getUUID() << ") in " << tfolder->getParentUUID()
|
|
// << llendl;
|
|
if(tfolder->getUUID().notNull())
|
|
{
|
|
folders.push_back(tfolder);
|
|
LLViewerInventoryCategory* folderp = gInventory.getCategory(tfolder->getUUID());
|
|
if(folderp)
|
|
{
|
|
if(tfolder->getParentUUID() == folderp->getParentUUID())
|
|
{
|
|
// [RLVa:KB] - Checked: 2010-04-18 (RLVa-1.2.0e) | Added: RLVa-1.2.0e
|
|
// NOTE-RLVa: not sure if this is a hack or a bug-fix :o
|
|
// -> if we rename the folder on the first BulkUpdateInventory message subsequent messages will still contain
|
|
// the old folder name and gInventory.updateCategory() below will "undo" the folder name change but on the
|
|
// viewer-side *only* so the folder name actually becomes out of sync with what's on the inventory server
|
|
// -> so instead we keep the name of the existing folder and only do it for #RLV/~ in case this causes issues
|
|
// -> a better solution would be to only do the rename *after* the transaction completes but there doesn't seem
|
|
// to be any way to accomplish that either *sighs*
|
|
if ( (rlv_handler_t::isEnabled()) && (!folderp->getName().empty()) && (tfolder->getName() != folderp->getName()) &&
|
|
((tfolder->getName().find(RLV_PUTINV_PREFIX) == 0)) )
|
|
{
|
|
tfolder->rename(folderp->getName());
|
|
}
|
|
// [/RLVa:KB]
|
|
update[tfolder->getParentUUID()];
|
|
}
|
|
else
|
|
{
|
|
++update[tfolder->getParentUUID()];
|
|
--update[folderp->getParentUUID()];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we could not find the folder, so it is probably
|
|
// new. However, we only want to attempt accounting
|
|
// for the parent if we can find the parent.
|
|
folderp = gInventory.getCategory(tfolder->getParentUUID());
|
|
if(folderp)
|
|
{
|
|
++update[tfolder->getParentUUID()];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
count = msg->getNumberOfBlocksFast(_PREHASH_ItemData);
|
|
std::vector<LLUUID> wearable_ids;
|
|
item_array_t items;
|
|
std::list<InventoryCallbackInfo> cblist;
|
|
for(i = 0; i < count; ++i)
|
|
{
|
|
LLPointer<LLViewerInventoryItem> titem = new LLViewerInventoryItem;
|
|
titem->unpackMessage(msg, _PREHASH_ItemData, i);
|
|
//llinfos << "unpaked item '" << titem->getName() << "' in "
|
|
// << titem->getParentUUID() << llendl;
|
|
U32 callback_id;
|
|
msg->getU32Fast(_PREHASH_ItemData, _PREHASH_CallbackID, callback_id);
|
|
if(titem->getUUID().notNull())
|
|
{
|
|
items.push_back(titem);
|
|
cblist.push_back(InventoryCallbackInfo(callback_id, titem->getUUID()));
|
|
if (titem->getInventoryType() == LLInventoryType::IT_WEARABLE)
|
|
{
|
|
wearable_ids.push_back(titem->getUUID());
|
|
}
|
|
// examine update for changes.
|
|
LLViewerInventoryItem* itemp = gInventory.getItem(titem->getUUID());
|
|
if(itemp)
|
|
{
|
|
if(titem->getParentUUID() == itemp->getParentUUID())
|
|
{
|
|
update[titem->getParentUUID()];
|
|
}
|
|
else
|
|
{
|
|
++update[titem->getParentUUID()];
|
|
--update[itemp->getParentUUID()];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LLViewerInventoryCategory* folderp = gInventory.getCategory(titem->getParentUUID());
|
|
if(folderp)
|
|
{
|
|
++update[titem->getParentUUID()];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cblist.push_back(InventoryCallbackInfo(callback_id, LLUUID::null));
|
|
}
|
|
}
|
|
gInventory.accountForUpdate(update);
|
|
|
|
for (cat_array_t::iterator cit = folders.begin(); cit != folders.end(); ++cit)
|
|
{
|
|
gInventory.updateCategory(*cit);
|
|
}
|
|
for (item_array_t::iterator iit = items.begin(); iit != items.end(); ++iit)
|
|
{
|
|
gInventory.updateItem(*iit);
|
|
}
|
|
gInventory.notifyObservers();
|
|
|
|
// 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)
|
|
{
|
|
LLInventoryView::sWearNewClothingTransactionID = tid;
|
|
LLInventoryView::sWearNewClothing = FALSE;
|
|
}
|
|
|
|
if (tid == LLInventoryView::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);
|
|
}
|
|
}
|
|
|
|
std::list<InventoryCallbackInfo>::iterator inv_it;
|
|
for (inv_it = cblist.begin(); inv_it != cblist.end(); ++inv_it)
|
|
{
|
|
InventoryCallbackInfo cbinfo = (*inv_it);
|
|
gInventoryCallbacks.fire(cbinfo.mCallback, cbinfo.mInvID);
|
|
}
|
|
// Don't show the inventory. We used to call showAgentInventory here.
|
|
//LLInventoryView* view = LLInventoryView::getActiveInventory();
|
|
//if(view)
|
|
//{
|
|
// const BOOL take_keyboard_focus = FALSE;
|
|
// view->setSelection(category.getUUID(), take_keyboard_focus );
|
|
// LLView* focus_view = gFocusMgr.getKeyboardFocus();
|
|
// LLFocusMgr::FocusLostCallback callback = gFocusMgr.getFocusCallback();
|
|
// // 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)
|
|
// {
|
|
// view->openSelected();
|
|
// LLInventoryView::sOpenNextNewItem = FALSE;
|
|
// }
|
|
//
|
|
// // restore keyboard focus
|
|
// gFocusMgr.setKeyboardFocus(focus_view);
|
|
//}
|
|
}
|
|
|
|
// static
|
|
void LLInventoryModel::processInventoryDescendents(LLMessageSystem* msg,void**)
|
|
{
|
|
LLUUID agent_id;
|
|
msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id);
|
|
if(agent_id != gAgent.getID())
|
|
{
|
|
llwarns << "Got a UpdateInventoryItem for the wrong agent."
|
|
<< llendl;
|
|
return;
|
|
}
|
|
LLUUID parent_id;
|
|
msg->getUUID("AgentData", "FolderID", parent_id);
|
|
LLUUID owner_id;
|
|
msg->getUUID("AgentData", "OwnerID", owner_id);
|
|
S32 version;
|
|
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);
|
|
for(i = 0; i < count; ++i)
|
|
{
|
|
tcategory->unpackMessage(msg, _PREHASH_FolderData, i);
|
|
gInventory.updateCategory(tcategory);
|
|
}
|
|
|
|
count = msg->getNumberOfBlocksFast(_PREHASH_ItemData);
|
|
LLPointer<LLViewerInventoryItem> titem = new LLViewerInventoryItem;
|
|
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);
|
|
}
|
|
|
|
// set version and descendentcount according to message.
|
|
LLViewerInventoryCategory* cat = gInventory.getCategory(parent_id);
|
|
if(cat)
|
|
{
|
|
cat->setVersion(version);
|
|
cat->setDescendentCount(descendents);
|
|
}
|
|
gInventory.notifyObservers();
|
|
}
|
|
|
|
// static
|
|
void LLInventoryModel::processMoveInventoryItem(LLMessageSystem* msg, void**)
|
|
{
|
|
lldebugs << "LLInventoryModel::processMoveInventoryItem()" << llendl;
|
|
LLUUID agent_id;
|
|
msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id);
|
|
if(agent_id != gAgent.getID())
|
|
{
|
|
llwarns << "Got a MoveInventoryItem message for the wrong agent."
|
|
<< llendl;
|
|
return;
|
|
}
|
|
|
|
LLUUID item_id;
|
|
LLUUID folder_id;
|
|
std::string new_name;
|
|
bool anything_changed = false;
|
|
S32 count = msg->getNumberOfBlocksFast(_PREHASH_InventoryData);
|
|
for(S32 i = 0; i < count; ++i)
|
|
{
|
|
msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_ItemID, item_id, i);
|
|
LLViewerInventoryItem* item = gInventory.getItem(item_id);
|
|
if(item)
|
|
{
|
|
LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item);
|
|
msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_FolderID, folder_id, i);
|
|
msg->getString("InventoryData", "NewName", new_name, i);
|
|
|
|
lldebugs << "moving item " << item_id << " to folder "
|
|
<< folder_id << llendl;
|
|
update_list_t update;
|
|
LLCategoryUpdate old_folder(item->getParentUUID(), -1);
|
|
update.push_back(old_folder);
|
|
LLCategoryUpdate new_folder(folder_id, 1);
|
|
update.push_back(new_folder);
|
|
gInventory.accountForUpdate(update);
|
|
|
|
new_item->setParent(folder_id);
|
|
if (new_name.length() > 0)
|
|
{
|
|
new_item->rename(new_name);
|
|
}
|
|
gInventory.updateItem(new_item);
|
|
anything_changed = true;
|
|
}
|
|
else
|
|
{
|
|
llinfos << "LLInventoryModel::processMoveInventoryItem item not found: " << item_id << llendl;
|
|
}
|
|
}
|
|
if(anything_changed)
|
|
{
|
|
gInventory.notifyObservers();
|
|
}
|
|
}
|
|
|
|
// *NOTE: DEBUG functionality
|
|
void LLInventoryModel::dumpInventory() const
|
|
{
|
|
LL_DEBUGS("Inventory") << "\nBegin Inventory Dump\n**********************:" << LL_ENDL;
|
|
LL_DEBUGS("Inventory") << "mCategroy[] contains " << mCategoryMap.size() << " items." << LL_ENDL;
|
|
for(cat_map_t::const_iterator cit = mCategoryMap.begin(); cit != mCategoryMap.end(); ++cit)
|
|
{
|
|
LLViewerInventoryCategory* cat = cit->second;
|
|
if(cat)
|
|
{
|
|
LL_DEBUGS("Inventory") << " " << cat->getUUID() << " '" << cat->getName() << "' "
|
|
<< cat->getVersion() << " " << cat->getDescendentCount() << " parent: " << cat->getParentUUID()
|
|
<< LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_DEBUGS("Inventory") << " NULL!" << LL_ENDL;
|
|
}
|
|
}
|
|
LL_DEBUGS("Inventory") << "mItemMap[] contains " << mItemMap.size() << " items." << LL_ENDL;
|
|
for(item_map_t::const_iterator iit = mItemMap.begin(); iit != mItemMap.end(); ++iit)
|
|
{
|
|
LLViewerInventoryItem* item = iit->second;
|
|
if(item)
|
|
{
|
|
LL_DEBUGS("Inventory") << " " << item->getUUID() << " "
|
|
<< item->getName() << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
LL_DEBUGS("Inventory") << " NULL!" << LL_ENDL;
|
|
}
|
|
}
|
|
LL_DEBUGS("Inventory") << "\n**********************\nEnd Inventory Dump" << LL_ENDL;
|
|
}
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// LLInventoryCollectFunctor implementations
|
|
///----------------------------------------------------------------------------
|
|
|
|
// static
|
|
bool LLInventoryCollectFunctor::itemTransferCommonlyAllowed(LLInventoryItem* item)
|
|
{
|
|
if (!item)
|
|
return false;
|
|
|
|
bool allowed = false;
|
|
LLVOAvatar* my_avatar = NULL;
|
|
|
|
switch(item->getType())
|
|
{
|
|
case LLAssetType::AT_CALLINGCARD:
|
|
// not allowed
|
|
break;
|
|
|
|
case LLAssetType::AT_OBJECT:
|
|
my_avatar = gAgent.getAvatarObject();
|
|
if(my_avatar && !my_avatar->isWearingAttachment(item->getUUID()))
|
|
{
|
|
allowed = true;
|
|
}
|
|
break;
|
|
|
|
case LLAssetType::AT_BODYPART:
|
|
case LLAssetType::AT_CLOTHING:
|
|
if(!gAgent.isWearingItem(item->getUUID()))
|
|
{
|
|
allowed = true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
allowed = true;
|
|
break;
|
|
}
|
|
|
|
return allowed;
|
|
}
|
|
|
|
bool LLIsType::operator()(LLInventoryCategory* cat, LLInventoryItem* item)
|
|
{
|
|
if(mType == LLAssetType::AT_CATEGORY)
|
|
{
|
|
if(cat) return TRUE;
|
|
}
|
|
if(item)
|
|
{
|
|
if(item->getType() == mType) return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
bool LLIsNotType::operator()(LLInventoryCategory* cat, LLInventoryItem* item)
|
|
{
|
|
if(mType == LLAssetType::AT_CATEGORY)
|
|
{
|
|
if(cat) return FALSE;
|
|
}
|
|
if(item)
|
|
{
|
|
if(item->getType() == mType) return FALSE;
|
|
else return TRUE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
bool LLIsTypeWithPermissions::operator()(LLInventoryCategory* cat, LLInventoryItem* item)
|
|
{
|
|
if(mType == LLAssetType::AT_CATEGORY)
|
|
{
|
|
if(cat)
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
if(item)
|
|
{
|
|
if(item->getType() == mType)
|
|
{
|
|
LLPermissions perm = item->getPermissions();
|
|
if ((perm.getMaskBase() & mPerm) == mPerm)
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
if(item)
|
|
{
|
|
if((LLAssetType::AT_CALLINGCARD == item->getType())
|
|
&& (!item->getCreatorUUID().isNull())
|
|
&& (item->getCreatorUUID() != gAgent.getID()))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool LLUniqueBuddyCollector::operator()(LLInventoryCategory* cat,
|
|
LLInventoryItem* item)
|
|
{
|
|
if(item)
|
|
{
|
|
if((LLAssetType::AT_CALLINGCARD == item->getType())
|
|
&& (item->getCreatorUUID().notNull())
|
|
&& (item->getCreatorUUID() != gAgent.getID()))
|
|
{
|
|
mSeen.insert(item->getCreatorUUID());
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool LLParticularBuddyCollector::operator()(LLInventoryCategory* cat,
|
|
LLInventoryItem* item)
|
|
{
|
|
if(item)
|
|
{
|
|
if((LLAssetType::AT_CALLINGCARD == item->getType())
|
|
&& (item->getCreatorUUID() == mBuddyID))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
bool LLNameCategoryCollector::operator()(
|
|
LLInventoryCategory* cat, LLInventoryItem* item)
|
|
{
|
|
if(cat)
|
|
{
|
|
if (!LLStringUtil::compareInsensitive(mName, cat->getName()))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// Observers
|
|
///----------------------------------------------------------------------------
|
|
|
|
void LLInventoryCompletionObserver::changed(U32 mask)
|
|
{
|
|
// 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();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLInventoryCompletionObserver::watchItem(const LLUUID& id)
|
|
{
|
|
if(id.notNull())
|
|
{
|
|
mIncomplete.push_back(id);
|
|
}
|
|
}
|
|
|
|
|
|
void LLInventoryFetchObserver::changed(U32 mask)
|
|
{
|
|
// 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)
|
|
{
|
|
// 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();
|
|
}
|
|
}
|
|
//llinfos << "LLInventoryFetchObserver::changed() mComplete size " << mComplete.size() << llendl;
|
|
//llinfos << "LLInventoryFetchObserver::changed() mIncomplete size " << mIncomplete.size() << llendl;
|
|
}
|
|
|
|
bool LLInventoryFetchObserver::isEverythingComplete() const
|
|
{
|
|
return mIncomplete.empty();
|
|
}
|
|
|
|
void fetch_items_from_llsd(const LLSD& items_llsd)
|
|
{
|
|
if (!items_llsd.size()) return;
|
|
LLSD body;
|
|
body[0]["cap_name"] = "FetchInventory";
|
|
body[1]["cap_name"] = "FetchLib";
|
|
for (S32 i=0; i<items_llsd.size();i++)
|
|
{
|
|
if (items_llsd[i]["owner_id"].asString() == gAgent.getID().asString())
|
|
{
|
|
body[0]["items"].append(items_llsd[i]);
|
|
continue;
|
|
}
|
|
if (items_llsd[i]["owner_id"].asString() == ALEXANDRIA_LINDEN_ID.asString())
|
|
{
|
|
body[1]["items"].append(items_llsd[i]);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
for (S32 i=0; i<body.size(); i++)
|
|
{
|
|
if (0 >= body[i].size()) continue;
|
|
std::string url = gAgent.getRegion()->getCapability(body[i]["cap_name"].asString());
|
|
|
|
if (!url.empty())
|
|
{
|
|
body[i]["agent_id"] = gAgent.getID();
|
|
LLHTTPClient::post(url, body[i], new LLInventoryModel::fetchInventoryResponder(body[i]));
|
|
break;
|
|
}
|
|
|
|
LLMessageSystem* msg = gMessageSystem;
|
|
BOOL start_new_message = TRUE;
|
|
for (S32 j=0; j<body[i]["items"].size(); j++)
|
|
{
|
|
LLSD item_entry = body[i]["items"][j];
|
|
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, item_entry["owner_id"].asUUID());
|
|
msg->addUUIDFast(_PREHASH_ItemID, item_entry["item_id"].asUUID());
|
|
if(msg->isSendFull(NULL))
|
|
{
|
|
start_new_message = TRUE;
|
|
gAgent.sendReliableMessage();
|
|
}
|
|
}
|
|
if(!start_new_message)
|
|
{
|
|
gAgent.sendReliableMessage();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLInventoryFetchObserver::fetchItems(
|
|
const LLInventoryFetchObserver::item_ref_t& ids)
|
|
{
|
|
LLUUID owner_id;
|
|
LLSD items_llsd;
|
|
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();
|
|
}
|
|
|
|
// It's incomplete, so put it on the incomplete container, and
|
|
// pack this on the message.
|
|
mIncomplete.push_back(*it);
|
|
|
|
// Prepare the data to fetch
|
|
LLSD item_entry;
|
|
item_entry["owner_id"] = owner_id;
|
|
item_entry["item_id"] = (*it);
|
|
items_llsd.append(item_entry);
|
|
}
|
|
fetch_items_from_llsd(items_llsd);
|
|
}
|
|
|
|
// virtual
|
|
void LLInventoryFetchDescendentsObserver::changed(U32 mask)
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
|
|
void LLInventoryFetchDescendentsObserver::fetchDescendents(
|
|
const folder_ref_t& ids)
|
|
{
|
|
for(folder_ref_t::const_iterator it = ids.begin(); it != ids.end(); ++it)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool LLInventoryFetchDescendentsObserver::isEverythingComplete() const
|
|
{
|
|
return mIncompleteFolders.empty();
|
|
}
|
|
|
|
bool LLInventoryFetchDescendentsObserver::isComplete(LLViewerInventoryCategory* cat)
|
|
{
|
|
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)
|
|
{
|
|
// hey - we're done.
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void LLInventoryFetchComboObserver::changed(U32 mask)
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
|
|
void LLInventoryFetchComboObserver::fetch(
|
|
const folder_ref_t& folder_ids,
|
|
const item_ref_t& item_ids)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
LLSD items_llsd;
|
|
LLUUID owner_id;
|
|
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())
|
|
{
|
|
LLSD item_entry;
|
|
item_entry["owner_id"] = owner_id;
|
|
item_entry["item_id"] = (*iit);
|
|
items_llsd.append(item_entry);
|
|
}
|
|
else
|
|
{
|
|
lldebugs << "not worrying about " << *iit << llendl;
|
|
}
|
|
}
|
|
fetch_items_from_llsd(items_llsd);
|
|
}
|
|
|
|
void LLInventoryExistenceObserver::watchItem(const LLUUID& id)
|
|
{
|
|
if(id.notNull())
|
|
{
|
|
mMIA.push_back(id);
|
|
}
|
|
}
|
|
|
|
void LLInventoryExistenceObserver::changed(U32 mask)
|
|
{
|
|
// scan through the incomplete items and move or erase them as
|
|
// appropriate.
|
|
if(!mMIA.empty())
|
|
{
|
|
for(item_ref_t::iterator it = mMIA.begin(); it < mMIA.end(); )
|
|
{
|
|
LLViewerInventoryItem* item = gInventory.getItem(*it);
|
|
if(!item)
|
|
{
|
|
++it;
|
|
continue;
|
|
}
|
|
mExist.push_back(*it);
|
|
it = mMIA.erase(it);
|
|
}
|
|
if(mMIA.empty())
|
|
{
|
|
done();
|
|
}
|
|
}
|
|
}
|
|
|
|
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.
|
|
LLMessageSystem* msg = gMessageSystem;
|
|
|
|
std::string msg_name;
|
|
if (mMessageName.empty())
|
|
{
|
|
msg_name = msg->getMessageName();
|
|
}
|
|
else
|
|
{
|
|
msg_name = mMessageName;
|
|
}
|
|
|
|
if (msg_name.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// We only want newly created inventory items. JC
|
|
if ( msg_name != "UpdateCreateInventoryItem")
|
|
{
|
|
return;
|
|
}
|
|
|
|
LLPointer<LLViewerInventoryItem> titem = new LLViewerInventoryItem;
|
|
S32 num_blocks = msg->getNumberOfBlocksFast(_PREHASH_InventoryData);
|
|
for(S32 i = 0; i < num_blocks; ++i)
|
|
{
|
|
titem->unpackMessage(msg, _PREHASH_InventoryData, i);
|
|
if (!(titem->getUUID().isNull()))
|
|
{
|
|
//we don't do anything with null keys
|
|
mAdded.push_back(titem->getUUID());
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// LLAssetIDMatches
|
|
///----------------------------------------------------------------------------
|
|
bool LLAssetIDMatches ::operator()(LLInventoryCategory* cat, LLInventoryItem* item)
|
|
{
|
|
return (item && item->getAssetUUID() == mAssetID);
|
|
}
|
|
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// LLLinkedItemIDMatches
|
|
///----------------------------------------------------------------------------
|
|
bool LLLinkedItemIDMatches::operator()(LLInventoryCategory* cat, LLInventoryItem* item)
|
|
{
|
|
return (item &&
|
|
(item->getIsLinkType()) &&
|
|
(item->getLinkedUUID() == mBaseItemID)); // A linked item's assetID will be the compared-to item's itemID.
|
|
}
|
|
|
|
///----------------------------------------------------------------------------
|
|
/// Local function definitions
|
|
///----------------------------------------------------------------------------
|
|
|
|
|
|
/*
|
|
BOOL decompress_file(const char* src_filename, const char* dst_filename)
|
|
{
|
|
BOOL rv = FALSE;
|
|
gzFile src = NULL;
|
|
U8* buffer = NULL;
|
|
LLFILE* dst = NULL;
|
|
S32 bytes = 0;
|
|
const S32 DECOMPRESS_BUFFER_SIZE = 32000;
|
|
|
|
// open the files
|
|
src = gzopen(src_filename, "rb");
|
|
if(!src) goto err_decompress;
|
|
dst = LLFile::fopen(dst_filename, "wb");
|
|
if(!dst) goto err_decompress;
|
|
|
|
// decompress.
|
|
buffer = new U8[DECOMPRESS_BUFFER_SIZE + 1];
|
|
|
|
do
|
|
{
|
|
bytes = gzread(src, buffer, DECOMPRESS_BUFFER_SIZE);
|
|
if (bytes < 0)
|
|
{
|
|
goto err_decompress;
|
|
}
|
|
|
|
fwrite(buffer, bytes, 1, dst);
|
|
} while(gzeof(src) == 0);
|
|
|
|
// success
|
|
rv = TRUE;
|
|
|
|
err_decompress:
|
|
if(src != NULL) gzclose(src);
|
|
if(buffer != NULL) delete[] buffer;
|
|
if(dst != NULL) fclose(dst);
|
|
return rv;
|
|
}
|
|
*/
|