diff options
29 files changed, 801 insertions, 194 deletions
diff --git a/BuildParams b/BuildParams index c5f96d5ee3..e55c0c7bda 100755 --- a/BuildParams +++ b/BuildParams @@ -36,6 +36,7 @@ buildscripts_shared_more_NAMEs="build_secrets build_variables" ################################################################ viewer_channel = "Second Life Test" + ################################################################ # Special packaging parameters. # These parameters can be used to create additional packages diff --git a/indra/llaudio/llaudioengine_fmodstudio.cpp b/indra/llaudio/llaudioengine_fmodstudio.cpp index 70b3a08473..b0c87b0208 100644 --- a/indra/llaudio/llaudioengine_fmodstudio.cpp +++ b/indra/llaudio/llaudioengine_fmodstudio.cpp @@ -661,7 +661,7 @@ bool LLAudioBufferFMODSTUDIO::loadWAV(const std::string& filename) return false; } - if (!LLAPRFile::isExist(filename, NULL, LL_APR_RPB)) + if (!gDirUtilp->fileExists(filename)) { // File not found, abort. return false; diff --git a/indra/llinventory/llfoldertype.cpp b/indra/llinventory/llfoldertype.cpp index 7241b3c0c2..675da65af2 100644 --- a/indra/llinventory/llfoldertype.cpp +++ b/indra/llinventory/llfoldertype.cpp @@ -37,15 +37,22 @@ struct FolderEntry : public LLDictionaryEntry { FolderEntry(const std::string &type_name, // 8 character limit! - bool is_protected) // can the viewer change categories of this type? + bool is_protected, // can the viewer change categories of this type? + bool is_automatic, // always made before first login? + bool is_singleton // should exist as a unique copy under root + ) : LLDictionaryEntry(type_name), - mIsProtected(is_protected) + mIsProtected(is_protected), + mIsAutomatic(is_automatic), + mIsSingleton(is_singleton) { llassert(type_name.length() <= 8); } const bool mIsProtected; + const bool mIsAutomatic; + const bool mIsSingleton; }; class LLFolderDictionary : public LLSingleton<LLFolderDictionary>, @@ -59,50 +66,64 @@ protected: } }; +// Folder types +// +// PROTECTED means that folders of this type can't be moved, deleted +// or otherwise modified by the viewer. +// +// SINGLETON means that there should always be exactly one folder of +// this type, and it should be the root or a child of the root. This +// is true for most types of folders. +// +// AUTOMATIC means that a copy of this folder should be created under +// the root before the user ever logs in, and should never be created +// from the viewer. A missing AUTOMATIC folder should be treated as a +// fatal error by the viewer, since it indicates either corrupted +// inventory or a failure in the inventory services. +// LLFolderDictionary::LLFolderDictionary() { - // TYPE NAME PROTECTED - // |-----------|---------| - addEntry(LLFolderType::FT_TEXTURE, new FolderEntry("texture", TRUE)); - addEntry(LLFolderType::FT_SOUND, new FolderEntry("sound", TRUE)); - addEntry(LLFolderType::FT_CALLINGCARD, new FolderEntry("callcard", TRUE)); - addEntry(LLFolderType::FT_LANDMARK, new FolderEntry("landmark", TRUE)); - addEntry(LLFolderType::FT_CLOTHING, new FolderEntry("clothing", TRUE)); - addEntry(LLFolderType::FT_OBJECT, new FolderEntry("object", TRUE)); - addEntry(LLFolderType::FT_NOTECARD, new FolderEntry("notecard", TRUE)); - addEntry(LLFolderType::FT_ROOT_INVENTORY, new FolderEntry("root_inv", TRUE)); - addEntry(LLFolderType::FT_LSL_TEXT, new FolderEntry("lsltext", TRUE)); - addEntry(LLFolderType::FT_BODYPART, new FolderEntry("bodypart", TRUE)); - addEntry(LLFolderType::FT_TRASH, new FolderEntry("trash", TRUE)); - addEntry(LLFolderType::FT_SNAPSHOT_CATEGORY, new FolderEntry("snapshot", TRUE)); - addEntry(LLFolderType::FT_LOST_AND_FOUND, new FolderEntry("lstndfnd", TRUE)); - addEntry(LLFolderType::FT_ANIMATION, new FolderEntry("animatn", TRUE)); - addEntry(LLFolderType::FT_GESTURE, new FolderEntry("gesture", TRUE)); - addEntry(LLFolderType::FT_FAVORITE, new FolderEntry("favorite", TRUE)); + // TYPE NAME, PROTECTED, AUTOMATIC, SINGLETON + addEntry(LLFolderType::FT_TEXTURE, new FolderEntry("texture", TRUE, TRUE, TRUE)); + addEntry(LLFolderType::FT_SOUND, new FolderEntry("sound", TRUE, TRUE, TRUE)); + addEntry(LLFolderType::FT_CALLINGCARD, new FolderEntry("callcard", TRUE, TRUE, FALSE)); + addEntry(LLFolderType::FT_LANDMARK, new FolderEntry("landmark", TRUE, FALSE, FALSE)); + addEntry(LLFolderType::FT_CLOTHING, new FolderEntry("clothing", TRUE, TRUE, TRUE)); + addEntry(LLFolderType::FT_OBJECT, new FolderEntry("object", TRUE, TRUE, TRUE)); + addEntry(LLFolderType::FT_NOTECARD, new FolderEntry("notecard", TRUE, TRUE, TRUE)); + addEntry(LLFolderType::FT_ROOT_INVENTORY, new FolderEntry("root_inv", TRUE, TRUE, TRUE)); + addEntry(LLFolderType::FT_LSL_TEXT, new FolderEntry("lsltext", TRUE, TRUE, TRUE)); + addEntry(LLFolderType::FT_BODYPART, new FolderEntry("bodypart", TRUE, TRUE, TRUE)); + addEntry(LLFolderType::FT_TRASH, new FolderEntry("trash", TRUE, FALSE, TRUE)); + addEntry(LLFolderType::FT_SNAPSHOT_CATEGORY, new FolderEntry("snapshot", TRUE, TRUE, TRUE)); + addEntry(LLFolderType::FT_LOST_AND_FOUND, new FolderEntry("lstndfnd", TRUE, TRUE, TRUE)); + addEntry(LLFolderType::FT_ANIMATION, new FolderEntry("animatn", TRUE, TRUE, TRUE)); + addEntry(LLFolderType::FT_GESTURE, new FolderEntry("gesture", TRUE, TRUE, TRUE)); + addEntry(LLFolderType::FT_FAVORITE, new FolderEntry("favorite", TRUE, FALSE, TRUE)); for (S32 ensemble_num = S32(LLFolderType::FT_ENSEMBLE_START); ensemble_num <= S32(LLFolderType::FT_ENSEMBLE_END); ensemble_num++) { - addEntry(LLFolderType::EType(ensemble_num), new FolderEntry("ensemble", FALSE)); + addEntry(LLFolderType::EType(ensemble_num), new FolderEntry("ensemble", FALSE, FALSE, FALSE)); // Not used } - addEntry(LLFolderType::FT_CURRENT_OUTFIT, new FolderEntry("current", TRUE)); - addEntry(LLFolderType::FT_OUTFIT, new FolderEntry("outfit", FALSE)); - addEntry(LLFolderType::FT_MY_OUTFITS, new FolderEntry("my_otfts", TRUE)); + addEntry(LLFolderType::FT_CURRENT_OUTFIT, new FolderEntry("current", TRUE, FALSE, TRUE)); + addEntry(LLFolderType::FT_OUTFIT, new FolderEntry("outfit", FALSE, FALSE, FALSE)); + addEntry(LLFolderType::FT_MY_OUTFITS, new FolderEntry("my_otfts", TRUE, FALSE, TRUE)); - addEntry(LLFolderType::FT_MESH, new FolderEntry("mesh", TRUE)); + addEntry(LLFolderType::FT_MESH, new FolderEntry("mesh", TRUE, FALSE, FALSE)); // Not used? - addEntry(LLFolderType::FT_INBOX, new FolderEntry("inbox", TRUE)); - addEntry(LLFolderType::FT_OUTBOX, new FolderEntry("outbox", TRUE)); + addEntry(LLFolderType::FT_INBOX, new FolderEntry("inbox", TRUE, FALSE, TRUE)); + addEntry(LLFolderType::FT_OUTBOX, new FolderEntry("outbox", TRUE, FALSE, FALSE)); - addEntry(LLFolderType::FT_BASIC_ROOT, new FolderEntry("basic_rt", TRUE)); + addEntry(LLFolderType::FT_BASIC_ROOT, new FolderEntry("basic_rt", TRUE, FALSE, FALSE)); - addEntry(LLFolderType::FT_MARKETPLACE_LISTINGS, new FolderEntry("merchant", FALSE)); - addEntry(LLFolderType::FT_MARKETPLACE_STOCK, new FolderEntry("stock", FALSE)); - addEntry(LLFolderType::FT_MARKETPLACE_VERSION, new FolderEntry("version", FALSE)); + addEntry(LLFolderType::FT_MARKETPLACE_LISTINGS, new FolderEntry("merchant", FALSE, FALSE, FALSE)); + addEntry(LLFolderType::FT_MARKETPLACE_STOCK, new FolderEntry("stock", FALSE, FALSE, FALSE)); + addEntry(LLFolderType::FT_MARKETPLACE_VERSION, new FolderEntry("version", FALSE, FALSE, FALSE)); - addEntry(LLFolderType::FT_SETTINGS, new FolderEntry("settings", TRUE)); + addEntry(LLFolderType::FT_SETTINGS, new FolderEntry("settings", TRUE, FALSE, TRUE)); - addEntry(LLFolderType::FT_NONE, new FolderEntry("-1", FALSE)); + addEntry(LLFolderType::FT_NONE, new FolderEntry("-1", FALSE, FALSE, FALSE)); }; // static @@ -126,8 +147,8 @@ const std::string &LLFolderType::lookup(LLFolderType::EType folder_type) } // static -// Only ensembles and plain folders aren't protected. "Protected" means -// you can't change certain properties such as their type. +// Only plain folders and a few other types aren't protected. "Protected" means +// you can't move, deleted, or change certain properties such as their type. bool LLFolderType::lookupIsProtectedType(EType folder_type) { const LLFolderDictionary *dict = LLFolderDictionary::getInstance(); @@ -138,6 +159,32 @@ bool LLFolderType::lookupIsProtectedType(EType folder_type) } return true; } + +// static +// Is this folder type automatically created outside the viewer? +bool LLFolderType::lookupIsAutomaticType(EType folder_type) +{ + const LLFolderDictionary *dict = LLFolderDictionary::getInstance(); + const FolderEntry *entry = dict->lookup(folder_type); + if (entry) + { + return entry->mIsAutomatic; + } + return true; +} + +// static +// Should this folder always exist as a single copy under (or as) the root? +bool LLFolderType::lookupIsSingletonType(EType folder_type) +{ + const LLFolderDictionary *dict = LLFolderDictionary::getInstance(); + const FolderEntry *entry = dict->lookup(folder_type); + if (entry) + { + return entry->mIsSingleton; + } + return true; +} // static bool LLFolderType::lookupIsEnsembleType(EType folder_type) diff --git a/indra/llinventory/llfoldertype.h b/indra/llinventory/llfoldertype.h index 85b86f9ce5..1f174520da 100644 --- a/indra/llinventory/llfoldertype.h +++ b/indra/llinventory/llfoldertype.h @@ -102,6 +102,8 @@ public: static const std::string& lookup(EType folder_type); static bool lookupIsProtectedType(EType folder_type); + static bool lookupIsAutomaticType(EType folder_type); + static bool lookupIsSingletonType(EType folder_type); static bool lookupIsEnsembleType(EType folder_type); static LLAssetType::EType folderTypeToAssetType(LLFolderType::EType folder_type); diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index cce618487b..f781ff4110 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -122,6 +122,7 @@ set(llui_SOURCE_FILES lluictrl.cpp lluictrlfactory.cpp lluistring.cpp + lluiusage.cpp llundo.cpp llurlaction.cpp llurlentry.cpp @@ -241,6 +242,7 @@ set(llui_HEADER_FILES llui.h lluicolor.h lluistring.h + lluiusage.h llundo.h llurlaction.h llurlentry.h diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index 9682c3bc10..0e59fdf519 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -47,6 +47,7 @@ #include "llnotificationsutil.h" #include "llrender.h" #include "lluictrlfactory.h" +#include "lluiusage.h" #include "llhelp.h" #include "lldockablefloater.h" #include "llviewereventrecorder.h" @@ -437,6 +438,13 @@ BOOL LLButton::handleMouseDown(S32 x, S32 y, MASK mask) setFocus(TRUE); } + if (!mFunctionName.empty()) + { + LL_DEBUGS("UIUsage") << "calling mouse down function " << mFunctionName << LL_ENDL; + LLUIUsage::instance().logCommand(mFunctionName); + LLUIUsage::instance().logControl(getPathname()); + } + /* * ATTENTION! This call fires another mouse down callback. * If you wish to remove this call emit that signal directly diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index e9c980ad9a..c03b024dd5 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -58,6 +58,7 @@ #include "llhelp.h" #include "llmultifloater.h" #include "llsdutil.h" +#include "lluiusage.h" #include <boost/foreach.hpp> @@ -1631,6 +1632,7 @@ void LLFloater::bringToFront( S32 x, S32 y ) // virtual void LLFloater::setVisibleAndFrontmost(BOOL take_focus,const LLSD& key) { + LLUIUsage::instance().logFloater(getInstanceName()); LLMultiFloater* hostp = getHost(); if (hostp) { diff --git a/indra/llui/llfloaterreg.cpp b/indra/llui/llfloaterreg.cpp index 85e07fc6a6..7c0933f554 100644 --- a/indra/llui/llfloaterreg.cpp +++ b/indra/llui/llfloaterreg.cpp @@ -32,6 +32,7 @@ #include "llfloater.h" #include "llmultifloater.h" #include "llfloaterreglistener.h" +#include "lluiusage.h" //******************************************************* @@ -472,7 +473,6 @@ void LLFloaterReg::toggleInstanceOrBringToFront(const LLSD& sdname, const LLSD& std::string name = sdname.asString(); LLFloater* instance = getInstance(name, key); - if (!instance) { LL_DEBUGS() << "Unable to get instance of floater '" << name << "'" << LL_ENDL; diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index e6b43da8e5..459fdcf2ae 100644 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -38,6 +38,7 @@ #include "llrender.h" #include "llfloater.h" #include "lltrans.h" +#include "lluiusage.h" //---------------------------------------------------------------------------- @@ -1556,6 +1557,8 @@ BOOL LLTabContainer::setTab(S32 which) if (is_selected) { + LLUIUsage::instance().logPanel(tuple->mTabPanel->getName()); + // Make sure selected tab is within scroll region if (mIsVertical) { diff --git a/indra/llui/lltoolbar.cpp b/indra/llui/lltoolbar.cpp index e6f466ec78..4868c66d1b 100644 --- a/indra/llui/lltoolbar.cpp +++ b/indra/llui/lltoolbar.cpp @@ -967,6 +967,8 @@ LLToolBarButton* LLToolBar::createButton(const LLCommandId& id) executeStopParam.function_name = executeStopFunction; executeStopParam.parameter = commandp->executeStopParameters(); LLUICtrl::commit_callback_t execute_func = initCommitCallback(executeParam); + button->setFunctionName(commandp->executeFunctionName()); + LL_DEBUGS("UIUsage") << "button function name a -> " << commandp->executeFunctionName() << LL_ENDL; LLUICtrl::commit_callback_t stop_func = initCommitCallback(executeStopParam); button->setMouseDownCallback(boost::bind(&LLToolBarButton::callIfEnabled, button, execute_func, _1, _2)); @@ -974,6 +976,8 @@ LLToolBarButton* LLToolBar::createButton(const LLCommandId& id) } else { + button->setFunctionName(commandp->executeFunctionName()); + LL_DEBUGS("UIUsage") << "button function name b -> " << commandp->executeFunctionName() << LL_ENDL; button->setCommitCallback(executeParam); } diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index 544a76e8d5..5924542a19 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -35,6 +35,7 @@ #include "lluictrlfactory.h" #include "lltabcontainer.h" #include "llaccordionctrltab.h" +#include "lluiusage.h" static LLDefaultChildRegistry::Register<LLUICtrl> r("ui_ctrl"); @@ -282,6 +283,7 @@ LLUICtrl::commit_signal_t::slot_type LLUICtrl::initCommitCallback(const CommitCa else { std::string function_name = cb.function_name; + setFunctionName(function_name); commit_callback_t* func = (CommitCallbackRegistry::getValue(function_name)); if (func) { @@ -422,7 +424,19 @@ BOOL LLUICtrl::canFocusChildren() const void LLUICtrl::onCommit() { if (mCommitSignal) - (*mCommitSignal)(this, getValue()); + { + if (!mFunctionName.empty()) + { + LL_DEBUGS("UIUsage") << "calling commit function " << mFunctionName << LL_ENDL; + LLUIUsage::instance().logCommand(mFunctionName); + LLUIUsage::instance().logControl(getPathname()); + } + else + { + //LL_DEBUGS("UIUsage") << "calling commit function " << "UNKNOWN" << LL_ENDL; + } + (*mCommitSignal)(this, getValue()); + } } //virtual @@ -597,6 +611,12 @@ void LLUICtrl::setMakeInvisibleControlVariable(LLControlVariable* control) setVisible(!(mMakeInvisibleControlVariable->getValue().asBoolean())); } } + +void LLUICtrl::setFunctionName(const std::string& function_name) +{ + mFunctionName = function_name; +} + // static bool LLUICtrl::controlListener(const LLSD& newvalue, LLHandle<LLUICtrl> handle, std::string type) { diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h index 7360bd7659..67dd24341c 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -183,6 +183,8 @@ public: void setMakeVisibleControlVariable(LLControlVariable* control); void setMakeInvisibleControlVariable(LLControlVariable* control); + void setFunctionName(const std::string& function_name); + virtual void setTentative(BOOL b); virtual BOOL getTentative() const; virtual void setValue(const LLSD& value); @@ -310,6 +312,8 @@ protected: LLControlVariable* mMakeInvisibleControlVariable; boost::signals2::connection mMakeInvisibleControlConnection; + std::string mFunctionName; + static F32 sActiveControlTransparency; static F32 sInactiveControlTransparency; diff --git a/indra/llui/lluiusage.cpp b/indra/llui/lluiusage.cpp new file mode 100644 index 0000000000..ccae6643b9 --- /dev/null +++ b/indra/llui/lluiusage.cpp @@ -0,0 +1,146 @@ +/** +* @file lluiuisage.cpp +* @brief Source file for LLUIUsage +* +* $LicenseInfo:firstyear=2021&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2021, 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 "linden_common.h" +#include "lluiusage.h" +#include <boost/algorithm/string.hpp> + +LLUIUsage::LLUIUsage() +{ +} + +LLUIUsage::~LLUIUsage() +{ +} + +// static +std::string LLUIUsage::sanitized(const std::string& s) +{ + // Remove characters that make the ViewerStats db unhappy + std::string result(s); + std::replace(result.begin(), result.end(), '.', '_'); + std::replace(result.begin(), result.end(), ' ', '_'); + return result; +} + +// static +void LLUIUsage::setLLSDPath(LLSD& sd, const std::string& path, S32 max_elts, const LLSD& val) +{ + // Keep the last max_elts components of the specified path + std::vector<std::string> fields; + boost::split(fields, path, boost::is_any_of("/")); + auto first_pos = std::max(fields.begin(), fields.end() - max_elts); + auto end_pos = fields.end(); + std::vector<std::string> last_fields(first_pos,end_pos); + + setLLSDNested(sd, last_fields, val); +} + +// setLLSDNested() +// Accomplish the equivalent of +// sd[fields[0]][fields[1]]... = val; +// for an arbitrary number of fields. +// This might be useful as an LLSD utility function; is not specific to LLUIUsage +// +// static +void LLUIUsage::setLLSDNested(LLSD& sd, const std::vector<std::string>& fields, const LLSD& val) +{ + LLSD* fsd = &sd; + for (auto it=fields.begin(); it!=fields.end(); ++it) + { + if (it == fields.end()-1) + { + (*fsd)[*it] = val; + } + else + { + if (!(*fsd)[*it].isMap()) + { + (*fsd)[*it] = LLSD::emptyMap(); + } + fsd = &(*fsd)[*it]; + } + } +} + +void LLUIUsage::logCommand(const std::string& command) +{ + mCommandCounts[sanitized(command)]++; + LL_DEBUGS("UIUsage") << "command " << command << LL_ENDL; +} + +void LLUIUsage::logControl(const std::string& control) +{ + mControlCounts[sanitized(control)]++; + LL_DEBUGS("UIUsage") << "control " << control << LL_ENDL; +} + + +void LLUIUsage::logFloater(const std::string& floater) +{ + mFloaterCounts[sanitized(floater)]++; + LL_DEBUGS("UIUsage") << "floater " << floater << LL_ENDL; +} + +void LLUIUsage::logPanel(const std::string& p) +{ + mPanelCounts[sanitized(p)]++; + LL_DEBUGS("UIUsage") << "panel " << p << LL_ENDL; +} + +LLSD LLUIUsage::asLLSD() const +{ + LLSD result; + for (auto const& it : mCommandCounts) + { + result["commands"][it.first] = LLSD::Integer(it.second); + } + for (auto const& it : mControlCounts) + { + setLLSDPath(result["controls"], it.first, 2, LLSD::Integer(it.second)); + } + for (auto const& it : mFloaterCounts) + { + result["floaters"][it.first] = LLSD::Integer(it.second); + } + for (auto const& it : mPanelCounts) + { + result["panels"][it.first] = LLSD::Integer(it.second); + } + return result; +} + +// Clear up some junk content generated during initial login/UI initialization +void LLUIUsage::clear() +{ + + LL_DEBUGS("UIUsage") << "clear" << LL_ENDL; + mCommandCounts.clear(); + mControlCounts.clear(); + mFloaterCounts.clear(); + mPanelCounts.clear(); +} + diff --git a/indra/llui/lluiusage.h b/indra/llui/lluiusage.h new file mode 100644 index 0000000000..a30cd80db3 --- /dev/null +++ b/indra/llui/lluiusage.h @@ -0,0 +1,57 @@ +/** +* @file lluiuisage.h +* @brief Header file for LLUIUsage +* +* $LicenseInfo:firstyear=2021&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2021, 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$ +*/ + +#ifndef LL_LLUIUSAGE_H +#define LL_LLUIUSAGE_H + +#include <map> +#include "llsd.h" +#include "llsingleton.h" + +// UIUsage tracking to see which operations and UI elements are most popular in a session +class LLUIUsage : public LLSingleton<LLUIUsage> +{ +public: + LLSINGLETON(LLUIUsage); + ~LLUIUsage(); +public: + static std::string sanitized(const std::string& s); + static void setLLSDPath(LLSD& sd, const std::string& path, S32 max_elts, const LLSD& val); + static void setLLSDNested(LLSD& sd, const std::vector<std::string>& fields, const LLSD& val); + void logCommand(const std::string& command); + void logControl(const std::string& control); + void logFloater(const std::string& floater); + void logPanel(const std::string& p); + LLSD asLLSD() const; + void clear(); +private: + std::map<std::string,U32> mCommandCounts; + std::map<std::string,U32> mControlCounts; + std::map<std::string,U32> mFloaterCounts; + std::map<std::string,U32> mPanelCounts; +}; + +#endif // LLUIUIUSAGE.h diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 626a3d1ff3..f1c5fc0b14 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8337,7 +8337,18 @@ <key>QAModeMetrics</key> <map> <key>Comment</key> - <string>"Enables QA features (logging, faster cycling) for metrics collector"</string> + <string>Enables QA features (logging, faster cycling) for metrics collector</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>QAModeFakeSystemFolderIssues</key> + <map> + <key>Comment</key> + <string>Simulates system folder issues in inventory</string> <key>Persist</key> <integer>1</integer> <key>Type</key> diff --git a/indra/newview/llattachmentsmgr.cpp b/indra/newview/llattachmentsmgr.cpp index d3e66289d1..443c5ae02f 100644 --- a/indra/newview/llattachmentsmgr.cpp +++ b/indra/newview/llattachmentsmgr.cpp @@ -99,22 +99,22 @@ void LLAttachmentsMgr::onIdle() return; } - if (LLApp::isExiting()) - { - return; - } + if (LLApp::isExiting()) + { + return; + } requestPendingAttachments(); - linkRecentlyArrivedAttachments(); + linkRecentlyArrivedAttachments(); - expireOldAttachmentRequests(); + expireOldAttachmentRequests(); - expireOldDetachRequests(); + expireOldDetachRequests(); - checkInvalidCOFLinks(); - - spamStatusInfo(); + checkInvalidCOFLinks(); + + spamStatusInfo(); } void LLAttachmentsMgr::requestPendingAttachments() @@ -452,51 +452,55 @@ bool LLAttachmentsMgr::isAttachmentStateComplete() const // void LLAttachmentsMgr::checkInvalidCOFLinks() { - 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(); - const LLUUID& item_id = inv_item->getLinkedUUID(); - if (inv_item->getType() == LLAssetType::AT_OBJECT) - { - LLTimer timer; - bool is_flagged_questionable = mQuestionableCOFLinks.getTime(item_id,timer); - bool is_wearing_attachment = isAgentAvatarValid() && gAgentAvatarp->isWearingAttachment(item_id); - if (is_wearing_attachment && is_flagged_questionable) - { - LL_DEBUGS("Avatar") << "ATT was flagged questionable but is now " - << (is_wearing_attachment ? "attached " : "") - <<"removing flag after " - << timer.getElapsedTimeF32() << " item " - << inv_item->getName() << " id " << item_id << LL_ENDL; - mQuestionableCOFLinks.removeTime(item_id); - } - } - } + if (!gInventory.isInventoryUsable()) + { + return; + } + 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(); + const LLUUID& item_id = inv_item->getLinkedUUID(); + if (inv_item->getType() == LLAssetType::AT_OBJECT) + { + LLTimer timer; + bool is_flagged_questionable = mQuestionableCOFLinks.getTime(item_id,timer); + bool is_wearing_attachment = isAgentAvatarValid() && gAgentAvatarp->isWearingAttachment(item_id); + if (is_wearing_attachment && is_flagged_questionable) + { + LL_DEBUGS("Avatar") << "ATT was flagged questionable but is now " + << (is_wearing_attachment ? "attached " : "") + <<"removing flag after " + << timer.getElapsedTimeF32() << " item " + << inv_item->getName() << " id " << item_id << LL_ENDL; + mQuestionableCOFLinks.removeTime(item_id); + } + } + } - for(LLItemRequestTimes::iterator it = mQuestionableCOFLinks.begin(); - it != mQuestionableCOFLinks.end(); ) - { - LLItemRequestTimes::iterator curr_it = it; - ++it; - const LLUUID& item_id = curr_it->first; - LLViewerInventoryItem *inv_item = gInventory.getItem(item_id); - if (curr_it->second.getElapsedTimeF32() > MAX_BAD_COF_TIME) - { - if (LLAppearanceMgr::instance().isLinkedInCOF(item_id)) - { - LL_DEBUGS("Avatar") << "ATT Linked in COF but not attached or requested, deleting link after " - << curr_it->second.getElapsedTimeF32() << " seconds for " - << (inv_item ? inv_item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; - LLAppearanceMgr::instance().removeCOFItemLinks(item_id); - } - mQuestionableCOFLinks.erase(curr_it); - continue; - } - } + for(LLItemRequestTimes::iterator it = mQuestionableCOFLinks.begin(); + it != mQuestionableCOFLinks.end(); ) + { + LLItemRequestTimes::iterator curr_it = it; + ++it; + const LLUUID& item_id = curr_it->first; + LLViewerInventoryItem *inv_item = gInventory.getItem(item_id); + if (curr_it->second.getElapsedTimeF32() > MAX_BAD_COF_TIME) + { + if (LLAppearanceMgr::instance().isLinkedInCOF(item_id)) + { + LL_DEBUGS("Avatar") << "ATT Linked in COF but not attached or requested, deleting link after " + << curr_it->second.getElapsedTimeF32() << " seconds for " + << (inv_item ? inv_item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; + LLAppearanceMgr::instance().removeCOFItemLinks(item_id); + } + mQuestionableCOFLinks.erase(curr_it); + continue; + } + } } void LLAttachmentsMgr::spamStatusInfo() diff --git a/indra/newview/llchatbar.cpp b/indra/newview/llchatbar.cpp index 3ab5c669c4..e400609a74 100644 --- a/indra/newview/llchatbar.cpp +++ b/indra/newview/llchatbar.cpp @@ -58,6 +58,7 @@ #include "llmultigesture.h" #include "llui.h" #include "lluictrlfactory.h" +#include "lluiusage.h" // // Globals @@ -566,6 +567,8 @@ void LLChatBar::sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL // as soon as we say something, we no longer care about teaching the user // how to chat gWarningSettings.setBOOL("FirstOtherChatBeforeUser", FALSE); + + LLUIUsage::instance().logCommand("Chat.Send"); // Pseudo-command // Look for "/20 foo" channel chats. S32 channel = 0; diff --git a/indra/newview/llfavoritesbar.cpp b/indra/newview/llfavoritesbar.cpp index 347997a69a..d258bea519 100644 --- a/indra/newview/llfavoritesbar.cpp +++ b/indra/newview/llfavoritesbar.cpp @@ -1859,9 +1859,17 @@ BOOL LLFavoritesOrderStorage::saveFavoritesRecord(bool pref_changed) pref_changed |= mRecreateFavoriteStorage; mRecreateFavoriteStorage = false; + // Can get called before inventory is done initializing. + if (!gInventory.isInventoryUsable()) + { + return FALSE; + } + LLUUID favorite_folder= gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); if (favorite_folder.isNull()) - return FALSE; + { + return FALSE; + } LLInventoryModel::item_array_t items; LLInventoryModel::cat_array_t cats; diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp index a6531ed7e1..3a850d4b68 100644 --- a/indra/newview/llfloaterimnearbychat.cpp +++ b/indra/newview/llfloaterimnearbychat.cpp @@ -67,6 +67,7 @@ #include "llviewerchat.h" #include "lltranslate.h" #include "llautoreplace.h" +#include "lluiusage.h" S32 LLFloaterIMNearbyChat::sLastSpecialChatChannel = 0; @@ -697,6 +698,7 @@ void LLFloaterIMNearbyChat::sendChatFromViewer(const std::string &utf8text, ECha void LLFloaterIMNearbyChat::sendChatFromViewer(const LLWString &wtext, EChatType type, BOOL animate) { + LLUIUsage::instance().logCommand("Chat.Send"); // pseuo-command // Look for "/20 foo" channel chats. S32 channel = 0; LLWString out_text = stripChannelNumber(wtext, &channel); diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp index d2bd716f55..65c91b54b7 100644 --- a/indra/newview/llgroupactions.cpp +++ b/indra/newview/llgroupactions.cpp @@ -366,6 +366,11 @@ void LLGroupActions::show(const LLUUID& group_id) params["open_tab_name"] = "panel_group_info_sidetray"; LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); + LLFloater *floater = LLFloaterReg::getTypedInstance<LLFloaterSidePanelContainer>("people"); + if (!floater->isFrontmost()) + { + floater->setVisibleAndFrontmost(TRUE, params); + } } void LLGroupActions::refresh_notices() diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 28db6a5808..6e30a31e6d 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -27,6 +27,7 @@ #include "llviewerprecompiledheaders.h" #include <typeinfo> +#include <random> #include "llinventorymodel.h" @@ -67,6 +68,9 @@ #include "process.h" #endif +#include <algorithm> +#include <boost/algorithm/string/join.hpp> + // Increment this if the inventory contents change in a non-backwards-compatible way. // For viewer 2, the addition of link items makes a pre-viewer-2 cache incorrect. const S32 LLInventoryModel::sCurrentInvCacheVersion = 2; @@ -129,6 +133,54 @@ bool LLCanCache::operator()(LLInventoryCategory* cat, LLInventoryItem* item) } ///---------------------------------------------------------------------------- +/// Class LLInventoryValidationInfo +///---------------------------------------------------------------------------- +LLInventoryValidationInfo::LLInventoryValidationInfo(): + mFatalErrorCount(0), + mWarningCount(0), + mInitialized(false) +{ +} + +void LLInventoryValidationInfo::toOstream(std::ostream& os) const +{ + os << "mFatalErrorCount " << mFatalErrorCount << " mWarningCount " << mWarningCount; +} + + +std::ostream& operator<<(std::ostream& os, const LLInventoryValidationInfo& v) +{ + v.toOstream(os); + return os; +} + +void LLInventoryValidationInfo::asLLSD(LLSD& sd) const +{ + sd["fatal_error_count"] = mFatalErrorCount; + sd["warning_count"] = mWarningCount; + sd["initialized"] = mInitialized; + sd["missing_system_folders_count"] = LLSD::Integer(mMissingRequiredSystemFolders.size()); + if (mMissingRequiredSystemFolders.size()>0) + { + sd["missing_system_folders"] = LLSD::emptyArray(); + for(auto ft: mMissingRequiredSystemFolders) + { + sd["missing_system_folders"].append(LLFolderType::lookup(ft)); + } + } + sd["duplicate_system_folders_count"] = LLSD::Integer(mDuplicateRequiredSystemFolders.size()); + if (mDuplicateRequiredSystemFolders.size()>0) + { + sd["duplicate_system_folders"] = LLSD::emptyArray(); + for(auto ft: mDuplicateRequiredSystemFolders) + { + sd["duplicate_system_folders"].append(LLFolderType::lookup(ft)); + } + } + +} + +///---------------------------------------------------------------------------- /// Class LLInventoryModel ///---------------------------------------------------------------------------- @@ -160,7 +212,8 @@ LLInventoryModel::LLInventoryModel() mHttpPriorityFG(0), mHttpPriorityBG(0), mCategoryLock(), - mItemLock() + mItemLock(), + mValidationInfo(new LLInventoryValidationInfo) {} @@ -499,12 +552,18 @@ const LLUUID LLInventoryModel::findCategoryUUIDForTypeInRoot( } } - if(rv.isNull() && isInventoryUsable() && create_folder) + if(rv.isNull() && create_folder && root_id.notNull()) { - if(root_id.notNull()) + + if (isInventoryUsable()) { return createNewCategory(root_id, preferred_type, LLStringUtil::null); } + else + { + LL_WARNS("Inventory") << "Can't create requested folder, type " << preferred_type + << " because inventory is not usable" << LL_ENDL; + } } return rv; } @@ -568,20 +627,30 @@ LLUUID LLInventoryModel::createNewCategory(const LLUUID& parent_id, const std::string& pname, inventory_func_type callback) { - LLUUID id; - if(!isInventoryUsable()) + if (!isInventoryUsable()) { - LL_WARNS(LOG_INV) << "Inventory is broken." << LL_ENDL; + LL_WARNS(LOG_INV) << "Inventory is not usable; can't create requested category of type " + << preferred_type << LL_ENDL; + // FIXME failing but still returning an id? return id; } if(LLFolderType::lookup(preferred_type) == LLFolderType::badLookup()) { LL_DEBUGS(LOG_INV) << "Attempt to create undefined category." << LL_ENDL; + // FIXME failing but still returning an id? return id; } + if (preferred_type != LLFolderType::FT_NONE) + { + // Ultimately this should only be done for non-singleton + // types. Requires back-end changes to guarantee that others + // already exist. + LL_WARNS(LOG_INV) << "Creating new system folder, type " << preferred_type << LL_ENDL; + } + id.generate(); std::string name = pname; if(!pname.empty()) @@ -611,7 +680,7 @@ LLUUID LLInventoryModel::createNewCategory(const LLUUID& parent_id, request["message"] = "CreateInventoryCategory"; request["payload"] = body; - LL_DEBUGS(LOG_INV) << "create category request: " << ll_pretty_print_sd(request) << LL_ENDL; + LL_DEBUGS(LOG_INV) << "Creating category via request: " << ll_pretty_print_sd(request) << LL_ENDL; LLCoros::instance().launch("LLInventoryModel::createNewCategoryCoro", boost::bind(&LLInventoryModel::createNewCategoryCoro, this, url, body, callback)); @@ -623,6 +692,10 @@ LLUUID LLInventoryModel::createNewCategory(const LLUUID& parent_id, return LLUUID::null; } + // FIXME this UDP code path needs to be removed. Requires + // reworking many of the callers to use callbacks rather than + // assuming instant success. + // Add the category to the internal representation LLPointer<LLViewerInventoryCategory> cat = new LLViewerInventoryCategory(id, parent_id, preferred_type, name, gAgent.getID()); @@ -632,6 +705,8 @@ LLUUID LLInventoryModel::createNewCategory(const LLUUID& parent_id, accountForUpdate(update); updateCategory(cat); + LL_DEBUGS(LOG_INV) << "Creating category via UDP message CreateInventoryFolder, type " << preferred_type << LL_ENDL; + // Create the category on the server. We do this to prevent people // from munging their protected folders. LLMessageSystem* msg = gMessageSystem; @@ -2247,11 +2322,11 @@ bool LLInventoryModel::loadSkeleton( } } - LL_INFOS(LOG_INV) << "Attempted to add " << bad_link_count - << " cached link items without baseobj present. " - << good_link_count << " link items were successfully added. " - << recovered_link_count << " links added in recovery. " - << "The corresponding categories were invalidated." << LL_ENDL; + LL_DEBUGS(LOG_INV) << "Attempted to add " << bad_link_count + << " cached link items without baseobj present. " + << good_link_count << " link items were successfully added. " + << recovered_link_count << " links added in recovery. " + << "The corresponding categories were invalidated." << LL_ENDL; } } @@ -2277,7 +2352,10 @@ bool LLInventoryModel::loadSkeleton( cat->setVersion(NO_VERSION); LL_DEBUGS(LOG_INV) << "Invalidating category name: " << cat->getName() << " UUID: " << cat->getUUID() << " due to invalid descendents cache" << LL_ENDL; } - LL_INFOS(LOG_INV) << "Invalidated " << invalid_categories.size() << " categories due to invalid descendents cache" << LL_ENDL; + if (invalid_categories.size() > 0) + { + LL_DEBUGS(LOG_INV) << "Invalidated " << invalid_categories.size() << " categories due to invalid descendents cache" << LL_ENDL; + } // At this point, we need to set the known descendents for each // category which successfully cached so that we do not @@ -2565,10 +2643,22 @@ void LLInventoryModel::buildParentChildMap() } } - // 'My Inventory', - // root of the agent's inv found. - // The inv tree is built. - mIsAgentInvUsable = true; + LLPointer<LLInventoryValidationInfo> validation_info = validate(); + if (validation_info->mFatalErrorCount > 0) + { + // Fatal inventory error. Will not be able to engage in many inventory operations. + // This should be followed by an error dialog leading to logout. + LL_WARNS("Inventory") << "Fatal errors were found in validate(): unable to initialize inventory! " + << "Will not be able to do normal inventory operations in this session." + << LL_ENDL; + mIsAgentInvUsable = false; + } + else + { + mIsAgentInvUsable = true; + } + validation_info->mInitialized = true; + mValidationInfo = validation_info; // notifyObservers() has been moved to // llstartup/idle_startup() after this func completes. @@ -2576,11 +2666,6 @@ void LLInventoryModel::buildParentChildMap() // observers start firing. } } - - if (!gInventory.validate()) - { - LL_WARNS(LOG_INV) << "model failed validity check!" << LL_ENDL; - } } // Would normally do this at construction but that's too early @@ -3702,56 +3787,68 @@ void LLInventoryModel::dumpInventory() const } // Do various integrity checks on model, logging issues found and -// returning an overall good/bad flag. -bool LLInventoryModel::validate() const +// returning an overall good/bad flag. +LLPointer<LLInventoryValidationInfo> LLInventoryModel::validate() const { - bool valid = true; + LLPointer<LLInventoryValidationInfo> validation_info = new LLInventoryValidationInfo; + S32 fatalities = 0; + S32 warnings = 0; if (getRootFolderID().isNull()) { - LL_WARNS() << "no root folder id" << LL_ENDL; - valid = false; + LL_WARNS("Inventory") << "Fatal inventory corruption: no root folder id" << LL_ENDL; + fatalities++; } if (getLibraryRootFolderID().isNull()) { - LL_WARNS() << "no root folder id" << LL_ENDL; - valid = false; + LL_WARNS("Inventory") << "Fatal inventory corruption: no library root folder id" << LL_ENDL; + fatalities++; } if (mCategoryMap.size() + 1 != mParentChildCategoryTree.size()) { // ParentChild should be one larger because of the special entry for null uuid. - LL_INFOS() << "unexpected sizes: cat map size " << mCategoryMap.size() - << " parent/child " << mParentChildCategoryTree.size() << LL_ENDL; - valid = false; + LL_INFOS("Inventory") << "unexpected sizes: cat map size " << mCategoryMap.size() + << " parent/child " << mParentChildCategoryTree.size() << LL_ENDL; + warnings++; } S32 cat_lock = 0; S32 item_lock = 0; S32 desc_unknown_count = 0; S32 version_unknown_count = 0; + + typedef std::map<LLFolderType::EType, S32> ft_count_map; + ft_count_map ft_counts_under_root; + ft_count_map ft_counts_elsewhere; + + // Loop over all categories and check. for(cat_map_t::const_iterator cit = mCategoryMap.begin(); cit != mCategoryMap.end(); ++cit) { const LLUUID& cat_id = cit->first; const LLViewerInventoryCategory *cat = cit->second; if (!cat) { - LL_WARNS() << "invalid cat" << LL_ENDL; - valid = false; + LL_WARNS("Inventory") << "null cat" << LL_ENDL; + warnings++; continue; } + LLUUID topmost_ancestor_id; + // Will leave as null uuid on failure + getObjectTopmostAncestor(cat_id, topmost_ancestor_id); if (cat_id != cat->getUUID()) { - LL_WARNS() << "cat id/index mismatch " << cat_id << " " << cat->getUUID() << LL_ENDL; - valid = false; + LL_WARNS("Inventory") << "cat id/index mismatch " << cat_id << " " << cat->getUUID() << LL_ENDL; + warnings++; } if (cat->getParentUUID().isNull()) { if (cat_id != getRootFolderID() && cat_id != getLibraryRootFolderID()) { - LL_WARNS() << "cat " << cat_id << " has no parent, but is not root (" - << getRootFolderID() << ") or library root (" - << getLibraryRootFolderID() << ")" << LL_ENDL; + LL_WARNS("Inventory") << "cat " << cat_id << " has no parent, but is not root (" + << getRootFolderID() << ") or library root (" + << getLibraryRootFolderID() << ")" << LL_ENDL; + warnings++; } } cat_array_t* cats; @@ -3759,8 +3856,8 @@ bool LLInventoryModel::validate() const getDirectDescendentsOf(cat_id,cats,items); if (!cats || !items) { - LL_WARNS() << "invalid direct descendents for " << cat_id << LL_ENDL; - valid = false; + LL_WARNS("Inventory") << "invalid direct descendents for " << cat_id << LL_ENDL; + warnings++; continue; } if (cat->getDescendentCount() == LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN) @@ -3769,22 +3866,29 @@ bool LLInventoryModel::validate() const } else if (cats->size() + items->size() != cat->getDescendentCount()) { - LL_WARNS() << "invalid desc count for " << cat_id << " name [" << cat->getName() - << "] parent " << cat->getParentUUID() - << " cached " << cat->getDescendentCount() - << " expected " << cats->size() << "+" << items->size() - << "=" << cats->size() +items->size() << LL_ENDL; - valid = false; + // In the case of library this is not unexpected, since + // different user accounts may be getting the library + // contents from different inventory hosts. + if (topmost_ancestor_id.isNull() || topmost_ancestor_id != getLibraryRootFolderID()) + { + LL_WARNS("Inventory") << "invalid desc count for " << cat_id << " [" << getFullPath(cat) << "]" + << " cached " << cat->getDescendentCount() + << " expected " << cats->size() << "+" << items->size() + << "=" << cats->size() +items->size() << LL_ENDL; + warnings++; + } } if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) { version_unknown_count++; } - if (mCategoryLock.count(cat_id)) + auto cat_lock_it = mCategoryLock.find(cat_id); + if (cat_lock_it != mCategoryLock.end() && cat_lock_it->second) { cat_lock++; } - if (mItemLock.count(cat_id)) + auto item_lock_it = mItemLock.find(cat_id); + if (item_lock_it != mItemLock.end() && item_lock_it->second) { item_lock++; } @@ -3794,8 +3898,8 @@ bool LLInventoryModel::validate() const if (!item) { - LL_WARNS() << "null item at index " << i << " for cat " << cat_id << LL_ENDL; - valid = false; + LL_WARNS("Inventory") << "null item at index " << i << " for cat " << cat_id << LL_ENDL; + warnings++; continue; } @@ -3803,10 +3907,10 @@ bool LLInventoryModel::validate() const if (item->getParentUUID() != cat_id) { - LL_WARNS() << "wrong parent for " << item_id << " found " - << item->getParentUUID() << " expected " << cat_id - << LL_ENDL; - valid = false; + LL_WARNS("Inventory") << "wrong parent for " << item_id << " found " + << item->getParentUUID() << " expected " << cat_id + << LL_ENDL; + warnings++; } @@ -3814,17 +3918,17 @@ bool LLInventoryModel::validate() const item_map_t::const_iterator it = mItemMap.find(item_id); if (it == mItemMap.end()) { - LL_WARNS() << "item " << item_id << " found as child of " - << cat_id << " but not in top level mItemMap" << LL_ENDL; - valid = false; + LL_WARNS("Inventory") << "item " << item_id << " found as child of " + << cat_id << " but not in top level mItemMap" << LL_ENDL; + warnings++; } else { LLViewerInventoryItem *top_item = it->second; if (top_item != item) { - LL_WARNS() << "item mismatch, item_id " << item_id - << " top level entry is different, uuid " << top_item->getUUID() << LL_ENDL; + LL_WARNS("Inventory") << "item mismatch, item_id " << item_id + << " top level entry is different, uuid " << top_item->getUUID() << LL_ENDL; } } @@ -3833,19 +3937,19 @@ bool LLInventoryModel::validate() const bool found = getObjectTopmostAncestor(item_id, topmost_ancestor_id); if (!found) { - LL_WARNS() << "unable to find topmost ancestor for " << item_id << LL_ENDL; - valid = false; + LL_WARNS("Inventory") << "unable to find topmost ancestor for " << item_id << LL_ENDL; + warnings++; } else { if (topmost_ancestor_id != getRootFolderID() && topmost_ancestor_id != getLibraryRootFolderID()) { - LL_WARNS() << "unrecognized top level ancestor for " << item_id - << " got " << topmost_ancestor_id - << " expected " << getRootFolderID() - << " or " << getLibraryRootFolderID() << LL_ENDL; - valid = false; + LL_WARNS("Inventory") << "unrecognized top level ancestor for " << item_id + << " got " << topmost_ancestor_id + << " expected " << getRootFolderID() + << " or " << getLibraryRootFolderID() << LL_ENDL; + warnings++; } } } @@ -3859,9 +3963,9 @@ bool LLInventoryModel::validate() const getDirectDescendentsOf(parent_id,cats,items); if (!cats) { - LL_WARNS() << "cat " << cat_id << " name [" << cat->getName() - << "] orphaned - no child cat array for alleged parent " << parent_id << LL_ENDL; - valid = false; + LL_WARNS("Inventory") << "cat " << cat_id << " name [" << cat->getName() + << "] orphaned - no child cat array for alleged parent " << parent_id << LL_ENDL; + warnings++; } else { @@ -3877,27 +3981,56 @@ bool LLInventoryModel::validate() const } if (!found) { - LL_WARNS() << "cat " << cat_id << " name [" << cat->getName() - << "] orphaned - not found in child cat array of alleged parent " << parent_id << LL_ENDL; + LL_WARNS("Inventory") << "cat " << cat_id << " name [" << cat->getName() + << "] orphaned - not found in child cat array of alleged parent " << parent_id << LL_ENDL; + } + } + } + + // Update count of preferred types + LLFolderType::EType folder_type = cat->getPreferredType(); + bool cat_is_in_library = false; + LLUUID topmost_id; + if (getObjectTopmostAncestor(cat->getUUID(),topmost_id) && topmost_id == getLibraryRootFolderID()) + { + cat_is_in_library = true; + } + if (!cat_is_in_library) + { + if (getRootFolderID().notNull() && (cat->getUUID()==getRootFolderID() || cat->getParentUUID()==getRootFolderID())) + { + ft_counts_under_root[folder_type]++; + if (folder_type != LLFolderType::FT_NONE) + { + LL_DEBUGS("Inventory") << "Under root cat: " << getFullPath(cat) << " folder_type " << folder_type << LL_ENDL; + } + } + else + { + ft_counts_elsewhere[folder_type]++; + if (folder_type != LLFolderType::FT_NONE) + { + LL_DEBUGS("Inventory") << "Elsewhere cat: " << getFullPath(cat) << " folder_type " << folder_type << LL_ENDL; } } } } + // Loop over all items and check for(item_map_t::const_iterator iit = mItemMap.begin(); iit != mItemMap.end(); ++iit) { const LLUUID& item_id = iit->first; LLViewerInventoryItem *item = iit->second; if (item->getUUID() != item_id) { - LL_WARNS() << "item_id " << item_id << " does not match " << item->getUUID() << LL_ENDL; - valid = false; + LL_WARNS("Inventory") << "item_id " << item_id << " does not match " << item->getUUID() << LL_ENDL; + warnings++; } const LLUUID& parent_id = item->getParentUUID(); if (parent_id.isNull()) { - LL_WARNS() << "item " << item_id << " name [" << item->getName() << "] has null parent id!" << LL_ENDL; + LL_WARNS("Inventory") << "item " << item_id << " name [" << item->getName() << "] has null parent id!" << LL_ENDL; } else { @@ -3906,8 +4039,8 @@ bool LLInventoryModel::validate() const getDirectDescendentsOf(parent_id,cats,items); if (!items) { - LL_WARNS() << "item " << item_id << " name [" << item->getName() - << "] orphaned - alleged parent has no child items list " << parent_id << LL_ENDL; + LL_WARNS("Inventory") << "item " << item_id << " name [" << item->getName() + << "] orphaned - alleged parent has no child items list " << parent_id << LL_ENDL; } else { @@ -3922,8 +4055,8 @@ bool LLInventoryModel::validate() const } if (!found) { - LL_WARNS() << "item " << item_id << " name [" << item->getName() - << "] orphaned - not found as child of alleged parent " << parent_id << LL_ENDL; + LL_WARNS("Inventory") << "item " << item_id << " name [" << item->getName() + << "] orphaned - not found as child of alleged parent " << parent_id << LL_ENDL; } } @@ -3938,30 +4071,30 @@ bool LLInventoryModel::validate() const // Linked-to UUID should have back reference to this link. if (!hasBacklinkInfo(link_id, target_id)) { - LL_WARNS() << "link " << item->getUUID() << " type " << item->getActualType() - << " missing backlink info at target_id " << target_id - << LL_ENDL; + LL_WARNS("Inventory") << "link " << item->getUUID() << " type " << item->getActualType() + << " missing backlink info at target_id " << target_id + << LL_ENDL; } // Links should have referents. if (item->getActualType() == LLAssetType::AT_LINK && !target_item) { - LL_WARNS() << "broken item link " << item->getName() << " id " << item->getUUID() << LL_ENDL; + LL_WARNS("Inventory") << "broken item link " << item->getName() << " id " << item->getUUID() << LL_ENDL; } else if (item->getActualType() == LLAssetType::AT_LINK_FOLDER && !target_cat) { - LL_WARNS() << "broken folder link " << item->getName() << " id " << item->getUUID() << LL_ENDL; + LL_WARNS("Inventory") << "broken folder link " << item->getName() << " id " << item->getUUID() << LL_ENDL; } if (target_item && target_item->getIsLinkType()) { - LL_WARNS() << "link " << item->getName() << " references a link item " - << target_item->getName() << " " << target_item->getUUID() << LL_ENDL; + LL_WARNS("Inventory") << "link " << item->getName() << " references a link item " + << target_item->getName() << " " << target_item->getUUID() << LL_ENDL; } // Links should not have backlinks. std::pair<backlink_mmap_t::const_iterator, backlink_mmap_t::const_iterator> range = mBacklinkMMap.equal_range(link_id); if (range.first != range.second) { - LL_WARNS() << "Link item " << item->getName() << " has backlinks!" << LL_ENDL; + LL_WARNS("Inventory") << "Link item " << item->getName() << " has backlinks!" << LL_ENDL; } } else @@ -3975,29 +4108,116 @@ bool LLInventoryModel::validate() const LLViewerInventoryItem *link_item = getItem(link_id); if (!link_item || !link_item->getIsLinkType()) { - LL_WARNS() << "invalid backlink from target " << item->getName() << " to " << link_id << LL_ENDL; + LL_WARNS("Inventory") << "invalid backlink from target " << item->getName() << " to " << link_id << LL_ENDL; } } } } - + + // Check system folders + for (auto fit=ft_counts_under_root.begin(); fit != ft_counts_under_root.end(); ++fit) + { + LL_DEBUGS("Inventory") << "Folder type " << fit->first << " count " << fit->second << " under root" << LL_ENDL; + } + for (auto fit=ft_counts_elsewhere.begin(); fit != ft_counts_elsewhere.end(); ++fit) + { + LL_DEBUGS("Inventory") << "Folder type " << fit->first << " count " << fit->second << " elsewhere" << LL_ENDL; + } + + static LLCachedControl<bool> fake_system_folder_issues(gSavedSettings, "QAModeFakeSystemFolderIssues", false); + static std::default_random_engine e{}; + static std::uniform_int_distribution<> distrib(0, 1); + for (S32 ft=LLFolderType::FT_TEXTURE; ft<LLFolderType::FT_COUNT; ft++) + { + LLFolderType::EType folder_type = static_cast<LLFolderType::EType>(ft); + if (LLFolderType::lookup(folder_type)==LLFolderType::badLookup()) + { + continue; + } + bool is_automatic = LLFolderType::lookupIsAutomaticType(folder_type); + bool is_singleton = LLFolderType::lookupIsSingletonType(folder_type); + S32 count_under_root = ft_counts_under_root[folder_type]; + S32 count_elsewhere = ft_counts_elsewhere[folder_type]; + if (fake_system_folder_issues) + { + // Force all counts to be either 0 or 2, thus flagged as an error. + count_under_root = 2*distrib(e); + count_elsewhere = 2*distrib(e); + } + if (is_singleton) + { + if (count_under_root==0) + { + LL_WARNS("Inventory") << "Expected system folder type " << ft << " was not found under root" << LL_ENDL; + // Need to create, if allowed. + if (is_automatic) + { + LL_WARNS("Inventory") << "Fatal inventory corruption: cannot create system folder of type " << ft << LL_ENDL; + fatalities++; + validation_info->mMissingRequiredSystemFolders.insert(LLFolderType::EType(ft)); + } + else + { + // Can create, and will when needed. + warnings++; + } + } + else if (count_under_root > 1) + { + LL_WARNS("Inventory") << "Fatal inventory corruption: system folder type has excess copies under root, type " << ft << " count " << count_under_root << LL_ENDL; + validation_info->mDuplicateRequiredSystemFolders.insert(LLFolderType::EType(ft)); + fatalities++; + } + if (count_elsewhere > 0) + { + LL_WARNS("Inventory") << "Found " << count_elsewhere << " extra folders of type " << ft << " outside of root" << LL_ENDL; + warnings++; + } + } + } + + if (cat_lock > 0 || item_lock > 0) { - LL_INFOS() << "Found locks on some categories: sub-cat arrays " + LL_INFOS("Inventory") << "Found locks on some categories: sub-cat arrays " << cat_lock << ", item arrays " << item_lock << LL_ENDL; } if (desc_unknown_count != 0) { - LL_INFOS() << "Found " << desc_unknown_count << " cats with unknown descendent count" << LL_ENDL; + LL_DEBUGS() << "Found " << desc_unknown_count << " cats with unknown descendent count" << LL_ENDL; } if (version_unknown_count != 0) { - LL_INFOS() << "Found " << version_unknown_count << " cats with unknown version" << LL_ENDL; + LL_DEBUGS("Inventory") << "Found " << version_unknown_count << " cats with unknown version" << LL_ENDL; } - LL_INFOS() << "Validate done, valid = " << (U32) valid << LL_ENDL; + // FIXME need to fail login and tell user to retry, contact support if problem persists. + bool valid = (fatalities == 0); + LL_INFOS("Inventory") << "Validate done, fatal errors: " << fatalities << ", warnings: " << warnings << ", valid: " << valid << LL_ENDL; + + validation_info->mFatalErrorCount = fatalities; + validation_info->mWarningCount = warnings; - return valid; + return validation_info; +} + +// Provides a unix-style path from root, like "/My Inventory/Clothing/.../myshirt" +std::string LLInventoryModel::getFullPath(const LLInventoryObject *obj) const +{ + std::vector<std::string> path_elts; + std::map<LLUUID,bool> visited; + while (obj != NULL && !visited[obj->getUUID()]) + { + path_elts.push_back(obj->getName()); + // avoid infinite loop in the unlikely event of a cycle + visited[obj->getUUID()] = true; + obj = getObject(obj->getParentUUID()); + } + std::stringstream s; + std::string delim("/"); + std::reverse(path_elts.begin(), path_elts.end()); + std::string result = "/" + boost::algorithm::join(path_elts, delim); + return result; } ///---------------------------------------------------------------------------- diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h index a4326aaeed..bddaf3a147 100644 --- a/indra/newview/llinventorymodel.h +++ b/indra/newview/llinventorymodel.h @@ -55,7 +55,26 @@ class LLInventoryCategory; class LLMessageSystem; class LLInventoryCollectFunctor; -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +///---------------------------------------------------------------------------- +/// LLInventoryValidationInfo +///---------------------------------------------------------------------------- +class LLInventoryValidationInfo: public LLRefCount +{ +public: + LLInventoryValidationInfo(); + void toOstream(std::ostream& os) const; + void asLLSD(LLSD& sd) const; + + + S32 mFatalErrorCount; + S32 mWarningCount; + bool mInitialized; + std::set<LLFolderType::EType> mMissingRequiredSystemFolders; + std::set<LLFolderType::EType> mDuplicateRequiredSystemFolders; +}; +std::ostream& operator<<(std::ostream& s, const LLInventoryValidationInfo& v); + +///---------------------------------------------------------------------------- // LLInventoryModel // // Represents a collection of inventory, and provides efficient ways to access @@ -63,7 +82,7 @@ class LLInventoryCollectFunctor; // NOTE: This class could in theory be used for any place where you need // inventory, though it optimizes for time efficiency - not space efficiency, // probably making it inappropriate for use on tasks. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +///---------------------------------------------------------------------------- class LLInventoryModel { LOG_CLASS(LLInventoryModel); @@ -656,7 +675,9 @@ private: //-------------------------------------------------------------------- public: void dumpInventory() const; - bool validate() const; + LLPointer<LLInventoryValidationInfo> validate() const; + LLPointer<LLInventoryValidationInfo> mValidationInfo; + std::string getFullPath(const LLInventoryObject *obj) const; /** Miscellaneous ** ** diff --git a/indra/newview/llpanellandmarkinfo.cpp b/indra/newview/llpanellandmarkinfo.cpp index 6751c25fb9..5b24800038 100644 --- a/indra/newview/llpanellandmarkinfo.cpp +++ b/indra/newview/llpanellandmarkinfo.cpp @@ -360,6 +360,11 @@ void LLPanelLandmarkInfo::toggleLandmarkEditMode(BOOL enabled) setFocus(TRUE); } +void LLPanelLandmarkInfo::setCanEdit(BOOL enabled) +{ + getChild<LLButton>("edit_btn")->setEnabled(enabled); +} + const std::string& LLPanelLandmarkInfo::getLandmarkTitle() const { return mLandmarkTitleEditor->getText(); diff --git a/indra/newview/llpanellandmarkinfo.h b/indra/newview/llpanellandmarkinfo.h index 9712736182..f303d87ccf 100644 --- a/indra/newview/llpanellandmarkinfo.h +++ b/indra/newview/llpanellandmarkinfo.h @@ -51,6 +51,7 @@ public: void displayItemInfo(const LLInventoryItem* pItem); void toggleLandmarkEditMode(BOOL enabled); + void setCanEdit(BOOL enabled); const std::string& getLandmarkTitle() const; const std::string getLandmarkNotes() const; diff --git a/indra/newview/llpanelplaces.cpp b/indra/newview/llpanelplaces.cpp index 53870fb5c7..5dfeb7f619 100644 --- a/indra/newview/llpanelplaces.cpp +++ b/indra/newview/llpanelplaces.cpp @@ -430,10 +430,15 @@ void LLPanelPlaces::onOpen(const LLSD& key) { mLandmarkInfo->setInfoType(LLPanelPlaceInfo::LANDMARK); - LLInventoryItem* item = gInventory.getItem(key["id"].asUUID()); + LLUUID id = key["id"].asUUID(); + LLInventoryItem* item = gInventory.getItem(id); if (!item) return; + BOOL is_editable = gInventory.isObjectDescendentOf(id, gInventory.getRootFolderID()) + && item->getPermissions().allowModifyBy(gAgent.getID()); + mLandmarkInfo->setCanEdit(is_editable); + setItem(item); } else if (mPlaceInfoType == REMOTE_PLACE_INFO_TYPE) diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index e2a39bdf86..3f22a26474 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -142,6 +142,7 @@ #include "lltoolmgr.h" #include "lltrans.h" #include "llui.h" +#include "lluiusage.h" #include "llurldispatcher.h" #include "llurlentry.h" #include "llslurl.h" @@ -1810,6 +1811,13 @@ bool idle_startup() // This method MUST be called before gInventory.findCategoryUUIDForType because of // gInventory.mIsAgentInvUsable is set to true in the gInventory.buildParentChildMap. gInventory.buildParentChildMap(); + + // If buildParentChildMap succeeded, inventory will now be in + // a usable state and gInventory.isInventoryUsable() will be + // true. + + // FIXME if inventory is unusable, we need to bail out. + gInventory.createCommonSystemCategories(); // It's debatable whether this flag is a good idea - sets all @@ -1853,6 +1861,7 @@ bool idle_startup() display_startup(); LLStartUp::setStartupState( STATE_MISC ); display_startup(); + return FALSE; } @@ -2260,6 +2269,8 @@ bool idle_startup() gAgentAvatarp->sendHoverHeight(); + LLUIUsage::instance().clear(); + return TRUE; } diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 458fc3b13d..1385eb0609 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -116,7 +116,6 @@ #include "llviewerregion.h" #include "llfloaterregionrestarting.h" -#include <boost/algorithm/string/split.hpp> // #include <boost/foreach.hpp> #include "llnotificationmanager.h" // @@ -3852,7 +3851,12 @@ void process_sound_trigger(LLMessageSystem *msg, void **) } // Don't play sounds from gestures if they are not enabled. - if (object_id == owner_id && !gSavedSettings.getBOOL("EnableGestureSounds")) + // Do play sounds triggered by avatar, since muting your own + // gesture sounds and your own sounds played inworld from + // Inventory can cause confusion. + if (object_id == owner_id + && owner_id != gAgentID + && !gSavedSettings.getBOOL("EnableGestureSounds")) { return; } diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index 05f88b0a75..606e679ab1 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -63,6 +63,8 @@ #include "llsdutil.h" #include "llcorehttputil.h" #include "llvoicevivox.h" +#include "llinventorymodel.h" +#include "lluiusage.h" namespace LLStatViewer { @@ -577,6 +579,13 @@ void send_viewer_stats(bool include_preferences) fail["invalid"] = (S32) gMessageSystem->mInvalidOnCircuitPackets; fail["missing_updater"] = (S32) LLAppViewer::instance()->isUpdaterMissing(); + LLSD &inventory = body["inventory"]; + inventory["usable"] = gInventory.isInventoryUsable(); + LLSD& validation_info = inventory["validation_info"]; + gInventory.mValidationInfo->asLLSD(validation_info); + + body["ui"] = LLUIUsage::instance().asLLSD(); + body["stats"]["voice"] = LLVoiceVivoxStats::getInstance()->read(); // Misc stats, two strings and two ints diff --git a/indra/newview/llworldmipmap.cpp b/indra/newview/llworldmipmap.cpp index a2e519a61a..040d0deaf3 100644 --- a/indra/newview/llworldmipmap.cpp +++ b/indra/newview/llworldmipmap.cpp @@ -190,6 +190,8 @@ LLPointer<LLViewerFetchedTexture> LLWorldMipmap::loadObjectsTile(U32 grid_x, U32 //LL_INFOS("WorldMap") << "LLWorldMipmap::loadObjectsTile(), URL = " << imageurl << LL_ENDL; LLPointer<LLViewerFetchedTexture> img = LLViewerTextureManager::getFetchedTextureFromUrl(imageurl, FTT_MAP_TILE, TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + LL_INFOS("MAPURL") << "fetching map tile from " << imageurl << LL_ENDL; + img->setBoostLevel(LLGLTexture::BOOST_MAP); // Return the smart pointer |