PageRenderTime 86ms CodeModel.GetById 40ms app.highlight 40ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/newview/llmutelist.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 739 lines | 537 code | 86 blank | 116 comment | 96 complexity | e1d0ad97dbc648c2b274b9dc860c62db MD5 | raw file
  1/** 
  2 * @file llmutelist.cpp
  3 * @author Richard Nelson, James Cook
  4 * @brief Management of list of muted players
  5 *
  6 * $LicenseInfo:firstyear=2003&license=viewerlgpl$
  7 * Second Life Viewer Source Code
  8 * Copyright (C) 2010, Linden Research, Inc.
  9 * 
 10 * This library is free software; you can redistribute it and/or
 11 * modify it under the terms of the GNU Lesser General Public
 12 * License as published by the Free Software Foundation;
 13 * version 2.1 of the License only.
 14 * 
 15 * This library is distributed in the hope that it will be useful,
 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 18 * Lesser General Public License for more details.
 19 * 
 20 * You should have received a copy of the GNU Lesser General Public
 21 * License along with this library; if not, write to the Free Software
 22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 23 * 
 24 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 25 * $/LicenseInfo$
 26 */
 27
 28/*
 29 * How should muting work?
 30 * Mute an avatar
 31 * Mute a specific object (accidentally spamming)
 32 *
 33 * right-click avatar, mute
 34 * see list of recent chatters, mute
 35 * type a name to mute?
 36 *
 37 * show in list whether chatter is avatar or object
 38 *
 39 * need fast lookup by id
 40 * need lookup by name, doesn't have to be fast
 41 */
 42
 43#include "llviewerprecompiledheaders.h"
 44
 45#include "llmutelist.h"
 46
 47#include <boost/tokenizer.hpp>
 48
 49#include "lldispatcher.h"
 50#include "llxfermanager.h"
 51
 52#include "llagent.h"
 53#include "llviewergenericmessage.h"	// for gGenericDispatcher
 54#include "llworld.h" //for particle system banning
 55#include "llimview.h"
 56#include "llnotifications.h"
 57#include "llviewerobjectlist.h"
 58#include "lltrans.h"
 59
 60namespace 
 61{
 62	// This method is used to return an object to mute given an object id.
 63	// Its used by the LLMute constructor and LLMuteList::isMuted.
 64	LLViewerObject* get_object_to_mute_from_id(LLUUID object_id)
 65	{
 66		LLViewerObject *objectp = gObjectList.findObject(object_id);
 67		if ((objectp) && (!objectp->isAvatar()))
 68		{
 69			LLViewerObject *parentp = (LLViewerObject *)objectp->getParent();
 70			if (parentp && parentp->getID() != gAgent.getID())
 71			{
 72				objectp = parentp;
 73			}
 74		}
 75		return objectp;
 76	}
 77}
 78
 79// "emptymutelist"
 80class LLDispatchEmptyMuteList : public LLDispatchHandler
 81{
 82public:
 83	virtual bool operator()(
 84		const LLDispatcher* dispatcher,
 85		const std::string& key,
 86		const LLUUID& invoice,
 87		const sparam_t& strings)
 88	{
 89		LLMuteList::getInstance()->setLoaded();
 90		return true;
 91	}
 92};
 93
 94static LLDispatchEmptyMuteList sDispatchEmptyMuteList;
 95
 96//-----------------------------------------------------------------------------
 97// LLMute()
 98//-----------------------------------------------------------------------------
 99
100LLMute::LLMute(const LLUUID& id, const std::string& name, EType type, U32 flags)
101  : mID(id),
102	mName(name),
103	mType(type),
104	mFlags(flags)
105{
106	// muting is done by root objects only - try to find this objects root
107	LLViewerObject* mute_object = get_object_to_mute_from_id(id);
108	if(mute_object && mute_object->getID() != id)
109	{
110		mID = mute_object->getID();
111		LLNameValue* firstname = mute_object->getNVPair("FirstName");
112		LLNameValue* lastname = mute_object->getNVPair("LastName");
113		if (firstname && lastname)
114		{
115			mName = LLCacheName::buildFullName(
116				firstname->getString(), lastname->getString());
117		}
118		mType = mute_object->isAvatar() ? AGENT : OBJECT;
119	}
120
121}
122
123
124std::string LLMute::getDisplayType() const
125{
126	switch (mType)
127	{
128		case BY_NAME:
129		default:
130			return LLTrans::getString("MuteByName");
131			break;
132		case AGENT:
133			return LLTrans::getString("MuteAgent");
134			break;
135		case OBJECT:
136			return LLTrans::getString("MuteObject");
137			break;
138		case GROUP:
139			return LLTrans::getString("MuteGroup");
140			break;
141		case EXTERNAL:
142			return LLTrans::getString("MuteExternal");
143			break;
144	}
145}
146
147
148/* static */
149LLMuteList* LLMuteList::getInstance()
150{
151	// Register callbacks at the first time that we find that the message system has been created.
152	static BOOL registered = FALSE;
153	if( !registered && gMessageSystem != NULL)
154	{
155		registered = TRUE;
156		// Register our various callbacks
157		gMessageSystem->setHandlerFuncFast(_PREHASH_MuteListUpdate, processMuteListUpdate);
158		gMessageSystem->setHandlerFuncFast(_PREHASH_UseCachedMuteList, processUseCachedMuteList);
159	}
160	return LLSingleton<LLMuteList>::getInstance(); // Call the "base" implementation.
161}
162
163//-----------------------------------------------------------------------------
164// LLMuteList()
165//-----------------------------------------------------------------------------
166LLMuteList::LLMuteList() :
167	mIsLoaded(FALSE)
168{
169	gGenericDispatcher.addHandler("emptymutelist", &sDispatchEmptyMuteList);
170}
171
172//-----------------------------------------------------------------------------
173// ~LLMuteList()
174//-----------------------------------------------------------------------------
175LLMuteList::~LLMuteList()
176{
177
178}
179
180BOOL LLMuteList::isLinden(const std::string& name) const
181{
182	typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
183	boost::char_separator<char> sep(" ");
184	tokenizer tokens(name, sep);
185	tokenizer::iterator token_iter = tokens.begin();
186	
187	if (token_iter == tokens.end()) return FALSE;
188	token_iter++;
189	if (token_iter == tokens.end()) return FALSE;
190	
191	std::string last_name = *token_iter;
192	return last_name == "Linden";
193}
194
195
196BOOL LLMuteList::add(const LLMute& mute, U32 flags)
197{
198	// Can't mute text from Lindens
199	if ((mute.mType == LLMute::AGENT)
200		&& isLinden(mute.mName) && (flags & LLMute::flagTextChat || flags == 0))
201	{
202		LLNotifications::instance().add("MuteLinden", LLSD(), LLSD());
203		return FALSE;
204	}
205	
206	// Can't mute self.
207	if (mute.mType == LLMute::AGENT
208		&& mute.mID == gAgent.getID())
209	{
210		return FALSE;
211	}
212	
213	if (mute.mType == LLMute::BY_NAME)
214	{		
215		// Can't mute empty string by name
216		if (mute.mName.empty()) 
217		{
218			llwarns << "Trying to mute empty string by-name" << llendl;
219			return FALSE;
220		}
221
222		// Null mutes must have uuid null
223		if (mute.mID.notNull())
224		{
225			llwarns << "Trying to add by-name mute with non-null id" << llendl;
226			return FALSE;
227		}
228
229		std::pair<string_set_t::iterator, bool> result = mLegacyMutes.insert(mute.mName);
230		if (result.second)
231		{
232			llinfos << "Muting by name " << mute.mName << llendl;
233			updateAdd(mute);
234			notifyObservers();
235			return TRUE;
236		}
237		else
238		{
239			// was duplicate
240			return FALSE;
241		}
242	}
243	else
244	{
245		// Need a local (non-const) copy to set up flags properly.
246		LLMute localmute = mute;
247		
248		// If an entry for the same entity is already in the list, remove it, saving flags as necessary.
249		mute_set_t::iterator it = mMutes.find(localmute);
250		if (it != mMutes.end())
251		{
252			// This mute is already in the list.  Save the existing entry's flags if that's warranted.
253			localmute.mFlags = it->mFlags;
254			
255			mMutes.erase(it);
256			// Don't need to call notifyObservers() here, since it will happen after the entry has been re-added below.
257		}
258		else
259		{
260			// There was no entry in the list previously.  Fake things up by making it look like the previous entry had all properties unmuted.
261			localmute.mFlags = LLMute::flagAll;
262		}
263
264		if(flags)
265		{
266			// The user passed some combination of flags.  Make sure those flag bits are turned off (i.e. those properties will be muted).
267			localmute.mFlags &= (~flags);
268		}
269		else
270		{
271			// The user passed 0.  Make sure all flag bits are turned off (i.e. all properties will be muted).
272			localmute.mFlags = 0;
273		}
274		
275		// (re)add the mute entry.
276		{			
277			std::pair<mute_set_t::iterator, bool> result = mMutes.insert(localmute);
278			if (result.second)
279			{
280				llinfos << "Muting " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << llendl;
281				updateAdd(localmute);
282				notifyObservers();
283				if(!(localmute.mFlags & LLMute::flagParticles))
284				{
285					//Kill all particle systems owned by muted task
286					if(localmute.mType == LLMute::AGENT || localmute.mType == LLMute::OBJECT)
287					{
288						LLViewerPartSim::getInstance()->clearParticlesByOwnerID(localmute.mID);
289					}
290				}
291				return TRUE;
292			}
293		}
294	}
295	
296	// If we were going to return success, we'd have done it by now.
297	return FALSE;
298}
299
300void LLMuteList::updateAdd(const LLMute& mute)
301{
302	// External mutes (e.g. Avaline callers) are local only, don't send them to the server.
303	if (mute.mType == LLMute::EXTERNAL)
304	{
305		return;
306	}
307
308	// Update the database
309	LLMessageSystem* msg = gMessageSystem;
310	msg->newMessageFast(_PREHASH_UpdateMuteListEntry);
311	msg->nextBlockFast(_PREHASH_AgentData);
312	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
313	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
314	msg->nextBlockFast(_PREHASH_MuteData);
315	msg->addUUIDFast(_PREHASH_MuteID, mute.mID);
316	msg->addStringFast(_PREHASH_MuteName, mute.mName);
317	msg->addS32("MuteType", mute.mType);
318	msg->addU32("MuteFlags", mute.mFlags);
319	gAgent.sendReliableMessage();
320
321	mIsLoaded = TRUE; // why is this here? -MG
322}
323
324
325BOOL LLMuteList::remove(const LLMute& mute, U32 flags)
326{
327	BOOL found = FALSE;
328	
329	// First, remove from main list.
330	mute_set_t::iterator it = mMutes.find(mute);
331	if (it != mMutes.end())
332	{
333		LLMute localmute = *it;
334		bool remove = true;
335		if(flags)
336		{
337			// If the user passed mute flags, we may only want to turn some flags on.
338			localmute.mFlags |= flags;
339			
340			if(localmute.mFlags == LLMute::flagAll)
341			{
342				// Every currently available mute property has been masked out.
343				// Remove the mute entry entirely.
344			}
345			else
346			{
347				// Only some of the properties are masked out.  Update the entry.
348				remove = false;
349			}
350		}
351		else
352		{
353			// The caller didn't pass any flags -- just remove the mute entry entirely.
354		}
355		
356		// Always remove the entry from the set -- it will be re-added with new flags if necessary.
357		mMutes.erase(it);
358
359		if(remove)
360		{
361			// The entry was actually removed.  Notify the server.
362			updateRemove(localmute);
363			llinfos << "Unmuting " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << llendl;
364		}
365		else
366		{
367			// Flags were updated, the mute entry needs to be retransmitted to the server and re-added to the list.
368			mMutes.insert(localmute);
369			updateAdd(localmute);
370			llinfos << "Updating mute entry " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << llendl;
371		}
372		
373		// Must be after erase.
374		setLoaded();  // why is this here? -MG
375	}
376	else
377	{
378		// Clean up any legacy mutes
379		string_set_t::iterator legacy_it = mLegacyMutes.find(mute.mName);
380		if (legacy_it != mLegacyMutes.end())
381		{
382			// Database representation of legacy mute is UUID null.
383			LLMute mute(LLUUID::null, *legacy_it, LLMute::BY_NAME);
384			updateRemove(mute);
385			mLegacyMutes.erase(legacy_it);
386			// Must be after erase.
387			setLoaded(); // why is this here? -MG
388		}
389	}
390	
391	return found;
392}
393
394
395void LLMuteList::updateRemove(const LLMute& mute)
396{
397	// External mutes are not sent to the server anyway, no need to remove them.
398	if (mute.mType == LLMute::EXTERNAL)
399	{
400		return;
401	}
402
403	LLMessageSystem* msg = gMessageSystem;
404	msg->newMessageFast(_PREHASH_RemoveMuteListEntry);
405	msg->nextBlockFast(_PREHASH_AgentData);
406	msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID());
407	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
408	msg->nextBlockFast(_PREHASH_MuteData);
409	msg->addUUIDFast(_PREHASH_MuteID, mute.mID);
410	msg->addString("MuteName", mute.mName);
411	gAgent.sendReliableMessage();
412}
413
414void notify_automute_callback(const LLUUID& agent_id, const std::string& full_name, bool is_group, LLMuteList::EAutoReason reason)
415{
416	std::string notif_name;
417	switch (reason)
418	{
419	default:
420	case LLMuteList::AR_IM:
421		notif_name = "AutoUnmuteByIM";
422		break;
423	case LLMuteList::AR_INVENTORY:
424		notif_name = "AutoUnmuteByInventory";
425		break;
426	case LLMuteList::AR_MONEY:
427		notif_name = "AutoUnmuteByMoney";
428		break;
429	}
430
431	LLSD args;
432	args["NAME"] = full_name;
433    
434	LLNotificationPtr notif_ptr = LLNotifications::instance().add(notif_name, args, LLSD());
435	if (notif_ptr)
436	{
437		std::string message = notif_ptr->getMessage();
438
439		if (reason == LLMuteList::AR_IM)
440		{
441			LLIMModel::getInstance()->addMessage(agent_id, SYSTEM_FROM, LLUUID::null, message);
442		}
443	}
444}
445
446
447BOOL LLMuteList::autoRemove(const LLUUID& agent_id, const EAutoReason reason)
448{
449	BOOL removed = FALSE;
450
451	if (isMuted(agent_id))
452	{
453		LLMute automute(agent_id, LLStringUtil::null, LLMute::AGENT);
454		removed = TRUE;
455		remove(automute);
456
457		std::string full_name;
458		if (gCacheName->getFullName(agent_id, full_name))
459			{
460				// name in cache, call callback directly
461			notify_automute_callback(agent_id, full_name, false, reason);
462			}
463			else
464			{
465				// not in cache, lookup name from cache
466			gCacheName->get(agent_id, false,
467				boost::bind(&notify_automute_callback, _1, _2, _3, reason));
468		}
469	}
470
471	return removed;
472}
473
474
475std::vector<LLMute> LLMuteList::getMutes() const
476{
477	std::vector<LLMute> mutes;
478	
479	for (mute_set_t::const_iterator it = mMutes.begin();
480		 it != mMutes.end();
481		 ++it)
482	{
483		mutes.push_back(*it);
484	}
485	
486	for (string_set_t::const_iterator it = mLegacyMutes.begin();
487		 it != mLegacyMutes.end();
488		 ++it)
489	{
490		LLMute legacy(LLUUID::null, *it);
491		mutes.push_back(legacy);
492	}
493	
494	std::sort(mutes.begin(), mutes.end(), compare_by_name());
495	return mutes;
496}
497
498//-----------------------------------------------------------------------------
499// loadFromFile()
500//-----------------------------------------------------------------------------
501BOOL LLMuteList::loadFromFile(const std::string& filename)
502{
503	if(!filename.size())
504	{
505		llwarns << "Mute List Filename is Empty!" << llendl;
506		return FALSE;
507	}
508
509	LLFILE* fp = LLFile::fopen(filename, "rb");		/*Flawfinder: ignore*/
510	if (!fp)
511	{
512		llwarns << "Couldn't open mute list " << filename << llendl;
513		return FALSE;
514	}
515
516	// *NOTE: Changing the size of these buffers will require changes
517	// in the scanf below.
518	char id_buffer[MAX_STRING];		/*Flawfinder: ignore*/
519	char name_buffer[MAX_STRING];		/*Flawfinder: ignore*/
520	char buffer[MAX_STRING];		/*Flawfinder: ignore*/
521	while (!feof(fp) 
522		   && fgets(buffer, MAX_STRING, fp))
523	{
524		id_buffer[0] = '\0';
525		name_buffer[0] = '\0';
526		S32 type = 0;
527		U32 flags = 0;
528		sscanf(	/* Flawfinder: ignore */
529			buffer, " %d %254s %254[^|]| %u\n", &type, id_buffer, name_buffer,
530			&flags);
531		LLUUID id = LLUUID(id_buffer);
532		LLMute mute(id, std::string(name_buffer), (LLMute::EType)type, flags);
533		if (mute.mID.isNull()
534			|| mute.mType == LLMute::BY_NAME)
535		{
536			mLegacyMutes.insert(mute.mName);
537		}
538		else
539		{
540			mMutes.insert(mute);
541		}
542	}
543	fclose(fp);
544	setLoaded();
545	return TRUE;
546}
547
548//-----------------------------------------------------------------------------
549// saveToFile()
550//-----------------------------------------------------------------------------
551BOOL LLMuteList::saveToFile(const std::string& filename)
552{
553	if(!filename.size())
554	{
555		llwarns << "Mute List Filename is Empty!" << llendl;
556		return FALSE;
557	}
558
559	LLFILE* fp = LLFile::fopen(filename, "wb");		/*Flawfinder: ignore*/
560	if (!fp)
561	{
562		llwarns << "Couldn't open mute list " << filename << llendl;
563		return FALSE;
564	}
565	// legacy mutes have null uuid
566	std::string id_string;
567	LLUUID::null.toString(id_string);
568	for (string_set_t::iterator it = mLegacyMutes.begin();
569		 it != mLegacyMutes.end();
570		 ++it)
571	{
572		fprintf(fp, "%d %s %s|\n", (S32)LLMute::BY_NAME, id_string.c_str(), it->c_str());
573	}
574	for (mute_set_t::iterator it = mMutes.begin();
575		 it != mMutes.end();
576		 ++it)
577	{
578		// Don't save external mutes as they are not sent to the server and probably won't
579		//be valid next time anyway.
580		if (it->mType != LLMute::EXTERNAL)
581		{
582			it->mID.toString(id_string);
583			const std::string& name = it->mName;
584			fprintf(fp, "%d %s %s|%u\n", (S32)it->mType, id_string.c_str(), name.c_str(), it->mFlags);
585		}
586	}
587	fclose(fp);
588	return TRUE;
589}
590
591
592BOOL LLMuteList::isMuted(const LLUUID& id, const std::string& name, U32 flags) const
593{
594	// for objects, check for muting on their parent prim
595	LLViewerObject* mute_object = get_object_to_mute_from_id(id);
596	LLUUID id_to_check  = (mute_object) ? mute_object->getID() : id;
597
598	// don't need name or type for lookup
599	LLMute mute(id_to_check);
600	mute_set_t::const_iterator mute_it = mMutes.find(mute);
601	if (mute_it != mMutes.end())
602	{
603		// If any of the flags the caller passed are set, this item isn't considered muted for this caller.
604		if(flags & mute_it->mFlags)
605		{
606			return FALSE;
607		}
608		return TRUE;
609	}
610
611	// empty names can't be legacy-muted
612	bool avatar = mute_object && mute_object->isAvatar();
613	if (name.empty() || avatar) return FALSE;
614
615	// Look in legacy pile
616	string_set_t::const_iterator legacy_it = mLegacyMutes.find(name);
617	return legacy_it != mLegacyMutes.end();
618}
619
620//-----------------------------------------------------------------------------
621// requestFromServer()
622//-----------------------------------------------------------------------------
623void LLMuteList::requestFromServer(const LLUUID& agent_id)
624{
625	std::string agent_id_string;
626	std::string filename;
627	agent_id.toString(agent_id_string);
628	filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute";
629	LLCRC crc;
630	crc.update(filename);
631
632	LLMessageSystem* msg = gMessageSystem;
633	msg->newMessageFast(_PREHASH_MuteListRequest);
634	msg->nextBlockFast(_PREHASH_AgentData);
635	msg->addUUIDFast(_PREHASH_AgentID, agent_id);
636	msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID());
637	msg->nextBlockFast(_PREHASH_MuteData);
638	msg->addU32Fast(_PREHASH_MuteCRC, crc.getCRC());
639	gAgent.sendReliableMessage();
640}
641
642//-----------------------------------------------------------------------------
643// cache()
644//-----------------------------------------------------------------------------
645
646void LLMuteList::cache(const LLUUID& agent_id)
647{
648	// Write to disk even if empty.
649	if(mIsLoaded)
650	{
651		std::string agent_id_string;
652		std::string filename;
653		agent_id.toString(agent_id_string);
654		filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute";
655		saveToFile(filename);
656	}
657}
658
659//-----------------------------------------------------------------------------
660// Static message handlers
661//-----------------------------------------------------------------------------
662
663void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**)
664{
665	llinfos << "LLMuteList::processMuteListUpdate()" << llendl;
666	LLUUID agent_id;
667	msg->getUUIDFast(_PREHASH_MuteData, _PREHASH_AgentID, agent_id);
668	if(agent_id != gAgent.getID())
669	{
670		llwarns << "Got an mute list update for the wrong agent." << llendl;
671		return;
672	}
673	std::string unclean_filename;
674	msg->getStringFast(_PREHASH_MuteData, _PREHASH_Filename, unclean_filename);
675	std::string filename = LLDir::getScrubbedFileName(unclean_filename);
676	
677	std::string *local_filename_and_path = new std::string(gDirUtilp->getExpandedFilename( LL_PATH_CACHE, filename ));
678	gXferManager->requestFile(*local_filename_and_path,
679							  filename,
680							  LL_PATH_CACHE,
681							  msg->getSender(),
682							  TRUE, // make the remote file temporary.
683							  onFileMuteList,
684							  (void**)local_filename_and_path,
685							  LLXferManager::HIGH_PRIORITY);
686}
687
688void LLMuteList::processUseCachedMuteList(LLMessageSystem* msg, void**)
689{
690	llinfos << "LLMuteList::processUseCachedMuteList()" << llendl;
691
692	std::string agent_id_string;
693	gAgent.getID().toString(agent_id_string);
694	std::string filename;
695	filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute";
696	LLMuteList::getInstance()->loadFromFile(filename);
697}
698
699void LLMuteList::onFileMuteList(void** user_data, S32 error_code, LLExtStat ext_status)
700{
701	llinfos << "LLMuteList::processMuteListFile()" << llendl;
702
703	std::string* local_filename_and_path = (std::string*)user_data;
704	if(local_filename_and_path && !local_filename_and_path->empty() && (error_code == 0))
705	{
706		LLMuteList::getInstance()->loadFromFile(*local_filename_and_path);
707		LLFile::remove(*local_filename_and_path);
708	}
709	delete local_filename_and_path;
710}
711
712void LLMuteList::addObserver(LLMuteListObserver* observer)
713{
714	mObservers.insert(observer);
715}
716
717void LLMuteList::removeObserver(LLMuteListObserver* observer)
718{
719	mObservers.erase(observer);
720}
721
722void LLMuteList::setLoaded()
723{
724	mIsLoaded = TRUE;
725	notifyObservers();
726}
727
728void LLMuteList::notifyObservers()
729{
730	for (observer_set_t::iterator it = mObservers.begin();
731		it != mObservers.end();
732		)
733	{
734		LLMuteListObserver* observer = *it;
735		observer->onChange();
736		// In case onChange() deleted an entry.
737		it = mObservers.upper_bound(observer);
738	}
739}