/** 
 * @file llappearancemgr.cpp
 * @brief Manager for initiating appearance changes on the viewer
 *
 * $LicenseInfo:firstyear=2004&license=viewergpl$
 * 
 * Copyright (c) 2004-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 "llappearancemgr.h"
#include "llinventorymodel.h"
#include "llnotifications.h"
#include "llgesturemgr.h"
#include "llinventorybridge.h"
#include "llwearablelist.h"
#include "llagentwearables.h"
#include "llagent.h"
#include "llvoavatar.h"
#include "llvoavatarself.h"
#include "llviewerregion.h"
#include "llfloatercustomize.h"

class LLWearInventoryCategoryCallback : public LLInventoryCallback
{
public:
	LLWearInventoryCategoryCallback(const LLUUID& cat_id, bool append)
	{
		mCatID = cat_id;
		mAppend = append;
	}
	void fire(const LLUUID& item_id)
	{
		/*
		 * Do nothing.  We only care about the destructor
		 *
		 * The reason for this is that this callback is used in a hack where the
		 * same callback is given to dozens of items, and the destructor is called
		 * after the last item has fired the event and dereferenced it -- if all
		 * the events actually fire!
		 */
	}

protected:
	~LLWearInventoryCategoryCallback()
	{
		// Is the destructor called by ordinary dereference, or because the app's shutting down?
		// If the inventory callback manager goes away, we're shutting down, no longer want the callback.
		if( LLInventoryCallbackManager::is_instantiated() )
		{
			LLAppearanceManager::wearInventoryCategoryOnAvatar(gInventory.getCategory(mCatID), mAppend);
		}
		else
		{
			llwarns << "Dropping unhandled LLWearInventoryCategoryCallback" << llendl;
		}
	}

private:
	LLUUID mCatID;
	bool mAppend;
};

class LLOutfitObserver : public LLInventoryFetchObserver
{
public:
	LLOutfitObserver(const LLUUID& cat_id, bool copy_items, bool append) :
		mCatID(cat_id),
		mCopyItems(copy_items),
		mAppend(append)
	{}
	~LLOutfitObserver() {}
	virtual void done(); //public

protected:
	LLUUID mCatID;
	bool mCopyItems;
	bool mAppend;
};

void LLOutfitObserver::done()
{
	// We now have an outfit ready to be copied to agent inventory. Do
	// it, and wear that outfit normally.
	if(mCopyItems)
	{
		LLInventoryCategory* cat = gInventory.getCategory(mCatID);
		std::string name;
		if(!cat)
		{
			// should never happen.
			name = "New Outfit";
		}
		else
		{
			name = cat->getName();
		}
		LLViewerInventoryItem* item = NULL;
		item_ref_t::iterator it = mComplete.begin();
		item_ref_t::iterator end = mComplete.end();
		LLUUID pid;
		for(; it < end; ++it)
		{
			item = (LLViewerInventoryItem*)gInventory.getItem(*it);
			if(item)
			{
				if(LLInventoryType::IT_GESTURE == item->getInventoryType())
				{
					pid = gInventory.findCategoryUUIDForType(LLAssetType::AT_GESTURE);
				}
				else
				{
					pid = gInventory.findCategoryUUIDForType(LLAssetType::AT_CLOTHING);
				}
				break;
			}
		}
		if(pid.isNull())
		{
			pid = gInventory.getRootFolderID();
		}
		
		LLUUID cat_id = gInventory.createNewCategory(
			pid,
			LLAssetType::AT_NONE,
			name);
		mCatID = cat_id;
		LLPointer<LLInventoryCallback> cb = new LLWearInventoryCategoryCallback(mCatID, mAppend);
		it = mComplete.begin();
		for(; it < end; ++it)
		{
			item = (LLViewerInventoryItem*)gInventory.getItem(*it);
			if(item)
			{
				copy_inventory_item(
					gAgent.getID(),
					item->getPermissions().getOwner(),
					item->getUUID(),
					cat_id,
					std::string(),
					cb);
			}
		}
	}
	else
	{
		// Wear the inventory category.
		LLAppearanceManager::wearInventoryCategoryOnAvatar(gInventory.getCategory(mCatID), mAppend);
	}
}

class LLOutfitFetch : public LLInventoryFetchDescendentsObserver
{
public:
	LLOutfitFetch(bool copy_items, bool append) : mCopyItems(copy_items), mAppend(append) {}
	~LLOutfitFetch() {}
	virtual void done();
protected:
	bool mCopyItems;
	bool mAppend;
};

void LLOutfitFetch::done()
{
	// What we do here is get the complete information on the items in
	// the library, and set up an observer that will wait for that to
	// happen.
	LLInventoryModel::cat_array_t cat_array;
	LLInventoryModel::item_array_t item_array;
	gInventory.collectDescendents(mCompleteFolders.front(),
								  cat_array,
								  item_array,
								  LLInventoryModel::EXCLUDE_TRASH);
	S32 count = item_array.count();
	if(!count)
	{
		llwarns << "Nothing fetched in category " << mCompleteFolders.front()
				<< llendl;
		//dec_busy_count();
		gInventory.removeObserver(this);
		delete this;
		return;
	}

	LLOutfitObserver* outfit_observer = new LLOutfitObserver(mCompleteFolders.front(), mCopyItems, mAppend);
	LLInventoryFetchObserver::item_ref_t ids;
	for(S32 i = 0; i < count; ++i)
	{
		ids.push_back(item_array.get(i)->getUUID());
	}

	// clean up, and remove this as an observer since the call to the
	// outfit could notify observers and throw us into an infinite
	// loop.
	//dec_busy_count();
	gInventory.removeObserver(this);
	delete this;

	// increment busy count and either tell the inventory to check &
	// call done, or add this object to the inventory for observation.
	//inc_busy_count();

	// do the fetch
	outfit_observer->fetchItems(ids);
	if(outfit_observer->isEverythingComplete())
	{
		// everything is already here - call done.
		outfit_observer->done();
	}
	else
	{
		// it's all on it's way - add an observer, and the inventory
		// will call done for us when everything is here.
		gInventory.addObserver(outfit_observer);
	}
}

class LLUpdateAppearanceOnCount: public LLInventoryCallback
{
public:
	LLUpdateAppearanceOnCount(S32 count):
		mCount(count)
	{
	}

	virtual ~LLUpdateAppearanceOnCount()
	{
	}

	/* virtual */ void fire(const LLUUID& inv_item)
	{
		mCount--;
		if (mCount==0)
		{
			done();
		}
	}

	void done()
	{
		LLAppearanceManager::updateAppearanceFromCOF();
	}
private:
	S32 mCount;
};

struct LLFoundData
{
	LLFoundData(const LLUUID& item_id,
				const LLUUID& asset_id,
				const std::string& name,
				LLAssetType::EType asset_type) :
		mItemID(item_id),
		mAssetID(asset_id),
		mName(name),
		mAssetType(asset_type),
		mWearable( NULL ) {}
	
	LLUUID mItemID;
	LLUUID mAssetID;
	std::string mName;
	LLAssetType::EType mAssetType;
	LLWearable* mWearable;
};

	
struct LLWearableHoldingPattern
{
	LLWearableHoldingPattern() : mResolved(0) {}
	~LLWearableHoldingPattern()
	{
		for_each(mFoundList.begin(), mFoundList.end(), DeletePointer());
		mFoundList.clear();
	}
	typedef std::list<LLFoundData*> found_list_t;
	found_list_t mFoundList;
	S32 mResolved;
	bool append;
};


void removeDuplicateItems(LLInventoryModel::item_array_t& dst, const LLInventoryModel::item_array_t& src)
{
	LLInventoryModel::item_array_t new_dst;
	std::set<LLUUID> mark_inventory;
	std::set<LLUUID> mark_asset;

	S32 inventory_dups = 0;
	S32 asset_dups = 0;
	
	for (LLInventoryModel::item_array_t::const_iterator src_pos = src.begin();
		  src_pos != src.end();
		  ++src_pos)
	{
		LLUUID src_item_id = (*src_pos)->getLinkedUUID();
		mark_inventory.insert(src_item_id);
		LLUUID src_asset_id = (*src_pos)->getAssetUUID();
		mark_asset.insert(src_asset_id);
	}

	for (LLInventoryModel::item_array_t::const_iterator dst_pos = dst.begin();
		  dst_pos != dst.end();
		  ++dst_pos)
	{
		LLUUID dst_item_id = (*dst_pos)->getLinkedUUID();

		if (mark_inventory.find(dst_item_id) == mark_inventory.end())
		{
		}
		else
		{
			inventory_dups++;
		}

		LLUUID dst_asset_id = (*dst_pos)->getAssetUUID();

		if (mark_asset.find(dst_asset_id) == mark_asset.end())
		{
			// Item is not already present in COF.
			new_dst.put(*dst_pos);
			mark_asset.insert(dst_item_id);
		}
		else
		{
			asset_dups++;
		}
	}
	llinfos << "removeDups, original " << dst.count() << " final " << new_dst.count()
			<< " inventory dups " << inventory_dups << " asset_dups " << asset_dups << llendl;
	
	dst = new_dst;
}


/* static */ LLUUID LLAppearanceManager::getCOF()
{
	return gInventory.findCategoryUUIDForType(LLAssetType::AT_CURRENT_OUTFIT);
}

// Update appearance from outfit folder.
/* static */ void LLAppearanceManager::changeOutfit(bool proceed, const LLUUID& category, bool append, bool follow_folder_links)
{
	if (!proceed)
		return;
	
	updateCOFFromOutfit(category, append, follow_folder_links);
}

// Update COF contents from outfit folder.
/* static */ void LLAppearanceManager::updateCOFFromOutfit(const LLUUID& category, bool append, bool follow_folder_links)
{
	// BAP consolidate into one "get all 3 types of descendents" function, use both places.
	LLInventoryModel::item_array_t wear_items;
	LLInventoryModel::item_array_t	obj_items;
	LLInventoryModel::item_array_t	gest_items;
	getUserDescendents(category, wear_items, obj_items, gest_items, follow_folder_links);

	// Find all the wearables that are in the category's subtree.	
	lldebugs << "updateCOFFromOutfit()" << llendl;
	if( !wear_items.count() && !obj_items.count() && !gest_items.count())
	{
		LLNotifications::instance().add("CouldNotPutOnOutfit");
		return;
	}
		
	const LLUUID &current_outfit_id = gInventory.findCategoryUUIDForType(LLAssetType::AT_CURRENT_OUTFIT);
	// Processes that take time should show the busy cursor
	//inc_busy_count();
		
	LLInventoryModel::cat_array_t cof_cats;
	LLInventoryModel::item_array_t cof_items;
	gInventory.collectDescendents(current_outfit_id, cof_cats, cof_items,
								  LLInventoryModel::EXCLUDE_TRASH);
	if (append)
	{
		// Remove duplicates
		removeDuplicateItems(wear_items, cof_items);
		removeDuplicateItems(obj_items, cof_items);
		removeDuplicateItems(gest_items, cof_items);
	}
			

	if (wear_items.count() > 0 || obj_items.count() > 0)
	{
		if (!append) 
		{
			// Remove all current outfit folder links if we're now replacing the contents.
			for (S32 i = 0; i < cof_items.count(); ++i)
			{
				gInventory.purgeObject(cof_items.get(i)->getUUID());
			}
		}
	}

	// BAP should we just link all contents, rather than restricting to these 3 types?

	S32 total_links = gest_items.count() + wear_items.count() + obj_items.count();
	LLPointer<LLUpdateAppearanceOnCount> link_waiter = new LLUpdateAppearanceOnCount(total_links);
	
	// Link all gestures in this folder
	if (gest_items.count() > 0)
	{
		llinfos << "Linking " << gest_items.count() << " gestures" << llendl;
		for (S32 i = 0; i < gest_items.count(); ++i)
		{
			const LLInventoryItem* gest_item = gest_items.get(i).get();
			link_inventory_item(gAgent.getID(), gest_item->getLinkedUUID(), current_outfit_id,
								gest_item->getName(),
								LLAssetType::AT_LINK, link_waiter);
		}
	}

	// Link all wearables
	if(wear_items.count() > 0)
	{
		llinfos << "Linking " << wear_items.count() << " wearables" << llendl;
		for(S32 i = 0; i < wear_items.count(); ++i)
		{
			// Populate the current outfit folder with links to the newly added wearables
			const LLInventoryItem* wear_item = wear_items.get(i).get();
			link_inventory_item(gAgent.getID(), 
								wear_item->getLinkedUUID(), // If this item is a link, then we'll use the linked item's UUID.
								current_outfit_id, 
								wear_item->getName(),
								LLAssetType::AT_LINK, 
								link_waiter);
		}
	}

	// Link all attachments.
	if( obj_items.count() > 0 )
	{
		llinfos << "Linking " << obj_items.count() << " attachments" << llendl;
		LLVOAvatar* avatar = gAgent.getAvatarObject();
		if( avatar )
		{
			for(S32 i = 0; i < obj_items.count(); ++i)
			{
				const LLInventoryItem* obj_item = obj_items.get(i).get();
				link_inventory_item(gAgent.getID(), 
									obj_item->getLinkedUUID(), // If this item is a link, then we'll use the linked item's UUID.
									current_outfit_id, 
									obj_item->getName(),
									LLAssetType::AT_LINK, link_waiter);
			}
		}
	}

	// In the particular case that we're switching to a different outfit,
	// create a link to the folder that we wore.
	LLViewerInventoryCategory* catp = gInventory.getCategory(category);
	if (!append && catp && catp->getPreferredType() == LLAssetType::AT_OUTFIT)
	{
		link_inventory_item(gAgent.getID(), category, current_outfit_id, catp->getName(),
							LLAssetType::AT_LINK_FOLDER, LLPointer<LLInventoryCallback>(NULL));
	}
}

/* static */
void LLAppearanceManager::onWearableAssetFetch(LLWearable* wearable, void* data)
{
	LLWearableHoldingPattern* holder = (LLWearableHoldingPattern*)data;
	bool append = holder->append;
	
	if(wearable)
	{
		for (LLWearableHoldingPattern::found_list_t::iterator iter = holder->mFoundList.begin();
			 iter != holder->mFoundList.end(); ++iter)
		{
			LLFoundData* data = *iter;
			if(wearable->getAssetID() == data->mAssetID)
			{
				data->mWearable = wearable;
				break;
			}
		}
	}
	holder->mResolved += 1;
	if(holder->mResolved >= (S32)holder->mFoundList.size())
	{
		LLAppearanceManager::updateAgentWearables(holder, append);
	}
}

/* static */
void LLAppearanceManager::updateAgentWearables(LLWearableHoldingPattern* holder, bool append)
{
	lldebugs << "updateAgentWearables()" << llendl;
	LLInventoryItem::item_array_t items;
	LLDynamicArray< LLWearable* > wearables;

	// For each wearable type, find the first instance in the category
	// that we recursed through.
	for( S32 i = 0; i < WT_COUNT; i++ )
	{
		for (LLWearableHoldingPattern::found_list_t::iterator iter = holder->mFoundList.begin();
			 iter != holder->mFoundList.end(); ++iter)
		{
			LLFoundData* data = *iter;
			LLWearable* wearable = data->mWearable;
			if( wearable && ((S32)wearable->getType() == i) )
			{
				LLViewerInventoryItem* item;
				item = (LLViewerInventoryItem*)gInventory.getItem(data->mItemID);
				if( item && (item->getAssetUUID() == wearable->getAssetID()) )
				{
					items.put(item);
					wearables.put(wearable);
				}
				break;
			}
		}
	}

	if(wearables.count() > 0)
	{
		gAgentWearables.setWearableOutfit(items, wearables, !append);
		gInventory.notifyObservers();
	}

	delete holder;

//	dec_busy_count();
}

/* static */ void LLAppearanceManager::updateAppearanceFromCOF()
{
	bool follow_folder_links = true;
	LLUUID current_outfit_id = getCOF();

	// Find all the wearables that are in the COF's subtree.	
	lldebugs << "LLAppearanceManager::updateFromCOF()" << llendl;
	LLInventoryModel::item_array_t wear_items;
	LLInventoryModel::item_array_t obj_items;
	LLInventoryModel::item_array_t gest_items;
	getUserDescendents(current_outfit_id, wear_items, obj_items, gest_items, follow_folder_links);
	
	if( !wear_items.count() && !obj_items.count() && !gest_items.count())
	{
		LLNotifications::instance().add("CouldNotPutOnOutfit");
		return;
	}
		
	// Processes that take time should show the busy cursor
	//inc_busy_count(); // BAP this is currently a no-op in llinventorybridge.cpp - do we need it?
		
	// Activate all gestures in this folder
	if (gest_items.count() > 0)
	{
		llinfos << "Activating " << gest_items.count() << " gestures" << llendl;

		LLGestureManager::instance().activateGestures(gest_items);

		// Update the inventory item labels to reflect the fact
		// they are active.
		LLViewerInventoryCategory* catp = gInventory.getCategory(current_outfit_id);
		if (catp)
		{
			gInventory.updateCategory(catp);
			gInventory.notifyObservers();
		}
	}

	if(wear_items.count() > 0)
	{
		// Note: can't do normal iteration, because if all the
		// wearables can be resolved immediately, then the
		// callback will be called (and this object deleted)
		// before the final getNextData().
		LLWearableHoldingPattern* holder = new LLWearableHoldingPattern;
		LLFoundData* found;
		LLDynamicArray<LLFoundData*> found_container;
		for(S32 i = 0; i  < wear_items.count(); ++i)
		{
			found = new LLFoundData(wear_items.get(i)->getUUID(),
									wear_items.get(i)->getAssetUUID(),
									wear_items.get(i)->getName(),
									wear_items.get(i)->getType());
			holder->mFoundList.push_front(found);
			found_container.put(found);
		}
		for(S32 i = 0; i < wear_items.count(); ++i)
		{
			holder->append = false;
			found = found_container.get(i);
				
			// Fetch the wearables about to be worn.
			LLWearableList::instance().getAsset(found->mAssetID,
												found->mName,
												found->mAssetType,
												LLAppearanceManager::onWearableAssetFetch,
												(void*)holder);
		}
	}


	//If the folder doesn't contain only gestures, take off all attachments.
	if (!(wear_items.count() == 0 && obj_items.count() == 0 && gest_items.count() > 0) )
	{
		LLAgentWearables::userRemoveAllAttachments(NULL);
	}

	if( obj_items.count() > 0 )
	{
		// We've found some attachments.  Add these.
		LLVOAvatar* avatar = gAgent.getAvatarObject();
		if( avatar )
		{
			LLAgentWearables::userAttachMultipleAttachments(obj_items);
		}
	}
}

/* static */ void LLAppearanceManager::getUserDescendents(const LLUUID& category, 
														  LLInventoryModel::item_array_t& wear_items,
														  LLInventoryModel::item_array_t& obj_items,
														  LLInventoryModel::item_array_t& gest_items,
														  bool follow_folder_links)
{
	LLInventoryModel::cat_array_t wear_cats;
	LLFindWearables is_wearable;
	gInventory.collectDescendentsIf(category,
									wear_cats,
									wear_items,
									LLInventoryModel::EXCLUDE_TRASH,
									is_wearable,
									follow_folder_links);

	LLInventoryModel::cat_array_t obj_cats;
	LLIsType is_object( LLAssetType::AT_OBJECT );
	gInventory.collectDescendentsIf(category,
									obj_cats,
									obj_items,
									LLInventoryModel::EXCLUDE_TRASH,
									is_object,
									follow_folder_links);

	// Find all gestures in this folder
	LLInventoryModel::cat_array_t gest_cats;
	LLIsType is_gesture( LLAssetType::AT_GESTURE );
	gInventory.collectDescendentsIf(category,
									gest_cats,
									gest_items,
									LLInventoryModel::EXCLUDE_TRASH,
									is_gesture,
									follow_folder_links);
}

void LLAppearanceManager::wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append)
{
	if(!category) return;

	lldebugs << "wearInventoryCategory( " << category->getName()
			 << " )" << llendl;
	// What we do here is get the complete information on the items in
	// the inventory, and set up an observer that will wait for that to
	// happen.
	LLOutfitFetch* outfit_fetcher = new LLOutfitFetch(copy, append);
	LLInventoryFetchDescendentsObserver::folder_ref_t folders;
	folders.push_back(category->getUUID());
	outfit_fetcher->fetchDescendents(folders);
	//inc_busy_count();
	if(outfit_fetcher->isEverythingComplete())
	{
		// everything is already here - call done.
		outfit_fetcher->done();
	}
	else
	{
		// it's all on it's way - add an observer, and the inventory
		// will call done for us when everything is here.
		gInventory.addObserver(outfit_fetcher);
	}
}

// *NOTE: hack to get from avatar inventory to avatar
/* static */
void LLAppearanceManager::wearInventoryCategoryOnAvatar( LLInventoryCategory* category, bool append )
{
	// Avoid unintentionally overwriting old wearables.  We have to do
	// this up front to avoid having to deal with the case of multiple
	// wearables being dirty.
	if(!category) return;
	lldebugs << "wearInventoryCategoryOnAvatar( " << category->getName()
			 << " )" << llendl;
			 	
	bool follow_folder_links = (category->getPreferredType() == LLAssetType::AT_CURRENT_OUTFIT || category->getPreferredType() == LLAssetType::AT_OUTFIT ); 
	if( gFloaterCustomize )
	{
		gFloaterCustomize->askToSaveIfDirty(boost::bind(LLAppearanceManager::changeOutfit, _1, category->getUUID(), append, follow_folder_links));
	}
	else
	{
		LLAppearanceManager::changeOutfit(TRUE, category->getUUID(), append, follow_folder_links );
	}
}

/* static */
void LLAppearanceManager::wearOutfitByName(const std::string& name)
{
	llinfos << "Wearing category " << name << llendl;
	//inc_busy_count();

	LLInventoryModel::cat_array_t cat_array;
	LLInventoryModel::item_array_t item_array;
	LLNameCategoryCollector has_name(name);
	gInventory.collectDescendentsIf(gInventory.getRootFolderID(),
									cat_array,
									item_array,
									LLInventoryModel::EXCLUDE_TRASH,
									has_name);
	bool copy_items = false;
	LLInventoryCategory* cat = NULL;
	if (cat_array.count() > 0)
	{
		// Just wear the first one that matches
		cat = cat_array.get(0);
	}
	else
	{
		gInventory.collectDescendentsIf(LLUUID::null,
										cat_array,
										item_array,
										LLInventoryModel::EXCLUDE_TRASH,
										has_name);
		if(cat_array.count() > 0)
		{
			cat = cat_array.get(0);
			copy_items = true;
		}
	}

	if(cat)
	{
		LLAppearanceManager::wearInventoryCategory(cat, copy_items, false);
	}
	else
	{
		llwarns << "Couldn't find outfit " <<name<< " in wearOutfitByName()"
				<< llendl;
	}

	//dec_busy_count();
}

void LLAppearanceManager::wearItem( LLInventoryItem* item, bool do_update )
{
	// BAP add check for already in COF.
	LLPointer<LLInventoryCallback> cb = do_update ? new ModifiedCOFCallback : 0;
	link_inventory_item( gAgent.getID(),
						 item->getLinkedUUID(),
						 getCOF(),
						 item->getName(),
						 LLAssetType::AT_LINK,
						 cb);
}

void LLAppearanceManager::wearEnsemble( LLInventoryCategory* cat, bool do_update )
{
	// BAP add check for already in COF.
	LLPointer<LLInventoryCallback> cb = do_update ? new ModifiedCOFCallback : 0;
	link_inventory_item( gAgent.getID(),
						 cat->getLinkedUUID(),
						 getCOF(),
						 cat->getName(),
						 LLAssetType::AT_LINK_FOLDER,
						 cb);
}