diff options
Diffstat (limited to 'indra/newview')
-rwxr-xr-x | indra/newview/llagent.cpp | 156 | ||||
-rwxr-xr-x | indra/newview/llagent.h | 14 | ||||
-rwxr-xr-x | indra/newview/llappearancemgr.cpp | 8084 | ||||
-rwxr-xr-x | indra/newview/llappearancemgr.h | 15 |
4 files changed, 4154 insertions, 4115 deletions
diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index f151b15e29..ff0e2c42c1 100755 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -95,6 +95,8 @@ #include "lscript_byteformat.h" #include "stringize.h" #include "boost/foreach.hpp" +#include "llhttpsdhandler.h" +#include "llcorehttputil.h" using namespace LLAvatarAppearanceDefines; @@ -323,6 +325,7 @@ bool LLAgent::isMicrophoneOn(const LLSD& sdname) // For a toggled version, see viewer.h for the // TOGGLE_HACKED_GODLIKE_VIEWER define, instead. // ************************************************************ +bool LLAgent::mActive = true; // Constructors and Destructors @@ -361,7 +364,12 @@ LLAgent::LLAgent() : mMaturityPreferenceNumRetries(0U), mLastKnownRequestMaturity(SIM_ACCESS_MIN), mLastKnownResponseMaturity(SIM_ACCESS_MIN), - mTeleportState( TELEPORT_NONE ), + mHttpRequest(), + mHttpHeaders(), + mHttpOptions(), + mHttpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mHttpPriority(0), + mTeleportState(TELEPORT_NONE), mRegionp(NULL), mAgentOriginGlobal(), @@ -459,6 +467,15 @@ void LLAgent::init() mTeleportFailedSlot = LLViewerParcelMgr::getInstance()->setTeleportFailedCallback(boost::bind(&LLAgent::handleTeleportFailed, this)); } + LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); + + mHttpRequest = LLCore::HttpRequest::ptr_t(new LLCore::HttpRequest()); + mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders(), false); + mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions(), false); + mHttpPolicy = app_core_http.getPolicy(LLAppCoreHttp::AP_AVATAR); + + doOnIdleRepeating(boost::bind(&LLAgent::onIdle, this)); + mInitialized = TRUE; } @@ -467,6 +484,7 @@ void LLAgent::init() //----------------------------------------------------------------------------- void LLAgent::cleanup() { + mActive = false; mRegionp = NULL; if (mTeleportFinishedSlot.connected()) { @@ -498,6 +516,17 @@ LLAgent::~LLAgent() mTeleportSourceSLURL = NULL; } +//----------------------------------------------------------------------------- +// Idle processing +//----------------------------------------------------------------------------- +bool LLAgent::onIdle() +{ + if (!LLAgent::mActive) + return true; + mHttpRequest->update(0L); + return false; +} + // Handle any actions that need to be performed when the main app gains focus // (such as through alt-tab). //----------------------------------------------------------------------------- @@ -2515,66 +2544,61 @@ int LLAgent::convertTextToMaturity(char text) return LLAgentAccess::convertTextToMaturity(text); } -class LLMaturityPreferencesResponder : public LLHTTPClient::Responder +//========================================================================= +class LLMaturityHttpHandler : public LLHttpSDHandler { - LOG_CLASS(LLMaturityPreferencesResponder); public: - LLMaturityPreferencesResponder(LLAgent *pAgent, U8 pPreferredMaturity, U8 pPreviousMaturity); - virtual ~LLMaturityPreferencesResponder(); + LLMaturityHttpHandler(const std::string& capabilityURL, LLAgent *agent, U8 preferred, U8 previous): + LLHttpSDHandler(capabilityURL), + mAgent(agent), + mPreferredMaturity(preferred), + mPreviousMaturity(previous) + { } -protected: - virtual void httpSuccess(); - virtual void httpFailure(); + virtual ~LLMaturityHttpHandler() + { } protected: + virtual void onSuccess(LLCore::HttpResponse * response, LLSD &content); + virtual void onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status); private: - U8 parseMaturityFromServerResponse(const LLSD &pContent) const; - - LLAgent *mAgent; - U8 mPreferredMaturity; - U8 mPreviousMaturity; -}; + U8 LLMaturityHttpHandler::parseMaturityFromServerResponse(const LLSD &pContent) const; -LLMaturityPreferencesResponder::LLMaturityPreferencesResponder(LLAgent *pAgent, U8 pPreferredMaturity, U8 pPreviousMaturity) - : LLHTTPClient::Responder(), - mAgent(pAgent), - mPreferredMaturity(pPreferredMaturity), - mPreviousMaturity(pPreviousMaturity) -{ -} + LLAgent * mAgent; + U8 mPreferredMaturity; + U8 mPreviousMaturity; -LLMaturityPreferencesResponder::~LLMaturityPreferencesResponder() -{ -} +}; -void LLMaturityPreferencesResponder::httpSuccess() +//------------------------------------------------------------------------- +void LLMaturityHttpHandler::onSuccess(LLCore::HttpResponse * response, LLSD &content) { - U8 actualMaturity = parseMaturityFromServerResponse(getContent()); + U8 actualMaturity = parseMaturityFromServerResponse(content); if (actualMaturity != mPreferredMaturity) { LL_WARNS() << "while attempting to change maturity preference from '" - << LLViewerRegion::accessToString(mPreviousMaturity) - << "' to '" << LLViewerRegion::accessToString(mPreferredMaturity) - << "', the server responded with '" - << LLViewerRegion::accessToString(actualMaturity) - << "' [value:" << static_cast<U32>(actualMaturity) - << "], " << dumpResponse() << LL_ENDL; + << LLViewerRegion::accessToString(mPreviousMaturity) + << "' to '" << LLViewerRegion::accessToString(mPreferredMaturity) + << "', the server responded with '" + << LLViewerRegion::accessToString(actualMaturity) + << "' [value:" << static_cast<U32>(actualMaturity) + << "], " << LL_ENDL; } mAgent->handlePreferredMaturityResult(actualMaturity); } -void LLMaturityPreferencesResponder::httpFailure() +void LLMaturityHttpHandler::onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status) { - LL_WARNS() << "while attempting to change maturity preference from '" - << LLViewerRegion::accessToString(mPreviousMaturity) - << "' to '" << LLViewerRegion::accessToString(mPreferredMaturity) - << "', " << dumpResponse() << LL_ENDL; + LL_WARNS() << "while attempting to change maturity preference from '" + << LLViewerRegion::accessToString(mPreviousMaturity) + << "' to '" << LLViewerRegion::accessToString(mPreferredMaturity) + << "', " << LL_ENDL; mAgent->handlePreferredMaturityError(); } -U8 LLMaturityPreferencesResponder::parseMaturityFromServerResponse(const LLSD &pContent) const +U8 LLMaturityHttpHandler::parseMaturityFromServerResponse(const LLSD &pContent) const { U8 maturity = SIM_ACCESS_MIN; @@ -2595,6 +2619,7 @@ U8 LLMaturityPreferencesResponder::parseMaturityFromServerResponse(const LLSD &p return maturity; } +//========================================================================= void LLAgent::handlePreferredMaturityResult(U8 pServerMaturity) { @@ -2724,38 +2749,41 @@ void LLAgent::sendMaturityPreferenceToServer(U8 pPreferredMaturity) // Update the last know maturity request mLastKnownRequestMaturity = pPreferredMaturity; - // Create a response handler - LLHTTPClient::ResponderPtr responderPtr = LLHTTPClient::ResponderPtr(new LLMaturityPreferencesResponder(this, pPreferredMaturity, mLastKnownResponseMaturity)); - // If we don't have a region, report it as an error if (getRegion() == NULL) { - responderPtr->failureResult(0U, "region is not defined", LLSD()); + LL_WARNS("Agent") << "Region is not defined, can not change Maturity setting." << LL_ENDL; + return; } - else + std::string url = getRegion()->getCapability("UpdateAgentInformation"); + + // If the capability is not defined, report it as an error + if (url.empty()) { - // Find the capability to send maturity preference - std::string url = getRegion()->getCapability("UpdateAgentInformation"); + LL_WARNS("Agent") << "'UpdateAgentInformation' is not defined for region" << LL_ENDL; + return; + } - // If the capability is not defined, report it as an error - if (url.empty()) - { - responderPtr->failureResult(0U, - "capability 'UpdateAgentInformation' is not defined for region", LLSD()); - } - else - { - // Set new access preference - LLSD access_prefs = LLSD::emptyMap(); - access_prefs["max"] = LLViewerRegion::accessToShortString(pPreferredMaturity); - - LLSD body = LLSD::emptyMap(); - body["access_prefs"] = access_prefs; - LL_INFOS() << "Sending viewer preferred maturity to '" << LLViewerRegion::accessToString(pPreferredMaturity) - << "' via capability to: " << url << LL_ENDL; - LLSD headers; - LLHTTPClient::post(url, body, responderPtr, headers, 30.0f); - } + LLMaturityHttpHandler * handler = new LLMaturityHttpHandler(url, this, pPreferredMaturity, mLastKnownResponseMaturity); + + LLSD access_prefs = LLSD::emptyMap(); + access_prefs["max"] = LLViewerRegion::accessToShortString(pPreferredMaturity); + + LLSD postData = LLSD::emptyMap(); + postData["access_prefs"] = access_prefs; + LL_INFOS() << "Sending viewer preferred maturity to '" << LLViewerRegion::accessToString(pPreferredMaturity) + << "' via capability to: " << url << LL_ENDL; + + LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, + mHttpPolicy, mHttpPriority, url, + postData, mHttpOptions, mHttpHeaders, handler); + + if (handle == LLCORE_HTTP_HANDLE_INVALID) + { + delete handler; + LLCore::HttpStatus status = mHttpRequest->getStatus(); + LL_WARNS("Avatar") << "Maturity request post failed Reason " << status.toTerseString() + << " \"" << status.toString() << "\"" << LL_ENDL; } } } diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index 56bd1428ce..278e4c0fa1 100755 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -35,6 +35,9 @@ #include "llavatarappearancedefines.h" #include "llpermissionsflags.h" #include "v3dmath.h" +#include "httprequest.h" +#include "httpheaders.h" +#include "httpoptions.h" #include <boost/function.hpp> #include <boost/shared_ptr.hpp> @@ -112,6 +115,10 @@ public: void init(); void cleanup(); +private: + bool onIdle(); + + static bool mActive; //-------------------------------------------------------------------- // Login //-------------------------------------------------------------------- @@ -754,11 +761,16 @@ private: unsigned int mMaturityPreferenceNumRetries; U8 mLastKnownRequestMaturity; U8 mLastKnownResponseMaturity; + LLCore::HttpRequest::ptr_t mHttpRequest; + LLCore::HttpHeaders::ptr_t mHttpHeaders; + LLCore::HttpOptions::ptr_t mHttpOptions; + LLCore::HttpRequest::policy_t mHttpPolicy; + LLCore::HttpRequest::priority_t mHttpPriority; bool isMaturityPreferenceSyncedWithServer() const; void sendMaturityPreferenceToServer(U8 pPreferredMaturity); - friend class LLMaturityPreferencesResponder; + friend class LLMaturityHttpHandler; void handlePreferredMaturityResult(U8 pServerMaturity); void handlePreferredMaturityError(); void reportPreferredMaturitySuccess(); diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index dbc858bdb0..bb4228dbb2 100755 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -1,3490 +1,3490 @@ -/**
- * @file llappearancemgr.cpp
- * @brief Manager for initiating appearance changes on the viewer
- *
- * $LicenseInfo:firstyear=2004&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#include "llviewerprecompiledheaders.h"
-
-#include <boost/lexical_cast.hpp>
-#include "llaccordionctrltab.h"
-#include "llagent.h"
-#include "llagentcamera.h"
-#include "llagentwearables.h"
-#include "llappearancemgr.h"
-#include "llattachmentsmgr.h"
-#include "llcommandhandler.h"
-#include "lleventtimer.h"
-#include "llfloatersidepanelcontainer.h"
-#include "llgesturemgr.h"
-#include "llinventorybridge.h"
-#include "llinventoryfunctions.h"
-#include "llinventoryobserver.h"
-#include "llnotificationsutil.h"
-#include "lloutfitobserver.h"
-#include "lloutfitslist.h"
-#include "llselectmgr.h"
-#include "llsidepanelappearance.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"
-#include "llhttpsdhandler.h"
-#include "llcorehttputil.h"
-#include "llappviewer.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:
-// check boost.
-class BoolSetter
-{
-public:
- BoolSetter(bool& var):
- mVar(var)
- {
- mVar = true;
- }
- ~BoolSetter()
- {
- mVar = false;
- }
-private:
- bool& mVar;
-};
-
-char ORDER_NUMBER_SEPARATOR('@');
-
-class LLOutfitUnLockTimer: public LLEventTimer
-{
-public:
- LLOutfitUnLockTimer(F32 period) : LLEventTimer(period)
- {
- // restart timer on BOF changed event
- LLOutfitObserver::instance().addBOFChangedCallback(boost::bind(
- &LLOutfitUnLockTimer::reset, this));
- stop();
- }
-
- /*virtual*/
- BOOL tick()
- {
- if(mEventTimer.hasExpired())
- {
- LLAppearanceMgr::instance().setOutfitLocked(false);
- }
- return FALSE;
- }
- void stop() { mEventTimer.stop(); }
- void start() { mEventTimer.start(); }
- void reset() { mEventTimer.reset(); }
- BOOL getStarted() { return mEventTimer.getStarted(); }
-
- LLTimer& getEventTimer() { return mEventTimer;}
-};
-
-// support for secondlife:///app/appearance SLapps
-class LLAppearanceHandler : public LLCommandHandler
-{
-public:
- // requests will be throttled from a non-trusted browser
- LLAppearanceHandler() : LLCommandHandler("appearance", UNTRUSTED_THROTTLE) {}
-
- bool handle(const LLSD& params, const LLSD& query_map, LLMediaCtrl* web)
- {
- // support secondlife:///app/appearance/show, but for now we just
- // make all secondlife:///app/appearance SLapps behave this way
- if (!LLUI::sSettingGroups["config"]->getBOOL("EnableAppearance"))
- {
- LLNotificationsUtil::add("NoAppearance", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit"));
- return true;
- }
-
- LLFloaterSidePanelContainer::showPanel("appearance", LLSD());
- return true;
- }
-};
-
-LLAppearanceHandler gAppearanceHandler;
-
-
-LLUUID findDescendentCategoryIDByName(const LLUUID& parent_id, const std::string& name)
-{
- LLInventoryModel::cat_array_t cat_array;
- LLInventoryModel::item_array_t item_array;
- LLNameCategoryCollector has_name(name);
- gInventory.collectDescendentsIf(parent_id,
- cat_array,
- item_array,
- LLInventoryModel::EXCLUDE_TRASH,
- has_name);
- if (0 == cat_array.size())
- return LLUUID();
- else
- {
- LLViewerInventoryCategory *cat = cat_array.at(0);
- if (cat)
- return cat->getUUID();
- else
- {
- LL_WARNS() << "null cat" << LL_ENDL;
- return LLUUID();
- }
- }
-}
-
-// 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:
- 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)
- {
- for (LLInventoryModel::item_array_t::const_iterator it = src_items.begin();
- it != src_items.end();
- ++it)
- {
- LLViewerInventoryItem* item = *it;
- llassert(item);
- addItem(item->getUUID());
- }
- }
-
- // Request or re-request operation for specified item.
- void addItem(const LLUUID& item_id)
- {
- 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]++;
- }
- }
-
- virtual bool requestOperation(const LLUUID& item_id) = 0;
-
- void onOp(const LLUUID& src_id, const LLUUID& dst_id, LLTimer timestamp)
- {
- 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;
-
- // 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)
- {
- onCompletion();
- }
- else
- {
- onFailure();
- }
- }
-
- 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;
- }
-
- 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:
- 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()
- {
- 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"))
- {
- 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;
- }
-
- static S32 getInstanceCount() { return sInstanceCount; }
-
-private:
- static S32 sInstanceCount;
-};
-
-S32 LLCallAfterInventoryCopyMgr::sInstanceCount = 0;
-
-class LLWearCategoryAfterCopy: public LLInventoryCallback
-{
-public:
- LLWearCategoryAfterCopy(bool append):
- mAppend(append)
- {}
-
- // 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
-{
-public:
- LLTrackPhaseWrapper(const std::string& phase_name, LLPointer<LLInventoryCallback> cb = NULL):
- mTrackingPhase(phase_name),
- mCB(cb)
- {
- 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)
-{
- LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(inv_item);
- const std::string item_name = item ? item->getName() : "ITEM NOT FOUND";
-#ifndef LL_RELEASE_FOR_DOWNLOAD
- 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() :
- mAssetType(LLAssetType::AT_NONE),
- mWearableType(LLWearableType::WT_INVALID),
- mWearable(NULL) {}
-
- LLFoundData(const LLUUID& item_id,
- const LLUUID& asset_id,
- const std::string& name,
- const LLAssetType::EType& asset_type,
- const LLWearableType::EType& wearable_type,
- const bool is_replacement = false
- ) :
- mItemID(item_id),
- mAssetID(asset_id),
- mName(name),
- mAssetType(asset_type),
- mWearableType(wearable_type),
- mIsReplacement(is_replacement),
- mWearable( NULL ) {}
-
- LLUUID mItemID;
- LLUUID mAssetID;
- std::string mName;
- LLAssetType::EType mAssetType;
- LLWearableType::EType mWearableType;
- LLViewerWearable* mWearable;
- bool mIsReplacement;
-};
-
-
-class LLWearableHoldingPattern
-{
- LOG_CLASS(LLWearableHoldingPattern);
-
-public:
- LLWearableHoldingPattern();
- ~LLWearableHoldingPattern();
-
- bool pollFetchCompletion();
- void onFetchCompletion();
- bool isFetchCompleted();
- bool isTimedOut();
-
- void checkMissingWearables();
- bool pollMissingWearables();
- bool isMissingCompleted();
- void recoverMissingWearable(LLWearableType::EType type);
- void clearCOFLinksForMissingWearables();
-
- void onWearableAssetFetch(LLViewerWearable *wearable);
- void onAllComplete();
-
- typedef std::list<LLFoundData> found_list_t;
- found_list_t& getFoundList();
- void eraseTypeToLink(LLWearableType::EType type);
- void eraseTypeToRecover(LLWearableType::EType type);
- void setObjItems(const LLInventoryModel::item_array_t& items);
- void setGestItems(const LLInventoryModel::item_array_t& items);
- bool isMostRecent();
- void handleLateArrivals();
- void resetTime(F32 timeout);
- static S32 countActive() { return sActiveHoldingPatterns.size(); }
- S32 index() { return mIndex; }
-
-private:
- found_list_t mFoundList;
- LLInventoryModel::item_array_t mObjItems;
- LLInventoryModel::item_array_t mGestItems;
- typedef std::set<S32> type_set_t;
- type_set_t mTypesToRecover;
- type_set_t mTypesToLink;
- S32 mResolved;
- LLTimer mWaitTime;
- bool mFired;
- typedef std::set<LLWearableHoldingPattern*> type_set_hp;
- static type_set_hp sActiveHoldingPatterns;
- static S32 sNextIndex;
- S32 mIndex;
- bool mIsMostRecent;
- std::set<LLViewerWearable*> mLateArrivals;
- bool mIsAllComplete;
-};
-
-LLWearableHoldingPattern::type_set_hp LLWearableHoldingPattern::sActiveHoldingPatterns;
-S32 LLWearableHoldingPattern::sNextIndex = 0;
-
-LLWearableHoldingPattern::LLWearableHoldingPattern():
- mResolved(0),
- mFired(false),
- mIsMostRecent(true),
- mIsAllComplete(false)
-{
- if (countActive()>0)
- {
- 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)
- {
- (*it)->mIsMostRecent = false;
- }
-
- }
- 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()
-{
- return mIsMostRecent;
-}
-
-LLWearableHoldingPattern::found_list_t& LLWearableHoldingPattern::getFoundList()
-{
- return mFoundList;
-}
-
-void LLWearableHoldingPattern::eraseTypeToLink(LLWearableType::EType type)
-{
- mTypesToLink.erase(type);
-}
-
-void LLWearableHoldingPattern::eraseTypeToRecover(LLWearableType::EType type)
-{
- mTypesToRecover.erase(type);
-}
-
-void LLWearableHoldingPattern::setObjItems(const LLInventoryModel::item_array_t& items)
-{
- mObjItems = items;
-}
-
-void LLWearableHoldingPattern::setGestItems(const LLInventoryModel::item_array_t& items)
-{
- mGestItems = items;
-}
-
-bool LLWearableHoldingPattern::isFetchCompleted()
-{
- return (mResolved >= (S32)getFoundList().size()); // have everything we were waiting for?
-}
-
-bool LLWearableHoldingPattern::isTimedOut()
-{
- return mWaitTime.hasExpired();
-}
-
-void LLWearableHoldingPattern::checkMissingWearables()
-{
- if (!isMostRecent())
- {
- // 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)
- {
- LLFoundData &data = *it;
- if (data.mWearableType < LLWearableType::WT_COUNT)
- requested_by_type[data.mWearableType]++;
- if (data.mWearable)
- found_by_type[data.mWearableType]++;
- }
-
- for (S32 type = 0; type < LLWearableType::WT_COUNT; ++type)
- {
- if (requested_by_type[type] > found_by_type[type])
- {
- 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;
- if (
- // If at least one wearable of certain types (pants/shirt/skirt)
- // was requested but none was found, create a default asset as a replacement.
- // In all other cases, don't do anything.
- // For critical types (shape/hair/skin/eyes), this will keep the avatar as a cloud
- // due to logic in LLVOAvatarSelf::getIsCloud().
- // For non-critical types (tatoo, socks, etc.) the wearable will just be missing.
- (requested_by_type[type] > 0) &&
- ((type == LLWearableType::WT_PANTS) || (type == LLWearableType::WT_SHIRT) || (type == LLWearableType::WT_SKIRT)))
- {
- mTypesToRecover.insert(type);
- mTypesToLink.insert(type);
- recoverMissingWearable((LLWearableType::EType)type);
- 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));
- }
-}
-
-void LLWearableHoldingPattern::onAllComplete()
-{
- if (isAgentAvatarValid())
- {
- gAgentAvatarp->outputRezTiming("Agent wearables fetch complete");
- }
-
- if (!isMostRecent())
- {
- // 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.size() > 0)
- {
- LL_DEBUGS("Avatar") << self_av_string() << "Activating " << mGestItems.size() << " gestures" << LL_ENDL;
-
- LLGestureMgr::instance().activateGestures(mGestItems);
-
- // Update the inventory item labels to reflect the fact
- // they are active.
- LLViewerInventoryCategory* catp =
- gInventory.getCategory(LLAppearanceMgr::instance().getCOF());
-
- if (catp)
- {
- gInventory.updateCategory(catp);
- gInventory.notifyObservers();
- }
- }
-
- if (isAgentAvatarValid())
- {
- 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())
- {
- // Only safe to delete if all wearable callbacks and all missing wearables completed.
- delete this;
- }
- else
- {
- mIsAllComplete = true;
- handleLateArrivals();
- }
-}
-
-void LLWearableHoldingPattern::onFetchCompletion()
-{
- if (isMostRecent())
- {
- selfStopPhase("get_wearables_2");
- }
-
- if (!isMostRecent())
- {
- // runway skip here?
- LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL;
- }
-
- checkMissingWearables();
-}
-
-// Runs as an idle callback until all wearables are fetched (or we time out).
-bool LLWearableHoldingPattern::pollFetchCompletion()
-{
- if (!isMostRecent())
- {
- // runway skip here?
- LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL;
- }
-
- bool completed = isFetchCompleted();
- bool timed_out = isTimedOut();
- bool done = completed || timed_out;
-
- if (done)
- {
- 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)
- {
- LL_WARNS() << self_av_string() << "Exceeded max wait time for wearables, updating appearance based on what has arrived" << LL_ENDL;
- }
-
- onFetchCompletion();
- }
- return done;
-}
-
-void recovered_item_link_cb(const LLUUID& item_id, LLWearableType::EType type, LLViewerWearable *wearable, LLWearableHoldingPattern* holder)
-{
- if (!holder->isMostRecent())
- {
- LL_WARNS() << "HP " << holder->index() << " skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL;
- // runway skip here?
- }
-
- 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 (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
- {
- LL_WARNS() << self_av_string() << "inventory link not found for recovered wearable" << LL_ENDL;
- }
- }
- else
- {
- LL_WARNS() << self_av_string() << "HP " << holder->index() << " inventory link not found for recovered wearable" << LL_ENDL;
- }
-}
-
-void recovered_item_cb(const LLUUID& item_id, LLWearableType::EType type, LLViewerWearable *wearable, LLWearableHoldingPattern* holder)
-{
- if (!holder->isMostRecent())
- {
- // runway skip here?
- LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL;
- }
-
- 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)
- {
- LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallback(boost::bind(recovered_item_link_cb,_1,type,wearable,holder));
-
- link_inventory_object(LLAppearanceMgr::instance().getCOF(), itemp, cb);
- }
-}
-
-void LLWearableHoldingPattern::recoverMissingWearable(LLWearableType::EType type)
-{
- if (!isMostRecent())
- {
- // 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");
- 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 LLBoostFuncInventoryCallback(boost::bind(recovered_item_cb,_1,type,wearable,this));
-
- create_inventory_item(gAgent.getID(),
- gAgent.getSessionID(),
- lost_and_found_id,
- wearable->getTransactionID(),
- wearable->getName(),
- wearable->getDescription(),
- wearable->getAssetType(),
- LLInventoryType::IT_WEARABLE,
- wearable->getType(),
- wearable->getPermissions().getMaskNextOwner(),
- cb);
-}
-
-bool LLWearableHoldingPattern::isMissingCompleted()
-{
- return mTypesToLink.size()==0 && mTypesToRecover.size()==0;
-}
-
-void LLWearableHoldingPattern::clearCOFLinksForMissingWearables()
-{
- for (found_list_t::iterator it = getFoundList().begin(); it != getFoundList().end(); ++it)
- {
- LLFoundData &data = *it;
- if ((data.mWearableType < LLWearableType::WT_COUNT) && (!data.mWearable))
- {
- // Wearable link that was never resolved; remove links to it from COF
- LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " removing link for unresolved item " << data.mItemID.asString() << LL_ENDL;
- LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID);
- }
- }
-}
-
-bool LLWearableHoldingPattern::pollMissingWearables()
-{
- if (!isMostRecent())
- {
- // 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();
- bool missing_completed = isMissingCompleted();
- bool done = timed_out || missing_completed;
-
- if (!done)
- {
- 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 << LL_ENDL;
- }
-
- if (done)
- {
- if (isMostRecent())
- {
- selfStopPhase("get_missing_wearables_2");
- }
-
- gAgentAvatarp->debugWearablesLoaded();
-
- // BAP - if we don't call clearCOFLinksForMissingWearables()
- // here, we won't have to add the link back in later if the
- // wearable arrives late. This is to avoid corruption of
- // wearable ordering info. Also has the effect of making
- // unworn item links visible in the COF under some
- // circumstances.
-
- //clearCOFLinksForMissingWearables();
- onAllComplete();
- }
- return done;
-}
-
-// Handle wearables that arrived after the timeout period expired.
-void LLWearableHoldingPattern::handleLateArrivals()
-{
- // Only safe to run if we have previously finished the missing
- // wearables and other processing - otherwise we could be in some
- // intermediate state - but have not been superceded by a later
- // outfit change request.
- if (mLateArrivals.size() == 0)
- {
- // Nothing to process.
- return;
- }
- if (!isMostRecent())
- {
- LL_WARNS() << self_av_string() << "Late arrivals not handled - outfit change no longer valid" << LL_ENDL;
- }
- if (!mIsAllComplete)
- {
- LL_WARNS() << self_av_string() << "Late arrivals not handled - in middle of missing wearables processing" << LL_ENDL;
- }
-
- 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;
- for (LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin();
- iter != getFoundList().end(); ++iter)
- {
- LLFoundData& data = *iter;
- for (std::set<LLViewerWearable*>::iterator wear_it = mLateArrivals.begin();
- wear_it != mLateArrivals.end();
- ++wear_it)
- {
- LLViewerWearable *wearable = *wear_it;
-
- if(wearable->getAssetID() == data.mAssetID)
- {
- data.mWearable = wearable;
-
- replaced_types.insert(data.mWearableType);
-
- // BAP - if we didn't call
- // clearCOFLinksForMissingWearables() earlier, we
- // don't need to restore the link here. Fixes
- // wearable ordering problems.
-
- // LLAppearanceMgr::instance().addCOFItemLink(data.mItemID,false);
-
- // BAP failing this means inventory or asset server
- // are corrupted in a way we don't handle.
- llassert((data.mWearableType < LLWearableType::WT_COUNT) && (wearable->getType() == data.mWearableType));
- break;
- }
- }
- }
-
- // Remove COF links for any default wearables previously used to replace the late arrivals.
- // All this pussyfooting around with a while loop and explicit
- // iterator incrementing is to allow removing items from the list
- // without clobbering the iterator we're using to navigate.
- LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin();
- while (iter != getFoundList().end())
- {
- LLFoundData& data = *iter;
-
- // If an item of this type has recently shown up, removed the corresponding replacement wearable from COF.
- if (data.mWearable && data.mIsReplacement &&
- replaced_types.find(data.mWearableType) != replaced_types.end())
- {
- LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID);
- std::list<LLFoundData>::iterator clobber_ator = iter;
- ++iter;
- getFoundList().erase(clobber_ator);
- }
- else
- {
- ++iter;
- }
- }
-
- // Clear contents of late arrivals.
- mLateArrivals.clear();
-
- // Update appearance based on mFoundList
- LLAppearanceMgr::instance().updateAgentWearables(this);
-}
-
-void LLWearableHoldingPattern::resetTime(F32 timeout)
-{
- mWaitTime.reset();
- mWaitTime.setTimerExpirySec(timeout);
-}
-
-void LLWearableHoldingPattern::onWearableAssetFetch(LLViewerWearable *wearable)
-{
- if (!isMostRecent())
- {
- 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.
- LL_DEBUGS("Avatar") << self_av_string() << "HP " << index() << " resolved " << mResolved << "/" << getFoundList().size() << LL_ENDL;
- if (!wearable)
- {
- LL_WARNS() << self_av_string() << "no wearable found" << LL_ENDL;
- }
-
- if (mFired)
- {
- LL_WARNS() << self_av_string() << "called after holder fired" << LL_ENDL;
- if (wearable)
- {
- mLateArrivals.insert(wearable);
- if (mIsAllComplete)
- {
- handleLateArrivals();
- }
- }
- return;
- }
-
- if (!wearable)
- {
- return;
- }
-
- for (LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin();
- iter != getFoundList().end(); ++iter)
- {
- LLFoundData& data = *iter;
- if(wearable->getAssetID() == data.mAssetID)
- {
- // 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))
- {
- LL_WARNS() << self_av_string() << "recovered wearable but type invalid. inventory wearable type: " << data.mWearableType << " asset wearable type: " << wearable->getType() << LL_ENDL;
- break;
- }
-
- data.mWearable = wearable;
- }
- }
-}
-
-static void onWearableAssetFetch(LLViewerWearable* wearable, void* data)
-{
- LLWearableHoldingPattern* holder = (LLWearableHoldingPattern*)data;
- holder->onWearableAssetFetch(wearable);
-}
-
-
-static void removeDuplicateItems(LLInventoryModel::item_array_t& items)
-{
- LLInventoryModel::item_array_t new_items;
- std::set<LLUUID> items_seen;
- std::deque<LLViewerInventoryItem*> tmp_list;
- // Traverse from the front and keep the first of each item
- // 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.size()-1; i>=0; i--)
- {
- LLViewerInventoryItem *item = items.at(i);
- LLUUID item_id = item->getLinkedUUID();
- if (items_seen.find(item_id)!=items_seen.end())
- continue;
- items_seen.insert(item_id);
- tmp_list.push_front(item);
- }
- for (std::deque<LLViewerInventoryItem*>::iterator it = tmp_list.begin();
- it != tmp_list.end();
- ++it)
- {
- new_items.push_back(*it);
- }
- items = new_items;
-}
-
-//=========================================================================
-class LLAppearanceMgrHttpHandler : public LLHttpSDHandler
-{
-public:
- LLAppearanceMgrHttpHandler(const std::string& capabilityURL, LLAppearanceMgr *mgr) :
- LLHttpSDHandler(capabilityURL),
- mManager(mgr)
- { }
-
- virtual ~LLAppearanceMgrHttpHandler()
- { }
-
+/** + * @file llappearancemgr.cpp + * @brief Manager for initiating appearance changes on the viewer + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include <boost/lexical_cast.hpp> +#include "llaccordionctrltab.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llagentwearables.h" +#include "llappearancemgr.h" +#include "llattachmentsmgr.h" +#include "llcommandhandler.h" +#include "lleventtimer.h" +#include "llfloatersidepanelcontainer.h" +#include "llgesturemgr.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llinventoryobserver.h" +#include "llnotificationsutil.h" +#include "lloutfitobserver.h" +#include "lloutfitslist.h" +#include "llselectmgr.h" +#include "llsidepanelappearance.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" +#include "llhttpsdhandler.h" +#include "llcorehttputil.h" +#include "llappviewer.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: +// check boost. +class BoolSetter +{ +public: + BoolSetter(bool& var): + mVar(var) + { + mVar = true; + } + ~BoolSetter() + { + mVar = false; + } +private: + bool& mVar; +}; + +char ORDER_NUMBER_SEPARATOR('@'); + +class LLOutfitUnLockTimer: public LLEventTimer +{ +public: + LLOutfitUnLockTimer(F32 period) : LLEventTimer(period) + { + // restart timer on BOF changed event + LLOutfitObserver::instance().addBOFChangedCallback(boost::bind( + &LLOutfitUnLockTimer::reset, this)); + stop(); + } + + /*virtual*/ + BOOL tick() + { + if(mEventTimer.hasExpired()) + { + LLAppearanceMgr::instance().setOutfitLocked(false); + } + return FALSE; + } + void stop() { mEventTimer.stop(); } + void start() { mEventTimer.start(); } + void reset() { mEventTimer.reset(); } + BOOL getStarted() { return mEventTimer.getStarted(); } + + LLTimer& getEventTimer() { return mEventTimer;} +}; + +// support for secondlife:///app/appearance SLapps +class LLAppearanceHandler : public LLCommandHandler +{ +public: + // requests will be throttled from a non-trusted browser + LLAppearanceHandler() : LLCommandHandler("appearance", UNTRUSTED_THROTTLE) {} + + bool handle(const LLSD& params, const LLSD& query_map, LLMediaCtrl* web) + { + // support secondlife:///app/appearance/show, but for now we just + // make all secondlife:///app/appearance SLapps behave this way + if (!LLUI::sSettingGroups["config"]->getBOOL("EnableAppearance")) + { + LLNotificationsUtil::add("NoAppearance", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); + return true; + } + + LLFloaterSidePanelContainer::showPanel("appearance", LLSD()); + return true; + } +}; + +LLAppearanceHandler gAppearanceHandler; + + +LLUUID findDescendentCategoryIDByName(const LLUUID& parent_id, const std::string& name) +{ + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + LLNameCategoryCollector has_name(name); + gInventory.collectDescendentsIf(parent_id, + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + has_name); + if (0 == cat_array.size()) + return LLUUID(); + else + { + LLViewerInventoryCategory *cat = cat_array.at(0); + if (cat) + return cat->getUUID(); + else + { + LL_WARNS() << "null cat" << LL_ENDL; + return LLUUID(); + } + } +} + +// 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: + 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) + { + for (LLInventoryModel::item_array_t::const_iterator it = src_items.begin(); + it != src_items.end(); + ++it) + { + LLViewerInventoryItem* item = *it; + llassert(item); + addItem(item->getUUID()); + } + } + + // Request or re-request operation for specified item. + void addItem(const LLUUID& item_id) + { + 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]++; + } + } + + virtual bool requestOperation(const LLUUID& item_id) = 0; + + void onOp(const LLUUID& src_id, const LLUUID& dst_id, LLTimer timestamp) + { + 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; + + // 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) + { + onCompletion(); + } + else + { + onFailure(); + } + } + + 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; + } + + 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: + 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() + { + 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")) + { + 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; + } + + static S32 getInstanceCount() { return sInstanceCount; } + +private: + static S32 sInstanceCount; +}; + +S32 LLCallAfterInventoryCopyMgr::sInstanceCount = 0; + +class LLWearCategoryAfterCopy: public LLInventoryCallback +{ +public: + LLWearCategoryAfterCopy(bool append): + mAppend(append) + {} + + // 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 +{ +public: + LLTrackPhaseWrapper(const std::string& phase_name, LLPointer<LLInventoryCallback> cb = NULL): + mTrackingPhase(phase_name), + mCB(cb) + { + 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) +{ + LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(inv_item); + const std::string item_name = item ? item->getName() : "ITEM NOT FOUND"; +#ifndef LL_RELEASE_FOR_DOWNLOAD + 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() : + mAssetType(LLAssetType::AT_NONE), + mWearableType(LLWearableType::WT_INVALID), + mWearable(NULL) {} + + LLFoundData(const LLUUID& item_id, + const LLUUID& asset_id, + const std::string& name, + const LLAssetType::EType& asset_type, + const LLWearableType::EType& wearable_type, + const bool is_replacement = false + ) : + mItemID(item_id), + mAssetID(asset_id), + mName(name), + mAssetType(asset_type), + mWearableType(wearable_type), + mIsReplacement(is_replacement), + mWearable( NULL ) {} + + LLUUID mItemID; + LLUUID mAssetID; + std::string mName; + LLAssetType::EType mAssetType; + LLWearableType::EType mWearableType; + LLViewerWearable* mWearable; + bool mIsReplacement; +}; + + +class LLWearableHoldingPattern +{ + LOG_CLASS(LLWearableHoldingPattern); + +public: + LLWearableHoldingPattern(); + ~LLWearableHoldingPattern(); + + bool pollFetchCompletion(); + void onFetchCompletion(); + bool isFetchCompleted(); + bool isTimedOut(); + + void checkMissingWearables(); + bool pollMissingWearables(); + bool isMissingCompleted(); + void recoverMissingWearable(LLWearableType::EType type); + void clearCOFLinksForMissingWearables(); + + void onWearableAssetFetch(LLViewerWearable *wearable); + void onAllComplete(); + + typedef std::list<LLFoundData> found_list_t; + found_list_t& getFoundList(); + void eraseTypeToLink(LLWearableType::EType type); + void eraseTypeToRecover(LLWearableType::EType type); + void setObjItems(const LLInventoryModel::item_array_t& items); + void setGestItems(const LLInventoryModel::item_array_t& items); + bool isMostRecent(); + void handleLateArrivals(); + void resetTime(F32 timeout); + static S32 countActive() { return sActiveHoldingPatterns.size(); } + S32 index() { return mIndex; } + +private: + found_list_t mFoundList; + LLInventoryModel::item_array_t mObjItems; + LLInventoryModel::item_array_t mGestItems; + typedef std::set<S32> type_set_t; + type_set_t mTypesToRecover; + type_set_t mTypesToLink; + S32 mResolved; + LLTimer mWaitTime; + bool mFired; + typedef std::set<LLWearableHoldingPattern*> type_set_hp; + static type_set_hp sActiveHoldingPatterns; + static S32 sNextIndex; + S32 mIndex; + bool mIsMostRecent; + std::set<LLViewerWearable*> mLateArrivals; + bool mIsAllComplete; +}; + +LLWearableHoldingPattern::type_set_hp LLWearableHoldingPattern::sActiveHoldingPatterns; +S32 LLWearableHoldingPattern::sNextIndex = 0; + +LLWearableHoldingPattern::LLWearableHoldingPattern(): + mResolved(0), + mFired(false), + mIsMostRecent(true), + mIsAllComplete(false) +{ + if (countActive()>0) + { + 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) + { + (*it)->mIsMostRecent = false; + } + + } + 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() +{ + return mIsMostRecent; +} + +LLWearableHoldingPattern::found_list_t& LLWearableHoldingPattern::getFoundList() +{ + return mFoundList; +} + +void LLWearableHoldingPattern::eraseTypeToLink(LLWearableType::EType type) +{ + mTypesToLink.erase(type); +} + +void LLWearableHoldingPattern::eraseTypeToRecover(LLWearableType::EType type) +{ + mTypesToRecover.erase(type); +} + +void LLWearableHoldingPattern::setObjItems(const LLInventoryModel::item_array_t& items) +{ + mObjItems = items; +} + +void LLWearableHoldingPattern::setGestItems(const LLInventoryModel::item_array_t& items) +{ + mGestItems = items; +} + +bool LLWearableHoldingPattern::isFetchCompleted() +{ + return (mResolved >= (S32)getFoundList().size()); // have everything we were waiting for? +} + +bool LLWearableHoldingPattern::isTimedOut() +{ + return mWaitTime.hasExpired(); +} + +void LLWearableHoldingPattern::checkMissingWearables() +{ + if (!isMostRecent()) + { + // 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) + { + LLFoundData &data = *it; + if (data.mWearableType < LLWearableType::WT_COUNT) + requested_by_type[data.mWearableType]++; + if (data.mWearable) + found_by_type[data.mWearableType]++; + } + + for (S32 type = 0; type < LLWearableType::WT_COUNT; ++type) + { + if (requested_by_type[type] > found_by_type[type]) + { + 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; + if ( + // If at least one wearable of certain types (pants/shirt/skirt) + // was requested but none was found, create a default asset as a replacement. + // In all other cases, don't do anything. + // For critical types (shape/hair/skin/eyes), this will keep the avatar as a cloud + // due to logic in LLVOAvatarSelf::getIsCloud(). + // For non-critical types (tatoo, socks, etc.) the wearable will just be missing. + (requested_by_type[type] > 0) && + ((type == LLWearableType::WT_PANTS) || (type == LLWearableType::WT_SHIRT) || (type == LLWearableType::WT_SKIRT))) + { + mTypesToRecover.insert(type); + mTypesToLink.insert(type); + recoverMissingWearable((LLWearableType::EType)type); + 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)); + } +} + +void LLWearableHoldingPattern::onAllComplete() +{ + if (isAgentAvatarValid()) + { + gAgentAvatarp->outputRezTiming("Agent wearables fetch complete"); + } + + if (!isMostRecent()) + { + // 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.size() > 0) + { + LL_DEBUGS("Avatar") << self_av_string() << "Activating " << mGestItems.size() << " gestures" << LL_ENDL; + + LLGestureMgr::instance().activateGestures(mGestItems); + + // Update the inventory item labels to reflect the fact + // they are active. + LLViewerInventoryCategory* catp = + gInventory.getCategory(LLAppearanceMgr::instance().getCOF()); + + if (catp) + { + gInventory.updateCategory(catp); + gInventory.notifyObservers(); + } + } + + if (isAgentAvatarValid()) + { + 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()) + { + // Only safe to delete if all wearable callbacks and all missing wearables completed. + delete this; + } + else + { + mIsAllComplete = true; + handleLateArrivals(); + } +} + +void LLWearableHoldingPattern::onFetchCompletion() +{ + if (isMostRecent()) + { + selfStopPhase("get_wearables_2"); + } + + if (!isMostRecent()) + { + // runway skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + } + + checkMissingWearables(); +} + +// Runs as an idle callback until all wearables are fetched (or we time out). +bool LLWearableHoldingPattern::pollFetchCompletion() +{ + if (!isMostRecent()) + { + // runway skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + } + + bool completed = isFetchCompleted(); + bool timed_out = isTimedOut(); + bool done = completed || timed_out; + + if (done) + { + 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) + { + LL_WARNS() << self_av_string() << "Exceeded max wait time for wearables, updating appearance based on what has arrived" << LL_ENDL; + } + + onFetchCompletion(); + } + return done; +} + +void recovered_item_link_cb(const LLUUID& item_id, LLWearableType::EType type, LLViewerWearable *wearable, LLWearableHoldingPattern* holder) +{ + if (!holder->isMostRecent()) + { + LL_WARNS() << "HP " << holder->index() << " skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + // runway skip here? + } + + 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 (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 + { + LL_WARNS() << self_av_string() << "inventory link not found for recovered wearable" << LL_ENDL; + } + } + else + { + LL_WARNS() << self_av_string() << "HP " << holder->index() << " inventory link not found for recovered wearable" << LL_ENDL; + } +} + +void recovered_item_cb(const LLUUID& item_id, LLWearableType::EType type, LLViewerWearable *wearable, LLWearableHoldingPattern* holder) +{ + if (!holder->isMostRecent()) + { + // runway skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + } + + 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) + { + LLPointer<LLInventoryCallback> cb = new LLBoostFuncInventoryCallback(boost::bind(recovered_item_link_cb,_1,type,wearable,holder)); + + link_inventory_object(LLAppearanceMgr::instance().getCOF(), itemp, cb); + } +} + +void LLWearableHoldingPattern::recoverMissingWearable(LLWearableType::EType type) +{ + if (!isMostRecent()) + { + // 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"); + 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 LLBoostFuncInventoryCallback(boost::bind(recovered_item_cb,_1,type,wearable,this)); + + create_inventory_item(gAgent.getID(), + gAgent.getSessionID(), + lost_and_found_id, + wearable->getTransactionID(), + wearable->getName(), + wearable->getDescription(), + wearable->getAssetType(), + LLInventoryType::IT_WEARABLE, + wearable->getType(), + wearable->getPermissions().getMaskNextOwner(), + cb); +} + +bool LLWearableHoldingPattern::isMissingCompleted() +{ + return mTypesToLink.size()==0 && mTypesToRecover.size()==0; +} + +void LLWearableHoldingPattern::clearCOFLinksForMissingWearables() +{ + for (found_list_t::iterator it = getFoundList().begin(); it != getFoundList().end(); ++it) + { + LLFoundData &data = *it; + if ((data.mWearableType < LLWearableType::WT_COUNT) && (!data.mWearable)) + { + // Wearable link that was never resolved; remove links to it from COF + LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " removing link for unresolved item " << data.mItemID.asString() << LL_ENDL; + LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID); + } + } +} + +bool LLWearableHoldingPattern::pollMissingWearables() +{ + if (!isMostRecent()) + { + // 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(); + bool missing_completed = isMissingCompleted(); + bool done = timed_out || missing_completed; + + if (!done) + { + 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 << LL_ENDL; + } + + if (done) + { + if (isMostRecent()) + { + selfStopPhase("get_missing_wearables_2"); + } + + gAgentAvatarp->debugWearablesLoaded(); + + // BAP - if we don't call clearCOFLinksForMissingWearables() + // here, we won't have to add the link back in later if the + // wearable arrives late. This is to avoid corruption of + // wearable ordering info. Also has the effect of making + // unworn item links visible in the COF under some + // circumstances. + + //clearCOFLinksForMissingWearables(); + onAllComplete(); + } + return done; +} + +// Handle wearables that arrived after the timeout period expired. +void LLWearableHoldingPattern::handleLateArrivals() +{ + // Only safe to run if we have previously finished the missing + // wearables and other processing - otherwise we could be in some + // intermediate state - but have not been superceded by a later + // outfit change request. + if (mLateArrivals.size() == 0) + { + // Nothing to process. + return; + } + if (!isMostRecent()) + { + LL_WARNS() << self_av_string() << "Late arrivals not handled - outfit change no longer valid" << LL_ENDL; + } + if (!mIsAllComplete) + { + LL_WARNS() << self_av_string() << "Late arrivals not handled - in middle of missing wearables processing" << LL_ENDL; + } + + 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; + for (LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin(); + iter != getFoundList().end(); ++iter) + { + LLFoundData& data = *iter; + for (std::set<LLViewerWearable*>::iterator wear_it = mLateArrivals.begin(); + wear_it != mLateArrivals.end(); + ++wear_it) + { + LLViewerWearable *wearable = *wear_it; + + if(wearable->getAssetID() == data.mAssetID) + { + data.mWearable = wearable; + + replaced_types.insert(data.mWearableType); + + // BAP - if we didn't call + // clearCOFLinksForMissingWearables() earlier, we + // don't need to restore the link here. Fixes + // wearable ordering problems. + + // LLAppearanceMgr::instance().addCOFItemLink(data.mItemID,false); + + // BAP failing this means inventory or asset server + // are corrupted in a way we don't handle. + llassert((data.mWearableType < LLWearableType::WT_COUNT) && (wearable->getType() == data.mWearableType)); + break; + } + } + } + + // Remove COF links for any default wearables previously used to replace the late arrivals. + // All this pussyfooting around with a while loop and explicit + // iterator incrementing is to allow removing items from the list + // without clobbering the iterator we're using to navigate. + LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin(); + while (iter != getFoundList().end()) + { + LLFoundData& data = *iter; + + // If an item of this type has recently shown up, removed the corresponding replacement wearable from COF. + if (data.mWearable && data.mIsReplacement && + replaced_types.find(data.mWearableType) != replaced_types.end()) + { + LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID); + std::list<LLFoundData>::iterator clobber_ator = iter; + ++iter; + getFoundList().erase(clobber_ator); + } + else + { + ++iter; + } + } + + // Clear contents of late arrivals. + mLateArrivals.clear(); + + // Update appearance based on mFoundList + LLAppearanceMgr::instance().updateAgentWearables(this); +} + +void LLWearableHoldingPattern::resetTime(F32 timeout) +{ + mWaitTime.reset(); + mWaitTime.setTimerExpirySec(timeout); +} + +void LLWearableHoldingPattern::onWearableAssetFetch(LLViewerWearable *wearable) +{ + if (!isMostRecent()) + { + 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. + LL_DEBUGS("Avatar") << self_av_string() << "HP " << index() << " resolved " << mResolved << "/" << getFoundList().size() << LL_ENDL; + if (!wearable) + { + LL_WARNS() << self_av_string() << "no wearable found" << LL_ENDL; + } + + if (mFired) + { + LL_WARNS() << self_av_string() << "called after holder fired" << LL_ENDL; + if (wearable) + { + mLateArrivals.insert(wearable); + if (mIsAllComplete) + { + handleLateArrivals(); + } + } + return; + } + + if (!wearable) + { + return; + } + + for (LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin(); + iter != getFoundList().end(); ++iter) + { + LLFoundData& data = *iter; + if(wearable->getAssetID() == data.mAssetID) + { + // 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)) + { + LL_WARNS() << self_av_string() << "recovered wearable but type invalid. inventory wearable type: " << data.mWearableType << " asset wearable type: " << wearable->getType() << LL_ENDL; + break; + } + + data.mWearable = wearable; + } + } +} + +static void onWearableAssetFetch(LLViewerWearable* wearable, void* data) +{ + LLWearableHoldingPattern* holder = (LLWearableHoldingPattern*)data; + holder->onWearableAssetFetch(wearable); +} + + +static void removeDuplicateItems(LLInventoryModel::item_array_t& items) +{ + LLInventoryModel::item_array_t new_items; + std::set<LLUUID> items_seen; + std::deque<LLViewerInventoryItem*> tmp_list; + // Traverse from the front and keep the first of each item + // 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.size()-1; i>=0; i--) + { + LLViewerInventoryItem *item = items.at(i); + LLUUID item_id = item->getLinkedUUID(); + if (items_seen.find(item_id)!=items_seen.end()) + continue; + items_seen.insert(item_id); + tmp_list.push_front(item); + } + for (std::deque<LLViewerInventoryItem*>::iterator it = tmp_list.begin(); + it != tmp_list.end(); + ++it) + { + new_items.push_back(*it); + } + items = new_items; +} + +//========================================================================= +class LLAppearanceMgrHttpHandler : public LLHttpSDHandler +{ +public: + LLAppearanceMgrHttpHandler(const std::string& capabilityURL, LLAppearanceMgr *mgr) : + LLHttpSDHandler(capabilityURL), + mManager(mgr) + { } + + virtual ~LLAppearanceMgrHttpHandler() + { } + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); -protected:
- virtual void onSuccess(LLCore::HttpResponse * response, LLSD &content);
- virtual void onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status);
-
-private:
- static void debugCOF(const LLSD& content);
-
- LLAppearanceMgr *mManager;
-
-};
-
-//-------------------------------------------------------------------------
+protected: + virtual void onSuccess(LLCore::HttpResponse * response, LLSD &content); + virtual void onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status); + +private: + static void debugCOF(const LLSD& content); + + LLAppearanceMgr *mManager; + +}; + +//------------------------------------------------------------------------- void LLAppearanceMgrHttpHandler::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) { - mManager->decrementInFlightCounter();
-
+ mManager->decrementInFlightCounter(); + LLHttpSDHandler::onCompleted(handle, response); } -
-void LLAppearanceMgrHttpHandler::onSuccess(LLCore::HttpResponse * response, LLSD &content)
-{
- if (!content.isMap())
- {
- LLCore::HttpStatus status = LLCore::HttpStatus(HTTP_INTERNAL_ERROR, "Malformed response contents");
- response->setStatus(status);
- onFailure(response, status);
- if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage"))
- {
- debugCOF(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);
- }
- }
- else
- {
- LLCore::HttpStatus status = LLCore::HttpStatus(HTTP_INTERNAL_ERROR, "Non-success response");
- response->setStatus(status);
- onFailure(response, status);
- if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage"))
- {
- debugCOF(content);
- }
- return;
- }
-}
-
-void LLAppearanceMgrHttpHandler::onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status)
-{
- LL_WARNS("Avatar") << "Appearance Mgr request failed to " << getUri()
- << ". Reason code: (" << status.toTerseString() << ") "
- << status.toString() << LL_ENDL;
-}
-
-void LLAppearanceMgrHttpHandler::debugCOF(const LLSD& content)
-{
- dump_sequential_xml(gAgentAvatarp->getFullname() + "_appearance_request_error", 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())
- {
- LL_INFOS("Avatar") << "LOCAL ONLY: " << *it << LL_ENDL;
- local_only++;
- }
- }
- for (std::set<LLUUID>::iterator it = ais_items.begin(); it != ais_items.end(); ++it)
- {
- if (local_items.find(*it) == local_items.end())
- {
- 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;
- }
-}
-
-//=========================================================================
-
-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()
-{
- const LLUUID& current_outfit_cat = getCOF();
- LLInventoryModel::cat_array_t cat_array;
- LLInventoryModel::item_array_t item_array;
- // Can't search on FT_OUTFIT since links to categories return FT_CATEGORY for type since they don't
- // return preferred type.
- LLIsType is_category( LLAssetType::AT_CATEGORY );
- gInventory.collectDescendentsIf(current_outfit_cat,
- cat_array,
- item_array,
- false,
- is_category);
- for (LLInventoryModel::item_array_t::const_iterator iter = item_array.begin();
- iter != item_array.end();
- iter++)
- {
- const LLViewerInventoryItem *item = (*iter);
- const LLViewerInventoryCategory *cat = item->getLinkedCategory();
- if (cat && cat->getPreferredType() == LLFolderType::FT_OUTFIT)
- {
- const LLUUID parent_id = cat->getParentUUID();
- LLViewerInventoryCategory* parent_cat = gInventory.getCategory(parent_id);
- // if base outfit moved to trash it means that we don't have base outfit
- if (parent_cat != NULL && parent_cat->getPreferredType() == LLFolderType::FT_TRASH)
- {
- return NULL;
- }
- return item;
- }
- }
- return NULL;
-}
-
-bool LLAppearanceMgr::getBaseOutfitName(std::string& name)
-{
- const LLViewerInventoryItem* outfit_link = getBaseOutfitLink();
- if(outfit_link)
- {
- const LLViewerInventoryCategory *cat = outfit_link->getLinkedCategory();
- if (cat)
- {
- name = cat->getName();
- return true;
- }
- }
- return false;
-}
-
-const LLUUID LLAppearanceMgr::getBaseOutfitUUID()
-{
- const LLViewerInventoryItem* outfit_link = getBaseOutfitLink();
- if (!outfit_link || !outfit_link->getIsLinkType()) return LLUUID::null;
-
- const LLViewerInventoryCategory* outfit_cat = outfit_link->getLinkedCategory();
- if (!outfit_cat) return LLUUID::null;
-
- if (outfit_cat->getPreferredType() != LLFolderType::FT_OUTFIT)
- {
- 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();
-}
-
-void wear_on_avatar_cb(const LLUUID& inv_item, bool do_replace = false)
-{
- if (inv_item.isNull())
- return;
-
- LLViewerInventoryItem *item = gInventory.getItem(inv_item);
- if (item)
- {
- LLAppearanceMgr::instance().wearItemOnAvatar(inv_item, true, do_replace);
- }
-}
-
-bool LLAppearanceMgr::wearItemOnAvatar(const LLUUID& item_id_to_wear,
- bool do_update,
- bool replace,
- LLPointer<LLInventoryCallback> cb)
-{
-
- 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 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);
- 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 (isLinkedInCOF(item_to_wear->getUUID())) // EXT-84911
- {
- return false;
- }
-
- switch (item_to_wear->getType())
- {
- case LLAssetType::AT_CLOTHING:
- if (gAgentWearables.areWearablesLoaded())
- {
- if (!cb && do_update)
- {
- cb = new LLUpdateAppearanceAndEditWearableOnDestroy(item_id_to_wear);
- }
- S32 wearable_count = gAgentWearables.getWearableCount(item_to_wear->getWearableType());
- if ((replace && wearable_count != 0) ||
- (wearable_count >= LLAgentWearables::MAX_CLOTHING_PER_TYPE) )
- {
- LLUUID item_id = gAgentWearables.getWearableItemID(item_to_wear->getWearableType(),
- wearable_count-1);
- removeCOFItemLinks(item_id, cb);
- }
-
- addCOFItemLink(item_to_wear, 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());
- if (!cb && do_update)
- {
- cb = new LLUpdateAppearanceAndEditWearableOnDestroy(item_id_to_wear);
- }
- addCOFItemLink(item_to_wear, cb);
- break;
-
- case LLAssetType::AT_OBJECT:
- rez_attachment(item_to_wear, NULL, replace);
- break;
-
- default: return false;;
- }
-
- return true;
-}
-
-// Update appearance from outfit folder.
-void LLAppearanceMgr::changeOutfit(bool proceed, const LLUUID& category, bool append)
-{
- if (!proceed)
- return;
- LLAppearanceMgr::instance().updateCOF(category,append);
-}
-
-void LLAppearanceMgr::replaceCurrentOutfit(const LLUUID& new_outfit)
-{
- LLViewerInventoryCategory* cat = gInventory.getCategory(new_outfit);
- wearInventoryCategory(cat, false, false);
-}
-
-// Open outfit renaming dialog.
-void LLAppearanceMgr::renameOutfit(const LLUUID& outfit_id)
-{
- LLViewerInventoryCategory* cat = gInventory.getCategory(outfit_id);
- if (!cat)
- {
- return;
- }
-
- LLSD args;
- args["NAME"] = cat->getName();
-
- LLSD payload;
- payload["cat_id"] = outfit_id;
-
- LLNotificationsUtil::add("RenameOutfit", args, payload, boost::bind(onOutfitRename, _1, _2));
-}
-
-// User typed new outfit name.
-// static
-void LLAppearanceMgr::onOutfitRename(const LLSD& notification, const LLSD& response)
-{
- S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
- if (option != 0) return; // canceled
-
- std::string outfit_name = response["new_name"].asString();
- LLStringUtil::trim(outfit_name);
- if (!outfit_name.empty())
- {
- LLUUID cat_id = notification["payload"]["cat_id"].asUUID();
- rename_category(&gInventory, cat_id, outfit_name);
- }
-}
-
-void LLAppearanceMgr::setOutfitLocked(bool locked)
-{
- if (mOutfitLocked == locked)
- {
- return;
- }
-
- mOutfitLocked = locked;
- if (locked)
- {
- mUnlockOutfitTimer->reset();
- mUnlockOutfitTimer->start();
- }
- else
- {
- mUnlockOutfitTimer->stop();
- }
-
- LLOutfitObserver::instance().notifyOutfitLockChanged();
-}
-
-void LLAppearanceMgr::addCategoryToCurrentOutfit(const LLUUID& cat_id)
-{
- LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
- wearInventoryCategory(cat, false, true);
-}
-
-void LLAppearanceMgr::takeOffOutfit(const LLUUID& cat_id)
-{
- LLInventoryModel::cat_array_t cats;
- LLInventoryModel::item_array_t items;
- LLFindWearablesEx collector(/*is_worn=*/ true, /*include_body_parts=*/ false);
-
- gInventory.collectDescendentsIf(cat_id, cats, items, FALSE, collector);
-
- 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;
- 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() );
- }
- }
-}
-
-// Create a copy of src_id + contents as a subfolder of dst_id.
-void LLAppearanceMgr::shallowCopyCategory(const LLUUID& src_id, const LLUUID& dst_id,
- LLPointer<LLInventoryCallback> cb)
-{
- LLInventoryCategory *src_cat = gInventory.getCategory(src_id);
- if (!src_cat)
- {
- LL_WARNS() << "folder not found for src " << src_id.asString() << LL_ENDL;
- return;
- }
- 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())
- {
- parent_id = gInventory.getRootFolderID();
- }
- LLUUID subfolder_id = gInventory.createNewCategory( parent_id,
- LLFolderType::FT_NONE,
- src_cat->getName());
- shallowCopyCategoryContents(src_id, subfolder_id, cb);
-
- 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)
-{
- LLInventoryModel::cat_array_t* cats;
- LLInventoryModel::item_array_t* items;
- gInventory.getDirectDescendentsOf(src_id, cats, items);
- 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)
- {
- const LLViewerInventoryItem* item = (*iter);
- switch (item->getActualType())
- {
- case LLAssetType::AT_LINK:
- {
- LL_DEBUGS("Avatar") << "linking inventory item " << item->getName() << LL_ENDL;
- link_array.push_back(LLConstPointer<LLInventoryObject>(item));
- break;
- }
- case LLAssetType::AT_LINK_FOLDER:
- {
- LLViewerInventoryCategory *catp = item->getLinkedCategory();
- // Skip copying outfit links.
- if (catp && catp->getPreferredType() != LLFolderType::FT_OUTFIT)
- {
- LL_DEBUGS("Avatar") << "linking inventory folder " << item->getName() << LL_ENDL;
- link_array.push_back(LLConstPointer<LLInventoryObject>(item));
- }
- break;
- }
- case LLAssetType::AT_CLOTHING:
- case LLAssetType::AT_OBJECT:
- case LLAssetType::AT_BODYPART:
- case LLAssetType::AT_GESTURE:
- {
- LL_DEBUGS("Avatar") << "copying inventory item " << item->getName() << LL_ENDL;
- copy_inventory_item(gAgent.getID(),
- item->getPermissions().getOwner(),
- item->getUUID(),
- dst_id,
- item->getName(),
- cb);
- break;
- }
- default:
- // Ignore non-outfit asset types
- break;
- }
- }
- if (!link_array.empty())
- {
- link_inventory_array(dst_id, link_array, cb);
- }
-}
-
-BOOL LLAppearanceMgr::getCanMakeFolderIntoOutfit(const LLUUID& folder_id)
-{
- // These are the wearable items that are required for considering this
- // folder as containing a complete outfit.
- U32 required_wearables = 0;
- required_wearables |= 1LL << LLWearableType::WT_SHAPE;
- required_wearables |= 1LL << LLWearableType::WT_SKIN;
- required_wearables |= 1LL << LLWearableType::WT_HAIR;
- required_wearables |= 1LL << LLWearableType::WT_EYES;
-
- // These are the wearables that the folder actually contains.
- U32 folder_wearables = 0;
- LLInventoryModel::cat_array_t* cats;
- LLInventoryModel::item_array_t* items;
- gInventory.getDirectDescendentsOf(folder_id, cats, items);
- for (LLInventoryModel::item_array_t::const_iterator iter = items->begin();
- iter != items->end();
- ++iter)
- {
- const LLViewerInventoryItem* item = (*iter);
- if (item->isWearableType())
- {
- const LLWearableType::EType wearable_type = item->getWearableType();
- folder_wearables |= 1LL << wearable_type;
- }
- }
-
- // If the folder contains the required wearables, return TRUE.
- return ((required_wearables & folder_wearables) == required_wearables);
-}
-
-bool LLAppearanceMgr::getCanRemoveOutfit(const LLUUID& outfit_cat_id)
-{
- // Disallow removing the base outfit.
- if (outfit_cat_id == getBaseOutfitUUID())
- {
- return false;
- }
-
- // Check if the outfit folder itself is removable.
- if (!get_is_category_removable(&gInventory, outfit_cat_id))
- {
- return false;
- }
-
- // Check for the folder's non-removable descendants.
- LLFindNonRemovableObjects filter_non_removable;
- LLInventoryModel::cat_array_t cats;
- LLInventoryModel::item_array_t items;
- LLInventoryModel::item_array_t::const_iterator it;
- gInventory.collectDescendentsIf(outfit_cat_id, cats, items, false, filter_non_removable);
- if (!cats.empty() || !items.empty())
- {
- return false;
- }
-
- return true;
-}
-
-// static
-bool LLAppearanceMgr::getCanRemoveFromCOF(const LLUUID& outfit_cat_id)
-{
- if (gAgentWearables.isCOFChangeInProgress())
- {
- return false;
- }
-
- LLFindWearablesEx is_worn(/*is_worn=*/ true, /*include_body_parts=*/ false);
- return gInventory.hasMatchingDirectDescendent(outfit_cat_id, is_worn);
-}
-
-// static
-bool LLAppearanceMgr::getCanAddToCOF(const LLUUID& outfit_cat_id)
-{
- if (gAgentWearables.isCOFChangeInProgress())
- {
- return false;
- }
-
- LLInventoryModel::cat_array_t cats;
- LLInventoryModel::item_array_t items;
- LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false);
- gInventory.collectDescendentsIf(outfit_cat_id,
- cats,
- items,
- LLInventoryModel::EXCLUDE_TRASH,
- not_worn);
-
- return items.size() > 0;
-}
-
-bool LLAppearanceMgr::getCanReplaceCOF(const LLUUID& outfit_cat_id)
-{
- // Don't allow wearing anything while we're changing appearance.
- if (gAgentWearables.isCOFChangeInProgress())
- {
- return false;
- }
-
- // Check whether it's the base outfit.
- if (outfit_cat_id.isNull() || outfit_cat_id == getBaseOutfitUUID())
- {
- return false;
- }
-
- // Check whether the outfit contains any wearables we aren't wearing already (STORM-702).
- LLInventoryModel::cat_array_t cats;
- LLInventoryModel::item_array_t items;
- LLFindWearablesEx is_worn(/*is_worn=*/ false, /*include_body_parts=*/ true);
- gInventory.collectDescendentsIf(outfit_cat_id,
- cats,
- items,
- LLInventoryModel::EXCLUDE_TRASH,
- is_worn);
-
- return items.size() > 0;
-}
-
-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.size(); ++i)
- {
- LLViewerInventoryItem *item = items.at(i);
- if (item->getActualType() != LLAssetType::AT_LINK_FOLDER)
- continue;
- LLViewerInventoryCategory* catp = item->getLinkedCategory();
- if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT)
- {
- remove_inventory_item(item->getUUID(), cb);
- }
- }
-}
-
-// 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)
-{
- // 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);
- 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();
-
- // Deactivate currently active gestures in the COF, if replacing outfit
- if (!append)
- {
- LLInventoryModel::item_array_t gest_items;
- getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE);
- for(S32 i = 0; i < gest_items.size(); ++i)
- {
- LLViewerInventoryItem *gest_item = gest_items.at(i);
- if ( LLGestureMgr::instance().isGestureActive( gest_item->getLinkedUUID()) )
- {
- LLGestureMgr::instance().deactivateGesture( gest_item->getLinkedUUID() );
- }
- }
- }
-
- // Collect and filter descendents to determine new COF contents.
-
- // - Body parts: always include COF contents as a fallback in case any
- // required parts are missing.
- // Preserve body parts from COF if appending.
- LLInventoryModel::item_array_t body_items;
- 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);
-
- // - Wearables: include COF contents only if appending.
- LLInventoryModel::item_array_t wear_items;
- if (append)
- 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);
-
- // - Attachments: include COF contents only if appending.
- LLInventoryModel::item_array_t obj_items;
- if (append)
- 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);
- getDescendentsOfAssetType(category, gest_items, LLAssetType::AT_GESTURE);
- removeDuplicateItems(gest_items);
-
- // Create links to new COF contents.
- 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();
- }
-
- 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"))
- {
- dump_sequential_xml(gAgentAvatarp->getFullname() + "_slam_request", contents);
- }
- 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 *>(LLFloaterSidePanelContainer::getPanel("appearance"));
- if (panel_appearance)
- {
- panel_appearance->refreshCurrentOutfitName(name);
- }
-}
-
-void LLAppearanceMgr::createBaseOutfitLink(const LLUUID& category, LLPointer<LLInventoryCallback> link_waiter)
-{
- const LLUUID cof = getCOF();
- LLViewerInventoryCategory* catp = gInventory.getCategory(category);
- std::string new_outfit_name = "";
-
- purgeBaseOutfitLink(cof, link_waiter);
-
- if (catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT)
- {
- link_inventory_object(cof, catp, link_waiter);
- new_outfit_name = catp->getName();
- }
-
- updatePanelOutfitName(new_outfit_name);
-}
-
-void LLAppearanceMgr::updateAgentWearables(LLWearableHoldingPattern* holder)
-{
- LL_DEBUGS("Avatar") << "updateAgentWearables()" << LL_ENDL;
- LLInventoryItem::item_array_t items;
- 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++ )
- {
- for (LLWearableHoldingPattern::found_list_t::iterator iter = holder->getFoundList().begin();
- iter != holder->getFoundList().end(); ++iter)
- {
- LLFoundData& data = *iter;
- LLViewerWearable* wearable = data.mWearable;
- if( wearable && ((S32)wearable->getType() == i) )
- {
- LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(data.mItemID);
- if( item && (item->getAssetUUID() == wearable->getAssetID()) )
- {
- items.push_back(item);
- wearables.push_back(wearable);
- }
- }
- }
- }
-
- if(wearables.size() > 0)
- {
- gAgentWearables.setWearableOutfit(items, wearables);
- }
-}
-
-S32 LLAppearanceMgr::countActiveHoldingPatterns()
-{
- return LLWearableHoldingPattern::countActive();
-}
-
-static void remove_non_link_items(LLInventoryModel::item_array_t &items)
-{
- LLInventoryModel::item_array_t pruned_items;
- for (LLInventoryModel::item_array_t::const_iterator iter = items.begin();
- iter != items.end();
- ++iter)
- {
- const LLViewerInventoryItem *item = (*iter);
- if (item && item->getIsLinkType())
- {
- pruned_items.push_back((*iter));
- }
- }
- items = pruned_items;
-}
-
-//a predicate for sorting inventory items by actual descriptions
-bool sort_by_actual_description(const LLInventoryItem* item1, const LLInventoryItem* item2)
-{
- if (!item1 || !item2)
- {
- LL_WARNS() << "either item1 or item2 is NULL" << LL_ENDL;
- return true;
- }
-
- return item1->getActualDescription() < item2->getActualDescription();
-}
-
-void item_array_diff(LLInventoryModel::item_array_t& full_list,
- LLInventoryModel::item_array_t& keep_list,
- LLInventoryModel::item_array_t& kill_list)
-
-{
- for (LLInventoryModel::item_array_t::iterator it = full_list.begin();
- it != full_list.end();
- ++it)
- {
- LLViewerInventoryItem *item = *it;
- if (std::find(keep_list.begin(), keep_list.end(), item) == keep_list.end())
- {
- kill_list.push_back(item);
- }
- }
-}
-
-S32 LLAppearanceMgr::findExcessOrDuplicateItems(const LLUUID& cat_id,
- LLAssetType::EType type,
- S32 max_items,
- LLInventoryObject::object_list_t& items_to_kill)
-{
- S32 to_kill_count = 0;
-
- LLInventoryModel::item_array_t items;
- getDescendentsOfAssetType(cat_id, items, type);
- LLInventoryModel::item_array_t curr_items = items;
- removeDuplicateItems(items);
- if (max_items > 0)
- {
- filterWearableItems(items, max_items);
- }
- LLInventoryModel::item_array_t kill_items;
- item_array_diff(curr_items,items,kill_items);
- for (LLInventoryModel::item_array_t::iterator it = kill_items.begin();
- it != kill_items.end();
- ++it)
- {
- items_to_kill.push_back(LLPointer<LLInventoryObject>(*it));
- to_kill_count++;
- }
- return to_kill_count;
-}
-
-
-void LLAppearanceMgr::findAllExcessOrDuplicateItems(const LLUUID& cat_id,
- LLInventoryObject::object_list_t& items_to_kill)
-{
- findExcessOrDuplicateItems(cat_id,LLAssetType::AT_BODYPART,
- 1, items_to_kill);
- findExcessOrDuplicateItems(cat_id,LLAssetType::AT_CLOTHING,
- LLAgentWearables::MAX_CLOTHING_PER_TYPE, items_to_kill);
- findExcessOrDuplicateItems(cat_id,LLAssetType::AT_OBJECT,
- -1, 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)
- {
- // 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 enforce_item_restrictions,
- bool enforce_ordering,
- nullary_func_t post_update_func)
-{
- if (mIsInUpdateAppearanceFromCOF)
- {
- LL_WARNS() << "Called updateAppearanceFromCOF inside updateAppearanceFromCOF, skipping" << LL_ENDL;
- return;
- }
-
- LL_DEBUGS("Avatar") << self_av_string() << "starting" << LL_ENDL;
-
- 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;
- }
-
- 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");
-
- // update dirty flag to see if the state of the COF matches
- // the saved outfit stored as a folder link
- updateIsDirty();
-
- // Send server request for appearance update
- if (gAgent.getRegion() && gAgent.getRegion()->getCentralBakeVersion())
- {
- requestServerAppearanceUpdate();
- }
-
- LLUUID current_outfit_id = getCOF();
-
- // Find all the wearables that are in the COF's subtree.
- 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);
- // Get rid of non-links in case somehow the COF was corrupted.
- remove_non_link_items(wear_items);
- remove_non_link_items(obj_items);
- remove_non_link_items(gest_items);
-
- dumpItemArray(wear_items,"asset_dump: wear_item");
- dumpItemArray(obj_items,"asset_dump: obj_item");
-
- 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;
- }
-
- //preparing the list of wearables in the correct order for LLAgentWearables
- sortItemsByActualDescription(wear_items);
-
-
- LL_DEBUGS("Avatar") << "HP block starts" << LL_ENDL;
- LLTimer hp_block_timer;
- LLWearableHoldingPattern* holder = new LLWearableHoldingPattern;
-
- holder->setObjItems(obj_items);
- holder->setGestItems(gest_items);
-
- // Note: can't do normal iteration, because if all the
- // wearables can be resolved immediately, then the
- // callback will be called (and this object deleted)
- // before the final getNextData().
-
- for(S32 i = 0; i < wear_items.size(); ++i)
- {
- LLViewerInventoryItem *item = wear_items.at(i);
- LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL;
-
- // Fault injection: use debug setting to test asset
- // fetch failures (should be replaced by new defaults in
- // lost&found).
- U32 skip_type = gSavedSettings.getU32("ForceAssetFail");
-
- if (item && item->getIsLinkType() && linked_item)
- {
- LLFoundData found(linked_item->getUUID(),
- linked_item->getAssetUUID(),
- linked_item->getName(),
- linked_item->getType(),
- linked_item->isWearableType() ? linked_item->getWearableType() : LLWearableType::WT_INVALID
- );
-
- if (skip_type != LLWearableType::WT_INVALID && skip_type == found.mWearableType)
- {
- found.mAssetID.generate(); // Replace with new UUID, guaranteed not to exist in DB
- }
- //pushing back, not front, to preserve order of wearables for LLAgentWearables
- holder->getFoundList().push_back(found);
- }
- else
- {
- if (!item)
- {
- LL_WARNS() << "Attempt to wear a null item " << LL_ENDL;
- }
- else if (!linked_item)
- {
- 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;
-
- 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);
-
- }
-
- holder->resetTime(gSavedSettings.getF32("MaxWearableWaitTime"));
- if (!holder->pollFetchCompletion())
- {
- 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)
-{
- LLInventoryModel::cat_array_t cats;
- LLIsType is_of_type(type);
- gInventory.collectDescendentsIf(category,
- cats,
- items,
- LLInventoryModel::EXCLUDE_TRASH,
- 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)
-{
- LLInventoryModel::cat_array_t wear_cats;
- LLFindWearables is_wearable;
- gInventory.collectDescendentsIf(category,
- wear_cats,
- wear_items,
- LLInventoryModel::EXCLUDE_TRASH,
- is_wearable);
-
- LLInventoryModel::cat_array_t obj_cats;
- LLIsType is_object( LLAssetType::AT_OBJECT );
- gInventory.collectDescendentsIf(category,
- obj_cats,
- obj_items,
- LLInventoryModel::EXCLUDE_TRASH,
- is_object);
-
- // Find all gestures in this folder
- LLInventoryModel::cat_array_t gest_cats;
- LLIsType is_gesture( LLAssetType::AT_GESTURE );
- gInventory.collectDescendentsIf(category,
- gest_cats,
- gest_items,
- LLInventoryModel::EXCLUDE_TRASH,
- is_gesture);
-}
-
-void LLAppearanceMgr::wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append)
-{
- if(!category) return;
-
- selfClearPhases();
- selfStartPhase("wear_inventory_category");
-
- gAgentWearables.notifyLoadingStarted();
-
- LL_INFOS("Avatar") << self_av_string() << "wearInventoryCategory( " << category->getName()
- << " )" << LL_ENDL;
-
- // 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)
-{
- 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.
- LLInventoryCategory* cat = gInventory.getCategory(cat_id);
- if(copy_items)
- {
- LLInventoryModel::cat_array_t* cats;
- LLInventoryModel::item_array_t* items;
- gInventory.getDirectDescendentsOf(cat_id, cats, items);
- std::string name;
- if(!cat)
- {
- // should never happen.
- name = "New Outfit";
- }
- else
- {
- name = cat->getName();
- }
- LLViewerInventoryItem* item = NULL;
- LLInventoryModel::item_array_t::const_iterator it = items->begin();
- LLInventoryModel::item_array_t::const_iterator end = items->end();
- LLUUID pid;
- for(; it < end; ++it)
- {
- item = *it;
- if(item)
- {
- if(LLInventoryType::IT_GESTURE == item->getInventoryType())
- {
- pid = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE);
- }
- else
- {
- pid = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING);
- }
- break;
- }
- }
- if(pid.isNull())
- {
- pid = gInventory.getRootFolderID();
- }
-
- LLUUID new_cat_id = gInventory.createNewCategory(
- pid,
- LLFolderType::FT_NONE,
- name);
-
- // 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();
- }
- else
- {
- // Wear the inventory category.
- LLAppearanceMgr::instance().wearInventoryCategoryOnAvatar(cat, append);
- }
-}
-
-// *NOTE: hack to get from avatar inventory to avatar
-void LLAppearanceMgr::wearInventoryCategoryOnAvatar( LLInventoryCategory* category, bool append )
-{
- // Avoid unintentionally overwriting old wearables. We have to do
- // this up front to avoid having to deal with the case of multiple
- // wearables being dirty.
- if (!category) return;
-
- if ( !LLInventoryCallbackManager::is_instantiated() )
- {
- // shutting down, ignore.
- return;
- }
-
- 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
- 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)
-{
- LL_INFOS("Avatar") << self_av_string() << "Wearing category " << name << LL_ENDL;
-
- LLInventoryModel::cat_array_t cat_array;
- LLInventoryModel::item_array_t item_array;
- LLNameCategoryCollector has_name(name);
- gInventory.collectDescendentsIf(gInventory.getRootFolderID(),
- cat_array,
- item_array,
- LLInventoryModel::EXCLUDE_TRASH,
- has_name);
- bool copy_items = false;
- LLInventoryCategory* cat = NULL;
- if (cat_array.size() > 0)
- {
- // Just wear the first one that matches
- cat = cat_array.at(0);
- }
- else
- {
- gInventory.collectDescendentsIf(LLUUID::null,
- cat_array,
- item_array,
- LLInventoryModel::EXCLUDE_TRASH,
- has_name);
- if(cat_array.size() > 0)
- {
- cat = cat_array.at(0);
- copy_items = true;
- }
- }
-
- if(cat)
- {
- LLAppearanceMgr::wearInventoryCategory(cat, copy_items, false);
- }
- else
- {
- LL_WARNS() << "Couldn't find outfit " <<name<< " in wearOutfitByName()"
- << LL_ENDL;
- }
-}
-
-bool areMatchingWearables(const LLViewerInventoryItem *a, const LLViewerInventoryItem *b)
-{
- return (a->isWearableType() && b->isWearableType() &&
- (a->getWearableType() == b->getWearableType()));
-}
-
-class LLDeferredCOFLinkObserver: public LLInventoryObserver
-{
-public:
- LLDeferredCOFLinkObserver(const LLUUID& item_id, LLPointer<LLInventoryCallback> cb, const std::string& description):
- mItemID(item_id),
- mCallback(cb),
- mDescription(description)
- {
- }
-
- ~LLDeferredCOFLinkObserver()
- {
- }
-
- /* virtual */ void changed(U32 mask)
- {
- const LLInventoryItem *item = gInventory.getItem(mItemID);
- if (item)
- {
- gInventory.removeObserver(this);
- LLAppearanceMgr::instance().addCOFItemLink(item, mCallback, mDescription);
- delete this;
- }
- }
-
-private:
- const LLUUID mItemID;
- 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,
- LLPointer<LLInventoryCallback> cb,
- const std::string description)
-{
- const LLInventoryItem *item = gInventory.getItem(item_id);
- if (!item)
- {
- LLDeferredCOFLinkObserver *observer = new LLDeferredCOFLinkObserver(item_id, cb, description);
- gInventory.addObserver(observer);
- }
- else
- {
- addCOFItemLink(item, cb, description);
- }
-}
-
-void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item,
- LLPointer<LLInventoryCallback> cb,
- const std::string description)
-{
- const LLViewerInventoryItem *vitem = dynamic_cast<const LLViewerInventoryItem*>(item);
- if (!vitem)
- {
- LL_WARNS() << "not an llviewerinventoryitem, failed" << LL_ENDL;
- return;
- }
-
- gInventory.addChangedMask(LLInventoryObserver::LABEL, vitem->getLinkedUUID());
-
- LLInventoryModel::cat_array_t cat_array;
- LLInventoryModel::item_array_t item_array;
- gInventory.collectDescendents(LLAppearanceMgr::getCOF(),
- cat_array,
- item_array,
- LLInventoryModel::EXCLUDE_TRASH);
- bool linked_already = false;
- U32 count = 0;
- for (S32 i=0; i<item_array.size(); i++)
- {
- // Are these links to the same object?
- 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)
- || (wearable_type == LLWearableType::WT_HAIR)
- || (wearable_type == LLWearableType::WT_EYES)
- || (wearable_type == LLWearableType::WT_SKIN);
-
- if (inv_item->getLinkedUUID() == vitem->getLinkedUUID())
- {
- linked_already = true;
- }
- // Are these links to different items of the same body part
- // 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))
- {
- remove_inventory_item(inv_item->getUUID(), cb);
- }
- else if (count >= LLAgentWearables::MAX_CLOTHING_PER_TYPE)
- {
- // MULTI-WEARABLES: make sure we don't go over MAX_CLOTHING_PER_TYPE
- remove_inventory_item(inv_item->getUUID(), cb);
- }
- }
- }
-
- if (!linked_already)
- {
- LLViewerInventoryItem *copy_item = new LLViewerInventoryItem;
- copy_item->copyViewerItem(vitem);
- copy_item->setDescription(description);
- link_inventory_object(getCOF(), copy_item, cb);
- }
-}
-
-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)
- {
- 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++)
- {
- const LLViewerInventoryItem* inv_item = item_array.at(i).get();
- if (inv_item->getLinkedUUID() == vitem->getLinkedUUID())
- {
- result.push_back(item_array.at(i));
- }
- }
- }
- return result;
-}
-
-bool LLAppearanceMgr::isLinkedInCOF(const LLUUID& item_id)
-{
- LLInventoryModel::item_array_t links = LLAppearanceMgr::instance().findCOFItemLinks(item_id);
- return links.size() > 0;
-}
-
-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);
-
- 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++)
- {
- const LLInventoryItem* item = item_array.at(i).get();
- if (item->getIsLinkType() && item->getLinkedUUID() == item_id)
- {
- bool immediate_delete = false;
- if (item->getType() == LLAssetType::AT_OBJECT)
- {
- immediate_delete = true;
- }
- remove_inventory_item(item->getUUID(), cb, immediate_delete);
- }
- }
-}
-
-void LLAppearanceMgr::removeCOFLinksOfType(LLWearableType::EType type, LLPointer<LLInventoryCallback> cb)
-{
- LLFindWearablesOfType filter_wearables_of_type(type);
- LLInventoryModel::cat_array_t cats;
- LLInventoryModel::item_array_t items;
- LLInventoryModel::item_array_t::const_iterator it;
-
- gInventory.collectDescendentsIf(getCOF(), cats, items, true, filter_wearables_of_type);
- for (it = items.begin(); it != items.end(); ++it)
- {
- const LLViewerInventoryItem* item = *it;
- if (item->getIsLinkType()) // we must operate on links only
- {
- remove_inventory_item(item->getUUID(), cb);
- }
- }
-}
-
-bool sort_by_linked_uuid(const LLViewerInventoryItem* item1, const LLViewerInventoryItem* item2)
-{
- if (!item1 || !item2)
- {
- LL_WARNS() << "item1, item2 cannot be null, something is very wrong" << LL_ENDL;
- return true;
- }
-
- return item1->getLinkedUUID() < item2->getLinkedUUID();
-}
-
-void LLAppearanceMgr::updateIsDirty()
-{
- LLUUID cof = getCOF();
- LLUUID base_outfit;
-
- // find base outfit link
- const LLViewerInventoryItem* base_outfit_item = getBaseOutfitLink();
- LLViewerInventoryCategory* catp = NULL;
- if (base_outfit_item && base_outfit_item->getIsLinkType())
- {
- catp = base_outfit_item->getLinkedCategory();
- }
- if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT)
- {
- base_outfit = catp->getUUID();
- }
-
- // Set dirty to "false" if no base outfit found to disable "Save"
- // and leave only "Save As" enabled in My Outfits.
- mOutfitIsDirty = false;
-
- if (base_outfit.notNull())
- {
- LLIsValidItemLink collector;
-
- LLInventoryModel::cat_array_t cof_cats;
- LLInventoryModel::item_array_t cof_items;
- gInventory.collectDescendentsIf(cof, cof_cats, cof_items,
- LLInventoryModel::EXCLUDE_TRASH, collector);
-
- LLInventoryModel::cat_array_t outfit_cats;
- LLInventoryModel::item_array_t outfit_items;
- gInventory.collectDescendentsIf(base_outfit, outfit_cats, outfit_items,
- LLInventoryModel::EXCLUDE_TRASH, collector);
-
- 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;
- return;
- }
-
- //"dirty" - also means a difference in linked UUIDs and/or a difference in wearables order (links' descriptions)
- std::sort(cof_items.begin(), cof_items.end(), sort_by_linked_uuid);
- std::sort(outfit_items.begin(), outfit_items.end(), sort_by_linked_uuid);
-
- for (U32 i = 0; i < cof_items.size(); ++i)
- {
- LLViewerInventoryItem *item1 = cof_items.at(i);
- LLViewerInventoryItem *item2 = outfit_items.at(i);
-
- if (item1->getLinkedUUID() != item2->getLinkedUUID() ||
- item1->getName() != item2->getName() ||
- 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;
-}
-
-// *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()
-{
- 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);
-
- 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);
-
- for(std::vector<std::string>::iterator it = gesture_folders_to_copy.begin();
- it != gesture_folders_to_copy.end();
- ++it)
- {
- 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));
- }
- }
-}
-
-// Handler for anything that's deferred until avatar de-clouds.
-void LLAppearanceMgr::onFirstFullyVisible()
-{
- gAgentAvatarp->outputRezTiming("Avatar fully loaded");
- gAgentAvatarp->reportAvatarRezTime();
- gAgentAvatarp->debugAvatarVisible();
-
- // 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())
- {
- // don't allow modify locked outfit
- 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;
-
- 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;
-}
-
-void LLAppearanceMgr::divvyWearablesByType(const LLInventoryModel::item_array_t& items, wearables_by_type_t& items_by_type)
-{
- items_by_type.resize(LLWearableType::WT_COUNT);
- if (items.empty()) return;
-
- for (S32 i=0; i<items.size(); i++)
- {
- LLViewerInventoryItem *item = items.at(i);
- if (!item)
- {
- LL_WARNS("Appearance") << "NULL item found" << LL_ENDL;
- continue;
- }
- // Ignore non-wearables.
- if (!item->isWearableType())
- continue;
- LLWearableType::EType type = item->getWearableType();
- if(type < 0 || type >= LLWearableType::WT_COUNT)
- {
- LL_WARNS("Appearance") << "Invalid wearable type. Inventory type does not match wearable flag bitfield." << LL_ENDL;
- continue;
- }
- items_by_type[type].push_back(item);
- }
-}
-
-std::string build_order_string(LLWearableType::EType type, U32 i)
-{
- std::ostringstream order_num;
- order_num << ORDER_NUMBER_SEPARATOR << type * 100 + i;
- return order_num.str();
-}
-
-struct WearablesOrderComparator
-{
- LOG_CLASS(WearablesOrderComparator);
- WearablesOrderComparator(const LLWearableType::EType type)
- {
- mControlSize = build_order_string(type, 0).size();
- };
-
- bool operator()(const LLInventoryItem* item1, const LLInventoryItem* item2)
- {
- 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]);
-
- if (item1_valid && item2_valid)
- return desc1 < desc2;
-
- //we need to sink down invalid items: items with empty descriptions, items with "Broken link" descriptions,
- //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 item1->getName() < item2->getName();
- }
-
- U32 mControlSize;
-};
-
-void LLAppearanceMgr::getWearableOrderingDescUpdates(LLInventoryModel::item_array_t& wear_items,
- desc_map_t& desc_map)
-{
- wearables_by_type_t items_by_type(LLWearableType::WT_COUNT);
- divvyWearablesByType(wear_items, items_by_type);
-
- 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->getActualDescription()) continue;
-
- desc_map[item->getUUID()] = new_order_str;
- }
- }
-}
-
-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);
- }
-
-}
-
-
-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()
-{
-
- if (!testCOFRequestVersion())
- {
- // *TODO: LL_LOG message here
- return;
- }
-
- if ((mInFlightCounter > 0) && (mInFlightTimer.hasExpired()))
- {
- LL_WARNS("Avatar") << "in flight timer expired, resetting " << LL_ENDL;
- mInFlightCounter = 0;
- }
-
- if (gAgentAvatarp->isEditingAppearance())
- {
- LL_WARNS("Avatar") << "Avatar editing appeance, not sending request." << LL_ENDL;
- // don't send out appearance updates if in appearance editing mode
- return;
- }
-
- if (!gAgent.getRegion())
- {
- LL_WARNS("Avatar") << "Region not set, cannot request server appearance update" << LL_ENDL;
- return;
- }
- if (gAgent.getRegion()->getCentralBakeVersion() == 0)
- {
- LL_WARNS("Avatar") << "Region does not support baking" << LL_ENDL;
- }
- std::string url = gAgent.getRegion()->getCapability("UpdateAvatarAppearance");
- if (url.empty())
- {
- LL_WARNS("Avatar") << "No cap for UpdateAvatarAppearance." << LL_ENDL;
- return;
- }
-
- LLSD postData;
- S32 cof_version = LLAppearanceMgr::instance().getCOFVersion();
- if (gSavedSettings.getBOOL("DebugAvatarExperimentalServerAppearanceUpdate"))
- {
- postData = LLAppearanceMgr::instance().dumpCOF();
- }
- else
- {
- postData["cof_version"] = cof_version;
- if (gSavedSettings.getBOOL("DebugForceAppearanceRequestFailure"))
- {
- postData["cof_version"] = cof_version + 999;
- }
- }
- LL_DEBUGS("Avatar") << "request url " << url << " my_cof_version " << cof_version << LL_ENDL;
-
- LLAppearanceMgrHttpHandler * handler = new LLAppearanceMgrHttpHandler(url, this);
-
- mInFlightCounter++;
- mInFlightTimer.setTimerExpirySec(60.0);
-
- llassert(cof_version >= gAgentAvatarp->mLastUpdateRequestCOFVersion);
- gAgentAvatarp->mLastUpdateRequestCOFVersion = cof_version;
-
- LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest,
- mHttpPolicy, mHttpPriority, url,
- postData, mHttpOptions, mHttpHeaders, handler);
-
- if (handle == LLCORE_HTTP_HANDLE_INVALID)
- {
+ +void LLAppearanceMgrHttpHandler::onSuccess(LLCore::HttpResponse * response, LLSD &content) +{ + if (!content.isMap()) + { + LLCore::HttpStatus status = LLCore::HttpStatus(HTTP_INTERNAL_ERROR, "Malformed response contents"); + response->setStatus(status); + onFailure(response, status); + if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) + { + debugCOF(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); + } + } + else + { + LLCore::HttpStatus status = LLCore::HttpStatus(HTTP_INTERNAL_ERROR, "Non-success response"); + response->setStatus(status); + onFailure(response, status); + if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) + { + debugCOF(content); + } + return; + } +} + +void LLAppearanceMgrHttpHandler::onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status) +{ + LL_WARNS("Avatar") << "Appearance Mgr request failed to " << getUri() + << ". Reason code: (" << status.toTerseString() << ") " + << status.toString() << LL_ENDL; +} + +void LLAppearanceMgrHttpHandler::debugCOF(const LLSD& content) +{ + dump_sequential_xml(gAgentAvatarp->getFullname() + "_appearance_request_error", 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()) + { + LL_INFOS("Avatar") << "LOCAL ONLY: " << *it << LL_ENDL; + local_only++; + } + } + for (std::set<LLUUID>::iterator it = ais_items.begin(); it != ais_items.end(); ++it) + { + if (local_items.find(*it) == local_items.end()) + { + 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; + } +} + +//========================================================================= + +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() +{ + const LLUUID& current_outfit_cat = getCOF(); + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + // Can't search on FT_OUTFIT since links to categories return FT_CATEGORY for type since they don't + // return preferred type. + LLIsType is_category( LLAssetType::AT_CATEGORY ); + gInventory.collectDescendentsIf(current_outfit_cat, + cat_array, + item_array, + false, + is_category); + for (LLInventoryModel::item_array_t::const_iterator iter = item_array.begin(); + iter != item_array.end(); + iter++) + { + const LLViewerInventoryItem *item = (*iter); + const LLViewerInventoryCategory *cat = item->getLinkedCategory(); + if (cat && cat->getPreferredType() == LLFolderType::FT_OUTFIT) + { + const LLUUID parent_id = cat->getParentUUID(); + LLViewerInventoryCategory* parent_cat = gInventory.getCategory(parent_id); + // if base outfit moved to trash it means that we don't have base outfit + if (parent_cat != NULL && parent_cat->getPreferredType() == LLFolderType::FT_TRASH) + { + return NULL; + } + return item; + } + } + return NULL; +} + +bool LLAppearanceMgr::getBaseOutfitName(std::string& name) +{ + const LLViewerInventoryItem* outfit_link = getBaseOutfitLink(); + if(outfit_link) + { + const LLViewerInventoryCategory *cat = outfit_link->getLinkedCategory(); + if (cat) + { + name = cat->getName(); + return true; + } + } + return false; +} + +const LLUUID LLAppearanceMgr::getBaseOutfitUUID() +{ + const LLViewerInventoryItem* outfit_link = getBaseOutfitLink(); + if (!outfit_link || !outfit_link->getIsLinkType()) return LLUUID::null; + + const LLViewerInventoryCategory* outfit_cat = outfit_link->getLinkedCategory(); + if (!outfit_cat) return LLUUID::null; + + if (outfit_cat->getPreferredType() != LLFolderType::FT_OUTFIT) + { + 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(); +} + +void wear_on_avatar_cb(const LLUUID& inv_item, bool do_replace = false) +{ + if (inv_item.isNull()) + return; + + LLViewerInventoryItem *item = gInventory.getItem(inv_item); + if (item) + { + LLAppearanceMgr::instance().wearItemOnAvatar(inv_item, true, do_replace); + } +} + +bool LLAppearanceMgr::wearItemOnAvatar(const LLUUID& item_id_to_wear, + bool do_update, + bool replace, + LLPointer<LLInventoryCallback> cb) +{ + + 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 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); + 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 (isLinkedInCOF(item_to_wear->getUUID())) // EXT-84911 + { + return false; + } + + switch (item_to_wear->getType()) + { + case LLAssetType::AT_CLOTHING: + if (gAgentWearables.areWearablesLoaded()) + { + if (!cb && do_update) + { + cb = new LLUpdateAppearanceAndEditWearableOnDestroy(item_id_to_wear); + } + S32 wearable_count = gAgentWearables.getWearableCount(item_to_wear->getWearableType()); + if ((replace && wearable_count != 0) || + (wearable_count >= LLAgentWearables::MAX_CLOTHING_PER_TYPE) ) + { + LLUUID item_id = gAgentWearables.getWearableItemID(item_to_wear->getWearableType(), + wearable_count-1); + removeCOFItemLinks(item_id, cb); + } + + addCOFItemLink(item_to_wear, 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()); + if (!cb && do_update) + { + cb = new LLUpdateAppearanceAndEditWearableOnDestroy(item_id_to_wear); + } + addCOFItemLink(item_to_wear, cb); + break; + + case LLAssetType::AT_OBJECT: + rez_attachment(item_to_wear, NULL, replace); + break; + + default: return false;; + } + + return true; +} + +// Update appearance from outfit folder. +void LLAppearanceMgr::changeOutfit(bool proceed, const LLUUID& category, bool append) +{ + if (!proceed) + return; + LLAppearanceMgr::instance().updateCOF(category,append); +} + +void LLAppearanceMgr::replaceCurrentOutfit(const LLUUID& new_outfit) +{ + LLViewerInventoryCategory* cat = gInventory.getCategory(new_outfit); + wearInventoryCategory(cat, false, false); +} + +// Open outfit renaming dialog. +void LLAppearanceMgr::renameOutfit(const LLUUID& outfit_id) +{ + LLViewerInventoryCategory* cat = gInventory.getCategory(outfit_id); + if (!cat) + { + return; + } + + LLSD args; + args["NAME"] = cat->getName(); + + LLSD payload; + payload["cat_id"] = outfit_id; + + LLNotificationsUtil::add("RenameOutfit", args, payload, boost::bind(onOutfitRename, _1, _2)); +} + +// User typed new outfit name. +// static +void LLAppearanceMgr::onOutfitRename(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return; // canceled + + std::string outfit_name = response["new_name"].asString(); + LLStringUtil::trim(outfit_name); + if (!outfit_name.empty()) + { + LLUUID cat_id = notification["payload"]["cat_id"].asUUID(); + rename_category(&gInventory, cat_id, outfit_name); + } +} + +void LLAppearanceMgr::setOutfitLocked(bool locked) +{ + if (mOutfitLocked == locked) + { + return; + } + + mOutfitLocked = locked; + if (locked) + { + mUnlockOutfitTimer->reset(); + mUnlockOutfitTimer->start(); + } + else + { + mUnlockOutfitTimer->stop(); + } + + LLOutfitObserver::instance().notifyOutfitLockChanged(); +} + +void LLAppearanceMgr::addCategoryToCurrentOutfit(const LLUUID& cat_id) +{ + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + wearInventoryCategory(cat, false, true); +} + +void LLAppearanceMgr::takeOffOutfit(const LLUUID& cat_id) +{ + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindWearablesEx collector(/*is_worn=*/ true, /*include_body_parts=*/ false); + + gInventory.collectDescendentsIf(cat_id, cats, items, FALSE, collector); + + 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; + 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() ); + } + } +} + +// Create a copy of src_id + contents as a subfolder of dst_id. +void LLAppearanceMgr::shallowCopyCategory(const LLUUID& src_id, const LLUUID& dst_id, + LLPointer<LLInventoryCallback> cb) +{ + LLInventoryCategory *src_cat = gInventory.getCategory(src_id); + if (!src_cat) + { + LL_WARNS() << "folder not found for src " << src_id.asString() << LL_ENDL; + return; + } + 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()) + { + parent_id = gInventory.getRootFolderID(); + } + LLUUID subfolder_id = gInventory.createNewCategory( parent_id, + LLFolderType::FT_NONE, + src_cat->getName()); + shallowCopyCategoryContents(src_id, subfolder_id, cb); + + 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) +{ + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(src_id, cats, items); + 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) + { + const LLViewerInventoryItem* item = (*iter); + switch (item->getActualType()) + { + case LLAssetType::AT_LINK: + { + LL_DEBUGS("Avatar") << "linking inventory item " << item->getName() << LL_ENDL; + link_array.push_back(LLConstPointer<LLInventoryObject>(item)); + break; + } + case LLAssetType::AT_LINK_FOLDER: + { + LLViewerInventoryCategory *catp = item->getLinkedCategory(); + // Skip copying outfit links. + if (catp && catp->getPreferredType() != LLFolderType::FT_OUTFIT) + { + LL_DEBUGS("Avatar") << "linking inventory folder " << item->getName() << LL_ENDL; + link_array.push_back(LLConstPointer<LLInventoryObject>(item)); + } + break; + } + case LLAssetType::AT_CLOTHING: + case LLAssetType::AT_OBJECT: + case LLAssetType::AT_BODYPART: + case LLAssetType::AT_GESTURE: + { + LL_DEBUGS("Avatar") << "copying inventory item " << item->getName() << LL_ENDL; + copy_inventory_item(gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + dst_id, + item->getName(), + cb); + break; + } + default: + // Ignore non-outfit asset types + break; + } + } + if (!link_array.empty()) + { + link_inventory_array(dst_id, link_array, cb); + } +} + +BOOL LLAppearanceMgr::getCanMakeFolderIntoOutfit(const LLUUID& folder_id) +{ + // These are the wearable items that are required for considering this + // folder as containing a complete outfit. + U32 required_wearables = 0; + required_wearables |= 1LL << LLWearableType::WT_SHAPE; + required_wearables |= 1LL << LLWearableType::WT_SKIN; + required_wearables |= 1LL << LLWearableType::WT_HAIR; + required_wearables |= 1LL << LLWearableType::WT_EYES; + + // These are the wearables that the folder actually contains. + U32 folder_wearables = 0; + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(folder_id, cats, items); + for (LLInventoryModel::item_array_t::const_iterator iter = items->begin(); + iter != items->end(); + ++iter) + { + const LLViewerInventoryItem* item = (*iter); + if (item->isWearableType()) + { + const LLWearableType::EType wearable_type = item->getWearableType(); + folder_wearables |= 1LL << wearable_type; + } + } + + // If the folder contains the required wearables, return TRUE. + return ((required_wearables & folder_wearables) == required_wearables); +} + +bool LLAppearanceMgr::getCanRemoveOutfit(const LLUUID& outfit_cat_id) +{ + // Disallow removing the base outfit. + if (outfit_cat_id == getBaseOutfitUUID()) + { + return false; + } + + // Check if the outfit folder itself is removable. + if (!get_is_category_removable(&gInventory, outfit_cat_id)) + { + return false; + } + + // Check for the folder's non-removable descendants. + LLFindNonRemovableObjects filter_non_removable; + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLInventoryModel::item_array_t::const_iterator it; + gInventory.collectDescendentsIf(outfit_cat_id, cats, items, false, filter_non_removable); + if (!cats.empty() || !items.empty()) + { + return false; + } + + return true; +} + +// static +bool LLAppearanceMgr::getCanRemoveFromCOF(const LLUUID& outfit_cat_id) +{ + if (gAgentWearables.isCOFChangeInProgress()) + { + return false; + } + + LLFindWearablesEx is_worn(/*is_worn=*/ true, /*include_body_parts=*/ false); + return gInventory.hasMatchingDirectDescendent(outfit_cat_id, is_worn); +} + +// static +bool LLAppearanceMgr::getCanAddToCOF(const LLUUID& outfit_cat_id) +{ + if (gAgentWearables.isCOFChangeInProgress()) + { + return false; + } + + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false); + gInventory.collectDescendentsIf(outfit_cat_id, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + not_worn); + + return items.size() > 0; +} + +bool LLAppearanceMgr::getCanReplaceCOF(const LLUUID& outfit_cat_id) +{ + // Don't allow wearing anything while we're changing appearance. + if (gAgentWearables.isCOFChangeInProgress()) + { + return false; + } + + // Check whether it's the base outfit. + if (outfit_cat_id.isNull() || outfit_cat_id == getBaseOutfitUUID()) + { + return false; + } + + // Check whether the outfit contains any wearables we aren't wearing already (STORM-702). + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindWearablesEx is_worn(/*is_worn=*/ false, /*include_body_parts=*/ true); + gInventory.collectDescendentsIf(outfit_cat_id, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + is_worn); + + return items.size() > 0; +} + +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.size(); ++i) + { + LLViewerInventoryItem *item = items.at(i); + if (item->getActualType() != LLAssetType::AT_LINK_FOLDER) + continue; + LLViewerInventoryCategory* catp = item->getLinkedCategory(); + if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) + { + remove_inventory_item(item->getUUID(), cb); + } + } +} + +// 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) +{ + // 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); + 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(); + + // Deactivate currently active gestures in the COF, if replacing outfit + if (!append) + { + LLInventoryModel::item_array_t gest_items; + getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE); + for(S32 i = 0; i < gest_items.size(); ++i) + { + LLViewerInventoryItem *gest_item = gest_items.at(i); + if ( LLGestureMgr::instance().isGestureActive( gest_item->getLinkedUUID()) ) + { + LLGestureMgr::instance().deactivateGesture( gest_item->getLinkedUUID() ); + } + } + } + + // Collect and filter descendents to determine new COF contents. + + // - Body parts: always include COF contents as a fallback in case any + // required parts are missing. + // Preserve body parts from COF if appending. + LLInventoryModel::item_array_t body_items; + 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); + + // - Wearables: include COF contents only if appending. + LLInventoryModel::item_array_t wear_items; + if (append) + 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); + + // - Attachments: include COF contents only if appending. + LLInventoryModel::item_array_t obj_items; + if (append) + 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); + getDescendentsOfAssetType(category, gest_items, LLAssetType::AT_GESTURE); + removeDuplicateItems(gest_items); + + // Create links to new COF contents. + 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(); + } + + 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")) + { + dump_sequential_xml(gAgentAvatarp->getFullname() + "_slam_request", contents); + } + 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 *>(LLFloaterSidePanelContainer::getPanel("appearance")); + if (panel_appearance) + { + panel_appearance->refreshCurrentOutfitName(name); + } +} + +void LLAppearanceMgr::createBaseOutfitLink(const LLUUID& category, LLPointer<LLInventoryCallback> link_waiter) +{ + const LLUUID cof = getCOF(); + LLViewerInventoryCategory* catp = gInventory.getCategory(category); + std::string new_outfit_name = ""; + + purgeBaseOutfitLink(cof, link_waiter); + + if (catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) + { + link_inventory_object(cof, catp, link_waiter); + new_outfit_name = catp->getName(); + } + + updatePanelOutfitName(new_outfit_name); +} + +void LLAppearanceMgr::updateAgentWearables(LLWearableHoldingPattern* holder) +{ + LL_DEBUGS("Avatar") << "updateAgentWearables()" << LL_ENDL; + LLInventoryItem::item_array_t items; + 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++ ) + { + for (LLWearableHoldingPattern::found_list_t::iterator iter = holder->getFoundList().begin(); + iter != holder->getFoundList().end(); ++iter) + { + LLFoundData& data = *iter; + LLViewerWearable* wearable = data.mWearable; + if( wearable && ((S32)wearable->getType() == i) ) + { + LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(data.mItemID); + if( item && (item->getAssetUUID() == wearable->getAssetID()) ) + { + items.push_back(item); + wearables.push_back(wearable); + } + } + } + } + + if(wearables.size() > 0) + { + gAgentWearables.setWearableOutfit(items, wearables); + } +} + +S32 LLAppearanceMgr::countActiveHoldingPatterns() +{ + return LLWearableHoldingPattern::countActive(); +} + +static void remove_non_link_items(LLInventoryModel::item_array_t &items) +{ + LLInventoryModel::item_array_t pruned_items; + for (LLInventoryModel::item_array_t::const_iterator iter = items.begin(); + iter != items.end(); + ++iter) + { + const LLViewerInventoryItem *item = (*iter); + if (item && item->getIsLinkType()) + { + pruned_items.push_back((*iter)); + } + } + items = pruned_items; +} + +//a predicate for sorting inventory items by actual descriptions +bool sort_by_actual_description(const LLInventoryItem* item1, const LLInventoryItem* item2) +{ + if (!item1 || !item2) + { + LL_WARNS() << "either item1 or item2 is NULL" << LL_ENDL; + return true; + } + + return item1->getActualDescription() < item2->getActualDescription(); +} + +void item_array_diff(LLInventoryModel::item_array_t& full_list, + LLInventoryModel::item_array_t& keep_list, + LLInventoryModel::item_array_t& kill_list) + +{ + for (LLInventoryModel::item_array_t::iterator it = full_list.begin(); + it != full_list.end(); + ++it) + { + LLViewerInventoryItem *item = *it; + if (std::find(keep_list.begin(), keep_list.end(), item) == keep_list.end()) + { + kill_list.push_back(item); + } + } +} + +S32 LLAppearanceMgr::findExcessOrDuplicateItems(const LLUUID& cat_id, + LLAssetType::EType type, + S32 max_items, + LLInventoryObject::object_list_t& items_to_kill) +{ + S32 to_kill_count = 0; + + LLInventoryModel::item_array_t items; + getDescendentsOfAssetType(cat_id, items, type); + LLInventoryModel::item_array_t curr_items = items; + removeDuplicateItems(items); + if (max_items > 0) + { + filterWearableItems(items, max_items); + } + LLInventoryModel::item_array_t kill_items; + item_array_diff(curr_items,items,kill_items); + for (LLInventoryModel::item_array_t::iterator it = kill_items.begin(); + it != kill_items.end(); + ++it) + { + items_to_kill.push_back(LLPointer<LLInventoryObject>(*it)); + to_kill_count++; + } + return to_kill_count; +} + + +void LLAppearanceMgr::findAllExcessOrDuplicateItems(const LLUUID& cat_id, + LLInventoryObject::object_list_t& items_to_kill) +{ + findExcessOrDuplicateItems(cat_id,LLAssetType::AT_BODYPART, + 1, items_to_kill); + findExcessOrDuplicateItems(cat_id,LLAssetType::AT_CLOTHING, + LLAgentWearables::MAX_CLOTHING_PER_TYPE, items_to_kill); + findExcessOrDuplicateItems(cat_id,LLAssetType::AT_OBJECT, + -1, 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) + { + // 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 enforce_item_restrictions, + bool enforce_ordering, + nullary_func_t post_update_func) +{ + if (mIsInUpdateAppearanceFromCOF) + { + LL_WARNS() << "Called updateAppearanceFromCOF inside updateAppearanceFromCOF, skipping" << LL_ENDL; + return; + } + + LL_DEBUGS("Avatar") << self_av_string() << "starting" << LL_ENDL; + + 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; + } + + 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"); + + // update dirty flag to see if the state of the COF matches + // the saved outfit stored as a folder link + updateIsDirty(); + + // Send server request for appearance update + if (gAgent.getRegion() && gAgent.getRegion()->getCentralBakeVersion()) + { + requestServerAppearanceUpdate(); + } + + LLUUID current_outfit_id = getCOF(); + + // Find all the wearables that are in the COF's subtree. + 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); + // Get rid of non-links in case somehow the COF was corrupted. + remove_non_link_items(wear_items); + remove_non_link_items(obj_items); + remove_non_link_items(gest_items); + + dumpItemArray(wear_items,"asset_dump: wear_item"); + dumpItemArray(obj_items,"asset_dump: obj_item"); + + 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; + } + + //preparing the list of wearables in the correct order for LLAgentWearables + sortItemsByActualDescription(wear_items); + + + LL_DEBUGS("Avatar") << "HP block starts" << LL_ENDL; + LLTimer hp_block_timer; + LLWearableHoldingPattern* holder = new LLWearableHoldingPattern; + + holder->setObjItems(obj_items); + holder->setGestItems(gest_items); + + // Note: can't do normal iteration, because if all the + // wearables can be resolved immediately, then the + // callback will be called (and this object deleted) + // before the final getNextData(). + + for(S32 i = 0; i < wear_items.size(); ++i) + { + LLViewerInventoryItem *item = wear_items.at(i); + LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL; + + // Fault injection: use debug setting to test asset + // fetch failures (should be replaced by new defaults in + // lost&found). + U32 skip_type = gSavedSettings.getU32("ForceAssetFail"); + + if (item && item->getIsLinkType() && linked_item) + { + LLFoundData found(linked_item->getUUID(), + linked_item->getAssetUUID(), + linked_item->getName(), + linked_item->getType(), + linked_item->isWearableType() ? linked_item->getWearableType() : LLWearableType::WT_INVALID + ); + + if (skip_type != LLWearableType::WT_INVALID && skip_type == found.mWearableType) + { + found.mAssetID.generate(); // Replace with new UUID, guaranteed not to exist in DB + } + //pushing back, not front, to preserve order of wearables for LLAgentWearables + holder->getFoundList().push_back(found); + } + else + { + if (!item) + { + LL_WARNS() << "Attempt to wear a null item " << LL_ENDL; + } + else if (!linked_item) + { + 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; + + 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); + + } + + holder->resetTime(gSavedSettings.getF32("MaxWearableWaitTime")); + if (!holder->pollFetchCompletion()) + { + 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) +{ + LLInventoryModel::cat_array_t cats; + LLIsType is_of_type(type); + gInventory.collectDescendentsIf(category, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + 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) +{ + LLInventoryModel::cat_array_t wear_cats; + LLFindWearables is_wearable; + gInventory.collectDescendentsIf(category, + wear_cats, + wear_items, + LLInventoryModel::EXCLUDE_TRASH, + is_wearable); + + LLInventoryModel::cat_array_t obj_cats; + LLIsType is_object( LLAssetType::AT_OBJECT ); + gInventory.collectDescendentsIf(category, + obj_cats, + obj_items, + LLInventoryModel::EXCLUDE_TRASH, + is_object); + + // Find all gestures in this folder + LLInventoryModel::cat_array_t gest_cats; + LLIsType is_gesture( LLAssetType::AT_GESTURE ); + gInventory.collectDescendentsIf(category, + gest_cats, + gest_items, + LLInventoryModel::EXCLUDE_TRASH, + is_gesture); +} + +void LLAppearanceMgr::wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append) +{ + if(!category) return; + + selfClearPhases(); + selfStartPhase("wear_inventory_category"); + + gAgentWearables.notifyLoadingStarted(); + + LL_INFOS("Avatar") << self_av_string() << "wearInventoryCategory( " << category->getName() + << " )" << LL_ENDL; + + // 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) +{ + 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. + LLInventoryCategory* cat = gInventory.getCategory(cat_id); + if(copy_items) + { + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(cat_id, cats, items); + std::string name; + if(!cat) + { + // should never happen. + name = "New Outfit"; + } + else + { + name = cat->getName(); + } + LLViewerInventoryItem* item = NULL; + LLInventoryModel::item_array_t::const_iterator it = items->begin(); + LLInventoryModel::item_array_t::const_iterator end = items->end(); + LLUUID pid; + for(; it < end; ++it) + { + item = *it; + if(item) + { + if(LLInventoryType::IT_GESTURE == item->getInventoryType()) + { + pid = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); + } + else + { + pid = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING); + } + break; + } + } + if(pid.isNull()) + { + pid = gInventory.getRootFolderID(); + } + + LLUUID new_cat_id = gInventory.createNewCategory( + pid, + LLFolderType::FT_NONE, + name); + + // 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(); + } + else + { + // Wear the inventory category. + LLAppearanceMgr::instance().wearInventoryCategoryOnAvatar(cat, append); + } +} + +// *NOTE: hack to get from avatar inventory to avatar +void LLAppearanceMgr::wearInventoryCategoryOnAvatar( LLInventoryCategory* category, bool append ) +{ + // Avoid unintentionally overwriting old wearables. We have to do + // this up front to avoid having to deal with the case of multiple + // wearables being dirty. + if (!category) return; + + if ( !LLInventoryCallbackManager::is_instantiated() ) + { + // shutting down, ignore. + return; + } + + 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 + 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) +{ + LL_INFOS("Avatar") << self_av_string() << "Wearing category " << name << LL_ENDL; + + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + LLNameCategoryCollector has_name(name); + gInventory.collectDescendentsIf(gInventory.getRootFolderID(), + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + has_name); + bool copy_items = false; + LLInventoryCategory* cat = NULL; + if (cat_array.size() > 0) + { + // Just wear the first one that matches + cat = cat_array.at(0); + } + else + { + gInventory.collectDescendentsIf(LLUUID::null, + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + has_name); + if(cat_array.size() > 0) + { + cat = cat_array.at(0); + copy_items = true; + } + } + + if(cat) + { + LLAppearanceMgr::wearInventoryCategory(cat, copy_items, false); + } + else + { + LL_WARNS() << "Couldn't find outfit " <<name<< " in wearOutfitByName()" + << LL_ENDL; + } +} + +bool areMatchingWearables(const LLViewerInventoryItem *a, const LLViewerInventoryItem *b) +{ + return (a->isWearableType() && b->isWearableType() && + (a->getWearableType() == b->getWearableType())); +} + +class LLDeferredCOFLinkObserver: public LLInventoryObserver +{ +public: + LLDeferredCOFLinkObserver(const LLUUID& item_id, LLPointer<LLInventoryCallback> cb, const std::string& description): + mItemID(item_id), + mCallback(cb), + mDescription(description) + { + } + + ~LLDeferredCOFLinkObserver() + { + } + + /* virtual */ void changed(U32 mask) + { + const LLInventoryItem *item = gInventory.getItem(mItemID); + if (item) + { + gInventory.removeObserver(this); + LLAppearanceMgr::instance().addCOFItemLink(item, mCallback, mDescription); + delete this; + } + } + +private: + const LLUUID mItemID; + 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, + LLPointer<LLInventoryCallback> cb, + const std::string description) +{ + const LLInventoryItem *item = gInventory.getItem(item_id); + if (!item) + { + LLDeferredCOFLinkObserver *observer = new LLDeferredCOFLinkObserver(item_id, cb, description); + gInventory.addObserver(observer); + } + else + { + addCOFItemLink(item, cb, description); + } +} + +void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item, + LLPointer<LLInventoryCallback> cb, + const std::string description) +{ + const LLViewerInventoryItem *vitem = dynamic_cast<const LLViewerInventoryItem*>(item); + if (!vitem) + { + LL_WARNS() << "not an llviewerinventoryitem, failed" << LL_ENDL; + return; + } + + gInventory.addChangedMask(LLInventoryObserver::LABEL, vitem->getLinkedUUID()); + + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendents(LLAppearanceMgr::getCOF(), + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH); + bool linked_already = false; + U32 count = 0; + for (S32 i=0; i<item_array.size(); i++) + { + // Are these links to the same object? + 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) + || (wearable_type == LLWearableType::WT_HAIR) + || (wearable_type == LLWearableType::WT_EYES) + || (wearable_type == LLWearableType::WT_SKIN); + + if (inv_item->getLinkedUUID() == vitem->getLinkedUUID()) + { + linked_already = true; + } + // Are these links to different items of the same body part + // 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)) + { + remove_inventory_item(inv_item->getUUID(), cb); + } + else if (count >= LLAgentWearables::MAX_CLOTHING_PER_TYPE) + { + // MULTI-WEARABLES: make sure we don't go over MAX_CLOTHING_PER_TYPE + remove_inventory_item(inv_item->getUUID(), cb); + } + } + } + + if (!linked_already) + { + LLViewerInventoryItem *copy_item = new LLViewerInventoryItem; + copy_item->copyViewerItem(vitem); + copy_item->setDescription(description); + link_inventory_object(getCOF(), copy_item, cb); + } +} + +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) + { + 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++) + { + const LLViewerInventoryItem* inv_item = item_array.at(i).get(); + if (inv_item->getLinkedUUID() == vitem->getLinkedUUID()) + { + result.push_back(item_array.at(i)); + } + } + } + return result; +} + +bool LLAppearanceMgr::isLinkedInCOF(const LLUUID& item_id) +{ + LLInventoryModel::item_array_t links = LLAppearanceMgr::instance().findCOFItemLinks(item_id); + return links.size() > 0; +} + +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); + + 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++) + { + const LLInventoryItem* item = item_array.at(i).get(); + if (item->getIsLinkType() && item->getLinkedUUID() == item_id) + { + bool immediate_delete = false; + if (item->getType() == LLAssetType::AT_OBJECT) + { + immediate_delete = true; + } + remove_inventory_item(item->getUUID(), cb, immediate_delete); + } + } +} + +void LLAppearanceMgr::removeCOFLinksOfType(LLWearableType::EType type, LLPointer<LLInventoryCallback> cb) +{ + LLFindWearablesOfType filter_wearables_of_type(type); + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLInventoryModel::item_array_t::const_iterator it; + + gInventory.collectDescendentsIf(getCOF(), cats, items, true, filter_wearables_of_type); + for (it = items.begin(); it != items.end(); ++it) + { + const LLViewerInventoryItem* item = *it; + if (item->getIsLinkType()) // we must operate on links only + { + remove_inventory_item(item->getUUID(), cb); + } + } +} + +bool sort_by_linked_uuid(const LLViewerInventoryItem* item1, const LLViewerInventoryItem* item2) +{ + if (!item1 || !item2) + { + LL_WARNS() << "item1, item2 cannot be null, something is very wrong" << LL_ENDL; + return true; + } + + return item1->getLinkedUUID() < item2->getLinkedUUID(); +} + +void LLAppearanceMgr::updateIsDirty() +{ + LLUUID cof = getCOF(); + LLUUID base_outfit; + + // find base outfit link + const LLViewerInventoryItem* base_outfit_item = getBaseOutfitLink(); + LLViewerInventoryCategory* catp = NULL; + if (base_outfit_item && base_outfit_item->getIsLinkType()) + { + catp = base_outfit_item->getLinkedCategory(); + } + if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) + { + base_outfit = catp->getUUID(); + } + + // Set dirty to "false" if no base outfit found to disable "Save" + // and leave only "Save As" enabled in My Outfits. + mOutfitIsDirty = false; + + if (base_outfit.notNull()) + { + LLIsValidItemLink collector; + + LLInventoryModel::cat_array_t cof_cats; + LLInventoryModel::item_array_t cof_items; + gInventory.collectDescendentsIf(cof, cof_cats, cof_items, + LLInventoryModel::EXCLUDE_TRASH, collector); + + LLInventoryModel::cat_array_t outfit_cats; + LLInventoryModel::item_array_t outfit_items; + gInventory.collectDescendentsIf(base_outfit, outfit_cats, outfit_items, + LLInventoryModel::EXCLUDE_TRASH, collector); + + 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; + return; + } + + //"dirty" - also means a difference in linked UUIDs and/or a difference in wearables order (links' descriptions) + std::sort(cof_items.begin(), cof_items.end(), sort_by_linked_uuid); + std::sort(outfit_items.begin(), outfit_items.end(), sort_by_linked_uuid); + + for (U32 i = 0; i < cof_items.size(); ++i) + { + LLViewerInventoryItem *item1 = cof_items.at(i); + LLViewerInventoryItem *item2 = outfit_items.at(i); + + if (item1->getLinkedUUID() != item2->getLinkedUUID() || + item1->getName() != item2->getName() || + 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; +} + +// *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() +{ + 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); + + 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); + + for(std::vector<std::string>::iterator it = gesture_folders_to_copy.begin(); + it != gesture_folders_to_copy.end(); + ++it) + { + 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)); + } + } +} + +// Handler for anything that's deferred until avatar de-clouds. +void LLAppearanceMgr::onFirstFullyVisible() +{ + gAgentAvatarp->outputRezTiming("Avatar fully loaded"); + gAgentAvatarp->reportAvatarRezTime(); + gAgentAvatarp->debugAvatarVisible(); + + // 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()) + { + // don't allow modify locked outfit + 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; + + 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; +} + +void LLAppearanceMgr::divvyWearablesByType(const LLInventoryModel::item_array_t& items, wearables_by_type_t& items_by_type) +{ + items_by_type.resize(LLWearableType::WT_COUNT); + if (items.empty()) return; + + for (S32 i=0; i<items.size(); i++) + { + LLViewerInventoryItem *item = items.at(i); + if (!item) + { + LL_WARNS("Appearance") << "NULL item found" << LL_ENDL; + continue; + } + // Ignore non-wearables. + if (!item->isWearableType()) + continue; + LLWearableType::EType type = item->getWearableType(); + if(type < 0 || type >= LLWearableType::WT_COUNT) + { + LL_WARNS("Appearance") << "Invalid wearable type. Inventory type does not match wearable flag bitfield." << LL_ENDL; + continue; + } + items_by_type[type].push_back(item); + } +} + +std::string build_order_string(LLWearableType::EType type, U32 i) +{ + std::ostringstream order_num; + order_num << ORDER_NUMBER_SEPARATOR << type * 100 + i; + return order_num.str(); +} + +struct WearablesOrderComparator +{ + LOG_CLASS(WearablesOrderComparator); + WearablesOrderComparator(const LLWearableType::EType type) + { + mControlSize = build_order_string(type, 0).size(); + }; + + bool operator()(const LLInventoryItem* item1, const LLInventoryItem* item2) + { + 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]); + + if (item1_valid && item2_valid) + return desc1 < desc2; + + //we need to sink down invalid items: items with empty descriptions, items with "Broken link" descriptions, + //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 item1->getName() < item2->getName(); + } + + U32 mControlSize; +}; + +void LLAppearanceMgr::getWearableOrderingDescUpdates(LLInventoryModel::item_array_t& wear_items, + desc_map_t& desc_map) +{ + wearables_by_type_t items_by_type(LLWearableType::WT_COUNT); + divvyWearablesByType(wear_items, items_by_type); + + 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->getActualDescription()) continue; + + desc_map[item->getUUID()] = new_order_str; + } + } +} + +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); + } + +} + + +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() +{ + + if (!testCOFRequestVersion()) + { + // *TODO: LL_LOG message here + return; + } + + if ((mInFlightCounter > 0) && (mInFlightTimer.hasExpired())) + { + LL_WARNS("Avatar") << "in flight timer expired, resetting " << LL_ENDL; + mInFlightCounter = 0; + } + + if (gAgentAvatarp->isEditingAppearance()) + { + LL_WARNS("Avatar") << "Avatar editing appeance, not sending request." << LL_ENDL; + // don't send out appearance updates if in appearance editing mode + return; + } + + if (!gAgent.getRegion()) + { + LL_WARNS("Avatar") << "Region not set, cannot request server appearance update" << LL_ENDL; + return; + } + if (gAgent.getRegion()->getCentralBakeVersion() == 0) + { + LL_WARNS("Avatar") << "Region does not support baking" << LL_ENDL; + } + std::string url = gAgent.getRegion()->getCapability("UpdateAvatarAppearance"); + if (url.empty()) + { + LL_WARNS("Avatar") << "No cap for UpdateAvatarAppearance." << LL_ENDL; + return; + } + + LLSD postData; + S32 cof_version = LLAppearanceMgr::instance().getCOFVersion(); + if (gSavedSettings.getBOOL("DebugAvatarExperimentalServerAppearanceUpdate")) + { + postData = LLAppearanceMgr::instance().dumpCOF(); + } + else + { + postData["cof_version"] = cof_version; + if (gSavedSettings.getBOOL("DebugForceAppearanceRequestFailure")) + { + postData["cof_version"] = cof_version + 999; + } + } + LL_DEBUGS("Avatar") << "request url " << url << " my_cof_version " << cof_version << LL_ENDL; + + LLAppearanceMgrHttpHandler * handler = new LLAppearanceMgrHttpHandler(url, this); + + mInFlightCounter++; + mInFlightTimer.setTimerExpirySec(60.0); + + llassert(cof_version >= gAgentAvatarp->mLastUpdateRequestCOFVersion); + gAgentAvatarp->mLastUpdateRequestCOFVersion = cof_version; + + LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, + mHttpPolicy, mHttpPriority, url, + postData, mHttpOptions, mHttpHeaders, handler); + + if (handle == LLCORE_HTTP_HANDLE_INVALID) + { delete handler; LLCore::HttpStatus status = mHttpRequest->getStatus(); - LL_WARNS("Avatar") << "Appearance request post failed Reason " << status.toTerseString()
- << " \"" << status.toString() << "\"" << LL_ENDL;
- }
-}
-
-bool LLAppearanceMgr::testCOFRequestVersion() const
-{
- // If we have already received an update for this or higher cof version, ignore.
- S32 cof_version = 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 (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 false;
- }
- 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 false;
- }
-
- // Actually send the request.
- LL_DEBUGS("Avatar") << "Will send request for cof_version " << cof_version << LL_ENDL;
- return true;
-}
-
+ LL_WARNS("Avatar") << "Appearance request post failed Reason " << status.toTerseString() + << " \"" << status.toString() << "\"" << LL_ENDL; + } +} + +bool LLAppearanceMgr::testCOFRequestVersion() const +{ + // If we have already received an update for this or higher cof version, ignore. + S32 cof_version = 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 (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 false; + } + 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 false; + } + + // Actually send the request. + LL_DEBUGS("Avatar") << "Will send request for cof_version " << cof_version << LL_ENDL; + return true; +} + bool LLAppearanceMgr::onIdle() { if (!LLAppearanceMgr::mActive) @@ -3492,566 +3492,566 @@ bool LLAppearanceMgr::onIdle() mHttpRequest->update(0L); return false; } -
-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()
-{
- const LLUUID& base_outfit_id = getBaseOutfitUUID();
- if (base_outfit_id.isNull()) return;
-
- 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)
-{
- LLUUID linked_item_id = gInventory.getLinkedItemID(id_to_remove);
- LLPointer<LLInventoryCallback> cb = new LLUpdateAppearanceOnDestroy;
- removeCOFItemLinks(linked_item_id, cb);
- addDoomedTempAttachment(linked_item_id);
-}
-
-
-// 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;
- }
-}
-
-// 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;
- if (item->getType() != LLAssetType::AT_CLOTHING) return false;
- if (!gInventory.isObjectDescendentOf(item->getUUID(), getCOF())) return false;
-
- LLInventoryModel::cat_array_t cats;
- LLInventoryModel::item_array_t items;
- LLFindWearablesOfType filter_wearables_of_type(item->getWearableType());
- gInventory.collectDescendentsIf(getCOF(), cats, items, true, filter_wearables_of_type);
- if (items.empty()) return false;
-
- // We assume that the items have valid descriptions.
- std::sort(items.begin(), items.end(), WearablesOrderComparator(item->getWearableType()));
-
- if (closer_to_body && items.front() == item) return false;
- if (!closer_to_body && items.back() == item) return false;
-
- LLInventoryModel::item_array_t::iterator it = std::find(items.begin(), items.end(), item);
- if (items.end() == it) return false;
-
-
- //swapping descriptions
- closer_to_body ? --it : ++it;
- LLViewerInventoryItem* swap_item = *it;
- if (!swap_item) return false;
- 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);
- gInventory.updateItem(item);
-
- swap_item->setComplete(TRUE);
- swap_item->updateServer(FALSE);
- gInventory.updateItem(swap_item);
-
- //to cause appearance of the agent to be updated
- bool result = false;
- if ((result = gAgentWearables.moveWearable(item, closer_to_body)))
- {
- gAgentAvatarp->wearableUpdated(item->getWearableType());
- }
-
- setOutfitDirty(true);
-
- //*TODO do we need to notify observers here in such a way?
- gInventory.notifyObservers();
-
- return result;
-}
-
-//static
-void LLAppearanceMgr::sortItemsByActualDescription(LLInventoryModel::item_array_t& items)
-{
- if (items.size() < 2) return;
-
- std::sort(items.begin(), items.end(), sort_by_actual_description);
-}
-
-//#define DUMP_CAT_VERBOSE
-
-void LLAppearanceMgr::dumpCat(const LLUUID& cat_id, const std::string& msg)
-{
- LLInventoryModel::cat_array_t cats;
- LLInventoryModel::item_array_t items;
- gInventory.collectDescendents(cat_id, cats, items, LLInventoryModel::EXCLUDE_TRASH);
-
-#ifdef DUMP_CAT_VERBOSE
- LL_INFOS() << LL_ENDL;
- LL_INFOS() << str << LL_ENDL;
- S32 hitcount = 0;
- for(S32 i=0; i<items.size(); i++)
- {
- LLViewerInventoryItem *item = items.get(i);
- if (item)
- hitcount++;
- LL_INFOS() << i <<" "<< item->getName() <<LL_ENDL;
- }
-#endif
- LL_INFOS() << msg << " count " << items.size() << LL_ENDL;
-}
-
-void LLAppearanceMgr::dumpItemArray(const LLInventoryModel::item_array_t& items,
- const std::string& msg)
-{
- for (S32 i=0; i<items.size(); i++)
- {
- LLViewerInventoryItem *item = items.at(i);
- LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL;
- LLUUID asset_id;
- if (linked_item)
- {
- asset_id = linked_item->getAssetUUID();
- }
- LL_DEBUGS("Avatar") << self_av_string() << msg << " " << i <<" " << (item ? item->getName() : "(nullitem)") << " " << asset_id.asString() << LL_ENDL;
- }
-}
-
-bool LLAppearanceMgr::mActive = true;
-
-LLAppearanceMgr::LLAppearanceMgr():
- mAttachmentInvLinkEnabled(false),
- mOutfitIsDirty(false),
- mOutfitLocked(false),
- mInFlightCounter(0),
- mInFlightTimer(),
- mIsInUpdateAppearanceFromCOF(false),
- //mAppearanceResponder(new RequestAgentUpdateAppearanceResponder),
- mHttpRequest(),
- mHttpHeaders(),
- mHttpOptions(),
- mHttpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID),
- mHttpPriority(0)
-{
- LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp());
-
- mHttpRequest = LLCore::HttpRequest::ptr_t(new LLCore::HttpRequest());
- mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders(), false);
- mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions(), false);
- mHttpPolicy = app_core_http.getPolicy(LLAppCoreHttp::AP_AVATAR);
-
- LLOutfitObserver& outfit_observer = LLOutfitObserver::instance();
- // unlock outfit on save operation completed
- outfit_observer.addCOFSavedCallback(boost::bind(
- &LLAppearanceMgr::setOutfitLocked, this, false));
-
- mUnlockOutfitTimer.reset(new LLOutfitUnLockTimer(gSavedSettings.getS32(
- "OutfitOperationsTimeout")));
-
- gIdleCallbacks.addFunction(&LLAttachmentsMgr::onIdle, NULL);
- doOnIdleRepeating(boost::bind(&LLAppearanceMgr::onIdle, this));
-}
-
-LLAppearanceMgr::~LLAppearanceMgr()
-{
- mActive = false;
-}
-
-void LLAppearanceMgr::setAttachmentInvLinkEnable(bool val)
-{
- LL_DEBUGS("Avatar") << "setAttachmentInvLinkEnable => " << (int) val << LL_ENDL;
- mAttachmentInvLinkEnabled = val;
-}
-
-void dumpAttachmentSet(const std::set<LLUUID>& atts, const std::string& msg)
-{
- LL_INFOS() << msg << LL_ENDL;
- for (std::set<LLUUID>::const_iterator it = atts.begin();
- it != atts.end();
- ++it)
- {
- LLUUID item_id = *it;
- LLViewerInventoryItem *item = gInventory.getItem(item_id);
- if (item)
- LL_INFOS() << "atts " << item->getName() << LL_ENDL;
- else
- LL_INFOS() << "atts " << "UNKNOWN[" << item_id.asString() << "]" << LL_ENDL;
- }
- LL_INFOS() << LL_ENDL;
-}
-
-void LLAppearanceMgr::registerAttachment(const LLUUID& item_id)
-{
- gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id);
-
- if (mAttachmentInvLinkEnabled)
- {
- // 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
- if (!isLinkedInCOF(item_id))
- {
- LLPointer<LLInventoryCallback> cb = new LLUpdateAppearanceOnDestroy();
- LLAppearanceMgr::addCOFItemLink(item_id, cb); // Add COF link for item.
- }
- }
- else
- {
- //LL_INFOS() << "no link changes, inv link not enabled" << LL_ENDL;
- }
-}
-
-void LLAppearanceMgr::unregisterAttachment(const LLUUID& item_id)
-{
- gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id);
-
- if (mAttachmentInvLinkEnabled)
- {
- LLAppearanceMgr::removeCOFItemLinks(item_id);
- }
- else
- {
- //LL_INFOS() << "no link changes, inv link not enabled" << LL_ENDL;
- }
-}
-
-BOOL LLAppearanceMgr::getIsInCOF(const LLUUID& obj_id) const
-{
- 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)
-{
- 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
-{
- if (!getIsInCOF(obj_id)) return FALSE;
-
- // If a non-link somehow ended up in COF, allow deletion.
- const LLInventoryObject *obj = gInventory.getObject(obj_id);
- if (obj && !obj->getIsLinkType())
- {
- return FALSE;
- }
-
- // For now, don't allow direct deletion from the COF. Instead, force users
- // to choose "Detach" or "Take Off".
- return TRUE;
-}
-
-class CallAfterCategoryFetchStage2: public LLInventoryFetchItemsObserver
-{
-public:
- CallAfterCategoryFetchStage2(const uuid_vec_t& ids,
- nullary_func_t callable) :
- LLInventoryFetchItemsObserver(ids),
- mCallable(callable)
- {
- }
- ~CallAfterCategoryFetchStage2()
- {
- }
- virtual void done()
- {
- LL_INFOS() << this << " done with incomplete " << mIncomplete.size()
- << " complete " << mComplete.size() << " calling callable" << LL_ENDL;
-
- gInventory.removeObserver(this);
- doOnIdleOneTime(mCallable);
- delete this;
- }
-protected:
- nullary_func_t mCallable;
-};
-
-class CallAfterCategoryFetchStage1: public LLInventoryFetchDescendentsObserver
-{
-public:
- CallAfterCategoryFetchStage1(const LLUUID& cat_id, nullary_func_t callable) :
- LLInventoryFetchDescendentsObserver(cat_id),
- mCallable(callable)
- {
- }
- ~CallAfterCategoryFetchStage1()
- {
- }
- virtual void done()
- {
- // 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.size();
- if(!count)
- {
- LL_WARNS() << "Nothing fetched in category " << mComplete.front()
- << LL_ENDL;
- gInventory.removeObserver(this);
- doOnIdleOneTime(mCallable);
-
- delete this;
- return;
- }
-
- 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.at(i)->getUUID());
- }
-
- gInventory.removeObserver(this);
-
- // do the fetch
- CallAfterCategoryFetchStage2 *stage2 = new CallAfterCategoryFetchStage2(ids, mCallable);
- stage2->startFetch();
- if(stage2->isFinished())
- {
- // everything is already here - call done.
- stage2->done();
- }
- else
- {
- // it's all on it's way - add an observer, and the inventory
- // will call done for us when everything is here.
- gInventory.addObserver(stage2);
- }
- delete this;
- }
-protected:
- nullary_func_t mCallable;
-};
-
-void callAfterCategoryFetch(const LLUUID& cat_id, nullary_func_t cb)
-{
- CallAfterCategoryFetchStage1 *stage1 = new CallAfterCategoryFetchStage1(cat_id, cb);
- stage1->startFetch();
- if (stage1->isFinished())
- {
- stage1->done();
- }
- else
- {
- gInventory.addObserver(stage1);
- }
-}
-
-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;
- }
-}
-
-// SLapp for easy-wearing of a stock (library) avatar
-//
-class LLWearFolderHandler : public LLCommandHandler
-{
-public:
- // not allowed from outside the app
- LLWearFolderHandler() : LLCommandHandler("wear_folder", UNTRUSTED_BLOCK) { }
-
- bool handle(const LLSD& tokens, const LLSD& query_map,
- LLMediaCtrl* web)
- {
- LLSD::UUID folder_uuid;
-
- 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;
- }
-};
-
-LLWearFolderHandler gWearFolderHandler;
+ +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() +{ + const LLUUID& base_outfit_id = getBaseOutfitUUID(); + if (base_outfit_id.isNull()) return; + + 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) +{ + LLUUID linked_item_id = gInventory.getLinkedItemID(id_to_remove); + LLPointer<LLInventoryCallback> cb = new LLUpdateAppearanceOnDestroy; + removeCOFItemLinks(linked_item_id, cb); + addDoomedTempAttachment(linked_item_id); +} + + +// 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; + } +} + +// 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; + if (item->getType() != LLAssetType::AT_CLOTHING) return false; + if (!gInventory.isObjectDescendentOf(item->getUUID(), getCOF())) return false; + + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindWearablesOfType filter_wearables_of_type(item->getWearableType()); + gInventory.collectDescendentsIf(getCOF(), cats, items, true, filter_wearables_of_type); + if (items.empty()) return false; + + // We assume that the items have valid descriptions. + std::sort(items.begin(), items.end(), WearablesOrderComparator(item->getWearableType())); + + if (closer_to_body && items.front() == item) return false; + if (!closer_to_body && items.back() == item) return false; + + LLInventoryModel::item_array_t::iterator it = std::find(items.begin(), items.end(), item); + if (items.end() == it) return false; + + + //swapping descriptions + closer_to_body ? --it : ++it; + LLViewerInventoryItem* swap_item = *it; + if (!swap_item) return false; + 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); + gInventory.updateItem(item); + + swap_item->setComplete(TRUE); + swap_item->updateServer(FALSE); + gInventory.updateItem(swap_item); + + //to cause appearance of the agent to be updated + bool result = false; + if ((result = gAgentWearables.moveWearable(item, closer_to_body))) + { + gAgentAvatarp->wearableUpdated(item->getWearableType()); + } + + setOutfitDirty(true); + + //*TODO do we need to notify observers here in such a way? + gInventory.notifyObservers(); + + return result; +} + +//static +void LLAppearanceMgr::sortItemsByActualDescription(LLInventoryModel::item_array_t& items) +{ + if (items.size() < 2) return; + + std::sort(items.begin(), items.end(), sort_by_actual_description); +} + +//#define DUMP_CAT_VERBOSE + +void LLAppearanceMgr::dumpCat(const LLUUID& cat_id, const std::string& msg) +{ + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + gInventory.collectDescendents(cat_id, cats, items, LLInventoryModel::EXCLUDE_TRASH); + +#ifdef DUMP_CAT_VERBOSE + LL_INFOS() << LL_ENDL; + LL_INFOS() << str << LL_ENDL; + S32 hitcount = 0; + for(S32 i=0; i<items.size(); i++) + { + LLViewerInventoryItem *item = items.get(i); + if (item) + hitcount++; + LL_INFOS() << i <<" "<< item->getName() <<LL_ENDL; + } +#endif + LL_INFOS() << msg << " count " << items.size() << LL_ENDL; +} + +void LLAppearanceMgr::dumpItemArray(const LLInventoryModel::item_array_t& items, + const std::string& msg) +{ + for (S32 i=0; i<items.size(); i++) + { + LLViewerInventoryItem *item = items.at(i); + LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL; + LLUUID asset_id; + if (linked_item) + { + asset_id = linked_item->getAssetUUID(); + } + LL_DEBUGS("Avatar") << self_av_string() << msg << " " << i <<" " << (item ? item->getName() : "(nullitem)") << " " << asset_id.asString() << LL_ENDL; + } +} + +bool LLAppearanceMgr::mActive = true; + +LLAppearanceMgr::LLAppearanceMgr(): + mAttachmentInvLinkEnabled(false), + mOutfitIsDirty(false), + mOutfitLocked(false), + mInFlightCounter(0), + mInFlightTimer(), + mIsInUpdateAppearanceFromCOF(false), + //mAppearanceResponder(new RequestAgentUpdateAppearanceResponder), + mHttpRequest(), + mHttpHeaders(), + mHttpOptions(), + mHttpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mHttpPriority(0) +{ + LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); + + mHttpRequest = LLCore::HttpRequest::ptr_t(new LLCore::HttpRequest()); + mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders(), false); + mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions(), false); + mHttpPolicy = app_core_http.getPolicy(LLAppCoreHttp::AP_AVATAR); + + LLOutfitObserver& outfit_observer = LLOutfitObserver::instance(); + // unlock outfit on save operation completed + outfit_observer.addCOFSavedCallback(boost::bind( + &LLAppearanceMgr::setOutfitLocked, this, false)); + + mUnlockOutfitTimer.reset(new LLOutfitUnLockTimer(gSavedSettings.getS32( + "OutfitOperationsTimeout"))); + + gIdleCallbacks.addFunction(&LLAttachmentsMgr::onIdle, NULL); + doOnIdleRepeating(boost::bind(&LLAppearanceMgr::onIdle, this)); +} + +LLAppearanceMgr::~LLAppearanceMgr() +{ + mActive = false; +} + +void LLAppearanceMgr::setAttachmentInvLinkEnable(bool val) +{ + LL_DEBUGS("Avatar") << "setAttachmentInvLinkEnable => " << (int) val << LL_ENDL; + mAttachmentInvLinkEnabled = val; +} + +void dumpAttachmentSet(const std::set<LLUUID>& atts, const std::string& msg) +{ + LL_INFOS() << msg << LL_ENDL; + for (std::set<LLUUID>::const_iterator it = atts.begin(); + it != atts.end(); + ++it) + { + LLUUID item_id = *it; + LLViewerInventoryItem *item = gInventory.getItem(item_id); + if (item) + LL_INFOS() << "atts " << item->getName() << LL_ENDL; + else + LL_INFOS() << "atts " << "UNKNOWN[" << item_id.asString() << "]" << LL_ENDL; + } + LL_INFOS() << LL_ENDL; +} + +void LLAppearanceMgr::registerAttachment(const LLUUID& item_id) +{ + gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); + + if (mAttachmentInvLinkEnabled) + { + // 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 + if (!isLinkedInCOF(item_id)) + { + LLPointer<LLInventoryCallback> cb = new LLUpdateAppearanceOnDestroy(); + LLAppearanceMgr::addCOFItemLink(item_id, cb); // Add COF link for item. + } + } + else + { + //LL_INFOS() << "no link changes, inv link not enabled" << LL_ENDL; + } +} + +void LLAppearanceMgr::unregisterAttachment(const LLUUID& item_id) +{ + gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); + + if (mAttachmentInvLinkEnabled) + { + LLAppearanceMgr::removeCOFItemLinks(item_id); + } + else + { + //LL_INFOS() << "no link changes, inv link not enabled" << LL_ENDL; + } +} + +BOOL LLAppearanceMgr::getIsInCOF(const LLUUID& obj_id) const +{ + 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) +{ + 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 +{ + if (!getIsInCOF(obj_id)) return FALSE; + + // If a non-link somehow ended up in COF, allow deletion. + const LLInventoryObject *obj = gInventory.getObject(obj_id); + if (obj && !obj->getIsLinkType()) + { + return FALSE; + } + + // For now, don't allow direct deletion from the COF. Instead, force users + // to choose "Detach" or "Take Off". + return TRUE; +} + +class CallAfterCategoryFetchStage2: public LLInventoryFetchItemsObserver +{ +public: + CallAfterCategoryFetchStage2(const uuid_vec_t& ids, + nullary_func_t callable) : + LLInventoryFetchItemsObserver(ids), + mCallable(callable) + { + } + ~CallAfterCategoryFetchStage2() + { + } + virtual void done() + { + LL_INFOS() << this << " done with incomplete " << mIncomplete.size() + << " complete " << mComplete.size() << " calling callable" << LL_ENDL; + + gInventory.removeObserver(this); + doOnIdleOneTime(mCallable); + delete this; + } +protected: + nullary_func_t mCallable; +}; + +class CallAfterCategoryFetchStage1: public LLInventoryFetchDescendentsObserver +{ +public: + CallAfterCategoryFetchStage1(const LLUUID& cat_id, nullary_func_t callable) : + LLInventoryFetchDescendentsObserver(cat_id), + mCallable(callable) + { + } + ~CallAfterCategoryFetchStage1() + { + } + virtual void done() + { + // 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.size(); + if(!count) + { + LL_WARNS() << "Nothing fetched in category " << mComplete.front() + << LL_ENDL; + gInventory.removeObserver(this); + doOnIdleOneTime(mCallable); + + delete this; + return; + } + + 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.at(i)->getUUID()); + } + + gInventory.removeObserver(this); + + // do the fetch + CallAfterCategoryFetchStage2 *stage2 = new CallAfterCategoryFetchStage2(ids, mCallable); + stage2->startFetch(); + if(stage2->isFinished()) + { + // everything is already here - call done. + stage2->done(); + } + else + { + // it's all on it's way - add an observer, and the inventory + // will call done for us when everything is here. + gInventory.addObserver(stage2); + } + delete this; + } +protected: + nullary_func_t mCallable; +}; + +void callAfterCategoryFetch(const LLUUID& cat_id, nullary_func_t cb) +{ + CallAfterCategoryFetchStage1 *stage1 = new CallAfterCategoryFetchStage1(cat_id, cb); + stage1->startFetch(); + if (stage1->isFinished()) + { + stage1->done(); + } + else + { + gInventory.addObserver(stage1); + } +} + +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; + } +} + +// SLapp for easy-wearing of a stock (library) avatar +// +class LLWearFolderHandler : public LLCommandHandler +{ +public: + // not allowed from outside the app + LLWearFolderHandler() : LLCommandHandler("wear_folder", UNTRUSTED_BLOCK) { } + + bool handle(const LLSD& tokens, const LLSD& query_map, + LLMediaCtrl* web) + { + LLSD::UUID folder_uuid; + + 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; + } +}; + +LLWearFolderHandler gWearFolderHandler; diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index 707b8d5a77..b90ef65f95 100755 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -34,7 +34,6 @@ #include "llinventorymodel.h" #include "llinventoryobserver.h" #include "llviewerinventory.h" -#include "llhttpclient.h" class LLWearableHoldingPattern; class LLInventoryCallback; @@ -218,10 +217,10 @@ public: bool testCOFRequestVersion() const; - void decrementInFlightCounter()
- {
- mInFlightCounter = llmax(mInFlightCounter - 1, 0);
- }
+ void decrementInFlightCounter() + { + mInFlightCounter = llmax(mInFlightCounter - 1, 0); + } private: @@ -263,9 +262,9 @@ private: * to avoid unsynchronized outfit state or performing duplicate operations. */ bool mOutfitLocked; - S32 mInFlightCounter;
- LLTimer mInFlightTimer;
- static bool mActive;
+ S32 mInFlightCounter; + LLTimer mInFlightTimer; + static bool mActive; std::auto_ptr<LLOutfitUnLockTimer> mUnlockOutfitTimer; |