From 6f224ca6db06a95f5d05c1320c982e7f28088c02 Mon Sep 17 00:00:00 2001 From: Darl Date: Wed, 1 Apr 2026 11:36:51 -0500 Subject: [PATCH 1/8] Allow mute list to fallback to cached copy when simulator response somehow isn't received Signed-off-by: Darl --- indra/newview/llmutelist.cpp | 130 ++++++++++++++++++++++++++--------- indra/newview/llmutelist.h | 21 +++++- 2 files changed, 117 insertions(+), 34 deletions(-) diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index b7bba02b9d8..d9e674a423c 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -92,7 +92,7 @@ class LLDispatchEmptyMuteList : public LLDispatchHandler const LLUUID& invoice, const sparam_t& strings) { - LLMuteList::getInstance()->setLoaded(); + LLMuteList::getInstance()->setLoaded(LLMuteList::MLS_SERVER_EMPTY); return true; } }; @@ -155,7 +155,9 @@ std::string LLMute::getDisplayType() const //----------------------------------------------------------------------------- LLMuteList::LLMuteList() : mLoadState(ML_INITIAL), - mRequestStartTime(0.f) + mLoadSource(MLS_NONE), + mRequestStartTime(0.f), + mTriedCacheFallback(false) { gGenericDispatcher.addHandler("emptymutelist", &sDispatchEmptyMuteList); @@ -210,7 +212,7 @@ bool LLMuteList::isLinden(const std::string& name) return last_name == "linden"; } -bool LLMuteList::getLoadFailed() const +bool LLMuteList::getLoadFailed() { if (mLoadState == ML_FAILED) { @@ -221,12 +223,78 @@ bool LLMuteList::getLoadFailed() const constexpr F64 WAIT_SECONDS = 30; if (mRequestStartTime + WAIT_SECONDS < LLTimer::getTotalSeconds()) { - return true; + LL_WARNS() << "Mute list request timed out; trying cache fallback once" << LL_ENDL; + tryLoadCacheFallback(gAgent.getID(), "request timeout"); + return mLoadState == ML_FAILED; } } return false; } +const char* LLMuteList::sourceToString(EMuteListSource source) +{ + switch (source) + { + case MLS_NONE: + return "none"; + case MLS_SERVER: + return "server"; + case MLS_SERVER_EMPTY: + return "server-empty"; + case MLS_SERVER_CACHE: + return "server-cached"; + case MLS_FALLBACK_CACHE: + return "fallback-cache"; + default: + return "unknown"; + } +} + +std::string LLMuteList::getCacheFilename(const LLUUID& agent_id) const +{ + std::string agent_id_string; + agent_id.toString(agent_id_string); + return gDirUtilp->getExpandedFilename(LL_PATH_CACHE, agent_id_string) + ".cached_mute"; +} + +void LLMuteList::setFailed(const std::string& reason) +{ + mLoadState = ML_FAILED; + if (mLoadSource == MLS_NONE) + { + LL_WARNS() << "Mute list unavailable: " << reason << LL_ENDL; + } + else + { + LL_WARNS() << "Mute list unavailable: " << reason << " (last source=" << sourceToString(mLoadSource) << ")" << LL_ENDL; + } +} + +bool LLMuteList::tryLoadCacheFallback(const LLUUID& agent_id, const std::string& reason) +{ + if (mTriedCacheFallback) + { + if (!isLoaded()) + { + setFailed("cache fallback already attempted before " + reason); + } + return isLoaded(); + } + + mTriedCacheFallback = true; + const std::string filename = getCacheFilename(agent_id); + LL_INFOS() << "Trying mute list cache fallback due to " << reason << ": " << filename << LL_ENDL; + + if (loadFromFile(filename, MLS_FALLBACK_CACHE)) + { + LL_WARNS() << "Loaded mute list from cache fallback due to " << reason << LL_ENDL; + return true; + } + + setFailed("cache fallback failed after " + reason); + return false; +} + static LLVOAvatar* find_avatar(const LLUUID& id) { LLViewerObject *obj = gObjectList.findObject(id); @@ -580,14 +648,14 @@ std::vector LLMuteList::getMutes() const //----------------------------------------------------------------------------- // loadFromFile() //----------------------------------------------------------------------------- -bool LLMuteList::loadFromFile(const std::string& filename) +bool LLMuteList::loadFromFile(const std::string& filename, EMuteListSource source) { LL_PROFILE_ZONE_SCOPED; if(!filename.size()) { LL_WARNS() << "Mute List Filename is Empty!" << LL_ENDL; - mLoadState = ML_FAILED; + setFailed("empty filename"); return false; } @@ -595,10 +663,14 @@ bool LLMuteList::loadFromFile(const std::string& filename) if (!fp) { LL_WARNS() << "Couldn't open mute list " << filename << LL_ENDL; - mLoadState = ML_FAILED; + setFailed("cannot open " + filename); return false; } + // Replace previous server-backed state so fallback can be superseded by authoritative data. + mMutes.clear(); + mLegacyMutes.clear(); + // *NOTE: Changing the size of these buffers will require changes // in the scanf below. char id_buffer[MAX_STRING]; /*Flawfinder: ignore*/ @@ -627,7 +699,7 @@ bool LLMuteList::loadFromFile(const std::string& filename) } } fclose(fp); - setLoaded(); + setLoaded(source); // server does not maintain up-to date account names (not display names!) // in this list, so it falls to viewer. @@ -737,12 +809,11 @@ bool LLMuteList::isMuted(const std::string& username, U32 flags) const //----------------------------------------------------------------------------- void LLMuteList::requestFromServer(const LLUUID& agent_id) { - std::string agent_id_string; - std::string filename; - agent_id.toString(agent_id_string); - filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute"; + const std::string filename = getCacheFilename(agent_id); LLCRC crc; crc.update(filename); + mTriedCacheFallback = false; + mLoadSource = MLS_NONE; LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_MuteListRequest); @@ -755,17 +826,17 @@ void LLMuteList::requestFromServer(const LLUUID& agent_id) if (gDisconnected) { LL_WARNS() << "Trying to request mute list when disconnected!" << LL_ENDL; - mLoadState = ML_FAILED; + tryLoadCacheFallback(agent_id, "disconnected before request"); return; } if (!gAgent.getRegion()) { LL_WARNS() << "No region for agent yet, skipping mute list request!" << LL_ENDL; - mLoadState = ML_FAILED; + tryLoadCacheFallback(agent_id, "no region for request"); return; } mLoadState = ML_REQUESTED; - mRequestStartTime = LLTimer::getElapsedSeconds(); + mRequestStartTime = LLTimer::getTotalSeconds(); // Double amount of retries due to this request happening during busy stage // Ideally this should be turned into a capability gMessageSystem->sendReliable(gAgent.getRegionHost(), LL_DEFAULT_RELIABLE_RETRIES * 2, true, LL_PING_BASED_TIMEOUT_DUMMY, NULL, NULL); @@ -777,15 +848,16 @@ void LLMuteList::requestFromServer(const LLUUID& agent_id) void LLMuteList::cache(const LLUUID& agent_id) { - // Write to disk even if empty. - if(isLoaded()) + // Write to disk even if empty, but never from degraded fallback state. + if (isLoaded() && mLoadSource != MLS_FALLBACK_CACHE) { - std::string agent_id_string; - std::string filename; - agent_id.toString(agent_id_string); - filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute"; + const std::string filename = getCacheFilename(agent_id); saveToFile(filename); } + else if (isLoaded()) + { + LL_WARNS() << "Skipping mute list cache write from fallback-only state" << LL_ENDL; + } } //----------------------------------------------------------------------------- @@ -812,7 +884,7 @@ void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**) LLMuteList* mute_list = getInstance(); mute_list->mLoadState = ML_REQUESTED; - mute_list->mRequestStartTime = LLTimer::getElapsedSeconds(); + mute_list->mRequestStartTime = LLTimer::getTotalSeconds(); // Todo: Based of logs and testing, there is no callback // from server if file doesn't exist server side. @@ -831,12 +903,7 @@ void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**) void LLMuteList::processUseCachedMuteList(LLMessageSystem* msg, void**) { LL_INFOS() << "LLMuteList::processUseCachedMuteList()" << LL_ENDL; - - std::string agent_id_string; - gAgent.getID().toString(agent_id_string); - std::string filename; - filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute"; - LLMuteList::getInstance()->loadFromFile(filename); + LLMuteList::getInstance()->loadFromFile(LLMuteList::getInstance()->getCacheFilename(gAgent.getID()), MLS_SERVER_CACHE); } void LLMuteList::onFileMuteList(void** user_data, S32 error_code, LLExtStat ext_status) @@ -845,13 +912,13 @@ void LLMuteList::onFileMuteList(void** user_data, S32 error_code, LLExtStat ext_ if(local_filename_and_path && !local_filename_and_path->empty() && (error_code == 0)) { LL_INFOS() << "Received mute list from server" << LL_ENDL; - LLMuteList::getInstance()->loadFromFile(*local_filename_and_path); + LLMuteList::getInstance()->loadFromFile(*local_filename_and_path, MLS_SERVER); LLFile::remove(*local_filename_and_path); } else { LL_INFOS() << "LLMuteList xfer failed with code " << error_code << LL_ENDL; - LLMuteList::getInstance()->mLoadState = ML_FAILED; + LLMuteList::getInstance()->tryLoadCacheFallback(gAgent.getID(), "xfer failure"); } delete local_filename_and_path; } @@ -908,9 +975,10 @@ void LLMuteList::removeObserver(LLMuteListObserver* observer) mObservers.erase(observer); } -void LLMuteList::setLoaded() +void LLMuteList::setLoaded(EMuteListSource source) { mLoadState = ML_LOADED; + mLoadSource = source; notifyObservers(); } diff --git a/indra/newview/llmutelist.h b/indra/newview/llmutelist.h index b65fd61fccf..c243b5a2bda 100644 --- a/indra/newview/llmutelist.h +++ b/indra/newview/llmutelist.h @@ -82,6 +82,15 @@ class LLMuteList : public LLSingleton ML_LOADED, ML_FAILED, }; + + enum EMuteListSource + { + MLS_NONE, + MLS_SERVER, + MLS_SERVER_EMPTY, + MLS_SERVER_CACHE, + MLS_FALLBACK_CACHE, + }; public: // reasons for auto-unmuting a resident enum EAutoReason @@ -116,7 +125,7 @@ class LLMuteList : public LLSingleton static bool isLinden(const std::string& name); bool isLoaded() const { return mLoadState == ML_LOADED; } - bool getLoadFailed() const; + bool getLoadFailed(); std::vector getMutes() const; @@ -127,10 +136,14 @@ class LLMuteList : public LLSingleton void cache(const LLUUID& agent_id); private: - bool loadFromFile(const std::string& filename); + bool loadFromFile(const std::string& filename, EMuteListSource source); bool saveToFile(const std::string& filename); + bool tryLoadCacheFallback(const LLUUID& agent_id, const std::string& reason); + void setFailed(const std::string& reason); + static const char* sourceToString(EMuteListSource source); + std::string getCacheFilename(const LLUUID& agent_id) const; - void setLoaded(); + void setLoaded(EMuteListSource source); void notifyObservers(); void notifyObserversDetailed(const LLMute &mute); @@ -177,7 +190,9 @@ class LLMuteList : public LLSingleton observer_set_t mObservers; EMuteListState mLoadState; + EMuteListSource mLoadSource; F64 mRequestStartTime; + bool mTriedCacheFallback; friend class LLDispatchEmptyMuteList; }; From 92b92db47f6c2235558f75e1a87ef9a08bde5081 Mon Sep 17 00:00:00 2001 From: Darl Date: Thu, 2 Apr 2026 10:23:50 -0500 Subject: [PATCH 2/8] Treat emptymutelist dispatch from the simulator as authoritative when in a cache fallback status. This yields to the simulator's responsibility as source-of-truth. e.g. Bob has Alice blocked for a while across all his devices 1. Bob unblocked Alice on his laptop 2. Bob logs in on his desktop with a cached mutelist 3. LLDispatchEmptyMuteList fires 4. LLMuteList becomes eventually-correct, reflecting most recent signaled user intent Signed-off-by: Darl --- indra/newview/llmutelist.cpp | 13 ++++++++++--- indra/newview/llmutelist.h | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index d9e674a423c..154e8261968 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -92,7 +92,9 @@ class LLDispatchEmptyMuteList : public LLDispatchHandler const LLUUID& invoice, const sparam_t& strings) { - LLMuteList::getInstance()->setLoaded(LLMuteList::MLS_SERVER_EMPTY); + LLMuteList* mute_list = LLMuteList::getInstance(); + mute_list->clearCachedMutes(); + mute_list->setLoaded(LLMuteList::MLS_SERVER_EMPTY); return true; } }; @@ -231,6 +233,12 @@ bool LLMuteList::getLoadFailed() return false; } +void LLMuteList::clearCachedMutes() +{ + mMutes.clear(); + mLegacyMutes.clear(); +} + const char* LLMuteList::sourceToString(EMuteListSource source) { switch (source) @@ -668,8 +676,7 @@ bool LLMuteList::loadFromFile(const std::string& filename, EMuteListSource sourc } // Replace previous server-backed state so fallback can be superseded by authoritative data. - mMutes.clear(); - mLegacyMutes.clear(); + clearCachedMutes(); // *NOTE: Changing the size of these buffers will require changes // in the scanf below. diff --git a/indra/newview/llmutelist.h b/indra/newview/llmutelist.h index c243b5a2bda..34857e20061 100644 --- a/indra/newview/llmutelist.h +++ b/indra/newview/llmutelist.h @@ -136,6 +136,7 @@ class LLMuteList : public LLSingleton void cache(const LLUUID& agent_id); private: + void clearCachedMutes(); bool loadFromFile(const std::string& filename, EMuteListSource source); bool saveToFile(const std::string& filename); bool tryLoadCacheFallback(const LLUUID& agent_id, const std::string& reason); From abca0d971648dfb4daca19448ece2f0b76169f0d Mon Sep 17 00:00:00 2001 From: Darl Date: Thu, 2 Apr 2026 11:27:48 -0500 Subject: [PATCH 3/8] Rename LLMuteList state machine touch points for clarity getLoadFailed -> updateLoadState - No longer labeled as a plain getter, but instead as a state machine advancement point - This name reflects its role in advancing the state according to design parameters when called from the idle loop - Call site in LLIMProcessing::requestOfflineMessages simplified by internalizing our readiness checks isFailed - Reintroduced const to match isLoaded for determining state Signed-off-by: Darl --- indra/newview/llimprocessing.cpp | 2 +- indra/newview/llmutelist.cpp | 6 +++--- indra/newview/llmutelist.h | 6 +++++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/indra/newview/llimprocessing.cpp b/indra/newview/llimprocessing.cpp index b1e42e11fb0..3f60dc5d26b 100644 --- a/indra/newview/llimprocessing.cpp +++ b/indra/newview/llimprocessing.cpp @@ -1534,7 +1534,7 @@ void LLIMProcessing::requestOfflineMessages() && isAgentAvatarValid() && gAgent.getRegion() && gAgent.getRegion()->capabilitiesReceived() - && (LLMuteList::getInstance()->isLoaded() || LLMuteList::getInstance()->getLoadFailed())) + && LLMuteList::getInstance()->updateLoadState()) { std::string cap_url = gAgent.getRegionCapability("ReadOfflineMsgs"); diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index 154e8261968..02a5a43c6bc 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -214,9 +214,9 @@ bool LLMuteList::isLinden(const std::string& name) return last_name == "linden"; } -bool LLMuteList::getLoadFailed() +bool LLMuteList::updateLoadState() { - if (mLoadState == ML_FAILED) + if (isLoaded() || isFailed()) { return true; } @@ -227,7 +227,7 @@ bool LLMuteList::getLoadFailed() { LL_WARNS() << "Mute list request timed out; trying cache fallback once" << LL_ENDL; tryLoadCacheFallback(gAgent.getID(), "request timeout"); - return mLoadState == ML_FAILED; + return isLoaded() || isFailed(); } } return false; diff --git a/indra/newview/llmutelist.h b/indra/newview/llmutelist.h index 34857e20061..ff010a02e89 100644 --- a/indra/newview/llmutelist.h +++ b/indra/newview/llmutelist.h @@ -125,7 +125,11 @@ class LLMuteList : public LLSingleton static bool isLinden(const std::string& name); bool isLoaded() const { return mLoadState == ML_LOADED; } - bool getLoadFailed(); + bool isFailed() const { return mLoadState == ML_FAILED; } + + // Advance the load state machine, trying cache fallback if necessary. + // Return value indicates mute list consumption readiness. + bool updateLoadState(); std::vector getMutes() const; From 34ca50b144ea2cd3663ece7868e82c438d637fd0 Mon Sep 17 00:00:00 2001 From: Darl Date: Thu, 2 Apr 2026 11:56:42 -0500 Subject: [PATCH 4/8] Eliminate pointless mutelist cache fallback attempt, instead manually set state to guard against a possible cache write to disk to be safe Signed-off-by: Darl --- indra/newview/llmutelist.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index 02a5a43c6bc..39588ba51d4 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -833,7 +833,9 @@ void LLMuteList::requestFromServer(const LLUUID& agent_id) if (gDisconnected) { LL_WARNS() << "Trying to request mute list when disconnected!" << LL_ENDL; - tryLoadCacheFallback(agent_id, "disconnected before request"); + // Guard against potentially writing back to disk since we're not recovering our connection + mLoadState = ML_LOADED; + mLoadSource = MLS_FALLBACK_CACHE; return; } if (!gAgent.getRegion()) From 3fd1d6a650cacd821051b91408fe9043b49adbc0 Mon Sep 17 00:00:00 2001 From: Darl Date: Fri, 3 Apr 2026 18:33:00 -0500 Subject: [PATCH 5/8] Improve documentation and logging of LLMuteList changes Signed-off-by: Darl --- indra/newview/llmutelist.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index 39588ba51d4..f8bdc453b2e 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -92,8 +92,16 @@ class LLDispatchEmptyMuteList : public LLDispatchHandler const LLUUID& invoice, const sparam_t& strings) { + // We've gotten a message from the server that indicates our mute list is empty there. + + // First we want to make sure that if we had a cached mute list, we clear it assuming it is outdated. LLMuteList* mute_list = LLMuteList::getInstance(); - mute_list->clearCachedMutes(); + if(mute_list->mLoadSource == LLMuteList::MLS_FALLBACK_CACHE && !mute_list->getMutes().empty()) + { + LL_WARNS() << "Our current mute list is not empty, but the server says that it should be. Is our cache outdated, or did our mutes get lost?" << LL_ENDL; + mute_list->clearCachedMutes(); + } + // Lastly we set the load state to loaded with the source of server empty. We are now in a clean and ready state. mute_list->setLoaded(LLMuteList::MLS_SERVER_EMPTY); return true; } @@ -237,6 +245,7 @@ void LLMuteList::clearCachedMutes() { mMutes.clear(); mLegacyMutes.clear(); + LL_WARNS() << "Cached mutes cleared" << LL_ENDL; } const char* LLMuteList::sourceToString(EMuteListSource source) @@ -276,6 +285,7 @@ void LLMuteList::setFailed(const std::string& reason) { LL_WARNS() << "Mute list unavailable: " << reason << " (last source=" << sourceToString(mLoadSource) << ")" << LL_ENDL; } + notifyObservers(); } bool LLMuteList::tryLoadCacheFallback(const LLUUID& agent_id, const std::string& reason) @@ -880,7 +890,7 @@ void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**) msg->getUUIDFast(_PREHASH_MuteData, _PREHASH_AgentID, agent_id); if(agent_id != gAgent.getID()) { - LL_WARNS() << "Got an mute list update for the wrong agent." << LL_ENDL; + LL_WARNS() << "Got a mute list update for the wrong agent." << LL_ENDL; return; } std::string unclean_filename; @@ -986,9 +996,14 @@ void LLMuteList::removeObserver(LLMuteListObserver* observer) void LLMuteList::setLoaded(EMuteListSource source) { + if(isLoaded()) + { + LL_WARNS() << "Mute list was already loaded from " << sourceToString(mLoadSource) << ", switching to " << sourceToString(source) << LL_ENDL; + } mLoadState = ML_LOADED; mLoadSource = source; notifyObservers(); + LL_INFOS() << "Mute list loaded from " << sourceToString(source) << LL_ENDL; } void LLMuteList::notifyObservers() From d8f86231d5735788f9b7643cae3f1e0deb4d73c4 Mon Sep 17 00:00:00 2001 From: Darl Date: Fri, 3 Apr 2026 18:36:10 -0500 Subject: [PATCH 6/8] Make LLBlockList more reactive to LLMuteList changes This allows the UI to update if already initialized before fallback or load occurs Signed-off-by: Darl --- indra/newview/llblocklist.cpp | 8 ++++++++ indra/newview/llblocklist.h | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/indra/newview/llblocklist.cpp b/indra/newview/llblocklist.cpp index 89516a8a843..230aa19ad2f 100644 --- a/indra/newview/llblocklist.cpp +++ b/indra/newview/llblocklist.cpp @@ -101,6 +101,14 @@ BlockListActionType LLBlockList::getCurrentMuteListActionType() return type; } +void LLBlockList::onChange() +{ + // Something changed, not sure what so force a refresh. + mShouldAddAll = true; + mActionType = NONE; + setDirty(); +} + void LLBlockList::onChangeDetailed(const LLMute &mute) { mActionType = getCurrentMuteListActionType(); diff --git a/indra/newview/llblocklist.h b/indra/newview/llblocklist.h index 64e8246f439..92bb910c575 100644 --- a/indra/newview/llblocklist.h +++ b/indra/newview/llblocklist.h @@ -58,7 +58,7 @@ class LLBlockList: public LLFlatListViewEx, public LLMuteListObserver LLToggleableMenu* getContextMenu() const { return mContextMenu.get(); } LLBlockedListItem* getBlockedItem() const; - virtual void onChange() { } + virtual void onChange(); virtual void onChangeDetailed(const LLMute& ); virtual void draw(); From 9dfcf21464b198c41e2e51f6785a06ef9a2aef60 Mon Sep 17 00:00:00 2001 From: Darl Date: Fri, 3 Apr 2026 18:38:08 -0500 Subject: [PATCH 7/8] Add noitems text to the block list UI, with translation friendly helper strings to denote loading or failed states Signed-off-by: Darl --- indra/newview/llblocklist.cpp | 27 ++++++++++++++++++- indra/newview/llblocklist.h | 5 ++++ indra/newview/llpanelblockedlist.cpp | 4 +++ .../xui/en/panel_block_list_sidetray.xml | 12 +++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/indra/newview/llblocklist.cpp b/indra/newview/llblocklist.cpp index 230aa19ad2f..05c882e3f1d 100644 --- a/indra/newview/llblocklist.cpp +++ b/indra/newview/llblocklist.cpp @@ -48,6 +48,7 @@ LLBlockList::LLBlockList(const Params& p) LLMuteList::getInstance()->addObserver(this); mMuteListSize = static_cast(LLMuteList::getInstance()->getMutes().size()); + updateNoItemsCommentText(); // Set up context menu. LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; @@ -101,16 +102,35 @@ BlockListActionType LLBlockList::getCurrentMuteListActionType() return type; } +void LLBlockList::updateNoItemsCommentText() +{ + const LLMuteList* mute_list = LLMuteList::getInstance(); + if (!mute_list->isLoaded() && !mute_list->isFailed()) + { + setNoItemsCommentText(mLoadingItemsMsg); + } + else if (mute_list->isFailed()) + { + setNoItemsCommentText(mFailedItemsMsg); + } + else + { + updateNoItemsMessage(mNameFilter); + } +} + void LLBlockList::onChange() { // Something changed, not sure what so force a refresh. mShouldAddAll = true; mActionType = NONE; + updateNoItemsCommentText(); setDirty(); } void LLBlockList::onChangeDetailed(const LLMute &mute) { + updateNoItemsCommentText(); mActionType = getCurrentMuteListActionType(); mCurItemId = mute.mID; @@ -205,6 +225,7 @@ void LLBlockList::addNewItem(const LLMute* mute) void LLBlockList::refresh() { + updateNoItemsCommentText(); bool have_filter = !mNameFilter.empty(); // save selection to restore it after list rebuilt @@ -216,6 +237,8 @@ void LLBlockList::refresh() clear(); createList(); mShouldAddAll = false; + // Full rebuild supersedes any queued incremental action. This ensures list consistency. + mActionType = NONE; } else { @@ -254,7 +277,9 @@ void LLBlockList::refresh() { LLBlockedListItem * curItem = dynamic_cast (*it); if(curItem) - { + { + // Refresh item text styling each pass so filtering keeps highlight in sync. + curItem->highlightName(mNameFilter); hideListItem(curItem, findInsensitive(curItem->getName(), mNameFilter)); } } diff --git a/indra/newview/llblocklist.h b/indra/newview/llblocklist.h index 92bb910c575..92665842563 100644 --- a/indra/newview/llblocklist.h +++ b/indra/newview/llblocklist.h @@ -68,6 +68,8 @@ class LLBlockList: public LLFlatListViewEx, public LLMuteListObserver void refresh(); U32 getMuteListSize() { return mMuteListSize; } + void setLoadingItemsMsg(const std::string& msg) { mLoadingItemsMsg = msg; updateNoItemsCommentText(); } + void setFailedItemsMsg(const std::string& msg) { mFailedItemsMsg = msg; updateNoItemsCommentText(); } private: @@ -83,6 +85,7 @@ class LLBlockList: public LLFlatListViewEx, public LLMuteListObserver bool isMenuItemVisible(const LLSD& userdata); void toggleMute(U32 flags); void createList(); + void updateNoItemsCommentText(); BlockListActionType getCurrentMuteListActionType(); @@ -100,6 +103,8 @@ class LLBlockList: public LLFlatListViewEx, public LLMuteListObserver LLMute::EType mCurItemType; U32 mCurItemFlags; std::string mPrevNameFilter; + std::string mLoadingItemsMsg; + std::string mFailedItemsMsg; }; diff --git a/indra/newview/llpanelblockedlist.cpp b/indra/newview/llpanelblockedlist.cpp index 69f51b03b67..178f994bebf 100644 --- a/indra/newview/llpanelblockedlist.cpp +++ b/indra/newview/llpanelblockedlist.cpp @@ -78,6 +78,10 @@ bool LLPanelBlockedList::postBuild() { mBlockedList = getChild("blocked"); mBlockedList->setCommitOnSelectionChange(true); + mBlockedList->setNoItemsMsg(getString("no_blocked")); + mBlockedList->setNoFilteredItemsMsg(getString("no_filtered_blocked")); + mBlockedList->setLoadingItemsMsg(getString("loading_blocked")); + mBlockedList->setFailedItemsMsg(getString("failed_blocked")); this->setVisibleCallback(boost::bind(&LLPanelBlockedList::removePicker, this)); switch (gSavedSettings.getU32("BlockPeopleSortOrder")) diff --git a/indra/newview/skins/default/xui/en/panel_block_list_sidetray.xml b/indra/newview/skins/default/xui/en/panel_block_list_sidetray.xml index 6bb891db636..d907a050b68 100644 --- a/indra/newview/skins/default/xui/en/panel_block_list_sidetray.xml +++ b/indra/newview/skins/default/xui/en/panel_block_list_sidetray.xml @@ -10,6 +10,18 @@ min_height="350" min_width="240" width="323"> + + + + Date: Fri, 3 Apr 2026 22:57:53 -0500 Subject: [PATCH 8/8] Attempt one additional mute list request from simulator after region change Signed-off-by: Darl --- indra/newview/llmutelist.cpp | 46 ++++++++++++++++++++++++++++++++++-- indra/newview/llmutelist.h | 16 +++++++++++-- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index f8bdc453b2e..b72301c5666 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -190,6 +190,8 @@ LLMuteList::LLMuteList() : // but this way is just more convinient onAccountNameChanged(id, av_name.getUserName()); }); + // Register our region change callback handler early, we'll clean it up if/when we don't need it anymore. + mRegionChangedCallback = gAgent.addRegionChangedCallback(boost::bind(&LLMuteList::onRegionChanged, this)); } //----------------------------------------------------------------------------- @@ -203,6 +205,10 @@ LLMuteList::~LLMuteList() void LLMuteList::cleanupSingleton() { LLAvatarNameCache::getInstance()->setAccountNameChangedCallback(nullptr); + if (mRegionChangedCallback.connected()) + { + mRegionChangedCallback.disconnect(); + } } bool LLMuteList::isLinden(const std::string& name) @@ -826,6 +832,11 @@ bool LLMuteList::isMuted(const std::string& username, U32 flags) const //----------------------------------------------------------------------------- void LLMuteList::requestFromServer(const LLUUID& agent_id) { + if(isLoadedFromServer()) + { + LL_WARNS() << "Blocked attempt to request mute list from server when already loaded from server!" << LL_ENDL; + return; + } const std::string filename = getCacheFilename(agent_id); LLCRC crc; crc.update(filename); @@ -886,6 +897,11 @@ void LLMuteList::cache(const LLUUID& agent_id) void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**) { LL_INFOS() << "LLMuteList::processMuteListUpdate()" << LL_ENDL; + LLMuteList* mute_list = getInstance(); + if(mute_list->mTriedRegionChangeRetry) + { + LL_WARNS() << "Received mute list update after retrying region change; success!" << LL_ENDL; + } LLUUID agent_id; msg->getUUIDFast(_PREHASH_MuteData, _PREHASH_AgentID, agent_id); if(agent_id != gAgent.getID()) @@ -901,7 +917,6 @@ void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**) LL_WARNS() << "Received empty mute list filename." << LL_ENDL; } - LLMuteList* mute_list = getInstance(); mute_list->mLoadState = ML_REQUESTED; mute_list->mRequestStartTime = LLTimer::getTotalSeconds(); @@ -922,7 +937,12 @@ void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**) void LLMuteList::processUseCachedMuteList(LLMessageSystem* msg, void**) { LL_INFOS() << "LLMuteList::processUseCachedMuteList()" << LL_ENDL; - LLMuteList::getInstance()->loadFromFile(LLMuteList::getInstance()->getCacheFilename(gAgent.getID()), MLS_SERVER_CACHE); + LLMuteList* mute_list = LLMuteList::getInstance(); + if(mute_list->mTriedRegionChangeRetry) + { + LL_WARNS() << "Received use cached mute list message after retrying region change; success!" << LL_ENDL; + } + mute_list->loadFromFile(mute_list->getCacheFilename(gAgent.getID()), MLS_SERVER_CACHE); } void LLMuteList::onFileMuteList(void** user_data, S32 error_code, LLExtStat ext_status) @@ -1004,6 +1024,11 @@ void LLMuteList::setLoaded(EMuteListSource source) mLoadSource = source; notifyObservers(); LL_INFOS() << "Mute list loaded from " << sourceToString(source) << LL_ENDL; + if(isLoadedFromServer() && mRegionChangedCallback.connected()) + { + LL_INFOS() << "Mute list loaded from server, disconnecting region change callback" << LL_ENDL; + mRegionChangedCallback.disconnect(); + } } void LLMuteList::notifyObservers() @@ -1032,6 +1057,23 @@ void LLMuteList::notifyObserversDetailed(const LLMute& mute) } } +void LLMuteList::onRegionChanged() +{ + // If we are in a degraded state, either some protocol messages got lost between us and our login region, or our login region was having a bad day. + // Since the previous region might've been unable to provide our mute list, we can try to request it again from our new region. + // This is limited to one retry per session. + if(isLoadedDegraded() && !mTriedRegionChangeRetry) + { + if(mRegionChangedCallback.connected()) + { + mRegionChangedCallback.disconnect(); + } + LL_WARNS() << "Region changed while mute list is in degraded state, queueing a retry of the mute list request" << LL_ENDL; + mTriedRegionChangeRetry = true; + requestFromServer(gAgent.getID()); + } +} + LLRenderMuteList::LLRenderMuteList() {} diff --git a/indra/newview/llmutelist.h b/indra/newview/llmutelist.h index ff010a02e89..2781e9b1779 100644 --- a/indra/newview/llmutelist.h +++ b/indra/newview/llmutelist.h @@ -31,6 +31,8 @@ #include "lluuid.h" #include "llextendedstatus.h" +#include + class LLViewerObject; class LLMessageSystem; class LLMuteListObserver; @@ -124,8 +126,13 @@ class LLMuteList : public LLSingleton static bool isLinden(const std::string& name); - bool isLoaded() const { return mLoadState == ML_LOADED; } - bool isFailed() const { return mLoadState == ML_FAILED; } + // Load state accessors. + bool isLoaded() const { return mLoadState == ML_LOADED; } // Loaded, but not necessarily from server. + bool isFailed() const { return mLoadState == ML_FAILED; } // Unable to load any mute list. Server did not reply. + // Loaded from server, which is the only source we consider authoritative. + bool isLoadedFromServer() const { return isLoaded() && (mLoadSource == MLS_SERVER || mLoadSource == MLS_SERVER_EMPTY); } + // Loaded, but from cache. Would be nice to upgrade to a server load from here if possible. + bool isLoadedDegraded() const { return isLoaded() && !isLoadedFromServer(); } // Advance the load state machine, trying cache fallback if necessary. // Return value indicates mute list consumption readiness. @@ -139,6 +146,9 @@ class LLMuteList : public LLSingleton // call this method on logout to save everything. void cache(const LLUUID& agent_id); + // Handler for region change event, used for server request retries if isLoadedDegraded() is true + void onRegionChanged(); + private: void clearCachedMutes(); bool loadFromFile(const std::string& filename, EMuteListSource source); @@ -198,6 +208,8 @@ class LLMuteList : public LLSingleton EMuteListSource mLoadSource; F64 mRequestStartTime; bool mTriedCacheFallback; + bool mTriedRegionChangeRetry; + boost::signals2::connection mRegionChangedCallback; friend class LLDispatchEmptyMuteList; };