diff options
Diffstat (limited to 'indra/newview/llappearancemgr.cpp')
-rwxr-xr-x[-rw-r--r--] | indra/newview/llappearancemgr.cpp | 2889 |
1 files changed, 2053 insertions, 836 deletions
diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index f40fed5ad3..b3317e937e 100644..100755 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -26,6 +26,7 @@ #include "llviewerprecompiledheaders.h" +#include <boost/lexical_cast.hpp> #include "llaccordionctrltab.h" #include "llagent.h" #include "llagentcamera.h" @@ -34,6 +35,7 @@ #include "llattachmentsmgr.h" #include "llcommandhandler.h" #include "lleventtimer.h" +#include "llfloatersidepanelcontainer.h" #include "llgesturemgr.h" #include "llinventorybridge.h" #include "llinventoryfunctions.h" @@ -43,12 +45,26 @@ #include "lloutfitslist.h" #include "llselectmgr.h" #include "llsidepanelappearance.h" -#include "llsidetray.h" #include "llviewerobjectlist.h" #include "llvoavatar.h" #include "llvoavatarself.h" #include "llviewerregion.h" #include "llwearablelist.h" +#include "llsdutil.h" +#include "llsdserialize.h" +#include "llhttpretrypolicy.h" +#include "llaisapi.h" + +#if LL_MSVC +// disable boost::lexical_cast warning +#pragma warning (disable:4702) +#endif + +std::string self_av_string() +{ + // On logout gAgentAvatarp can already be invalid + return isAgentAvatarValid() ? gAgentAvatarp->avString() : ""; +} // RAII thingy to guarantee that a variable gets reset when the Setter // goes out of scope. More general utility would be handy - TODO: @@ -110,7 +126,13 @@ public: { // support secondlife:///app/appearance/show, but for now we just // make all secondlife:///app/appearance SLapps behave this way - LLSideTray::getInstance()->showPanel("sidepanel_appearance", LLSD()); + if (!LLUI::sSettingGroups["config"]->getBOOL("EnableAppearance")) + { + LLNotificationsUtil::add("NoAppearance", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); + return true; + } + + LLFloaterSidePanelContainer::showPanel("appearance", LLSD()); return true; } }; @@ -128,94 +150,349 @@ LLUUID findDescendentCategoryIDByName(const LLUUID& parent_id, const std::string item_array, LLInventoryModel::EXCLUDE_TRASH, has_name); - if (0 == cat_array.count()) + if (0 == cat_array.size()) return LLUUID(); else { - LLViewerInventoryCategory *cat = cat_array.get(0); + LLViewerInventoryCategory *cat = cat_array.at(0); if (cat) return cat->getUUID(); else { - llwarns << "null cat" << llendl; + LL_WARNS() << "null cat" << LL_ENDL; return LLUUID(); } } } -class LLWearInventoryCategoryCallback : public LLInventoryCallback +// We want this to be much lower (e.g. 15.0 is usually fine), bumping +// up for now until we can diagnose some cases of very slow response +// to requests. +const F32 DEFAULT_RETRY_AFTER_INTERVAL = 300.0; + +// Given the current back-end problems, retrying is causing too many +// duplicate items. Bump this back to 2 once they are resolved (or can +// leave at 0 if the operations become actually reliable). +const S32 DEFAULT_MAX_RETRIES = 0; + +class LLCallAfterInventoryBatchMgr: public LLEventTimer { public: - LLWearInventoryCategoryCallback(const LLUUID& cat_id, bool append) + LLCallAfterInventoryBatchMgr(const LLUUID& dst_cat_id, + const std::string& phase_name, + nullary_func_t on_completion_func, + nullary_func_t on_failure_func = no_op, + F32 retry_after = DEFAULT_RETRY_AFTER_INTERVAL, + S32 max_retries = DEFAULT_MAX_RETRIES + ): + mDstCatID(dst_cat_id), + mTrackingPhase(phase_name), + mOnCompletionFunc(on_completion_func), + mOnFailureFunc(on_failure_func), + mRetryAfter(retry_after), + mMaxRetries(max_retries), + mPendingRequests(0), + mFailCount(0), + mCompletionOrFailureCalled(false), + mRetryCount(0), + LLEventTimer(5.0) + { + if (!mTrackingPhase.empty()) + { + selfStartPhase(mTrackingPhase); + } + } + + void addItems(LLInventoryModel::item_array_t& src_items) { - mCatID = cat_id; - mAppend = append; + for (LLInventoryModel::item_array_t::const_iterator it = src_items.begin(); + it != src_items.end(); + ++it) + { + LLViewerInventoryItem* item = *it; + llassert(item); + addItem(item->getUUID()); + } } - void fire(const LLUUID& item_id) + + // Request or re-request operation for specified item. + void addItem(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! - */ + LL_DEBUGS("Avatar") << "item_id " << item_id << LL_ENDL; + if (!requestOperation(item_id)) + { + LL_DEBUGS("Avatar") << "item_id " << item_id << " requestOperation false, skipping" << LL_ENDL; + return; + } + + mPendingRequests++; + // On a re-request, this will reset the timer. + mWaitTimes[item_id] = LLTimer(); + if (mRetryCounts.find(item_id) == mRetryCounts.end()) + { + mRetryCounts[item_id] = 0; + } + else + { + mRetryCounts[item_id]++; + } } -protected: - ~LLWearInventoryCategoryCallback() + virtual bool requestOperation(const LLUUID& item_id) = 0; + + void onOp(const LLUUID& src_id, const LLUUID& dst_id, LLTimer timestamp) { - llinfos << "done all inventory callbacks" << llendl; + if (ll_frand() < gSavedSettings.getF32("InventoryDebugSimulateLateOpRate")) + { + LL_WARNS() << "Simulating late operation by punting handling to later" << LL_ENDL; + doAfterInterval(boost::bind(&LLCallAfterInventoryBatchMgr::onOp,this,src_id,dst_id,timestamp), + mRetryAfter); + return; + } + mPendingRequests--; + F32 elapsed = timestamp.getElapsedTimeF32(); + LL_DEBUGS("Avatar") << "op done, src_id " << src_id << " dst_id " << dst_id << " after " << elapsed << " seconds" << LL_ENDL; + if (mWaitTimes.find(src_id) == mWaitTimes.end()) + { + // No longer waiting for this item - either serviced + // already or gave up after too many retries. + LL_WARNS() << "duplicate or late operation, src_id " << src_id << "dst_id " << dst_id + << " elapsed " << elapsed << " after end " << (S32) mCompletionOrFailureCalled << LL_ENDL; + } + mTimeStats.push(elapsed); + mWaitTimes.erase(src_id); + if (mWaitTimes.empty() && !mCompletionOrFailureCalled) + { + onCompletionOrFailure(); + } + } + + void onCompletionOrFailure() + { + assert (!mCompletionOrFailureCalled); + mCompletionOrFailureCalled = true; - // 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() ) + // Will never call onCompletion() if any item has been flagged as + // a failure - otherwise could wind up with corrupted + // outfit, involuntary nudity, etc. + reportStats(); + if (!mTrackingPhase.empty()) + { + selfStopPhase(mTrackingPhase); + } + if (!mFailCount) { - LLAppearanceMgr::instance().wearInventoryCategoryOnAvatar(gInventory.getCategory(mCatID), mAppend); + onCompletion(); } else { - llwarns << "Dropping unhandled LLWearInventoryCategoryCallback" << llendl; + onFailure(); } } -private: - LLUUID mCatID; - bool mAppend; -}; + void onFailure() + { + LL_INFOS() << "failed" << LL_ENDL; + mOnFailureFunc(); + } + + void onCompletion() + { + LL_INFOS() << "done" << LL_ENDL; + mOnCompletionFunc(); + } + + // virtual + // Will be deleted after returning true - only safe to do this if all callbacks have fired. + BOOL tick() + { + // mPendingRequests will be zero if all requests have been + // responded to. mWaitTimes.empty() will be true if we have + // received at least one reply for each UUID. If requests + // have been dropped and retried, these will not necessarily + // be the same. Only safe to return true if all requests have + // been serviced, since it will result in this object being + // deleted. + bool all_done = (mPendingRequests==0); + + if (!mWaitTimes.empty()) + { + LL_WARNS() << "still waiting on " << mWaitTimes.size() << " items" << LL_ENDL; + for (std::map<LLUUID,LLTimer>::iterator it = mWaitTimes.begin(); + it != mWaitTimes.end();) + { + // Use a copy of iterator because it may be erased/invalidated. + std::map<LLUUID,LLTimer>::iterator curr_it = it; + ++it; + + F32 time_waited = curr_it->second.getElapsedTimeF32(); + S32 retries = mRetryCounts[curr_it->first]; + if (time_waited > mRetryAfter) + { + if (retries < mMaxRetries) + { + LL_DEBUGS("Avatar") << "Waited " << time_waited << + " for " << curr_it->first << ", retrying" << LL_ENDL; + mRetryCount++; + addItem(curr_it->first); + } + else + { + LL_WARNS() << "Giving up on " << curr_it->first << " after too many retries" << LL_ENDL; + mWaitTimes.erase(curr_it); + mFailCount++; + } + } + if (mWaitTimes.empty()) + { + onCompletionOrFailure(); + } + } + } + return all_done; + } -//Inventory callback updating "dirty" state when destroyed -class LLUpdateDirtyState: public LLInventoryCallback + void reportStats() + { + LL_DEBUGS("Avatar") << "Phase: " << mTrackingPhase << LL_ENDL; + LL_DEBUGS("Avatar") << "mFailCount: " << mFailCount << LL_ENDL; + LL_DEBUGS("Avatar") << "mRetryCount: " << mRetryCount << LL_ENDL; + LL_DEBUGS("Avatar") << "Times: n " << mTimeStats.getCount() << " min " << mTimeStats.getMinValue() << " max " << mTimeStats.getMaxValue() << LL_ENDL; + LL_DEBUGS("Avatar") << "Mean " << mTimeStats.getMean() << " stddev " << mTimeStats.getStdDev() << LL_ENDL; + } + + virtual ~LLCallAfterInventoryBatchMgr() + { + LL_DEBUGS("Avatar") << "deleting" << LL_ENDL; + } + +protected: + std::string mTrackingPhase; + std::map<LLUUID,LLTimer> mWaitTimes; + std::map<LLUUID,S32> mRetryCounts; + LLUUID mDstCatID; + nullary_func_t mOnCompletionFunc; + nullary_func_t mOnFailureFunc; + F32 mRetryAfter; + S32 mMaxRetries; + S32 mPendingRequests; + S32 mFailCount; + S32 mRetryCount; + bool mCompletionOrFailureCalled; + LLViewerStats::StatsAccumulator mTimeStats; +}; + +class LLCallAfterInventoryCopyMgr: public LLCallAfterInventoryBatchMgr { public: - LLUpdateDirtyState() {} - virtual ~LLUpdateDirtyState() + LLCallAfterInventoryCopyMgr(LLInventoryModel::item_array_t& src_items, + const LLUUID& dst_cat_id, + const std::string& phase_name, + nullary_func_t on_completion_func, + nullary_func_t on_failure_func = no_op, + F32 retry_after = DEFAULT_RETRY_AFTER_INTERVAL, + S32 max_retries = DEFAULT_MAX_RETRIES + ): + LLCallAfterInventoryBatchMgr(dst_cat_id, phase_name, on_completion_func, on_failure_func, retry_after, max_retries) + { + addItems(src_items); + sInstanceCount++; + } + + ~LLCallAfterInventoryCopyMgr() { - if (LLAppearanceMgr::instanceExists()) + sInstanceCount--; + } + + virtual bool requestOperation(const LLUUID& item_id) + { + LLViewerInventoryItem *item = gInventory.getItem(item_id); + llassert(item); + LL_DEBUGS("Avatar") << "copying item " << item_id << LL_ENDL; + if (ll_frand() < gSavedSettings.getF32("InventoryDebugSimulateOpFailureRate")) { - LLAppearanceMgr::getInstance()->updateIsDirty(); + LL_DEBUGS("Avatar") << "simulating failure by not sending request for item " << item_id << LL_ENDL; + return true; } + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + mDstCatID, + std::string(), + new LLBoostFuncInventoryCallback(boost::bind(&LLCallAfterInventoryBatchMgr::onOp,this,item_id,_1,LLTimer())) + ); + return true; } - virtual void fire(const LLUUID&) {} + + static S32 getInstanceCount() { return sInstanceCount; } + +private: + static S32 sInstanceCount; }; +S32 LLCallAfterInventoryCopyMgr::sInstanceCount = 0; -LLUpdateAppearanceOnDestroy::LLUpdateAppearanceOnDestroy(bool update_base_outfit_ordering): - mFireCount(0), - mUpdateBaseOrder(update_base_outfit_ordering) +class LLWearCategoryAfterCopy: public LLInventoryCallback { -} +public: + LLWearCategoryAfterCopy(bool append): + mAppend(append) + {} -LLUpdateAppearanceOnDestroy::~LLUpdateAppearanceOnDestroy() + // virtual + void fire(const LLUUID& id) + { + // Wear the inventory category. + LLInventoryCategory* cat = gInventory.getCategory(id); + LLAppearanceMgr::instance().wearInventoryCategoryOnAvatar(cat, mAppend); + } + +private: + bool mAppend; +}; + +class LLTrackPhaseWrapper : public LLInventoryCallback { - llinfos << "done update appearance on destroy" << llendl; - - if (!LLApp::isExiting()) +public: + LLTrackPhaseWrapper(const std::string& phase_name, LLPointer<LLInventoryCallback> cb = NULL): + mTrackingPhase(phase_name), + mCB(cb) { - LLAppearanceMgr::instance().updateAppearanceFromCOF(mUpdateBaseOrder); + selfStartPhase(mTrackingPhase); } + + // virtual + void fire(const LLUUID& id) + { + if (mCB) + { + mCB->fire(id); + } + } + + // virtual + ~LLTrackPhaseWrapper() + { + selfStopPhase(mTrackingPhase); + } + +protected: + std::string mTrackingPhase; + LLPointer<LLInventoryCallback> mCB; +}; + +LLUpdateAppearanceOnDestroy::LLUpdateAppearanceOnDestroy(bool enforce_item_restrictions, + bool enforce_ordering, + nullary_func_t post_update_func + ): + mFireCount(0), + mEnforceItemRestrictions(enforce_item_restrictions), + mEnforceOrdering(enforce_ordering), + mPostUpdateFunc(post_update_func) +{ + selfStartPhase("update_appearance_on_destroy"); } void LLUpdateAppearanceOnDestroy::fire(const LLUUID& inv_item) @@ -223,11 +500,60 @@ void LLUpdateAppearanceOnDestroy::fire(const LLUUID& inv_item) LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(inv_item); const std::string item_name = item ? item->getName() : "ITEM NOT FOUND"; #ifndef LL_RELEASE_FOR_DOWNLOAD - llinfos << "callback fired [ name:" << item_name << " UUID:" << inv_item << " count:" << mFireCount << " ] " << llendl; + LL_DEBUGS("Avatar") << self_av_string() << "callback fired [ name:" << item_name << " UUID:" << inv_item << " count:" << mFireCount << " ] " << LL_ENDL; #endif mFireCount++; } +LLUpdateAppearanceOnDestroy::~LLUpdateAppearanceOnDestroy() +{ + if (!LLApp::isExiting()) + { + // speculative fix for MAINT-1150 + LL_INFOS("Avatar") << self_av_string() << "done update appearance on destroy" << LL_ENDL; + + selfStopPhase("update_appearance_on_destroy"); + + LLAppearanceMgr::instance().updateAppearanceFromCOF(mEnforceItemRestrictions, + mEnforceOrdering, + mPostUpdateFunc); + } +} + +LLUpdateAppearanceAndEditWearableOnDestroy::LLUpdateAppearanceAndEditWearableOnDestroy(const LLUUID& item_id): + mItemID(item_id) +{ +} + +void edit_wearable_and_customize_avatar(LLUUID item_id) +{ + // Start editing the item if previously requested. + gAgentWearables.editWearableIfRequested(item_id); + + // TODO: camera mode may not be changed if a debug setting is tweaked + if( gAgentCamera.cameraCustomizeAvatar() ) + { + // If we're in appearance editing mode, the current tab may need to be refreshed + LLSidepanelAppearance *panel = dynamic_cast<LLSidepanelAppearance*>( + LLFloaterSidePanelContainer::getPanel("appearance")); + if (panel) + { + panel->showDefaultSubpart(); + } + } +} + +LLUpdateAppearanceAndEditWearableOnDestroy::~LLUpdateAppearanceAndEditWearableOnDestroy() +{ + if (!LLApp::isExiting()) + { + LLAppearanceMgr::instance().updateAppearanceFromCOF( + true,true, + boost::bind(edit_wearable_and_customize_avatar, mItemID)); + } +} + + struct LLFoundData { LLFoundData() : @@ -255,13 +581,15 @@ struct LLFoundData std::string mName; LLAssetType::EType mAssetType; LLWearableType::EType mWearableType; - LLWearable* mWearable; + LLViewerWearable* mWearable; bool mIsReplacement; }; class LLWearableHoldingPattern { + LOG_CLASS(LLWearableHoldingPattern); + public: LLWearableHoldingPattern(); ~LLWearableHoldingPattern(); @@ -277,7 +605,7 @@ public: void recoverMissingWearable(LLWearableType::EType type); void clearCOFLinksForMissingWearables(); - void onWearableAssetFetch(LLWearable *wearable); + void onWearableAssetFetch(LLViewerWearable *wearable); void onAllComplete(); typedef std::list<LLFoundData> found_list_t; @@ -289,6 +617,8 @@ public: bool isMostRecent(); void handleLateArrivals(); void resetTime(F32 timeout); + static S32 countActive() { return sActiveHoldingPatterns.size(); } + S32 index() { return mIndex; } private: found_list_t mFoundList; @@ -302,12 +632,15 @@ private: bool mFired; typedef std::set<LLWearableHoldingPattern*> type_set_hp; static type_set_hp sActiveHoldingPatterns; + static S32 sNextIndex; + S32 mIndex; bool mIsMostRecent; - std::set<LLWearable*> mLateArrivals; + std::set<LLViewerWearable*> mLateArrivals; bool mIsAllComplete; }; LLWearableHoldingPattern::type_set_hp LLWearableHoldingPattern::sActiveHoldingPatterns; +S32 LLWearableHoldingPattern::sNextIndex = 0; LLWearableHoldingPattern::LLWearableHoldingPattern(): mResolved(0), @@ -315,13 +648,13 @@ LLWearableHoldingPattern::LLWearableHoldingPattern(): mIsMostRecent(true), mIsAllComplete(false) { - if (sActiveHoldingPatterns.size()>0) + if (countActive()>0) { - llinfos << "Creating LLWearableHoldingPattern when " - << sActiveHoldingPatterns.size() - << " other attempts are active." - << " Flagging others as invalid." - << llendl; + LL_INFOS() << "Creating LLWearableHoldingPattern when " + << countActive() + << " other attempts are active." + << " Flagging others as invalid." + << LL_ENDL; for (type_set_hp::iterator it = sActiveHoldingPatterns.begin(); it != sActiveHoldingPatterns.end(); ++it) @@ -330,12 +663,20 @@ LLWearableHoldingPattern::LLWearableHoldingPattern(): } } + mIndex = sNextIndex++; sActiveHoldingPatterns.insert(this); + LL_DEBUGS("Avatar") << "HP " << index() << " created" << LL_ENDL; + selfStartPhase("holding_pattern"); } LLWearableHoldingPattern::~LLWearableHoldingPattern() { sActiveHoldingPatterns.erase(this); + if (isMostRecent()) + { + selfStopPhase("holding_pattern"); + } + LL_DEBUGS("Avatar") << "HP " << index() << " deleted" << LL_ENDL; } bool LLWearableHoldingPattern::isMostRecent() @@ -382,9 +723,10 @@ void LLWearableHoldingPattern::checkMissingWearables() { if (!isMostRecent()) { - llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl; + // runway why don't we actually skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; } - + std::vector<S32> found_by_type(LLWearableType::WT_COUNT,0); std::vector<S32> requested_by_type(LLWearableType::WT_COUNT,0); for (found_list_t::iterator it = getFoundList().begin(); it != getFoundList().end(); ++it) @@ -400,7 +742,7 @@ void LLWearableHoldingPattern::checkMissingWearables() { if (requested_by_type[type] > found_by_type[type]) { - llwarns << "got fewer wearables than requested, type " << type << ": requested " << requested_by_type[type] << ", found " << found_by_type[type] << llendl; + LL_WARNS() << self_av_string() << "got fewer wearables than requested, type " << type << ": requested " << requested_by_type[type] << ", found " << found_by_type[type] << LL_ENDL; } if (found_by_type[type] > 0) continue; @@ -417,11 +759,16 @@ void LLWearableHoldingPattern::checkMissingWearables() mTypesToRecover.insert(type); mTypesToLink.insert(type); recoverMissingWearable((LLWearableType::EType)type); - llwarns << "need to replace " << type << llendl; + LL_WARNS() << self_av_string() << "need to replace " << type << LL_ENDL; } } resetTime(60.0F); + + if (isMostRecent()) + { + selfStartPhase("get_missing_wearables_2"); + } if (!pollMissingWearables()) { doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollMissingWearables,this)); @@ -430,15 +777,21 @@ void LLWearableHoldingPattern::checkMissingWearables() void LLWearableHoldingPattern::onAllComplete() { + if (isAgentAvatarValid()) + { + gAgentAvatarp->outputRezTiming("Agent wearables fetch complete"); + } + if (!isMostRecent()) { - llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl; + // runway need to skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; } // Activate all gestures in this folder - if (mGestItems.count() > 0) + if (mGestItems.size() > 0) { - llinfos << "Activating " << mGestItems.count() << " gestures" << llendl; + LL_DEBUGS("Avatar") << self_av_string() << "Activating " << mGestItems.size() << " gestures" << LL_ENDL; LLGestureMgr::instance().activateGestures(mGestItems); @@ -454,15 +807,48 @@ void LLWearableHoldingPattern::onAllComplete() } } - // Update wearables. - llinfos << "Updating agent wearables with " << mResolved << " wearable items " << llendl; - LLAppearanceMgr::instance().updateAgentWearables(this, false); - - // Update attachments to match those requested. if (isAgentAvatarValid()) { - llinfos << "Updating " << mObjItems.count() << " attachments" << llendl; - LLAgentWearables::userUpdateAttachments(mObjItems); + LL_DEBUGS("Avatar") << self_av_string() << "Updating " << mObjItems.size() << " attachments" << LL_ENDL; + LLAgentWearables::llvo_vec_t objects_to_remove; + LLAgentWearables::llvo_vec_t objects_to_retain; + LLInventoryModel::item_array_t items_to_add; + + LLAgentWearables::findAttachmentsAddRemoveInfo(mObjItems, + objects_to_remove, + objects_to_retain, + items_to_add); + + LL_DEBUGS("Avatar") << self_av_string() << "Removing " << objects_to_remove.size() + << " attachments" << LL_ENDL; + + // Here we remove the attachment pos overrides for *all* + // attachments, even those that are not being removed. This is + // needed to get joint positions all slammed down to their + // pre-attachment states. + gAgentAvatarp->clearAttachmentPosOverrides(); + + // Take off the attachments that will no longer be in the outfit. + LLAgentWearables::userRemoveMultipleAttachments(objects_to_remove); + + // Update wearables. + LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " updating agent wearables with " + << mResolved << " wearable items " << LL_ENDL; + LLAppearanceMgr::instance().updateAgentWearables(this); + + // Restore attachment pos overrides for the attachments that + // are remaining in the outfit. + for (LLAgentWearables::llvo_vec_t::iterator it = objects_to_retain.begin(); + it != objects_to_retain.end(); + ++it) + { + LLViewerObject *objectp = *it; + gAgentAvatarp->addAttachmentPosOverridesForObject(objectp); + } + + // Add new attachments to match those requested. + LL_DEBUGS("Avatar") << self_av_string() << "Adding " << items_to_add.size() << " attachments" << LL_ENDL; + LLAgentWearables::userAttachMultipleAttachments(items_to_add); } if (isFetchCompleted() && isMissingCompleted()) @@ -479,9 +865,15 @@ void LLWearableHoldingPattern::onAllComplete() void LLWearableHoldingPattern::onFetchCompletion() { + if (isMostRecent()) + { + selfStopPhase("get_wearables_2"); + } + if (!isMostRecent()) { - llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl; + // runway skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; } checkMissingWearables(); @@ -492,7 +884,8 @@ bool LLWearableHoldingPattern::pollFetchCompletion() { if (!isMostRecent()) { - llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl; + // runway skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; } bool completed = isFetchCompleted(); @@ -501,14 +894,14 @@ bool LLWearableHoldingPattern::pollFetchCompletion() if (done) { - llinfos << "polling, done status: " << completed << " timed out " << timed_out - << " elapsed " << mWaitTime.getElapsedTimeF32() << llendl; + LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " polling, done status: " << completed << " timed out " << timed_out + << " elapsed " << mWaitTime.getElapsedTimeF32() << LL_ENDL; mFired = true; if (timed_out) { - llwarns << "Exceeded max wait time for wearables, updating appearance based on what has arrived" << llendl; + LL_WARNS() << self_av_string() << "Exceeded max wait time for wearables, updating appearance based on what has arrived" << LL_ENDL; } onFetchCompletion(); @@ -516,115 +909,85 @@ bool LLWearableHoldingPattern::pollFetchCompletion() return done; } -class RecoveredItemLinkCB: public LLInventoryCallback +void recovered_item_link_cb(const LLUUID& item_id, LLWearableType::EType type, LLViewerWearable *wearable, LLWearableHoldingPattern* holder) { -public: - RecoveredItemLinkCB(LLWearableType::EType type, LLWearable *wearable, LLWearableHoldingPattern* holder): - mHolder(holder), - mWearable(wearable), - mType(type) + if (!holder->isMostRecent()) { + LL_WARNS() << "HP " << holder->index() << " skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + // runway skip here? } - void fire(const LLUUID& item_id) - { - if (!mHolder->isMostRecent()) - { - llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl; - } - llinfos << "Recovered item link for type " << mType << llendl; - mHolder->eraseTypeToLink(mType); - // Add wearable to FoundData for actual wearing - LLViewerInventoryItem *item = gInventory.getItem(item_id); - LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL; + LL_INFOS() << "HP " << holder->index() << " recovered item link for type " << type << LL_ENDL; + holder->eraseTypeToLink(type); + // Add wearable to FoundData for actual wearing + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL; - if (linked_item) - { - gInventory.addChangedMask(LLInventoryObserver::LABEL, linked_item->getUUID()); + if (linked_item) + { + gInventory.addChangedMask(LLInventoryObserver::LABEL, linked_item->getUUID()); - if (item) - { - LLFoundData found(linked_item->getUUID(), - linked_item->getAssetUUID(), - linked_item->getName(), - linked_item->getType(), - linked_item->isWearableType() ? linked_item->getWearableType() : LLWearableType::WT_INVALID, - true // is replacement - ); - found.mWearable = mWearable; - mHolder->getFoundList().push_front(found); - } - else - { - llwarns << "inventory item not found for recovered wearable" << llendl; - } + if (item) + { + LLFoundData found(linked_item->getUUID(), + linked_item->getAssetUUID(), + linked_item->getName(), + linked_item->getType(), + linked_item->isWearableType() ? linked_item->getWearableType() : LLWearableType::WT_INVALID, + true // is replacement + ); + found.mWearable = wearable; + holder->getFoundList().push_front(found); } else { - llwarns << "inventory link not found for recovered wearable" << llendl; + LL_WARNS() << self_av_string() << "inventory link not found for recovered wearable" << LL_ENDL; } } -private: - LLWearableHoldingPattern* mHolder; - LLWearable *mWearable; - LLWearableType::EType mType; -}; + else + { + LL_WARNS() << self_av_string() << "HP " << holder->index() << " inventory link not found for recovered wearable" << LL_ENDL; + } +} -class RecoveredItemCB: public LLInventoryCallback +void recovered_item_cb(const LLUUID& item_id, LLWearableType::EType type, LLViewerWearable *wearable, LLWearableHoldingPattern* holder) { -public: - RecoveredItemCB(LLWearableType::EType type, LLWearable *wearable, LLWearableHoldingPattern* holder): - mHolder(holder), - mWearable(wearable), - mType(type) + if (!holder->isMostRecent()) { + // runway skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; } - void fire(const LLUUID& item_id) + + LL_DEBUGS("Avatar") << self_av_string() << "Recovered item for type " << type << LL_ENDL; + LLConstPointer<LLInventoryObject> itemp = gInventory.getItem(item_id); + wearable->setItemID(item_id); + holder->eraseTypeToRecover(type); + llassert(itemp); + if (itemp) { - if (!mHolder->isMostRecent()) - { - llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl; - } + LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallback(boost::bind(recovered_item_link_cb,_1,type,wearable,holder)); - llinfos << "Recovered item for type " << mType << llendl; - LLViewerInventoryItem *itemp = gInventory.getItem(item_id); - mWearable->setItemID(item_id); - LLPointer<LLInventoryCallback> cb = new RecoveredItemLinkCB(mType,mWearable,mHolder); - mHolder->eraseTypeToRecover(mType); - llassert(itemp); - if (itemp) - { - link_inventory_item( gAgent.getID(), - item_id, - LLAppearanceMgr::instance().getCOF(), - itemp->getName(), - itemp->getDescription(), - LLAssetType::AT_LINK, - cb); - } + link_inventory_object(LLAppearanceMgr::instance().getCOF(), itemp, cb); } -private: - LLWearableHoldingPattern* mHolder; - LLWearable *mWearable; - LLWearableType::EType mType; -}; +} void LLWearableHoldingPattern::recoverMissingWearable(LLWearableType::EType type) { if (!isMostRecent()) { - llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl; + // runway skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; } // Try to recover by replacing missing wearable with a new one. LLNotificationsUtil::add("ReplacedMissingWearable"); - lldebugs << "Wearable " << LLWearableType::getTypeLabel(type) - << " could not be downloaded. Replaced inventory item with default wearable." << llendl; - LLWearable* wearable = LLWearableList::instance().createNewWearable(type); + LL_DEBUGS() << "Wearable " << LLWearableType::getTypeLabel(type) + << " could not be downloaded. Replaced inventory item with default wearable." << LL_ENDL; + LLViewerWearable* wearable = LLWearableList::instance().createNewWearable(type, gAgentAvatarp); // Add a new one in the lost and found folder. const LLUUID lost_and_found_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); - LLPointer<LLInventoryCallback> cb = new RecoveredItemCB(type,wearable,this); + LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallback(boost::bind(recovered_item_cb,_1,type,wearable,this)); create_inventory_item(gAgent.getID(), gAgent.getSessionID(), @@ -652,8 +1015,8 @@ void LLWearableHoldingPattern::clearCOFLinksForMissingWearables() if ((data.mWearableType < LLWearableType::WT_COUNT) && (!data.mWearable)) { // Wearable link that was never resolved; remove links to it from COF - llinfos << "removing link for unresolved item " << data.mItemID.asString() << llendl; - LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID,false); + LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " removing link for unresolved item " << data.mItemID.asString() << LL_ENDL; + LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID); } } } @@ -662,7 +1025,8 @@ bool LLWearableHoldingPattern::pollMissingWearables() { if (!isMostRecent()) { - llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl; + // runway skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; } bool timed_out = isTimedOut(); @@ -671,15 +1035,20 @@ bool LLWearableHoldingPattern::pollMissingWearables() if (!done) { - llinfos << "polling missing wearables, waiting for items " << mTypesToRecover.size() + LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " polling missing wearables, waiting for items " << mTypesToRecover.size() << " links " << mTypesToLink.size() << " wearables, timed out " << timed_out << " elapsed " << mWaitTime.getElapsedTimeF32() - << " done " << done << llendl; + << " done " << done << LL_ENDL; } if (done) { + if (isMostRecent()) + { + selfStopPhase("get_missing_wearables_2"); + } + gAgentAvatarp->debugWearablesLoaded(); // BAP - if we don't call clearCOFLinksForMissingWearables() @@ -709,14 +1078,14 @@ void LLWearableHoldingPattern::handleLateArrivals() } if (!isMostRecent()) { - llwarns << "Late arrivals not handled - outfit change no longer valid" << llendl; + LL_WARNS() << self_av_string() << "Late arrivals not handled - outfit change no longer valid" << LL_ENDL; } if (!mIsAllComplete) { - llwarns << "Late arrivals not handled - in middle of missing wearables processing" << llendl; + LL_WARNS() << self_av_string() << "Late arrivals not handled - in middle of missing wearables processing" << LL_ENDL; } - llinfos << "Need to handle " << mLateArrivals.size() << " late arriving wearables" << llendl; + LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " need to handle " << mLateArrivals.size() << " late arriving wearables" << LL_ENDL; // Update mFoundList using late-arriving wearables. std::set<LLWearableType::EType> replaced_types; @@ -724,11 +1093,11 @@ void LLWearableHoldingPattern::handleLateArrivals() iter != getFoundList().end(); ++iter) { LLFoundData& data = *iter; - for (std::set<LLWearable*>::iterator wear_it = mLateArrivals.begin(); + for (std::set<LLViewerWearable*>::iterator wear_it = mLateArrivals.begin(); wear_it != mLateArrivals.end(); ++wear_it) { - LLWearable *wearable = *wear_it; + LLViewerWearable *wearable = *wear_it; if(wearable->getAssetID() == data.mAssetID) { @@ -764,7 +1133,7 @@ void LLWearableHoldingPattern::handleLateArrivals() if (data.mWearable && data.mIsReplacement && replaced_types.find(data.mWearableType) != replaced_types.end()) { - LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID,false); + LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID); std::list<LLFoundData>::iterator clobber_ator = iter; ++iter; getFoundList().erase(clobber_ator); @@ -779,7 +1148,7 @@ void LLWearableHoldingPattern::handleLateArrivals() mLateArrivals.clear(); // Update appearance based on mFoundList - LLAppearanceMgr::instance().updateAgentWearables(this, false); + LLAppearanceMgr::instance().updateAgentWearables(this); } void LLWearableHoldingPattern::resetTime(F32 timeout) @@ -788,23 +1157,23 @@ void LLWearableHoldingPattern::resetTime(F32 timeout) mWaitTime.setTimerExpirySec(timeout); } -void LLWearableHoldingPattern::onWearableAssetFetch(LLWearable *wearable) +void LLWearableHoldingPattern::onWearableAssetFetch(LLViewerWearable *wearable) { if (!isMostRecent()) { - llwarns << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << llendl; + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; } mResolved += 1; // just counting callbacks, not successes. - llinfos << "resolved " << mResolved << "/" << getFoundList().size() << llendl; + LL_DEBUGS("Avatar") << self_av_string() << "HP " << index() << " resolved " << mResolved << "/" << getFoundList().size() << LL_ENDL; if (!wearable) { - llwarns << "no wearable found" << llendl; + LL_WARNS() << self_av_string() << "no wearable found" << LL_ENDL; } if (mFired) { - llwarns << "called after holder fired" << llendl; + LL_WARNS() << self_av_string() << "called after holder fired" << LL_ENDL; if (wearable) { mLateArrivals.insert(wearable); @@ -830,7 +1199,7 @@ void LLWearableHoldingPattern::onWearableAssetFetch(LLWearable *wearable) // Failing this means inventory or asset server are corrupted in a way we don't handle. if ((data.mWearableType >= LLWearableType::WT_COUNT) || (wearable->getType() != data.mWearableType)) { - llwarns << "recovered wearable but type invalid. inventory wearable type: " << data.mWearableType << " asset wearable type: " << wearable->getType() << llendl; + LL_WARNS() << self_av_string() << "recovered wearable but type invalid. inventory wearable type: " << data.mWearableType << " asset wearable type: " << wearable->getType() << LL_ENDL; break; } @@ -839,7 +1208,7 @@ void LLWearableHoldingPattern::onWearableAssetFetch(LLWearable *wearable) } } -static void onWearableAssetFetch(LLWearable* wearable, void* data) +static void onWearableAssetFetch(LLViewerWearable* wearable, void* data) { LLWearableHoldingPattern* holder = (LLWearableHoldingPattern*)data; holder->onWearableAssetFetch(wearable); @@ -855,9 +1224,9 @@ static void removeDuplicateItems(LLInventoryModel::item_array_t& items) // encountered, so we actually keep the *last* of each duplicate // item. This is needed to give the right priority when adding // duplicate items to an existing outfit. - for (S32 i=items.count()-1; i>=0; i--) + for (S32 i=items.size()-1; i>=0; i--) { - LLViewerInventoryItem *item = items.get(i); + LLViewerInventoryItem *item = items.at(i); LLUUID item_id = item->getLinkedUUID(); if (items_seen.find(item_id)!=items_seen.end()) continue; @@ -868,7 +1237,7 @@ static void removeDuplicateItems(LLInventoryModel::item_array_t& items) it != tmp_list.end(); ++it) { - new_items.put(*it); + new_items.push_back(*it); } items = new_items; } @@ -878,6 +1247,18 @@ const LLUUID LLAppearanceMgr::getCOF() const return gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); } +S32 LLAppearanceMgr::getCOFVersion() const +{ + LLViewerInventoryCategory *cof = gInventory.getCategory(getCOF()); + if (cof) + { + return cof->getVersion(); + } + else + { + return LLViewerInventoryCategory::VERSION_UNKNOWN; + } +} const LLViewerInventoryItem* LLAppearanceMgr::getBaseOutfitLink() { @@ -891,8 +1272,7 @@ const LLViewerInventoryItem* LLAppearanceMgr::getBaseOutfitLink() cat_array, item_array, false, - is_category, - false); + is_category); for (LLInventoryModel::item_array_t::const_iterator iter = item_array.begin(); iter != item_array.end(); iter++) @@ -939,80 +1319,132 @@ const LLUUID LLAppearanceMgr::getBaseOutfitUUID() if (outfit_cat->getPreferredType() != LLFolderType::FT_OUTFIT) { - llwarns << "Expected outfit type:" << LLFolderType::FT_OUTFIT << " but got type:" << outfit_cat->getType() << " for folder name:" << outfit_cat->getName() << llendl; + LL_WARNS() << "Expected outfit type:" << LLFolderType::FT_OUTFIT << " but got type:" << outfit_cat->getType() << " for folder name:" << outfit_cat->getName() << LL_ENDL; return LLUUID::null; } return outfit_cat->getUUID(); } -bool LLAppearanceMgr::wearItemOnAvatar(const LLUUID& item_id_to_wear, bool do_update, bool replace, LLPointer<LLInventoryCallback> cb) +void wear_on_avatar_cb(const LLUUID& inv_item, bool do_replace = false) { - if (item_id_to_wear.isNull()) return false; - - // *TODO: issue with multi-wearable should be fixed: - // in this case this method will be called N times - loading started for each item - // and than N times will be called - loading completed for each item. - // That means subscribers will be notified that loading is done after first item in a batch is worn. - // (loading indicator disappears for example before all selected items are worn) - // Have not fix this issue for 2.1 because of stability reason. EXT-7777. - - // Disabled for now because it is *not* acceptable to call updateAppearanceFromCOF() multiple times -// gAgentWearables.notifyLoadingStarted(); - - LLViewerInventoryItem* item_to_wear = gInventory.getItem(item_id_to_wear); - if (!item_to_wear) return false; - - if (gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.getLibraryRootFolderID())) - { - LLPointer<LLInventoryCallback> cb = new WearOnAvatarCallback(replace); - copy_inventory_item(gAgent.getID(), item_to_wear->getPermissions().getOwner(), item_to_wear->getUUID(), LLUUID::null, std::string(),cb); - return false; - } - else if (!gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.getRootFolderID())) - { - return false; // not in library and not in agent's inventory - } - else if (gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH))) - { - LLNotificationsUtil::add("CannotWearTrash"); - return false; - } - else if (gInventory.isObjectDescendentOf(item_to_wear->getUUID(), LLAppearanceMgr::instance().getCOF())) // EXT-84911 + if (inv_item.isNull()) + return; + + LLViewerInventoryItem *item = gInventory.getItem(inv_item); + if (item) { - return false; + LLAppearanceMgr::instance().wearItemOnAvatar(inv_item, true, do_replace); } +} - switch (item_to_wear->getType()) - { - case LLAssetType::AT_CLOTHING: - if (gAgentWearables.areWearablesLoaded()) - { - S32 wearable_count = gAgentWearables.getWearableCount(item_to_wear->getWearableType()); - if ((replace && wearable_count != 0) || - (wearable_count >= LLAgentWearables::MAX_CLOTHING_PER_TYPE) ) - { - removeCOFItemLinks(gAgentWearables.getWearableItemID(item_to_wear->getWearableType(), wearable_count-1), false); - } - addCOFItemLink(item_to_wear, do_update, cb); - } - break; - case LLAssetType::AT_BODYPART: - // TODO: investigate wearables may not be loaded at this point EXT-8231 - - // Remove the existing wearables of the same type. - // Remove existing body parts anyway because we must not be able to wear e.g. two skins. - removeCOFLinksOfType(item_to_wear->getWearableType(), false); - - addCOFItemLink(item_to_wear, do_update, cb); - break; - case LLAssetType::AT_OBJECT: - rez_attachment(item_to_wear, NULL, replace); - break; - default: return false;; - } +void LLAppearanceMgr::wearItemsOnAvatar(const uuid_vec_t& item_ids_to_wear, + bool do_update, + bool replace, + LLPointer<LLInventoryCallback> cb) +{ + bool first = true; + + LLInventoryObject::const_object_list_t items_to_link; + + for (uuid_vec_t::const_iterator it = item_ids_to_wear.begin(); + it != item_ids_to_wear.end(); + ++it) + { + replace = first && replace; + first = false; + + const LLUUID& item_id_to_wear = *it; + + if (item_id_to_wear.isNull()) continue; + + LLViewerInventoryItem* item_to_wear = gInventory.getItem(item_id_to_wear); + if (!item_to_wear) continue; + + if (gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.getLibraryRootFolderID())) + { + LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallback(boost::bind(wear_on_avatar_cb,_1,replace)); + copy_inventory_item(gAgent.getID(), item_to_wear->getPermissions().getOwner(), item_to_wear->getUUID(), LLUUID::null, std::string(), cb); + continue; + } + else if (!gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.getRootFolderID())) + { + continue; // not in library and not in agent's inventory + } + else if (gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH))) + { + LLNotificationsUtil::add("CannotWearTrash"); + continue; + } + else if (isLinkedInCOF(item_to_wear->getUUID())) // EXT-84911 + { + continue; + } + + switch (item_to_wear->getType()) + { + case LLAssetType::AT_CLOTHING: + { + if (gAgentWearables.areWearablesLoaded()) + { + if (!cb && do_update) + { + cb = new LLUpdateAppearanceAndEditWearableOnDestroy(item_id_to_wear); + } + LLWearableType::EType type = item_to_wear->getWearableType(); + S32 wearable_count = gAgentWearables.getWearableCount(type); + if ((replace && wearable_count != 0) || !gAgentWearables.canAddWearable(type)) + { + LLUUID item_id = gAgentWearables.getWearableItemID(item_to_wear->getWearableType(), + wearable_count-1); + removeCOFItemLinks(item_id, cb); + } + + items_to_link.push_back(item_to_wear); + } + } + break; + + case LLAssetType::AT_BODYPART: + { + // TODO: investigate wearables may not be loaded at this point EXT-8231 + + // Remove the existing wearables of the same type. + // Remove existing body parts anyway because we must not be able to wear e.g. two skins. + removeCOFLinksOfType(item_to_wear->getWearableType()); + if (!cb && do_update) + { + cb = new LLUpdateAppearanceAndEditWearableOnDestroy(item_id_to_wear); + } + items_to_link.push_back(item_to_wear); + } + break; + + case LLAssetType::AT_OBJECT: + { + rez_attachment(item_to_wear, NULL, replace); + } + break; + + default: continue; + } + } + + // Batch up COF link creation - more efficient if using AIS. + if (items_to_link.size()) + { + link_inventory_array(getCOF(), items_to_link, cb); + } +} - return true; +void LLAppearanceMgr::wearItemOnAvatar(const LLUUID& item_id_to_wear, + bool do_update, + bool replace, + LLPointer<LLInventoryCallback> cb) +{ + uuid_vec_t ids; + ids.push_back(item_id_to_wear); + wearItemsOnAvatar(ids, do_update, replace, cb); } // Update appearance from outfit folder. @@ -1100,10 +1532,24 @@ void LLAppearanceMgr::takeOffOutfit(const LLUUID& cat_id) LLInventoryModel::item_array_t::const_iterator it = items.begin(); const LLInventoryModel::item_array_t::const_iterator it_end = items.end(); + uuid_vec_t uuids_to_remove; for( ; it_end != it; ++it) { LLViewerInventoryItem* item = *it; - removeItemFromAvatar(item->getUUID()); + uuids_to_remove.push_back(item->getUUID()); + } + removeItemsFromAvatar(uuids_to_remove); + + // deactivate all gestures in the outfit folder + LLInventoryModel::item_array_t gest_items; + getDescendentsOfAssetType(cat_id, gest_items, LLAssetType::AT_GESTURE); + for(S32 i = 0; i < gest_items.size(); ++i) + { + LLViewerInventoryItem *gest_item = gest_items[i]; + if ( LLGestureMgr::instance().isGestureActive( gest_item->getLinkedUUID()) ) + { + LLGestureMgr::instance().deactivateGesture( gest_item->getLinkedUUID() ); + } } } @@ -1114,10 +1560,10 @@ void LLAppearanceMgr::shallowCopyCategory(const LLUUID& src_id, const LLUUID& ds LLInventoryCategory *src_cat = gInventory.getCategory(src_id); if (!src_cat) { - llwarns << "folder not found for src " << src_id.asString() << llendl; + LL_WARNS() << "folder not found for src " << src_id.asString() << LL_ENDL; return; } - llinfos << "starting, src_id " << src_id << " name " << src_cat->getName() << " dst_id " << dst_id << llendl; + LL_INFOS() << "starting, src_id " << src_id << " name " << src_cat->getName() << " dst_id " << dst_id << LL_ENDL; LLUUID parent_id = dst_id; if(parent_id.isNull()) { @@ -1131,6 +1577,58 @@ void LLAppearanceMgr::shallowCopyCategory(const LLUUID& src_id, const LLUUID& ds gInventory.notifyObservers(); } +void LLAppearanceMgr::slamCategoryLinks(const LLUUID& src_id, const LLUUID& dst_id, + bool include_folder_links, LLPointer<LLInventoryCallback> cb) +{ + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + LLSD contents = LLSD::emptyArray(); + gInventory.getDirectDescendentsOf(src_id, cats, items); + LL_INFOS() << "copying " << items->size() << " items" << LL_ENDL; + for (LLInventoryModel::item_array_t::const_iterator iter = items->begin(); + iter != items->end(); + ++iter) + { + const LLViewerInventoryItem* item = (*iter); + switch (item->getActualType()) + { + case LLAssetType::AT_LINK: + { + LL_DEBUGS("Avatar") << "linking inventory item " << item->getName() << LL_ENDL; + //getActualDescription() is used for a new description + //to propagate ordering information saved in descriptions of links + LLSD item_contents; + item_contents["name"] = item->getName(); + item_contents["desc"] = item->getActualDescription(); + item_contents["linked_id"] = item->getLinkedUUID(); + item_contents["type"] = LLAssetType::AT_LINK; + contents.append(item_contents); + break; + } + case LLAssetType::AT_LINK_FOLDER: + { + LLViewerInventoryCategory *catp = item->getLinkedCategory(); + if (catp && include_folder_links) + { + LL_DEBUGS("Avatar") << "linking inventory folder " << item->getName() << LL_ENDL; + LLSD base_contents; + base_contents["name"] = catp->getName(); + base_contents["desc"] = ""; // categories don't have descriptions. + base_contents["linked_id"] = catp->getLinkedUUID(); + base_contents["type"] = LLAssetType::AT_LINK_FOLDER; + contents.append(base_contents); + } + break; + } + default: + { + // Linux refuses to compile unless all possible enums are handled. Really, Linux? + break; + } + } + } + slam_inventory_folder(dst_id, contents, cb); +} // Copy contents of src_id to dst_id. void LLAppearanceMgr::shallowCopyCategoryContents(const LLUUID& src_id, const LLUUID& dst_id, LLPointer<LLInventoryCallback> cb) @@ -1138,7 +1636,8 @@ void LLAppearanceMgr::shallowCopyCategoryContents(const LLUUID& src_id, const LL LLInventoryModel::cat_array_t* cats; LLInventoryModel::item_array_t* items; gInventory.getDirectDescendentsOf(src_id, cats, items); - llinfos << "copying " << items->count() << " items" << llendl; + LL_INFOS() << "copying " << items->size() << " items" << LL_ENDL; + LLInventoryObject::const_object_list_t link_array; for (LLInventoryModel::item_array_t::const_iterator iter = items->begin(); iter != items->end(); ++iter) @@ -1148,14 +1647,8 @@ void LLAppearanceMgr::shallowCopyCategoryContents(const LLUUID& src_id, const LL { case LLAssetType::AT_LINK: { - //LLInventoryItem::getDescription() is used for a new description - //to propagate ordering information saved in descriptions of links - link_inventory_item(gAgent.getID(), - item->getLinkedUUID(), - dst_id, - item->getName(), - item->LLInventoryItem::getDescription(), - LLAssetType::AT_LINK, cb); + LL_DEBUGS("Avatar") << "linking inventory item " << item->getName() << LL_ENDL; + link_array.push_back(LLConstPointer<LLInventoryObject>(item)); break; } case LLAssetType::AT_LINK_FOLDER: @@ -1164,12 +1657,8 @@ void LLAppearanceMgr::shallowCopyCategoryContents(const LLUUID& src_id, const LL // Skip copying outfit links. if (catp && catp->getPreferredType() != LLFolderType::FT_OUTFIT) { - link_inventory_item(gAgent.getID(), - item->getLinkedUUID(), - dst_id, - item->getName(), - item->getDescription(), - LLAssetType::AT_LINK_FOLDER, cb); + LL_DEBUGS("Avatar") << "linking inventory folder " << item->getName() << LL_ENDL; + link_array.push_back(LLConstPointer<LLInventoryObject>(item)); } break; } @@ -1178,7 +1667,7 @@ void LLAppearanceMgr::shallowCopyCategoryContents(const LLUUID& src_id, const LL case LLAssetType::AT_BODYPART: case LLAssetType::AT_GESTURE: { - llinfos << "copying inventory item " << item->getName() << llendl; + LL_DEBUGS("Avatar") << "copying inventory item " << item->getName() << LL_ENDL; copy_inventory_item(gAgent.getID(), item->getPermissions().getOwner(), item->getUUID(), @@ -1192,6 +1681,10 @@ void LLAppearanceMgr::shallowCopyCategoryContents(const LLUUID& src_id, const LL break; } } + if (!link_array.empty()) + { + link_inventory_array(dst_id, link_array, cb); + } } BOOL LLAppearanceMgr::getCanMakeFolderIntoOutfit(const LLUUID& folder_id) @@ -1256,15 +1749,13 @@ bool LLAppearanceMgr::getCanRemoveOutfit(const LLUUID& outfit_cat_id) // static bool LLAppearanceMgr::getCanRemoveFromCOF(const LLUUID& outfit_cat_id) { - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; + if (gAgentWearables.isCOFChangeInProgress()) + { + return false; + } + LLFindWearablesEx is_worn(/*is_worn=*/ true, /*include_body_parts=*/ false); - gInventory.collectDescendentsIf(outfit_cat_id, - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - is_worn); - return items.size() > 0; + return gInventory.hasMatchingDirectDescendent(outfit_cat_id, is_worn); } // static @@ -1283,6 +1774,7 @@ bool LLAppearanceMgr::getCanAddToCOF(const LLUUID& outfit_cat_id) items, LLInventoryModel::EXCLUDE_TRASH, not_worn); + return items.size() > 0; } @@ -1309,45 +1801,68 @@ bool LLAppearanceMgr::getCanReplaceCOF(const LLUUID& outfit_cat_id) items, LLInventoryModel::EXCLUDE_TRASH, is_worn); + return items.size() > 0; } -void LLAppearanceMgr::purgeBaseOutfitLink(const LLUUID& category) +// Moved from LLWearableList::ContextMenu for wider utility. +bool LLAppearanceMgr::canAddWearables(const uuid_vec_t& item_ids) { - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - gInventory.collectDescendents(category, cats, items, - LLInventoryModel::EXCLUDE_TRASH); - for (S32 i = 0; i < items.count(); ++i) + // TODO: investigate wearables may not be loaded at this point EXT-8231 + + U32 n_objects = 0; + U32 n_clothes = 0; + + // Count given clothes (by wearable type) and objects. + for (uuid_vec_t::const_iterator it = item_ids.begin(); it != item_ids.end(); ++it) { - LLViewerInventoryItem *item = items.get(i); - if (item->getActualType() != LLAssetType::AT_LINK_FOLDER) - continue; - if (item->getIsLinkType()) + LLViewerInventoryItem* item = gInventory.getItem(*it); + if (!item) { - LLViewerInventoryCategory* catp = item->getLinkedCategory(); - if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) - { - gInventory.purgeObject(item->getUUID()); - } + return false; + } + + if (item->getType() == LLAssetType::AT_OBJECT) + { + ++n_objects; + } + else if (item->getType() == LLAssetType::AT_CLOTHING) + { + ++n_clothes; + } + else + { + LL_WARNS() << "Unexpected wearable type" << LL_ENDL; + return false; } } + + // Check whether we can add all the objects. + if (!isAgentAvatarValid() || !gAgentAvatarp->canAttachMoreObjects(n_objects)) + { + return false; + } + + // Check whether we can add all the clothes. + U32 sum_clothes = n_clothes + gAgentWearables.getClothingLayerCount(); + return sum_clothes <= LLAgentWearables::MAX_CLOTHING_LAYERS; } -void LLAppearanceMgr::purgeCategory(const LLUUID& category, bool keep_outfit_links) +void LLAppearanceMgr::purgeBaseOutfitLink(const LLUUID& category, LLPointer<LLInventoryCallback> cb) { LLInventoryModel::cat_array_t cats; LLInventoryModel::item_array_t items; gInventory.collectDescendents(category, cats, items, LLInventoryModel::EXCLUDE_TRASH); - for (S32 i = 0; i < items.count(); ++i) + for (S32 i = 0; i < items.size(); ++i) { - LLViewerInventoryItem *item = items.get(i); - if (keep_outfit_links && (item->getActualType() == LLAssetType::AT_LINK_FOLDER)) + LLViewerInventoryItem *item = items.at(i); + if (item->getActualType() != LLAssetType::AT_LINK_FOLDER) continue; - if (item->getIsLinkType()) + LLViewerInventoryCategory* catp = item->getLinkedCategory(); + if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) { - gInventory.purgeObject(item->getUUID()); + remove_inventory_item(item->getUUID(), cb); } } } @@ -1355,55 +1870,50 @@ void LLAppearanceMgr::purgeCategory(const LLUUID& category, bool keep_outfit_lin // Keep the last N wearables of each type. For viewer 2.0, N is 1 for // both body parts and clothing items. void LLAppearanceMgr::filterWearableItems( - LLInventoryModel::item_array_t& items, S32 max_per_type) + LLInventoryModel::item_array_t& items, S32 max_per_type, S32 max_total) { - // Divvy items into arrays by wearable type. - std::vector<LLInventoryModel::item_array_t> items_by_type(LLWearableType::WT_COUNT); - divvyWearablesByType(items, items_by_type); - - // rebuild items list, retaining the last max_per_type of each array - items.clear(); - for (S32 i=0; i<LLWearableType::WT_COUNT; i++) - { - S32 size = items_by_type[i].size(); - if (size <= 0) - continue; - S32 start_index = llmax(0,size-max_per_type); - for (S32 j = start_index; j<size; j++) - { - items.push_back(items_by_type[i][j]); - } - } -} - -// Create links to all listed items. -void LLAppearanceMgr::linkAll(const LLUUID& cat_uuid, - LLInventoryModel::item_array_t& items, - LLPointer<LLInventoryCallback> cb) -{ - for (S32 i=0; i<items.count(); i++) - { - const LLInventoryItem* item = items.get(i).get(); - link_inventory_item(gAgent.getID(), - item->getLinkedUUID(), - cat_uuid, - item->getName(), - item->LLInventoryItem::getDescription(), - LLAssetType::AT_LINK, - cb); - - const LLViewerInventoryCategory *cat = gInventory.getCategory(cat_uuid); - const std::string cat_name = cat ? cat->getName() : "CAT NOT FOUND"; -#ifndef LL_RELEASE_FOR_DOWNLOAD - llinfos << "Linking Item [ name:" << item->getName() << " UUID:" << item->getUUID() << " ] to Category [ name:" << cat_name << " UUID:" << cat_uuid << " ] " << llendl; -#endif - } + // Restrict by max total items first. + if ((max_total > 0) && (items.size() > max_total)) + { + LLInventoryModel::item_array_t items_to_keep; + for (S32 i=0; i<max_total; i++) + { + items_to_keep.push_back(items[i]); + } + items = items_to_keep; + } + + if (max_per_type > 0) + { + // Divvy items into arrays by wearable type. + std::vector<LLInventoryModel::item_array_t> items_by_type(LLWearableType::WT_COUNT); + divvyWearablesByType(items, items_by_type); + + // rebuild items list, retaining the last max_per_type of each array + items.clear(); + for (S32 i=0; i<LLWearableType::WT_COUNT; i++) + { + S32 size = items_by_type[i].size(); + if (size <= 0) + continue; + S32 start_index = llmax(0,size-max_per_type); + for (S32 j = start_index; j<size; j++) + { + items.push_back(items_by_type[i][j]); + } + } + } } void LLAppearanceMgr::updateCOF(const LLUUID& category, bool append) { LLViewerInventoryCategory *pcat = gInventory.getCategory(category); - llinfos << "starting, cat " << (pcat ? pcat->getName() : "[UNKNOWN]") << llendl; + if (!pcat) + { + LL_WARNS() << "no category found for id " << category << LL_ENDL; + return; + } + LL_INFOS("Avatar") << self_av_string() << "starting, cat '" << (pcat ? pcat->getName() : "[UNKNOWN]") << "'" << LL_ENDL; const LLUUID cof = getCOF(); @@ -1411,10 +1921,10 @@ void LLAppearanceMgr::updateCOF(const LLUUID& category, bool append) if (!append) { LLInventoryModel::item_array_t gest_items; - getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE, false); - for(S32 i = 0; i < gest_items.count(); ++i) + getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE); + for(S32 i = 0; i < gest_items.size(); ++i) { - LLViewerInventoryItem *gest_item = gest_items.get(i); + LLViewerInventoryItem *gest_item = gest_items.at(i); if ( LLGestureMgr::instance().isGestureActive( gest_item->getLinkedUUID()) ) { LLGestureMgr::instance().deactivateGesture( gest_item->getLinkedUUID() ); @@ -1428,78 +1938,102 @@ void LLAppearanceMgr::updateCOF(const LLUUID& category, bool append) // required parts are missing. // Preserve body parts from COF if appending. LLInventoryModel::item_array_t body_items; - getDescendentsOfAssetType(cof, body_items, LLAssetType::AT_BODYPART, false); - getDescendentsOfAssetType(category, body_items, LLAssetType::AT_BODYPART, false); + getDescendentsOfAssetType(cof, body_items, LLAssetType::AT_BODYPART); + getDescendentsOfAssetType(category, body_items, LLAssetType::AT_BODYPART); if (append) reverse(body_items.begin(), body_items.end()); // Reduce body items to max of one per type. removeDuplicateItems(body_items); - filterWearableItems(body_items, 1); + filterWearableItems(body_items, 1, 0); // - Wearables: include COF contents only if appending. LLInventoryModel::item_array_t wear_items; if (append) - getDescendentsOfAssetType(cof, wear_items, LLAssetType::AT_CLOTHING, false); - getDescendentsOfAssetType(category, wear_items, LLAssetType::AT_CLOTHING, false); + getDescendentsOfAssetType(cof, wear_items, LLAssetType::AT_CLOTHING); + getDescendentsOfAssetType(category, wear_items, LLAssetType::AT_CLOTHING); // Reduce wearables to max of one per type. removeDuplicateItems(wear_items); - filterWearableItems(wear_items, LLAgentWearables::MAX_CLOTHING_PER_TYPE); + filterWearableItems(wear_items, 0, LLAgentWearables::MAX_CLOTHING_LAYERS); // - Attachments: include COF contents only if appending. LLInventoryModel::item_array_t obj_items; if (append) - getDescendentsOfAssetType(cof, obj_items, LLAssetType::AT_OBJECT, false); - getDescendentsOfAssetType(category, obj_items, LLAssetType::AT_OBJECT, false); + getDescendentsOfAssetType(cof, obj_items, LLAssetType::AT_OBJECT); + getDescendentsOfAssetType(category, obj_items, LLAssetType::AT_OBJECT); removeDuplicateItems(obj_items); // - Gestures: include COF contents only if appending. LLInventoryModel::item_array_t gest_items; if (append) - getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE, false); - getDescendentsOfAssetType(category, gest_items, LLAssetType::AT_GESTURE, false); + getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE); + getDescendentsOfAssetType(category, gest_items, LLAssetType::AT_GESTURE); removeDuplicateItems(gest_items); - // Remove current COF contents. - bool keep_outfit_links = append; - purgeCategory(cof, keep_outfit_links); - gInventory.notifyObservers(); - // Create links to new COF contents. - llinfos << "creating LLUpdateAppearanceOnDestroy" << llendl; - LLPointer<LLInventoryCallback> link_waiter = new LLUpdateAppearanceOnDestroy(!append); - -#ifndef LL_RELEASE_FOR_DOWNLOAD - llinfos << "Linking body items" << llendl; -#endif - linkAll(cof, body_items, link_waiter); - -#ifndef LL_RELEASE_FOR_DOWNLOAD - llinfos << "Linking wear items" << llendl; -#endif - linkAll(cof, wear_items, link_waiter); - -#ifndef LL_RELEASE_FOR_DOWNLOAD - llinfos << "Linking obj items" << llendl; -#endif - linkAll(cof, obj_items, link_waiter); - -#ifndef LL_RELEASE_FOR_DOWNLOAD - llinfos << "Linking gesture items" << llendl; -#endif - linkAll(cof, gest_items, link_waiter); + LLInventoryModel::item_array_t all_items; + std::copy(body_items.begin(), body_items.end(), std::back_inserter(all_items)); + std::copy(wear_items.begin(), wear_items.end(), std::back_inserter(all_items)); + std::copy(obj_items.begin(), obj_items.end(), std::back_inserter(all_items)); + std::copy(gest_items.begin(), gest_items.end(), std::back_inserter(all_items)); + + // Find any wearables that need description set to enforce ordering. + desc_map_t desc_map; + getWearableOrderingDescUpdates(wear_items, desc_map); + + // Will link all the above items. + // link_waiter enforce flags are false because we've already fixed everything up in updateCOF(). + LLPointer<LLInventoryCallback> link_waiter = new LLUpdateAppearanceOnDestroy(false,false); + LLSD contents = LLSD::emptyArray(); + + for (LLInventoryModel::item_array_t::const_iterator it = all_items.begin(); + it != all_items.end(); ++it) + { + LLSD item_contents; + LLInventoryItem *item = *it; + + std::string desc; + desc_map_t::const_iterator desc_iter = desc_map.find(item->getUUID()); + if (desc_iter != desc_map.end()) + { + desc = desc_iter->second; + LL_DEBUGS("Avatar") << item->getName() << " overriding desc to: " << desc + << " (was: " << item->getActualDescription() << ")" << LL_ENDL; + } + else + { + desc = item->getActualDescription(); + } - // Add link to outfit if category is an outfit. - if (!append) + item_contents["name"] = item->getName(); + item_contents["desc"] = desc; + item_contents["linked_id"] = item->getLinkedUUID(); + item_contents["type"] = LLAssetType::AT_LINK; + contents.append(item_contents); + } + const LLUUID& base_id = append ? getBaseOutfitUUID() : category; + LLViewerInventoryCategory *base_cat = gInventory.getCategory(base_id); + if (base_cat) + { + LLSD base_contents; + base_contents["name"] = base_cat->getName(); + base_contents["desc"] = ""; + base_contents["linked_id"] = base_cat->getLinkedUUID(); + base_contents["type"] = LLAssetType::AT_LINK_FOLDER; + contents.append(base_contents); + } + if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) { - createBaseOutfitLink(category, link_waiter); + dump_sequential_xml(gAgentAvatarp->getFullname() + "_slam_request", contents); } - llinfos << "waiting for LLUpdateAppearanceOnDestroy" << llendl; + slam_inventory_folder(getCOF(), contents, link_waiter); + + LL_DEBUGS("Avatar") << self_av_string() << "waiting for LLUpdateAppearanceOnDestroy" << LL_ENDL; } void LLAppearanceMgr::updatePanelOutfitName(const std::string& name) { LLSidepanelAppearance* panel_appearance = - dynamic_cast<LLSidepanelAppearance *>(LLSideTray::getInstance()->getPanel("sidepanel_appearance")); + dynamic_cast<LLSidepanelAppearance *>(LLFloaterSidePanelContainer::getPanel("appearance")); if (panel_appearance) { panel_appearance->refreshCurrentOutfitName(name); @@ -1512,23 +2046,23 @@ void LLAppearanceMgr::createBaseOutfitLink(const LLUUID& category, LLPointer<LLI LLViewerInventoryCategory* catp = gInventory.getCategory(category); std::string new_outfit_name = ""; - purgeBaseOutfitLink(cof); + purgeBaseOutfitLink(cof, link_waiter); if (catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) { - link_inventory_item(gAgent.getID(), category, cof, catp->getName(), "", - LLAssetType::AT_LINK_FOLDER, link_waiter); + link_inventory_object(cof, catp, link_waiter); new_outfit_name = catp->getName(); } updatePanelOutfitName(new_outfit_name); } -void LLAppearanceMgr::updateAgentWearables(LLWearableHoldingPattern* holder, bool append) +void LLAppearanceMgr::updateAgentWearables(LLWearableHoldingPattern* holder) { - lldebugs << "updateAgentWearables()" << llendl; + LL_DEBUGS("Avatar") << "updateAgentWearables()" << LL_ENDL; LLInventoryItem::item_array_t items; - LLDynamicArray< LLWearable* > wearables; + std::vector< LLViewerWearable* > wearables; + wearables.reserve(32); // For each wearable type, find the wearables of that type. for( S32 i = 0; i < LLWearableType::WT_COUNT; i++ ) @@ -1537,25 +2071,28 @@ void LLAppearanceMgr::updateAgentWearables(LLWearableHoldingPattern* holder, boo iter != holder->getFoundList().end(); ++iter) { LLFoundData& data = *iter; - LLWearable* wearable = data.mWearable; + LLViewerWearable* wearable = data.mWearable; if( wearable && ((S32)wearable->getType() == i) ) { LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(data.mItemID); if( item && (item->getAssetUUID() == wearable->getAssetID()) ) { - items.put(item); - wearables.put(wearable); + items.push_back(item); + wearables.push_back(wearable); } } } } - if(wearables.count() > 0) + if(wearables.size() > 0) { - gAgentWearables.setWearableOutfit(items, wearables, !append); + gAgentWearables.setWearableOutfit(items, wearables); } +} -// dec_busy_count(); +S32 LLAppearanceMgr::countActiveHoldingPatterns() +{ + return LLWearableHoldingPattern::countActive(); } static void remove_non_link_items(LLInventoryModel::item_array_t &items) @@ -1575,15 +2112,15 @@ static void remove_non_link_items(LLInventoryModel::item_array_t &items) } //a predicate for sorting inventory items by actual descriptions -bool sort_by_description(const LLInventoryItem* item1, const LLInventoryItem* item2) +bool sort_by_actual_description(const LLInventoryItem* item1, const LLInventoryItem* item2) { if (!item1 || !item2) { - llwarning("either item1 or item2 is NULL", 0); + LL_WARNS() << "either item1 or item2 is NULL" << LL_ENDL; return true; } - return item1->LLInventoryItem::getDescription() < item2->LLInventoryItem::getDescription(); + return item1->getActualDescription() < item2->getActualDescription(); } void item_array_diff(LLInventoryModel::item_array_t& full_list, @@ -1596,7 +2133,7 @@ void item_array_diff(LLInventoryModel::item_array_t& full_list, ++it) { LLViewerInventoryItem *item = *it; - if (keep_list.find(item) < 0) // Why on earth does LLDynamicArray need to redefine find()? + if (std::find(keep_list.begin(), keep_list.end(), item) == keep_list.end()) { kill_list.push_back(item); } @@ -1605,18 +2142,19 @@ void item_array_diff(LLInventoryModel::item_array_t& full_list, S32 LLAppearanceMgr::findExcessOrDuplicateItems(const LLUUID& cat_id, LLAssetType::EType type, - S32 max_items, - LLInventoryModel::item_array_t& items_to_kill) + S32 max_items_per_type, + S32 max_items_total, + LLInventoryObject::object_list_t& items_to_kill) { S32 to_kill_count = 0; LLInventoryModel::item_array_t items; - getDescendentsOfAssetType(cat_id, items, type, false); + getDescendentsOfAssetType(cat_id, items, type); LLInventoryModel::item_array_t curr_items = items; removeDuplicateItems(items); - if (max_items > 0) + if (max_items_per_type > 0 || max_items_total > 0) { - filterWearableItems(items, max_items); + filterWearableItems(items, max_items_per_type, max_items_total); } LLInventoryModel::item_array_t kill_items; item_array_diff(curr_items,items,kill_items); @@ -1624,74 +2162,101 @@ S32 LLAppearanceMgr::findExcessOrDuplicateItems(const LLUUID& cat_id, it != kill_items.end(); ++it) { - items_to_kill.push_back(*it); + items_to_kill.push_back(LLPointer<LLInventoryObject>(*it)); to_kill_count++; } return to_kill_count; } - -void LLAppearanceMgr::enforceItemRestrictions() -{ - S32 purge_count = 0; - LLInventoryModel::item_array_t items_to_kill; - purge_count += findExcessOrDuplicateItems(getCOF(),LLAssetType::AT_BODYPART, - 1, items_to_kill); - purge_count += findExcessOrDuplicateItems(getCOF(),LLAssetType::AT_CLOTHING, - LLAgentWearables::MAX_CLOTHING_PER_TYPE, items_to_kill); - purge_count += findExcessOrDuplicateItems(getCOF(),LLAssetType::AT_OBJECT, - -1, items_to_kill); +void LLAppearanceMgr::findAllExcessOrDuplicateItems(const LLUUID& cat_id, + LLInventoryObject::object_list_t& items_to_kill) +{ + findExcessOrDuplicateItems(cat_id,LLAssetType::AT_BODYPART, + 1, 0, items_to_kill); + findExcessOrDuplicateItems(cat_id,LLAssetType::AT_CLOTHING, + 0, LLAgentWearables::MAX_CLOTHING_LAYERS, items_to_kill); + findExcessOrDuplicateItems(cat_id,LLAssetType::AT_OBJECT, + 0, 0, items_to_kill); +} +void LLAppearanceMgr::enforceCOFItemRestrictions(LLPointer<LLInventoryCallback> cb) +{ + LLInventoryObject::object_list_t items_to_kill; + findAllExcessOrDuplicateItems(getCOF(), items_to_kill); if (items_to_kill.size()>0) { - for (LLInventoryModel::item_array_t::iterator it = items_to_kill.begin(); - it != items_to_kill.end(); - ++it) - { - LLViewerInventoryItem *item = *it; - llinfos << "purging duplicate or excess item " << item->getName() << llendl; - gInventory.purgeObject(item->getUUID()); - } - gInventory.notifyObservers(); + // Remove duplicate or excess wearables. Should normally be enforced at the UI level, but + // this should catch anything that gets through. + remove_inventory_items(items_to_kill, cb); } } -void LLAppearanceMgr::updateAppearanceFromCOF(bool update_base_outfit_ordering) +void LLAppearanceMgr::updateAppearanceFromCOF(bool enforce_item_restrictions, + bool enforce_ordering, + nullary_func_t post_update_func) { if (mIsInUpdateAppearanceFromCOF) { - llwarns << "Called updateAppearanceFromCOF inside updateAppearanceFromCOF, skipping" << llendl; + LL_WARNS() << "Called updateAppearanceFromCOF inside updateAppearanceFromCOF, skipping" << LL_ENDL; return; } - BoolSetter setIsInUpdateAppearanceFromCOF(mIsInUpdateAppearanceFromCOF); + LL_DEBUGS("Avatar") << self_av_string() << "starting" << LL_ENDL; - llinfos << "starting" << llendl; + if (enforce_item_restrictions) + { + // The point here is just to call + // updateAppearanceFromCOF() again after excess items + // have been removed. That time we will set + // enforce_item_restrictions to false so we don't get + // caught in a perpetual loop. + LLPointer<LLInventoryCallback> cb( + new LLUpdateAppearanceOnDestroy(false, enforce_ordering, post_update_func)); + enforceCOFItemRestrictions(cb); + return; + } - //checking integrity of the COF in terms of ordering of wearables, - //checking and updating links' descriptions of wearables in the COF (before analyzed for "dirty" state) - updateClothingOrderingInfo(LLUUID::null, update_base_outfit_ordering); + if (enforce_ordering) + { + //checking integrity of the COF in terms of ordering of wearables, + //checking and updating links' descriptions of wearables in the COF (before analyzed for "dirty" state) + + // As with enforce_item_restrictions handling above, we want + // to wait for the update callbacks, then (finally!) call + // updateAppearanceFromCOF() with no additional COF munging needed. + LLPointer<LLInventoryCallback> cb( + new LLUpdateAppearanceOnDestroy(false, false, post_update_func)); + updateClothingOrderingInfo(LLUUID::null, cb); + return; + } + + if (!validateClothingOrderingInfo()) + { + LL_WARNS() << "Clothing ordering error" << LL_ENDL; + } + + BoolSetter setIsInUpdateAppearanceFromCOF(mIsInUpdateAppearanceFromCOF); + selfStartPhase("update_appearance_from_cof"); - // Remove duplicate or excess wearables. Should normally be enforced at the UI level, but - // this should catch anything that gets through. - enforceItemRestrictions(); - // update dirty flag to see if the state of the COF matches // the saved outfit stored as a folder link updateIsDirty(); - //dumpCat(getCOF(),"COF, start"); + // Send server request for appearance update + if (gAgent.getRegion() && gAgent.getRegion()->getCentralBakeVersion()) + { + requestServerAppearanceUpdate(); + } - bool follow_folder_links = true; LLUUID current_outfit_id = getCOF(); // Find all the wearables that are in the COF's subtree. - lldebugs << "LLAppearanceMgr::updateFromCOF()" << llendl; + LL_DEBUGS() << "LLAppearanceMgr::updateFromCOF()" << LL_ENDL; 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); + getUserDescendents(current_outfit_id, wear_items, obj_items, gest_items); // Get rid of non-links in case somehow the COF was corrupted. remove_non_link_items(wear_items); remove_non_link_items(obj_items); @@ -1700,7 +2265,14 @@ void LLAppearanceMgr::updateAppearanceFromCOF(bool update_base_outfit_ordering) dumpItemArray(wear_items,"asset_dump: wear_item"); dumpItemArray(obj_items,"asset_dump: obj_item"); - if(!wear_items.count()) + LLViewerInventoryCategory *cof = gInventory.getCategory(current_outfit_id); + if (!gInventory.isCategoryComplete(current_outfit_id)) + { + LL_WARNS() << "COF info is not complete. Version " << cof->getVersion() + << " descendent_count " << cof->getDescendentCount() + << " viewer desc count " << cof->getViewerDescendentCount() << LL_ENDL; + } + if(!wear_items.size()) { LLNotificationsUtil::add("CouldNotPutOnOutfit"); return; @@ -1710,6 +2282,8 @@ void LLAppearanceMgr::updateAppearanceFromCOF(bool update_base_outfit_ordering) sortItemsByActualDescription(wear_items); + LL_DEBUGS("Avatar") << "HP block starts" << LL_ENDL; + LLTimer hp_block_timer; LLWearableHoldingPattern* holder = new LLWearableHoldingPattern; holder->setObjItems(obj_items); @@ -1720,9 +2294,9 @@ void LLAppearanceMgr::updateAppearanceFromCOF(bool update_base_outfit_ordering) // callback will be called (and this object deleted) // before the final getNextData(). - for(S32 i = 0; i < wear_items.count(); ++i) + for(S32 i = 0; i < wear_items.size(); ++i) { - LLViewerInventoryItem *item = wear_items.get(i); + LLViewerInventoryItem *item = wear_items.at(i); LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL; // Fault injection: use debug setting to test asset @@ -1750,25 +2324,28 @@ void LLAppearanceMgr::updateAppearanceFromCOF(bool update_base_outfit_ordering) { if (!item) { - llwarns << "Attempt to wear a null item " << llendl; + LL_WARNS() << "Attempt to wear a null item " << LL_ENDL; } else if (!linked_item) { - llwarns << "Attempt to wear a broken link [ name:" << item->getName() << " ] " << llendl; + LL_WARNS() << "Attempt to wear a broken link [ name:" << item->getName() << " ] " << LL_ENDL; } } } + selfStartPhase("get_wearables_2"); + for (LLWearableHoldingPattern::found_list_t::iterator it = holder->getFoundList().begin(); it != holder->getFoundList().end(); ++it) { LLFoundData& found = *it; - lldebugs << "waiting for onWearableAssetFetch callback, asset " << found.mAssetID.asString() << llendl; + LL_DEBUGS() << self_av_string() << "waiting for onWearableAssetFetch callback, asset " << found.mAssetID.asString() << LL_ENDL; // Fetch the wearables about to be worn. LLWearableList::instance().getAsset(found.mAssetID, found.mName, + gAgentAvatarp, found.mAssetType, onWearableAssetFetch, (void*)holder); @@ -1780,12 +2357,14 @@ void LLAppearanceMgr::updateAppearanceFromCOF(bool update_base_outfit_ordering) { doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollFetchCompletion,holder)); } + post_update_func(); + + LL_DEBUGS("Avatar") << "HP block ends, elapsed " << hp_block_timer.getElapsedTimeF32() << LL_ENDL; } void LLAppearanceMgr::getDescendentsOfAssetType(const LLUUID& category, LLInventoryModel::item_array_t& items, - LLAssetType::EType type, - bool follow_folder_links) + LLAssetType::EType type) { LLInventoryModel::cat_array_t cats; LLIsType is_of_type(type); @@ -1793,15 +2372,13 @@ void LLAppearanceMgr::getDescendentsOfAssetType(const LLUUID& category, cats, items, LLInventoryModel::EXCLUDE_TRASH, - is_of_type, - follow_folder_links); + is_of_type); } void LLAppearanceMgr::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::item_array_t& gest_items) { LLInventoryModel::cat_array_t wear_cats; LLFindWearables is_wearable; @@ -1809,8 +2386,7 @@ void LLAppearanceMgr::getUserDescendents(const LLUUID& category, wear_cats, wear_items, LLInventoryModel::EXCLUDE_TRASH, - is_wearable, - follow_folder_links); + is_wearable); LLInventoryModel::cat_array_t obj_cats; LLIsType is_object( LLAssetType::AT_OBJECT ); @@ -1818,8 +2394,7 @@ void LLAppearanceMgr::getUserDescendents(const LLUUID& category, obj_cats, obj_items, LLInventoryModel::EXCLUDE_TRASH, - is_object, - follow_folder_links); + is_object); // Find all gestures in this folder LLInventoryModel::cat_array_t gest_cats; @@ -1828,27 +2403,58 @@ void LLAppearanceMgr::getUserDescendents(const LLUUID& category, gest_cats, gest_items, LLInventoryModel::EXCLUDE_TRASH, - is_gesture, - follow_folder_links); + is_gesture); } void LLAppearanceMgr::wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append) { if(!category) return; + selfClearPhases(); + selfStartPhase("wear_inventory_category"); + gAgentWearables.notifyLoadingStarted(); - llinfos << "wearInventoryCategory( " << category->getName() - << " )" << llendl; + LL_INFOS("Avatar") << self_av_string() << "wearInventoryCategory( " << category->getName() + << " )" << LL_ENDL; - callAfterCategoryFetch(category->getUUID(),boost::bind(&LLAppearanceMgr::wearCategoryFinal, - &LLAppearanceMgr::instance(), - category->getUUID(), copy, append)); + // If we are copying from library, attempt to use AIS to copy the category. + bool ais_ran=false; + if (copy && AISCommand::isAPIAvailable()) + { + LLUUID parent_id; + parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING); + if (parent_id.isNull()) + { + parent_id = gInventory.getRootFolderID(); + } + + LLPointer<LLInventoryCallback> copy_cb = new LLWearCategoryAfterCopy(append); + LLPointer<LLInventoryCallback> track_cb = new LLTrackPhaseWrapper( + std::string("wear_inventory_category_callback"), copy_cb); + LLPointer<AISCommand> cmd_ptr = new CopyLibraryCategoryCommand(category->getUUID(), parent_id, track_cb); + ais_ran=cmd_ptr->run_command(); + } + + if (!ais_ran) + { + selfStartPhase("wear_inventory_category_fetch"); + callAfterCategoryFetch(category->getUUID(),boost::bind(&LLAppearanceMgr::wearCategoryFinal, + &LLAppearanceMgr::instance(), + category->getUUID(), copy, append)); + } +} + +S32 LLAppearanceMgr::getActiveCopyOperations() const +{ + return LLCallAfterInventoryCopyMgr::getInstanceCount(); } void LLAppearanceMgr::wearCategoryFinal(LLUUID& cat_id, bool copy_items, bool append) { - llinfos << "starting" << llendl; + LL_INFOS("Avatar") << self_av_string() << "starting" << LL_ENDL; + + selfStopPhase("wear_inventory_category_fetch"); // We now have an outfit ready to be copied to agent inventory. Do // it, and wear that outfit normally. @@ -1897,22 +2503,15 @@ void LLAppearanceMgr::wearCategoryFinal(LLUUID& cat_id, bool copy_items, bool ap pid, LLFolderType::FT_NONE, name); - LLPointer<LLInventoryCallback> cb = new LLWearInventoryCategoryCallback(new_cat_id, append); - it = items->begin(); - for(; it < end; ++it) - { - item = *it; - if(item) - { - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - new_cat_id, - std::string(), - cb); - } - } + + // Create a CopyMgr that will copy items, manage its own destruction + new LLCallAfterInventoryCopyMgr( + *items, new_cat_id, std::string("wear_inventory_category_callback"), + boost::bind(&LLAppearanceMgr::wearInventoryCategoryOnAvatar, + LLAppearanceMgr::getInstance(), + gInventory.getCategory(new_cat_id), + append)); + // BAP fixes a lag in display of created dir. gInventory.notifyObservers(); } @@ -1929,24 +2528,30 @@ void LLAppearanceMgr::wearInventoryCategoryOnAvatar( LLInventoryCategory* catego // 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; + if (!category) return; + + if ( !LLInventoryCallbackManager::is_instantiated() ) + { + // shutting down, ignore. + return; + } - llinfos << "wearInventoryCategoryOnAvatar( " << category->getName() - << " )" << llendl; + LL_INFOS("Avatar") << self_av_string() << "wearInventoryCategoryOnAvatar '" << category->getName() + << "'" << LL_ENDL; if (gAgentCamera.cameraCustomizeAvatar()) { // switching to outfit editor should automagically save any currently edited wearable - LLSideTray::getInstance()->showPanel("sidepanel_appearance", LLSD().with("type", "edit_outfit")); + LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit")); } LLAppearanceMgr::changeOutfit(TRUE, category->getUUID(), append); } +// FIXME do we really want to search entire inventory for matching name? void LLAppearanceMgr::wearOutfitByName(const std::string& name) { - llinfos << "Wearing category " << name << llendl; - //inc_busy_count(); + LL_INFOS("Avatar") << self_av_string() << "Wearing category " << name << LL_ENDL; LLInventoryModel::cat_array_t cat_array; LLInventoryModel::item_array_t item_array; @@ -1958,10 +2563,10 @@ void LLAppearanceMgr::wearOutfitByName(const std::string& name) has_name); bool copy_items = false; LLInventoryCategory* cat = NULL; - if (cat_array.count() > 0) + if (cat_array.size() > 0) { // Just wear the first one that matches - cat = cat_array.get(0); + cat = cat_array.at(0); } else { @@ -1970,9 +2575,9 @@ void LLAppearanceMgr::wearOutfitByName(const std::string& name) item_array, LLInventoryModel::EXCLUDE_TRASH, has_name); - if(cat_array.count() > 0) + if(cat_array.size() > 0) { - cat = cat_array.get(0); + cat = cat_array.at(0); copy_items = true; } } @@ -1983,11 +2588,9 @@ void LLAppearanceMgr::wearOutfitByName(const std::string& name) } else { - llwarns << "Couldn't find outfit " <<name<< " in wearOutfitByName()" - << llendl; + LL_WARNS() << "Couldn't find outfit " <<name<< " in wearOutfitByName()" + << LL_ENDL; } - - //dec_busy_count(); } bool areMatchingWearables(const LLViewerInventoryItem *a, const LLViewerInventoryItem *b) @@ -1999,10 +2602,10 @@ bool areMatchingWearables(const LLViewerInventoryItem *a, const LLViewerInventor class LLDeferredCOFLinkObserver: public LLInventoryObserver { public: - LLDeferredCOFLinkObserver(const LLUUID& item_id, bool do_update, LLPointer<LLInventoryCallback> cb = NULL): + LLDeferredCOFLinkObserver(const LLUUID& item_id, LLPointer<LLInventoryCallback> cb, const std::string& description): mItemID(item_id), - mDoUpdate(do_update), - mCallback(cb) + mCallback(cb), + mDescription(description) { } @@ -2016,40 +2619,44 @@ public: if (item) { gInventory.removeObserver(this); - LLAppearanceMgr::instance().addCOFItemLink(item,mDoUpdate,mCallback); + LLAppearanceMgr::instance().addCOFItemLink(item, mCallback, mDescription); delete this; } } private: const LLUUID mItemID; - bool mDoUpdate; + std::string mDescription; LLPointer<LLInventoryCallback> mCallback; }; // BAP - note that this runs asynchronously if the item is not already loaded from inventory. // Dangerous if caller assumes link will exist after calling the function. -void LLAppearanceMgr::addCOFItemLink(const LLUUID &item_id, bool do_update, LLPointer<LLInventoryCallback> cb) +void LLAppearanceMgr::addCOFItemLink(const LLUUID &item_id, + LLPointer<LLInventoryCallback> cb, + const std::string description) { const LLInventoryItem *item = gInventory.getItem(item_id); if (!item) { - LLDeferredCOFLinkObserver *observer = new LLDeferredCOFLinkObserver(item_id, do_update, cb); + LLDeferredCOFLinkObserver *observer = new LLDeferredCOFLinkObserver(item_id, cb, description); gInventory.addObserver(observer); } else { - addCOFItemLink(item, do_update, cb); + addCOFItemLink(item, cb, description); } } -void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item, bool do_update, LLPointer<LLInventoryCallback> cb) -{ +void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item, + LLPointer<LLInventoryCallback> cb, + const std::string description) +{ const LLViewerInventoryItem *vitem = dynamic_cast<const LLViewerInventoryItem*>(item); if (!vitem) { - llwarns << "not an llviewerinventoryitem, failed" << llendl; + LL_WARNS() << "not an llviewerinventoryitem, failed" << LL_ENDL; return; } @@ -2062,11 +2669,10 @@ void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item, bool do_update item_array, LLInventoryModel::EXCLUDE_TRASH); bool linked_already = false; - U32 count = 0; - for (S32 i=0; i<item_array.count(); i++) + for (S32 i=0; i<item_array.size(); i++) { // Are these links to the same object? - const LLViewerInventoryItem* inv_item = item_array.get(i).get(); + const LLViewerInventoryItem* inv_item = item_array.at(i).get(); const LLWearableType::EType wearable_type = inv_item->getWearableType(); const bool is_body_part = (wearable_type == LLWearableType::WT_SHAPE) @@ -2082,62 +2688,115 @@ void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item, bool do_update // type? If so, new item will replace old. else if ((vitem->isWearableType()) && (vitem->getWearableType() == wearable_type)) { - ++count; - if (is_body_part && inv_item->getIsLinkType() && (vitem->getWearableType() == wearable_type)) + if (is_body_part && inv_item->getIsLinkType()) { - gInventory.purgeObject(inv_item->getUUID()); + remove_inventory_item(inv_item->getUUID(), cb); } - else if (count >= LLAgentWearables::MAX_CLOTHING_PER_TYPE) + else if (!gAgentWearables.canAddWearable(wearable_type)) { - // MULTI-WEARABLES: make sure we don't go over MAX_CLOTHING_PER_TYPE - gInventory.purgeObject(inv_item->getUUID()); + // MULTI-WEARABLES: make sure we don't go over clothing limits + remove_inventory_item(inv_item->getUUID(), cb); } } } - if (linked_already) + if (!linked_already) { - if (do_update) - { - LLAppearanceMgr::updateAppearanceFromCOF(); - } - return; + LLViewerInventoryItem *copy_item = new LLViewerInventoryItem; + copy_item->copyViewerItem(vitem); + copy_item->setDescription(description); + link_inventory_object(getCOF(), copy_item, cb); } - else +} + +LLInventoryModel::item_array_t LLAppearanceMgr::findCOFItemLinks(const LLUUID& item_id) +{ + + LLInventoryModel::item_array_t result; + const LLViewerInventoryItem *vitem = + dynamic_cast<const LLViewerInventoryItem*>(gInventory.getItem(item_id)); + + if (vitem) { - if(do_update && cb.isNull()) + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendents(LLAppearanceMgr::getCOF(), + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH); + for (S32 i=0; i<item_array.size(); i++) { - cb = new ModifiedCOFCallback; + const LLViewerInventoryItem* inv_item = item_array.at(i).get(); + if (inv_item->getLinkedUUID() == vitem->getLinkedUUID()) + { + result.push_back(item_array.at(i)); + } } - const std::string description = vitem->getIsLinkType() ? vitem->getDescription() : ""; - link_inventory_item( gAgent.getID(), - vitem->getLinkedUUID(), - getCOF(), - vitem->getName(), - description, - LLAssetType::AT_LINK, - cb); } - return; + return result; } -// BAP remove ensemble code for 2.1? -void LLAppearanceMgr::addEnsembleLink( LLInventoryCategory* cat, bool do_update ) +bool LLAppearanceMgr::isLinkedInCOF(const LLUUID& item_id) { -#if SUPPORT_ENSEMBLES - // 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(), - cat->getDescription(), - LLAssetType::AT_LINK_FOLDER, - cb); -#endif + LLInventoryModel::item_array_t links = LLAppearanceMgr::instance().findCOFItemLinks(item_id); + return links.size() > 0; } -void LLAppearanceMgr::removeCOFItemLinks(const LLUUID& item_id, bool do_update) +void LLAppearanceMgr::removeAllClothesFromAvatar() +{ + // Fetch worn clothes (i.e. the ones in COF). + LLInventoryModel::item_array_t clothing_items; + LLInventoryModel::cat_array_t dummy; + LLIsType is_clothing(LLAssetType::AT_CLOTHING); + gInventory.collectDescendentsIf(getCOF(), + dummy, + clothing_items, + LLInventoryModel::EXCLUDE_TRASH, + is_clothing); + uuid_vec_t item_ids; + for (LLInventoryModel::item_array_t::iterator it = clothing_items.begin(); + it != clothing_items.end(); ++it) + { + item_ids.push_back((*it).get()->getLinkedUUID()); + } + + // Take them off by removing from COF. + removeItemsFromAvatar(item_ids); +} + +void LLAppearanceMgr::removeAllAttachmentsFromAvatar() +{ + if (!isAgentAvatarValid()) return; + + LLAgentWearables::llvo_vec_t objects_to_remove; + + for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); + iter != gAgentAvatarp->mAttachmentPoints.end();) + { + LLVOAvatar::attachment_map_t::iterator curiter = iter++; + LLViewerJointAttachment* attachment = curiter->second; + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject *attached_object = (*attachment_iter); + if (attached_object) + { + objects_to_remove.push_back(attached_object); + } + } + } + uuid_vec_t ids_to_remove; + for (LLAgentWearables::llvo_vec_t::iterator it = objects_to_remove.begin(); + it != objects_to_remove.end(); + ++it) + { + ids_to_remove.push_back((*it)->getAttachmentItemID()); + } + removeItemsFromAvatar(ids_to_remove); +} + +void LLAppearanceMgr::removeCOFItemLinks(const LLUUID& item_id, LLPointer<LLInventoryCallback> cb) { gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); @@ -2147,21 +2806,22 @@ void LLAppearanceMgr::removeCOFItemLinks(const LLUUID& item_id, bool do_update) cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH); - for (S32 i=0; i<item_array.count(); i++) + for (S32 i=0; i<item_array.size(); i++) { - const LLInventoryItem* item = item_array.get(i).get(); + const LLInventoryItem* item = item_array.at(i).get(); if (item->getIsLinkType() && item->getLinkedUUID() == item_id) { - gInventory.purgeObject(item->getUUID()); + bool immediate_delete = false; + if (item->getType() == LLAssetType::AT_OBJECT) + { + immediate_delete = true; + } + remove_inventory_item(item->getUUID(), cb, immediate_delete); } } - if (do_update) - { - LLAppearanceMgr::updateAppearanceFromCOF(); - } } -void LLAppearanceMgr::removeCOFLinksOfType(LLWearableType::EType type, bool do_update) +void LLAppearanceMgr::removeCOFLinksOfType(LLWearableType::EType type, LLPointer<LLInventoryCallback> cb) { LLFindWearablesOfType filter_wearables_of_type(type); LLInventoryModel::cat_array_t cats; @@ -2174,21 +2834,16 @@ void LLAppearanceMgr::removeCOFLinksOfType(LLWearableType::EType type, bool do_u const LLViewerInventoryItem* item = *it; if (item->getIsLinkType()) // we must operate on links only { - gInventory.purgeObject(item->getUUID()); + remove_inventory_item(item->getUUID(), cb); } } - - if (do_update) - { - updateAppearanceFromCOF(); - } } bool sort_by_linked_uuid(const LLViewerInventoryItem* item1, const LLViewerInventoryItem* item2) { if (!item1 || !item2) { - llwarning("item1, item2 cannot be null, something is very wrong", 0); + LL_WARNS() << "item1, item2 cannot be null, something is very wrong" << LL_ENDL; return true; } @@ -2218,7 +2873,7 @@ void LLAppearanceMgr::updateIsDirty() if (base_outfit.notNull()) { - LLIsOfAssetType collector = LLIsOfAssetType(LLAssetType::AT_LINK); + LLIsValidItemLink collector; LLInventoryModel::cat_array_t cof_cats; LLInventoryModel::item_array_t cof_items; @@ -2230,8 +2885,9 @@ void LLAppearanceMgr::updateIsDirty() gInventory.collectDescendentsIf(base_outfit, outfit_cats, outfit_items, LLInventoryModel::EXCLUDE_TRASH, collector); - if(outfit_items.count() != cof_items.count()) + if(outfit_items.size() != cof_items.size()) { + LL_DEBUGS("Avatar") << "item count different - base " << outfit_items.size() << " cof " << cof_items.size() << LL_ENDL; // Current outfit folder should have one more item than the outfit folder. // this one item is the link back to the outfit folder itself. mOutfitIsDirty = true; @@ -2244,44 +2900,160 @@ void LLAppearanceMgr::updateIsDirty() for (U32 i = 0; i < cof_items.size(); ++i) { - LLViewerInventoryItem *item1 = cof_items.get(i); - LLViewerInventoryItem *item2 = outfit_items.get(i); + LLViewerInventoryItem *item1 = cof_items.at(i); + LLViewerInventoryItem *item2 = outfit_items.at(i); if (item1->getLinkedUUID() != item2->getLinkedUUID() || item1->getName() != item2->getName() || - item1->LLInventoryItem::getDescription() != item2->LLInventoryItem::getDescription()) + item1->getActualDescription() != item2->getActualDescription()) { + if (item1->getLinkedUUID() != item2->getLinkedUUID()) + { + LL_DEBUGS("Avatar") << "link id different " << LL_ENDL; + } + else + { + if (item1->getName() != item2->getName()) + { + LL_DEBUGS("Avatar") << "name different " << item1->getName() << " " << item2->getName() << LL_ENDL; + } + if (item1->getActualDescription() != item2->getActualDescription()) + { + LL_DEBUGS("Avatar") << "desc different " << item1->getActualDescription() + << " " << item2->getActualDescription() + << " names " << item1->getName() << " " << item2->getName() << LL_ENDL; + } + } mOutfitIsDirty = true; return; } } } + llassert(!mOutfitIsDirty); + LL_DEBUGS("Avatar") << "clean" << LL_ENDL; } -void LLAppearanceMgr::autopopulateOutfits() +// *HACK: Must match name in Library or agent inventory +const std::string ROOT_GESTURES_FOLDER = "Gestures"; +const std::string COMMON_GESTURES_FOLDER = "Common Gestures"; +const std::string MALE_GESTURES_FOLDER = "Male Gestures"; +const std::string FEMALE_GESTURES_FOLDER = "Female Gestures"; +const std::string SPEECH_GESTURES_FOLDER = "Speech Gestures"; +const std::string OTHER_GESTURES_FOLDER = "Other Gestures"; + +void LLAppearanceMgr::copyLibraryGestures() { - // If this is the very first time the user has logged into viewer2+ (from a legacy viewer, or new account) - // then auto-populate outfits from the library into the My Outfits folder. + LL_INFOS("Avatar") << self_av_string() << "Copying library gestures" << LL_ENDL; + + // Copy gestures + LLUUID lib_gesture_cat_id = + gInventory.findLibraryCategoryUUIDForType(LLFolderType::FT_GESTURE,false); + if (lib_gesture_cat_id.isNull()) + { + LL_WARNS() << "Unable to copy gestures, source category not found" << LL_ENDL; + } + LLUUID dst_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); - llinfos << "avatar fully visible" << llendl; + std::vector<std::string> gesture_folders_to_copy; + gesture_folders_to_copy.push_back(MALE_GESTURES_FOLDER); + gesture_folders_to_copy.push_back(FEMALE_GESTURES_FOLDER); + gesture_folders_to_copy.push_back(COMMON_GESTURES_FOLDER); + gesture_folders_to_copy.push_back(SPEECH_GESTURES_FOLDER); + gesture_folders_to_copy.push_back(OTHER_GESTURES_FOLDER); - static bool check_populate_my_outfits = true; - if (check_populate_my_outfits && - (LLInventoryModel::getIsFirstTimeInViewer2() - || gSavedSettings.getBOOL("MyOutfitsAutofill"))) + for(std::vector<std::string>::iterator it = gesture_folders_to_copy.begin(); + it != gesture_folders_to_copy.end(); + ++it) { - gAgentWearables.populateMyOutfitsFolder(); + std::string& folder_name = *it; + + LLPointer<LLInventoryCallback> cb(NULL); + + // After copying gestures, activate Common, Other, plus + // Male and/or Female, depending upon the initial outfit gender. + ESex gender = gAgentAvatarp->getSex(); + + std::string activate_male_gestures; + std::string activate_female_gestures; + switch (gender) { + case SEX_MALE: + activate_male_gestures = MALE_GESTURES_FOLDER; + break; + case SEX_FEMALE: + activate_female_gestures = FEMALE_GESTURES_FOLDER; + break; + case SEX_BOTH: + activate_male_gestures = MALE_GESTURES_FOLDER; + activate_female_gestures = FEMALE_GESTURES_FOLDER; + break; + } + + if (folder_name == activate_male_gestures || + folder_name == activate_female_gestures || + folder_name == COMMON_GESTURES_FOLDER || + folder_name == OTHER_GESTURES_FOLDER) + { + cb = new LLBoostFuncInventoryCallback(activate_gesture_cb); + } + + LLUUID cat_id = findDescendentCategoryIDByName(lib_gesture_cat_id,folder_name); + if (cat_id.isNull()) + { + LL_WARNS() << self_av_string() << "failed to find gesture folder for " << folder_name << LL_ENDL; + } + else + { + LL_DEBUGS("Avatar") << self_av_string() << "initiating fetch and copy for " << folder_name << " cat_id " << cat_id << LL_ENDL; + callAfterCategoryFetch(cat_id, + boost::bind(&LLAppearanceMgr::shallowCopyCategory, + &LLAppearanceMgr::instance(), + cat_id, dst_id, cb)); + } } - check_populate_my_outfits = false; } // Handler for anything that's deferred until avatar de-clouds. void LLAppearanceMgr::onFirstFullyVisible() { + gAgentAvatarp->outputRezTiming("Avatar fully loaded"); + gAgentAvatarp->reportAvatarRezTime(); gAgentAvatarp->debugAvatarVisible(); - autopopulateOutfits(); + + // If this is the first time we've ever logged in, + // then copy default gestures from the library. + if (gAgent.isFirstLogin()) { + copyLibraryGestures(); + } } +// update "dirty" state - defined outside class to allow for calling +// after appearance mgr instance has been destroyed. +void appearance_mgr_update_dirty_state() +{ + if (LLAppearanceMgr::instanceExists()) + { + LLAppearanceMgr::getInstance()->updateIsDirty(); + LLAppearanceMgr::getInstance()->setOutfitLocked(false); + gAgentWearables.notifyLoadingFinished(); + } +} + +void update_base_outfit_after_ordering() +{ + LLAppearanceMgr& app_mgr = LLAppearanceMgr::instance(); + + LLPointer<LLInventoryCallback> dirty_state_updater = + new LLBoostFuncInventoryCallback(no_op_inventory_func, appearance_mgr_update_dirty_state); + + //COF contains only links so we copy to the Base Outfit only links + const LLUUID base_outfit_id = app_mgr.getBaseOutfitUUID(); + bool copy_folder_links = false; + app_mgr.slamCategoryLinks(app_mgr.getCOF(), base_outfit_id, copy_folder_links, dirty_state_updater); + +} + +// Save COF changes - update the contents of the current base outfit +// to match the current COF. Fails if no current base outfit is set. bool LLAppearanceMgr::updateBaseOutfit() { if (isOutfitLocked()) @@ -2290,23 +3062,20 @@ bool LLAppearanceMgr::updateBaseOutfit() llassert(!isOutfitLocked()); return false; } + setOutfitLocked(true); gAgentWearables.notifyLoadingStarted(); const LLUUID base_outfit_id = getBaseOutfitUUID(); if (base_outfit_id.isNull()) return false; + LL_DEBUGS("Avatar") << "saving cof to base outfit " << base_outfit_id << LL_ENDL; - updateClothingOrderingInfo(); - - // in a Base Outfit we do not remove items, only links - purgeCategory(base_outfit_id, false); - - - LLPointer<LLInventoryCallback> dirty_state_updater = new LLUpdateDirtyState(); - - //COF contains only links so we copy to the Base Outfit only links - shallowCopyCategoryContents(getCOF(), base_outfit_id, dirty_state_updater); + LLPointer<LLInventoryCallback> cb = + new LLBoostFuncInventoryCallback(no_op_inventory_func, update_base_outfit_after_ordering); + // Really shouldn't be needed unless there's a race condition - + // updateAppearanceFromCOF() already calls updateClothingOrderingInfo. + updateClothingOrderingInfo(LLUUID::null, cb); return true; } @@ -2316,12 +3085,12 @@ void LLAppearanceMgr::divvyWearablesByType(const LLInventoryModel::item_array_t& items_by_type.resize(LLWearableType::WT_COUNT); if (items.empty()) return; - for (S32 i=0; i<items.count(); i++) + for (S32 i=0; i<items.size(); i++) { - LLViewerInventoryItem *item = items.get(i); + LLViewerInventoryItem *item = items.at(i); if (!item) { - LL_WARNS("Appearance") << "NULL item found" << llendl; + LL_WARNS("Appearance") << "NULL item found" << LL_ENDL; continue; } // Ignore non-wearables. @@ -2354,14 +3123,8 @@ struct WearablesOrderComparator bool operator()(const LLInventoryItem* item1, const LLInventoryItem* item2) { - if (!item1 || !item2) - { - llwarning("either item1 or item2 is NULL", 0); - return true; - } - - const std::string& desc1 = item1->LLInventoryItem::getDescription(); - const std::string& desc2 = item2->LLInventoryItem::getDescription(); + const std::string& desc1 = item1->getActualDescription(); + const std::string& desc2 = item2->getActualDescription(); bool item1_valid = (desc1.size() == mControlSize) && (ORDER_NUMBER_SEPARATOR == desc1[0]); bool item2_valid = (desc2.size() == mControlSize) && (ORDER_NUMBER_SEPARATOR == desc2[0]); @@ -2373,134 +3136,630 @@ struct WearablesOrderComparator //items with ordering information but not for the associated wearables type if (!item1_valid && item2_valid) return false; + else if (item1_valid && !item2_valid) + return true; - return true; + return item1->getName() < item2->getName(); } U32 mControlSize; }; -void LLAppearanceMgr::updateClothingOrderingInfo(LLUUID cat_id, bool update_base_outfit_ordering) +void LLAppearanceMgr::getWearableOrderingDescUpdates(LLInventoryModel::item_array_t& wear_items, + desc_map_t& desc_map) { - if (cat_id.isNull()) - { - cat_id = getCOF(); - if (update_base_outfit_ordering) - { - const LLUUID base_outfit_id = getBaseOutfitUUID(); - if (base_outfit_id.notNull()) - { - updateClothingOrderingInfo(base_outfit_id,false); - } - } - } - - // COF is processed if cat_id is not specified - LLInventoryModel::item_array_t wear_items; - getDescendentsOfAssetType(cat_id, wear_items, LLAssetType::AT_CLOTHING, false); - wearables_by_type_t items_by_type(LLWearableType::WT_COUNT); divvyWearablesByType(wear_items, items_by_type); - bool inventory_changed = false; for (U32 type = LLWearableType::WT_SHIRT; type < LLWearableType::WT_COUNT; type++) { - U32 size = items_by_type[type].size(); if (!size) continue; - + //sinking down invalid items which need reordering std::sort(items_by_type[type].begin(), items_by_type[type].end(), WearablesOrderComparator((LLWearableType::EType) type)); - + //requesting updates only for those links which don't have "valid" descriptions for (U32 i = 0; i < size; i++) { LLViewerInventoryItem* item = items_by_type[type][i]; if (!item) continue; - + std::string new_order_str = build_order_string((LLWearableType::EType)type, i); - if (new_order_str == item->LLInventoryItem::getDescription()) continue; - - item->setDescription(new_order_str); - item->setComplete(TRUE); - item->updateServer(FALSE); - gInventory.updateItem(item); + if (new_order_str == item->getActualDescription()) continue; - inventory_changed = true; + desc_map[item->getUUID()] = new_order_str; } } +} - //*TODO do we really need to notify observers? - if (inventory_changed) gInventory.notifyObservers(); +bool LLAppearanceMgr::validateClothingOrderingInfo(LLUUID cat_id) +{ + // COF is processed if cat_id is not specified + if (cat_id.isNull()) + { + cat_id = getCOF(); + } + + LLInventoryModel::item_array_t wear_items; + getDescendentsOfAssetType(cat_id, wear_items, LLAssetType::AT_CLOTHING); + + // Identify items for which desc needs to change. + desc_map_t desc_map; + getWearableOrderingDescUpdates(wear_items, desc_map); + + for (desc_map_t::const_iterator it = desc_map.begin(); + it != desc_map.end(); ++it) + { + const LLUUID& item_id = it->first; + const std::string& new_order_str = it->second; + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LL_WARNS() << "Order validation fails: " << item->getName() + << " needs to update desc to: " << new_order_str + << " (from: " << item->getActualDescription() << ")" << LL_ENDL; + } + + return desc_map.size() == 0; } +void LLAppearanceMgr::updateClothingOrderingInfo(LLUUID cat_id, + LLPointer<LLInventoryCallback> cb) +{ + // COF is processed if cat_id is not specified + if (cat_id.isNull()) + { + cat_id = getCOF(); + } + LLInventoryModel::item_array_t wear_items; + getDescendentsOfAssetType(cat_id, wear_items, LLAssetType::AT_CLOTHING); + + // Identify items for which desc needs to change. + desc_map_t desc_map; + getWearableOrderingDescUpdates(wear_items, desc_map); + for (desc_map_t::const_iterator it = desc_map.begin(); + it != desc_map.end(); ++it) + { + LLSD updates; + const LLUUID& item_id = it->first; + const std::string& new_order_str = it->second; + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LL_DEBUGS("Avatar") << item->getName() << " updating desc to: " << new_order_str + << " (was: " << item->getActualDescription() << ")" << LL_ENDL; + updates["desc"] = new_order_str; + update_inventory_item(item_id,updates,cb); + } + +} -class LLShowCreatedOutfit: public LLInventoryCallback +class RequestAgentUpdateAppearanceResponder: public LLHTTPClient::Responder { + LOG_CLASS(RequestAgentUpdateAppearanceResponder); + + friend class LLAppearanceMgr; + public: - LLShowCreatedOutfit(LLUUID& folder_id, bool show_panel = true): mFolderID(folder_id), mShowPanel(show_panel) - {} + RequestAgentUpdateAppearanceResponder(); + + virtual ~RequestAgentUpdateAppearanceResponder(); + +private: + // Called when sendServerAppearanceUpdate called. May or may not + // trigger a request depending on various bits of state. + void onRequestRequested(); + + // Post the actual appearance request to cap. + void sendRequest(); + + void debugCOF(const LLSD& content); + +protected: + // Successful completion. + /* virtual */ void httpSuccess(); + + // Error + /*virtual*/ void httpFailure(); + + void onFailure(); + void onSuccess(); - virtual ~LLShowCreatedOutfit() + S32 mInFlightCounter; + LLTimer mInFlightTimer; + LLPointer<LLHTTPRetryPolicy> mRetryPolicy; +}; + +RequestAgentUpdateAppearanceResponder::RequestAgentUpdateAppearanceResponder() +{ + bool retry_on_4xx = true; + mRetryPolicy = new LLAdaptiveRetryPolicy(1.0, 32.0, 2.0, 10, retry_on_4xx); + mInFlightCounter = 0; +} + +RequestAgentUpdateAppearanceResponder::~RequestAgentUpdateAppearanceResponder() +{ +} + +void RequestAgentUpdateAppearanceResponder::onRequestRequested() +{ + // If we have already received an update for this or higher cof version, ignore. + S32 cof_version = LLAppearanceMgr::instance().getCOFVersion(); + S32 last_rcv = gAgentAvatarp->mLastUpdateReceivedCOFVersion; + S32 last_req = gAgentAvatarp->mLastUpdateRequestCOFVersion; + LL_DEBUGS("Avatar") << "cof_version " << cof_version + << " last_rcv " << last_rcv + << " last_req " << last_req + << " in flight " << mInFlightCounter << LL_ENDL; + if ((mInFlightCounter>0) && (mInFlightTimer.hasExpired())) + { + LL_WARNS("Avatar") << "in flight timer expired, resetting " << LL_ENDL; + mInFlightCounter = 0; + } + if (cof_version < last_rcv) + { + LL_DEBUGS("Avatar") << "Have already received update for cof version " << last_rcv + << " will not request for " << cof_version << LL_ENDL; + return; + } + if (mInFlightCounter>0 && last_req >= cof_version) + { + LL_DEBUGS("Avatar") << "Request already in flight for cof version " << last_req + << " will not request for " << cof_version << LL_ENDL; + return; + } + + // Actually send the request. + LL_DEBUGS("Avatar") << "Will send request for cof_version " << cof_version << LL_ENDL; + mRetryPolicy->reset(); + sendRequest(); +} + +void RequestAgentUpdateAppearanceResponder::sendRequest() +{ + if (gAgentAvatarp->isEditingAppearance()) + { + // don't send out appearance updates if in appearance editing mode + return; + } + + if (!gAgent.getRegion()) + { + LL_WARNS() << "Region not set, cannot request server appearance update" << LL_ENDL; + return; + } + if (gAgent.getRegion()->getCentralBakeVersion()==0) + { + LL_WARNS() << "Region does not support baking" << LL_ENDL; + } + std::string url = gAgent.getRegion()->getCapability("UpdateAvatarAppearance"); + if (url.empty()) { - if (!LLApp::isRunning()) + LL_WARNS() << "No cap for UpdateAvatarAppearance." << LL_ENDL; + return; + } + + LLSD body; + S32 cof_version = LLAppearanceMgr::instance().getCOFVersion(); + if (gSavedSettings.getBOOL("DebugAvatarExperimentalServerAppearanceUpdate")) + { + body = LLAppearanceMgr::instance().dumpCOF(); + } + else + { + body["cof_version"] = cof_version; + if (gSavedSettings.getBOOL("DebugForceAppearanceRequestFailure")) { - llwarns << "called during shutdown, skipping" << llendl; - return; + body["cof_version"] = cof_version+999; } + } + LL_DEBUGS("Avatar") << "request url " << url << " my_cof_version " << cof_version << LL_ENDL; - LLSD key; - - //EXT-7727. For new accounts LLShowCreatedOutfit is created during login process - // add may be processed after login process is finished - if (mShowPanel) + mInFlightCounter++; + mInFlightTimer.setTimerExpirySec(60.0); + LLHTTPClient::post(url, body, this); + llassert(cof_version >= gAgentAvatarp->mLastUpdateRequestCOFVersion); + gAgentAvatarp->mLastUpdateRequestCOFVersion = cof_version; +} + +void RequestAgentUpdateAppearanceResponder::debugCOF(const LLSD& content) +{ + LL_INFOS("Avatar") << "AIS COF, version received: " << content["expected"].asInteger() + << " ================================= " << LL_ENDL; + std::set<LLUUID> ais_items, local_items; + const LLSD& cof_raw = content["cof_raw"]; + for (LLSD::array_const_iterator it = cof_raw.beginArray(); + it != cof_raw.endArray(); ++it) + { + const LLSD& item = *it; + if (item["parent_id"].asUUID() == LLAppearanceMgr::instance().getCOF()) + { + ais_items.insert(item["item_id"].asUUID()); + if (item["type"].asInteger() == 24) // link + { + LL_INFOS("Avatar") << "AIS Link: item_id: " << item["item_id"].asUUID() + << " linked_item_id: " << item["asset_id"].asUUID() + << " name: " << item["name"].asString() + << LL_ENDL; + } + else if (item["type"].asInteger() == 25) // folder link + { + LL_INFOS("Avatar") << "AIS Folder link: item_id: " << item["item_id"].asUUID() + << " linked_item_id: " << item["asset_id"].asUUID() + << " name: " << item["name"].asString() + << LL_ENDL; + } + else + { + LL_INFOS("Avatar") << "AIS Other: item_id: " << item["item_id"].asUUID() + << " linked_item_id: " << item["asset_id"].asUUID() + << " name: " << item["name"].asString() + << " type: " << item["type"].asInteger() + << LL_ENDL; + } + } + } + LL_INFOS("Avatar") << LL_ENDL; + LL_INFOS("Avatar") << "Local COF, version requested: " << content["observed"].asInteger() + << " ================================= " << LL_ENDL; + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendents(LLAppearanceMgr::instance().getCOF(), + cat_array,item_array,LLInventoryModel::EXCLUDE_TRASH); + for (S32 i=0; i<item_array.size(); i++) + { + const LLViewerInventoryItem* inv_item = item_array.at(i).get(); + local_items.insert(inv_item->getUUID()); + LL_INFOS("Avatar") << "LOCAL: item_id: " << inv_item->getUUID() + << " linked_item_id: " << inv_item->getLinkedUUID() + << " name: " << inv_item->getName() + << " parent: " << inv_item->getParentUUID() + << LL_ENDL; + } + LL_INFOS("Avatar") << " ================================= " << LL_ENDL; + S32 local_only = 0, ais_only = 0; + for (std::set<LLUUID>::iterator it = local_items.begin(); it != local_items.end(); ++it) + { + if (ais_items.find(*it) == ais_items.end()) { - LLSideTray::getInstance()->showPanel("panel_outfits_inventory", key); + LL_INFOS("Avatar") << "LOCAL ONLY: " << *it << LL_ENDL; + local_only++; } - LLOutfitsList *outfits_list = - dynamic_cast<LLOutfitsList*>(LLSideTray::getInstance()->getPanel("outfitslist_tab")); - if (outfits_list) + } + for (std::set<LLUUID>::iterator it = ais_items.begin(); it != ais_items.end(); ++it) + { + if (local_items.find(*it) == local_items.end()) { - outfits_list->setSelectedOutfitByUUID(mFolderID); + LL_INFOS("Avatar") << "AIS ONLY: " << *it << LL_ENDL; + ais_only++; } + } + if (local_only==0 && ais_only==0) + { + LL_INFOS("Avatar") << "COF contents identical, only version numbers differ (req " + << content["observed"].asInteger() + << " rcv " << content["expected"].asInteger() + << ")" << LL_ENDL; + } +} - LLAppearanceMgr::getInstance()->updateIsDirty(); - gAgentWearables.notifyLoadingFinished(); // New outfit is saved. - LLAppearanceMgr::getInstance()->updatePanelOutfitName(""); +/* virtual */ void RequestAgentUpdateAppearanceResponder::httpSuccess() +{ + const LLSD& content = getContent(); + if (!content.isMap()) + { + failureResult(HTTP_INTERNAL_ERROR, "Malformed response contents", content); + return; } + if (content["success"].asBoolean()) + { + LL_DEBUGS("Avatar") << "succeeded" << LL_ENDL; + if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) + { + dump_sequential_xml(gAgentAvatarp->getFullname() + "_appearance_request_ok", content); + } - virtual void fire(const LLUUID&) - {} + onSuccess(); + } + else + { + failureResult(HTTP_INTERNAL_ERROR, "Non-success response", content); + } +} + +void RequestAgentUpdateAppearanceResponder::onSuccess() +{ + mInFlightCounter = llmax(mInFlightCounter-1,0); +} + +/*virtual*/ void RequestAgentUpdateAppearanceResponder::httpFailure() +{ + LL_WARNS("Avatar") << "appearance update request failed, status " + << getStatus() << " reason " << getReason() << LL_ENDL; + + if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) + { + const LLSD& content = getContent(); + dump_sequential_xml(gAgentAvatarp->getFullname() + "_appearance_request_error", content); + debugCOF(content); + } + onFailure(); +} + +void RequestAgentUpdateAppearanceResponder::onFailure() +{ + mInFlightCounter = llmax(mInFlightCounter-1,0); + + F32 seconds_to_wait; + mRetryPolicy->onFailure(getStatus(), getResponseHeaders()); + if (mRetryPolicy->shouldRetry(seconds_to_wait)) + { + LL_INFOS() << "retrying" << LL_ENDL; + doAfterInterval(boost::bind(&RequestAgentUpdateAppearanceResponder::sendRequest,this), + seconds_to_wait); + } + else + { + LL_WARNS() << "giving up after too many retries" << LL_ENDL; + } +} + + +LLSD LLAppearanceMgr::dumpCOF() const +{ + LLSD links = LLSD::emptyArray(); + LLMD5 md5; + + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendents(getCOF(),cat_array,item_array,LLInventoryModel::EXCLUDE_TRASH); + for (S32 i=0; i<item_array.size(); i++) + { + const LLViewerInventoryItem* inv_item = item_array.at(i).get(); + LLSD item; + LLUUID item_id(inv_item->getUUID()); + md5.update((unsigned char*)item_id.mData, 16); + item["description"] = inv_item->getActualDescription(); + md5.update(inv_item->getActualDescription()); + item["asset_type"] = inv_item->getActualType(); + LLUUID linked_id(inv_item->getLinkedUUID()); + item["linked_id"] = linked_id; + md5.update((unsigned char*)linked_id.mData, 16); + + if (LLAssetType::AT_LINK == inv_item->getActualType()) + { + const LLViewerInventoryItem* linked_item = inv_item->getLinkedItem(); + if (NULL == linked_item) + { + LL_WARNS() << "Broken link for item '" << inv_item->getName() + << "' (" << inv_item->getUUID() + << ") during requestServerAppearanceUpdate" << LL_ENDL; + continue; + } + // Some assets may be 'hidden' and show up as null in the viewer. + //if (linked_item->getAssetUUID().isNull()) + //{ + // LL_WARNS() << "Broken link (null asset) for item '" << inv_item->getName() + // << "' (" << inv_item->getUUID() + // << ") during requestServerAppearanceUpdate" << LL_ENDL; + // continue; + //} + LLUUID linked_asset_id(linked_item->getAssetUUID()); + md5.update((unsigned char*)linked_asset_id.mData, 16); + U32 flags = linked_item->getFlags(); + md5.update(boost::lexical_cast<std::string>(flags)); + } + else if (LLAssetType::AT_LINK_FOLDER != inv_item->getActualType()) + { + LL_WARNS() << "Non-link item '" << inv_item->getName() + << "' (" << inv_item->getUUID() + << ") type " << (S32) inv_item->getActualType() + << " during requestServerAppearanceUpdate" << LL_ENDL; + continue; + } + links.append(item); + } + LLSD result = LLSD::emptyMap(); + result["cof_contents"] = links; + char cof_md5sum[MD5HEX_STR_SIZE]; + md5.finalize(); + md5.hex_digest(cof_md5sum); + result["cof_md5sum"] = std::string(cof_md5sum); + return result; +} + +void LLAppearanceMgr::requestServerAppearanceUpdate() +{ + mAppearanceResponder->onRequestRequested(); +} + +class LLIncrementCofVersionResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLIncrementCofVersionResponder); +public: + LLIncrementCofVersionResponder() : LLHTTPClient::Responder() + { + mRetryPolicy = new LLAdaptiveRetryPolicy(1.0, 16.0, 2.0, 5); + } + + virtual ~LLIncrementCofVersionResponder() + { + } + +protected: + virtual void httpSuccess() + { + LL_INFOS() << "Successfully incremented agent's COF." << LL_ENDL; + const LLSD& content = getContent(); + if (!content.isMap()) + { + failureResult(HTTP_INTERNAL_ERROR, "Malformed response contents", content); + return; + } + S32 new_version = content["category"]["version"].asInteger(); + + // cof_version should have increased + llassert(new_version > gAgentAvatarp->mLastUpdateRequestCOFVersion); + + gAgentAvatarp->mLastUpdateRequestCOFVersion = new_version; + } + + virtual void httpFailure() + { + LL_WARNS("Avatar") << "While attempting to increment the agent's cof we got an error " + << dumpResponse() << LL_ENDL; + F32 seconds_to_wait; + mRetryPolicy->onFailure(getStatus(), getResponseHeaders()); + if (mRetryPolicy->shouldRetry(seconds_to_wait)) + { + LL_INFOS() << "retrying" << LL_ENDL; + doAfterInterval(boost::bind(&LLAppearanceMgr::incrementCofVersion, + LLAppearanceMgr::getInstance(), + LLHTTPClient::ResponderPtr(this)), + seconds_to_wait); + } + else + { + LL_WARNS() << "giving up after too many retries" << LL_ENDL; + } + } private: - LLUUID mFolderID; - bool mShowPanel; + LLPointer<LLHTTPRetryPolicy> mRetryPolicy; }; -LLUUID LLAppearanceMgr::makeNewOutfitLinks(const std::string& new_folder_name, bool show_panel) +void LLAppearanceMgr::incrementCofVersion(LLHTTPClient::ResponderPtr responder_ptr) { - if (!isAgentAvatarValid()) return LLUUID::null; + // If we don't have a region, report it as an error + if (gAgent.getRegion() == NULL) + { + LL_WARNS() << "Region not set, cannot request cof_version increment" << LL_ENDL; + return; + } - gAgentWearables.notifyLoadingStarted(); + std::string url = gAgent.getRegion()->getCapability("IncrementCofVersion"); + if (url.empty()) + { + LL_WARNS() << "No cap for IncrementCofVersion." << LL_ENDL; + return; + } - // First, make a folder in the My Outfits directory. - const LLUUID parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - LLUUID folder_id = gInventory.createNewCategory( - parent_id, - LLFolderType::FT_OUTFIT, - new_folder_name); + LL_INFOS() << "Requesting cof_version be incremented via capability to: " + << url << LL_ENDL; + LLSD headers; + LLSD body = LLSD::emptyMap(); + + if (!responder_ptr.get()) + { + responder_ptr = LLHTTPClient::ResponderPtr(new LLIncrementCofVersionResponder()); + } - updateClothingOrderingInfo(); + LLHTTPClient::get(url, body, responder_ptr, headers, 30.0f); +} - LLPointer<LLInventoryCallback> cb = new LLShowCreatedOutfit(folder_id,show_panel); - shallowCopyCategoryContents(getCOF(),folder_id, cb); - createBaseOutfitLink(folder_id, cb); +U32 LLAppearanceMgr::getNumAttachmentsInCOF() +{ + const LLUUID cof = getCOF(); + LLInventoryModel::item_array_t obj_items; + getDescendentsOfAssetType(cof, obj_items, LLAssetType::AT_OBJECT); + return obj_items.size(); +} - dumpCat(folder_id,"COF, new outfit"); - return folder_id; +std::string LLAppearanceMgr::getAppearanceServiceURL() const +{ + if (gSavedSettings.getString("DebugAvatarAppearanceServiceURLOverride").empty()) + { + return mAppearanceServiceURL; + } + return gSavedSettings.getString("DebugAvatarAppearanceServiceURLOverride"); +} + +void show_created_outfit(LLUUID& folder_id, bool show_panel = true) +{ + if (!LLApp::isRunning()) + { + LL_WARNS() << "called during shutdown, skipping" << LL_ENDL; + return; + } + + LL_DEBUGS("Avatar") << "called" << LL_ENDL; + LLSD key; + + //EXT-7727. For new accounts inventory callback is created during login process + // and may be processed after login process is finished + if (show_panel) + { + LL_DEBUGS("Avatar") << "showing panel" << LL_ENDL; + LLFloaterSidePanelContainer::showPanel("appearance", "panel_outfits_inventory", key); + + } + LLOutfitsList *outfits_list = + dynamic_cast<LLOutfitsList*>(LLFloaterSidePanelContainer::getPanel("appearance", "outfitslist_tab")); + if (outfits_list) + { + outfits_list->setSelectedOutfitByUUID(folder_id); + } + + LLAppearanceMgr::getInstance()->updateIsDirty(); + gAgentWearables.notifyLoadingFinished(); // New outfit is saved. + LLAppearanceMgr::getInstance()->updatePanelOutfitName(""); + + // For SSB, need to update appearance after we add a base outfit + // link, since, the COF version has changed. There is a race + // condition in initial outfit setup which can lead to rez + // failures - SH-3860. + LL_DEBUGS("Avatar") << "requesting appearance update after createBaseOutfitLink" << LL_ENDL; + LLPointer<LLInventoryCallback> cb = new LLUpdateAppearanceOnDestroy; + LLAppearanceMgr::getInstance()->createBaseOutfitLink(folder_id, cb); +} + +void LLAppearanceMgr::onOutfitFolderCreated(const LLUUID& folder_id, bool show_panel) +{ + LLPointer<LLInventoryCallback> cb = + new LLBoostFuncInventoryCallback(no_op_inventory_func, + boost::bind(&LLAppearanceMgr::onOutfitFolderCreatedAndClothingOrdered,this,folder_id,show_panel)); + updateClothingOrderingInfo(LLUUID::null, cb); +} + +void LLAppearanceMgr::onOutfitFolderCreatedAndClothingOrdered(const LLUUID& folder_id, bool show_panel) +{ + LLPointer<LLInventoryCallback> cb = + new LLBoostFuncInventoryCallback(no_op_inventory_func, + boost::bind(show_created_outfit,folder_id,show_panel)); + bool copy_folder_links = false; + slamCategoryLinks(getCOF(), folder_id, copy_folder_links, cb); +} + +void LLAppearanceMgr::makeNewOutfitLinks(const std::string& new_folder_name, bool show_panel) +{ + if (!isAgentAvatarValid()) return; + + LL_DEBUGS("Avatar") << "creating new outfit" << LL_ENDL; + + gAgentWearables.notifyLoadingStarted(); + + // First, make a folder in the My Outfits directory. + const LLUUID parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + if (AISCommand::isAPIAvailable()) + { + // cap-based category creation was buggy until recently. use + // existence of AIS as an indicator the fix is present. Does + // not actually use AIS to create the category. + inventory_func_type func = boost::bind(&LLAppearanceMgr::onOutfitFolderCreated,this,_1,show_panel); + LLUUID folder_id = gInventory.createNewCategory( + parent_id, + LLFolderType::FT_OUTFIT, + new_folder_name, + func); + } + else + { + LLUUID folder_id = gInventory.createNewCategory( + parent_id, + LLFolderType::FT_OUTFIT, + new_folder_name); + onOutfitFolderCreated(folder_id, show_panel); + } } void LLAppearanceMgr::wearBaseOutfit() @@ -2511,35 +3770,58 @@ void LLAppearanceMgr::wearBaseOutfit() updateCOF(base_outfit_id); } +void LLAppearanceMgr::removeItemsFromAvatar(const uuid_vec_t& ids_to_remove) +{ + if (ids_to_remove.empty()) + { + LL_WARNS() << "called with empty list, nothing to do" << LL_ENDL; + return; + } + LLPointer<LLInventoryCallback> cb = new LLUpdateAppearanceOnDestroy; + for (uuid_vec_t::const_iterator it = ids_to_remove.begin(); it != ids_to_remove.end(); ++it) + { + const LLUUID& id_to_remove = *it; + const LLUUID& linked_item_id = gInventory.getLinkedItemID(id_to_remove); + removeCOFItemLinks(linked_item_id, cb); + addDoomedTempAttachment(linked_item_id); + } +} + void LLAppearanceMgr::removeItemFromAvatar(const LLUUID& id_to_remove) { - LLViewerInventoryItem * item_to_remove = gInventory.getItem(id_to_remove); - if (!item_to_remove) return; + LLUUID linked_item_id = gInventory.getLinkedItemID(id_to_remove); + LLPointer<LLInventoryCallback> cb = new LLUpdateAppearanceOnDestroy; + removeCOFItemLinks(linked_item_id, cb); + addDoomedTempAttachment(linked_item_id); +} - switch (item_to_remove->getType()) - { - case LLAssetType::AT_CLOTHING: - if (get_is_item_worn(id_to_remove)) - { - //*TODO move here the exact removing code from LLWearableBridge::removeItemFromAvatar in the future - LLWearableBridge::removeItemFromAvatar(item_to_remove); - } - break; - case LLAssetType::AT_OBJECT: - LLVOAvatarSelf::detachAttachmentIntoInventory(item_to_remove->getLinkedUUID()); - default: - break; + +// Adds the given item ID to mDoomedTempAttachmentIDs iff it's a temp attachment +void LLAppearanceMgr::addDoomedTempAttachment(const LLUUID& id_to_remove) +{ + LLViewerObject * attachmentp = gAgentAvatarp->findAttachmentByID(id_to_remove); + if (attachmentp && + attachmentp->isTempAttachment()) + { // If this is a temp attachment and we want to remove it, record the ID + // so it will be deleted when attachments are synced up with COF + mDoomedTempAttachmentIDs.insert(id_to_remove); + //LL_INFOS() << "Will remove temp attachment id " << id_to_remove << LL_ENDL; } +} - // *HACK: Force to remove garbage from COF. - // Unworn links or objects can't be processed by existed removing functionality - // since it is not designed for such cases. As example attachment object can't be removed - // since sever don't sends message _PREHASH_KillObject in that case. - // Also we can't check is link was successfully removed from COF since in case - // deleting attachment link removing performs asynchronously in process_kill_object callback. - removeCOFItemLinks(id_to_remove,false); +// Find AND REMOVES the given UUID from mDoomedTempAttachmentIDs +bool LLAppearanceMgr::shouldRemoveTempAttachment(const LLUUID& item_id) +{ + doomed_temp_attachments_t::iterator iter = mDoomedTempAttachmentIDs.find(item_id); + if (iter != mDoomedTempAttachmentIDs.end()) + { + mDoomedTempAttachmentIDs.erase(iter); + return true; + } + return false; } + bool LLAppearanceMgr::moveWearable(LLViewerInventoryItem* item, bool closer_to_body) { if (!item || !item->isWearableType()) return false; @@ -2566,11 +3848,16 @@ bool LLAppearanceMgr::moveWearable(LLViewerInventoryItem* item, bool closer_to_b closer_to_body ? --it : ++it; LLViewerInventoryItem* swap_item = *it; if (!swap_item) return false; - std::string tmp = swap_item->LLInventoryItem::getDescription(); - swap_item->setDescription(item->LLInventoryItem::getDescription()); + std::string tmp = swap_item->getActualDescription(); + swap_item->setDescription(item->getActualDescription()); item->setDescription(tmp); + // LL_DEBUGS("Inventory") << "swap, item " + // << ll_pretty_print_sd(item->asLLSD()) + // << " swap_item " + // << ll_pretty_print_sd(swap_item->asLLSD()) << LL_ENDL; + // FIXME switch to use AISv3 where supported. //items need to be updated on a dataserver item->setComplete(TRUE); item->updateServer(FALSE); @@ -2582,9 +3869,9 @@ bool LLAppearanceMgr::moveWearable(LLViewerInventoryItem* item, bool closer_to_b //to cause appearance of the agent to be updated bool result = false; - if (result = gAgentWearables.moveWearable(item, closer_to_body)) + if ((result = gAgentWearables.moveWearable(item, closer_to_body))) { - gAgentAvatarp->wearableUpdated(item->getWearableType(), FALSE); + gAgentAvatarp->wearableUpdated(item->getWearableType()); } setOutfitDirty(true); @@ -2600,7 +3887,7 @@ void LLAppearanceMgr::sortItemsByActualDescription(LLInventoryModel::item_array_ { if (items.size() < 2) return; - std::sort(items.begin(), items.end(), sort_by_description); + std::sort(items.begin(), items.end(), sort_by_actual_description); } //#define DUMP_CAT_VERBOSE @@ -2612,42 +3899,42 @@ void LLAppearanceMgr::dumpCat(const LLUUID& cat_id, const std::string& msg) gInventory.collectDescendents(cat_id, cats, items, LLInventoryModel::EXCLUDE_TRASH); #ifdef DUMP_CAT_VERBOSE - llinfos << llendl; - llinfos << str << llendl; + LL_INFOS() << LL_ENDL; + LL_INFOS() << str << LL_ENDL; S32 hitcount = 0; - for(S32 i=0; i<items.count(); i++) + for(S32 i=0; i<items.size(); i++) { LLViewerInventoryItem *item = items.get(i); if (item) hitcount++; - llinfos << i <<" "<< item->getName() <<llendl; + LL_INFOS() << i <<" "<< item->getName() <<LL_ENDL; } #endif - llinfos << msg << " count " << items.count() << llendl; + LL_INFOS() << msg << " count " << items.size() << LL_ENDL; } void LLAppearanceMgr::dumpItemArray(const LLInventoryModel::item_array_t& items, - const std::string& msg) + const std::string& msg) { - for (S32 i=0; i<items.count(); i++) + for (S32 i=0; i<items.size(); i++) { - LLViewerInventoryItem *item = items.get(i); + LLViewerInventoryItem *item = items.at(i); LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL; LLUUID asset_id; if (linked_item) { asset_id = linked_item->getAssetUUID(); } - llinfos << msg << " " << i <<" " << (item ? item->getName() : "(nullitem)") << " " << asset_id.asString() << llendl; + LL_DEBUGS("Avatar") << self_av_string() << msg << " " << i <<" " << (item ? item->getName() : "(nullitem)") << " " << asset_id.asString() << LL_ENDL; } - llinfos << llendl; } LLAppearanceMgr::LLAppearanceMgr(): mAttachmentInvLinkEnabled(false), mOutfitIsDirty(false), mOutfitLocked(false), - mIsInUpdateAppearanceFromCOF(false) + mIsInUpdateAppearanceFromCOF(false), + mAppearanceResponder(new RequestAgentUpdateAppearanceResponder) { LLOutfitObserver& outfit_observer = LLOutfitObserver::instance(); @@ -2667,13 +3954,13 @@ LLAppearanceMgr::~LLAppearanceMgr() void LLAppearanceMgr::setAttachmentInvLinkEnable(bool val) { - llinfos << "setAttachmentInvLinkEnable => " << (int) val << llendl; + LL_DEBUGS("Avatar") << "setAttachmentInvLinkEnable => " << (int) val << LL_ENDL; mAttachmentInvLinkEnabled = val; } void dumpAttachmentSet(const std::set<LLUUID>& atts, const std::string& msg) { - llinfos << msg << llendl; + LL_INFOS() << msg << LL_ENDL; for (std::set<LLUUID>::const_iterator it = atts.begin(); it != atts.end(); ++it) @@ -2681,11 +3968,11 @@ void dumpAttachmentSet(const std::set<LLUUID>& atts, const std::string& msg) LLUUID item_id = *it; LLViewerInventoryItem *item = gInventory.getItem(item_id); if (item) - llinfos << "atts " << item->getName() << llendl; + LL_INFOS() << "atts " << item->getName() << LL_ENDL; else - llinfos << "atts " << "UNKNOWN[" << item_id.asString() << "]" << llendl; + LL_INFOS() << "atts " << "UNKNOWN[" << item_id.asString() << "]" << LL_ENDL; } - llinfos << llendl; + LL_INFOS() << LL_ENDL; } void LLAppearanceMgr::registerAttachment(const LLUUID& item_id) @@ -2697,11 +3984,15 @@ void LLAppearanceMgr::registerAttachment(const LLUUID& item_id) // we have to pass do_update = true to call LLAppearanceMgr::updateAppearanceFromCOF. // it will trigger gAgentWariables.notifyLoadingFinished() // But it is not acceptable solution. See EXT-7777 - LLAppearanceMgr::addCOFItemLink(item_id, false); // Add COF link for item. + if (!isLinkedInCOF(item_id)) + { + LLPointer<LLInventoryCallback> cb = new LLUpdateAppearanceOnDestroy(); + LLAppearanceMgr::addCOFItemLink(item_id, cb); // Add COF link for item. + } } else { - //llinfos << "no link changes, inv link not enabled" << llendl; + //LL_INFOS() << "no link changes, inv link not enabled" << LL_ENDL; } } @@ -2711,32 +4002,31 @@ void LLAppearanceMgr::unregisterAttachment(const LLUUID& item_id) if (mAttachmentInvLinkEnabled) { - LLAppearanceMgr::removeCOFItemLinks(item_id, false); + LLAppearanceMgr::removeCOFItemLinks(item_id); } else { - //llinfos << "no link changes, inv link not enabled" << llendl; + //LL_INFOS() << "no link changes, inv link not enabled" << LL_ENDL; } } BOOL LLAppearanceMgr::getIsInCOF(const LLUUID& obj_id) const { - return gInventory.isObjectDescendentOf(obj_id, getCOF()); + const LLUUID& cof = getCOF(); + if (obj_id == cof) + return TRUE; + const LLInventoryObject* obj = gInventory.getObject(obj_id); + if (obj && obj->getParentUUID() == cof) + return TRUE; + return FALSE; } // static bool LLAppearanceMgr::isLinkInCOF(const LLUUID& obj_id) { - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLLinkedItemIDMatches find_links(gInventory.getLinkedItemID(obj_id)); - gInventory.collectDescendentsIf(LLAppearanceMgr::instance().getCOF(), - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - find_links); - - return !items.empty(); + const LLUUID& target_id = gInventory.getLinkedItemID(obj_id); + LLLinkedItemIDMatches find_links(target_id); + return gInventory.hasMatchingDirectDescendent(LLAppearanceMgr::instance().getCOF(), find_links); } BOOL LLAppearanceMgr::getIsProtectedCOFItem(const LLUUID& obj_id) const @@ -2753,87 +4043,6 @@ BOOL LLAppearanceMgr::getIsProtectedCOFItem(const LLUUID& obj_id) const // For now, don't allow direct deletion from the COF. Instead, force users // to choose "Detach" or "Take Off". return TRUE; - /* - const LLInventoryObject *obj = gInventory.getObject(obj_id); - if (!obj) return FALSE; - - // Can't delete bodyparts, since this would be equivalent to removing the item. - if (obj->getType() == LLAssetType::AT_BODYPART) return TRUE; - - // Can't delete the folder link, since this is saved for bookkeeping. - if (obj->getActualType() == LLAssetType::AT_LINK_FOLDER) return TRUE; - - return FALSE; - */ -} - -// Shim class to allow arbitrary boost::bind -// expressions to be run as one-time idle callbacks. -// -// TODO: rework idle function spec to take a boost::function in the first place. -class OnIdleCallbackOneTime -{ -public: - OnIdleCallbackOneTime(nullary_func_t callable): - mCallable(callable) - { - } - static void onIdle(void *data) - { - gIdleCallbacks.deleteFunction(onIdle, data); - OnIdleCallbackOneTime* self = reinterpret_cast<OnIdleCallbackOneTime*>(data); - self->call(); - delete self; - } - void call() - { - mCallable(); - } -private: - nullary_func_t mCallable; -}; - -void doOnIdleOneTime(nullary_func_t callable) -{ - OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor); -} - -// Shim class to allow generic boost functions to be run as -// recurring idle callbacks. Callable should return true when done, -// false to continue getting called. -// -// TODO: rework idle function spec to take a boost::function in the first place. -class OnIdleCallbackRepeating -{ -public: - OnIdleCallbackRepeating(bool_func_t callable): - mCallable(callable) - { - } - // Will keep getting called until the callable returns true. - static void onIdle(void *data) - { - OnIdleCallbackRepeating* self = reinterpret_cast<OnIdleCallbackRepeating*>(data); - bool done = self->call(); - if (done) - { - gIdleCallbacks.deleteFunction(onIdle, data); - delete self; - } - } - bool call() - { - return mCallable(); - } -private: - bool_func_t mCallable; -}; - -void doOnIdleRepeating(bool_func_t callable) -{ - OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor); } class CallAfterCategoryFetchStage2: public LLInventoryFetchItemsObserver @@ -2850,8 +4059,8 @@ public: } virtual void done() { - llinfos << this << " done with incomplete " << mIncomplete.size() - << " complete " << mComplete.size() << " calling callable" << llendl; + LL_INFOS() << this << " done with incomplete " << mIncomplete.size() + << " complete " << mComplete.size() << " calling callable" << LL_ENDL; gInventory.removeObserver(this); doOnIdleOneTime(mCallable); @@ -2874,34 +4083,32 @@ public: } virtual void 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. + // What we do here is get the complete information on the + // items in the requested category, 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(mComplete.front(), cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH); - S32 count = item_array.count(); + S32 count = item_array.size(); if(!count) { - llwarns << "Nothing fetched in category " << mComplete.front() - << llendl; - //dec_busy_count(); + LL_WARNS() << "Nothing fetched in category " << mComplete.front() + << LL_ENDL; gInventory.removeObserver(this); + doOnIdleOneTime(mCallable); - // lets notify observers that loading is finished. - gAgentWearables.notifyLoadingFinished(); delete this; return; } - llinfos << "stage1 got " << item_array.count() << " items, passing to stage2 " << llendl; + LL_INFOS() << "stage1 got " << item_array.size() << " items, passing to stage2 " << LL_ENDL; uuid_vec_t ids; for(S32 i = 0; i < count; ++i) { - ids.push_back(item_array.get(i)->getUUID()); + ids.push_back(item_array.at(i)->getUUID()); } gInventory.removeObserver(this); @@ -2943,16 +4150,7 @@ void callAfterCategoryFetch(const LLUUID& cat_id, nullary_func_t cb) void wear_multiple(const uuid_vec_t& ids, bool replace) { LLPointer<LLInventoryCallback> cb = new LLUpdateAppearanceOnDestroy; - - bool first = true; - uuid_vec_t::const_iterator it; - for (it = ids.begin(); it != ids.end(); ++it) - { - // if replace is requested, the first item worn will replace the current top - // item, and others will be added. - LLAppearanceMgr::instance().wearItemOnAvatar(*it,false,first && replace,cb); - first = false; - } + LLAppearanceMgr::instance().wearItemsOnAvatar(ids, false, replace, cb); } // SLapp for easy-wearing of a stock (library) avatar @@ -2966,18 +4164,37 @@ public: bool handle(const LLSD& tokens, const LLSD& query_map, LLMediaCtrl* web) { - LLPointer<LLInventoryCategory> category = new LLInventoryCategory(query_map["folder_id"], - LLUUID::null, - LLFolderType::FT_CLOTHING, - "Quick Appearance"); - LLSD::UUID folder_uuid = query_map["folder_id"].asUUID(); - if ( gInventory.getCategory( folder_uuid ) != NULL ) - { - LLAppearanceMgr::getInstance()->wearInventoryCategory(category, true, false); + LLSD::UUID folder_uuid; - // *TODOw: This may not be necessary if initial outfit is chosen already -- josh - gAgent.setGenderChosen(TRUE); + if (folder_uuid.isNull() && query_map.has("folder_name")) + { + std::string outfit_folder_name = query_map["folder_name"]; + folder_uuid = findDescendentCategoryIDByName( + gInventory.getLibraryRootFolderID(), + outfit_folder_name); + } + if (folder_uuid.isNull() && query_map.has("folder_id")) + { + folder_uuid = query_map["folder_id"].asUUID(); } + + if (folder_uuid.notNull()) + { + LLPointer<LLInventoryCategory> category = new LLInventoryCategory(folder_uuid, + LLUUID::null, + LLFolderType::FT_CLOTHING, + "Quick Appearance"); + if ( gInventory.getCategory( folder_uuid ) != NULL ) + { + LLAppearanceMgr::getInstance()->wearInventoryCategory(category, true, false); + + // *TODOw: This may not be necessary if initial outfit is chosen already -- josh + gAgent.setOutfitChosen(TRUE); + } + } + + // release avatar picker keyboard focus + gFocusMgr.setKeyboardFocus( NULL ); return true; } |