/** * @file llmutelist.cpp * @author Richard Nelson, James Cook * @brief Management of list of muted players * * $LicenseInfo:firstyear=2003&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ /* * How should muting work? * Mute an avatar * Mute a specific object (accidentally spamming) * * right-click avatar, mute * see list of recent chatters, mute * type a name to mute? * * show in list whether chatter is avatar or object * * need fast lookup by id * need lookup by name, doesn't have to be fast */ #include "llviewerprecompiledheaders.h" #include "llmutelist.h" #include "pipeline.h" #include #include #include #include "lldispatcher.h" #include "llxfermanager.h" #include "llagent.h" #include "llavatarnamecache.h" #include "llviewergenericmessage.h" // for gGenericDispatcher #include "llworld.h" //for particle system banning #include "llimview.h" #include "llnotifications.h" #include "llviewercontrol.h" #include "llviewerobjectlist.h" #include "lltrans.h" namespace { // This method is used to return an object to mute given an object id. // Its used by the LLMute constructor and LLMuteList::isMuted. LLViewerObject* get_object_to_mute_from_id(LLUUID object_id) { LLViewerObject *objectp = gObjectList.findObject(object_id); if ((objectp) && (!objectp->isAvatar())) { LLViewerObject *parentp = (LLViewerObject *)objectp->getParent(); if (parentp && parentp->getID() != gAgent.getID()) { objectp = parentp; } } return objectp; } } // "emptymutelist" class LLDispatchEmptyMuteList : public LLDispatchHandler { public: virtual bool operator()( const LLDispatcher* dispatcher, const std::string& key, const LLUUID& invoice, const sparam_t& strings) { LLMuteList::getInstance()->setLoaded(); return true; } }; static LLDispatchEmptyMuteList sDispatchEmptyMuteList; //----------------------------------------------------------------------------- // LLMute() //----------------------------------------------------------------------------- LLMute::LLMute(const LLUUID& id, const std::string& name, EType type, U32 flags) : mID(id), mName(name), mType(type), mFlags(flags) { // muting is done by root objects only - try to find this objects root LLViewerObject* mute_object = get_object_to_mute_from_id(id); if(mute_object && mute_object->getID() != id) { mID = mute_object->getID(); LLNameValue* firstname = mute_object->getNVPair("FirstName"); LLNameValue* lastname = mute_object->getNVPair("LastName"); if (firstname && lastname) { mName = LLCacheName::buildFullName( firstname->getString(), lastname->getString()); } mType = mute_object->isAvatar() ? AGENT : OBJECT; } } std::string LLMute::getDisplayType() const { switch (mType) { case BY_NAME: default: return LLTrans::getString("MuteByName"); break; case AGENT: return LLTrans::getString("MuteAgent"); break; case OBJECT: return LLTrans::getString("MuteObject"); break; case GROUP: return LLTrans::getString("MuteGroup"); break; case EXTERNAL: return LLTrans::getString("MuteExternal"); break; } } //----------------------------------------------------------------------------- // LLMuteList() //----------------------------------------------------------------------------- LLMuteList::LLMuteList() : mIsLoaded(false) { gGenericDispatcher.addHandler("emptymutelist", &sDispatchEmptyMuteList); // Register our callbacks. We may be constructed before gMessageSystem, so // use callWhenReady() to register them as soon as gMessageSystem becomes // available. // When using bind(), must be explicit about default arguments such as // that last NULL. gMessageSystem.callWhenReady(boost::bind(&LLMessageSystem::setHandlerFuncFast, _1, _PREHASH_MuteListUpdate, processMuteListUpdate, static_cast(NULL))); gMessageSystem.callWhenReady(boost::bind(&LLMessageSystem::setHandlerFuncFast, _1, _PREHASH_UseCachedMuteList, processUseCachedMuteList, static_cast(NULL))); // make sure mute list's instance gets initialized before we start any name requests LLAvatarNameCache::getInstance()->setAccountNameChangedCallback([this](const LLUUID& id, const LLAvatarName& av_name) { // it would be better to just pass LLAvatarName instead of doing unnesssesary copies // but this way is just more convinient onAccountNameChanged(id, av_name.getUserName()); }); } //----------------------------------------------------------------------------- // ~LLMuteList() //----------------------------------------------------------------------------- LLMuteList::~LLMuteList() { } void LLMuteList::cleanupSingleton() { LLAvatarNameCache::getInstance()->setAccountNameChangedCallback(NULL); } bool LLMuteList::isLinden(const std::string& name) { std::string username = boost::replace_all_copy(name, ".", " "); typedef boost::tokenizer > tokenizer; boost::char_separator sep(" "); tokenizer tokens(username, sep); tokenizer::iterator token_iter = tokens.begin(); if (token_iter == tokens.end()) return false; token_iter++; if (token_iter == tokens.end()) return false; std::string last_name = *token_iter; LLStringUtil::toLower(last_name); return last_name == "linden"; } static LLVOAvatar* find_avatar(const LLUUID& id) { LLViewerObject *obj = gObjectList.findObject(id); while (obj && obj->isAttachment()) { obj = (LLViewerObject *)obj->getParent(); } if (obj && obj->isAvatar()) { return (LLVOAvatar*)obj; } else { return NULL; } } bool LLMuteList::add(const LLMute& mute, U32 flags) { // Can't mute text from Lindens if ((mute.mType == LLMute::AGENT) && isLinden(mute.mName) && (flags & LLMute::flagTextChat || flags == 0)) { LL_WARNS() << "Trying to mute a Linden; ignored" << LL_ENDL; LLNotifications::instance().add("MuteLinden", LLSD(), LLSD()); return false; } // Can't mute self. if (mute.mType == LLMute::AGENT && mute.mID == gAgent.getID()) { LL_WARNS() << "Trying to self; ignored" << LL_ENDL; return false; } static LLCachedControl mute_list_limit(gSavedSettings, "MuteListLimit", 1000); if (getMutes().size() >= mute_list_limit) { LL_WARNS() << "Mute limit is reached; ignored" << LL_ENDL; LLSD args; args["MUTE_LIMIT"] = mute_list_limit; LLNotifications::instance().add(LLNotification::Params("MuteLimitReached").substitutions(args)); return false; } if (mute.mType == LLMute::BY_NAME) { // Can't mute empty string by name if (mute.mName.empty()) { LL_WARNS() << "Trying to mute empty string by-name" << LL_ENDL; return false; } // Null mutes must have uuid null if (mute.mID.notNull()) { LL_WARNS() << "Trying to add by-name mute with non-null id" << LL_ENDL; return false; } std::pair result = mLegacyMutes.insert(mute.mName); if (result.second) { LL_INFOS() << "Muting by name " << mute.mName << LL_ENDL; updateAdd(mute); notifyObservers(); notifyObserversDetailed(mute); return true; } else { LL_INFOS() << "duplicate mute ignored" << LL_ENDL; // was duplicate return false; } } else { // Need a local (non-const) copy to set up flags properly. LLMute localmute = mute; // If an entry for the same entity is already in the list, remove it, saving flags as necessary. mute_set_t::iterator it = mMutes.find(localmute); if (it != mMutes.end()) { // This mute is already in the list. Save the existing entry's flags if that's warranted. localmute.mFlags = it->mFlags; mMutes.erase(it); // Don't need to call notifyObservers() here, since it will happen after the entry has been re-added below. } else { // There was no entry in the list previously. Fake things up by making it look like the previous entry had all properties unmuted. localmute.mFlags = LLMute::flagAll; } if(flags) { // The user passed some combination of flags. Make sure those flag bits are turned off (i.e. those properties will be muted). localmute.mFlags &= (~flags); } else { // The user passed 0. Make sure all flag bits are turned off (i.e. all properties will be muted). localmute.mFlags = 0; } // (re)add the mute entry. { std::pair result = mMutes.insert(localmute); if (result.second) { LL_INFOS() << "Muting " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << LL_ENDL; updateAdd(localmute); notifyObservers(); notifyObserversDetailed(localmute); //mute local lights that are attached to the avatar LLVOAvatar *avatarp = find_avatar(localmute.mID); if (avatarp) { LLPipeline::removeMutedAVsLights(avatarp); } //remove agent's notifications as well if (localmute.mType == LLMute::AGENT) { LLNotifications::instance().cancelByOwner(localmute.mID); } return true; } } } // If we were going to return success, we'd have done it by now. return false; } void LLMuteList::updateAdd(const LLMute& mute) { // External mutes are local only, don't send them to the server. if (mute.mType == LLMute::EXTERNAL) { return; } // Update the database LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_UpdateMuteListEntry); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->nextBlockFast(_PREHASH_MuteData); msg->addUUIDFast(_PREHASH_MuteID, mute.mID); msg->addStringFast(_PREHASH_MuteName, mute.mName); msg->addS32("MuteType", mute.mType); msg->addU32("MuteFlags", mute.mFlags); gAgent.sendReliableMessage(); if (!mIsLoaded) { LL_WARNS() << "Added elements to non-initialized block list" << LL_ENDL; } mIsLoaded = true; // why is this here? -MG } bool LLMuteList::remove(const LLMute& mute, U32 flags) { bool found = false; // First, remove from main list. mute_set_t::iterator it = mMutes.find(mute); if (it != mMutes.end()) { LLMute localmute = *it; bool remove = true; if(flags) { // If the user passed mute flags, we may only want to turn some flags on. localmute.mFlags |= flags; if(localmute.mFlags == LLMute::flagAll) { // Every currently available mute property has been masked out. // Remove the mute entry entirely. } else { // Only some of the properties are masked out. Update the entry. remove = false; } } else { // The caller didn't pass any flags -- just remove the mute entry entirely. // set flags to notify observers with (flag being present means that something is allowed) localmute.mFlags = LLMute::flagAll; } // Always remove the entry from the set -- it will be re-added with new flags if necessary. mMutes.erase(it); if(remove) { // The entry was actually removed. Notify the server. updateRemove(localmute); LL_INFOS() << "Unmuting " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << LL_ENDL; } else { // Flags were updated, the mute entry needs to be retransmitted to the server and re-added to the list. mMutes.insert(localmute); updateAdd(localmute); LL_INFOS() << "Updating mute entry " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << LL_ENDL; } // Must be after erase. notifyObservers(); notifyObserversDetailed(localmute); } else { // Clean up any legacy mutes string_set_t::iterator legacy_it = mLegacyMutes.find(mute.mName); if (legacy_it != mLegacyMutes.end()) { // Database representation of legacy mute is UUID null. LLMute mute(LLUUID::null, *legacy_it, LLMute::BY_NAME); updateRemove(mute); mLegacyMutes.erase(legacy_it); // Must be after erase. notifyObservers(); notifyObserversDetailed(mute); } } return found; } void LLMuteList::updateRemove(const LLMute& mute) { // External mutes are not sent to the server anyway, no need to remove them. if (mute.mType == LLMute::EXTERNAL) { return; } LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_RemoveMuteListEntry); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->nextBlockFast(_PREHASH_MuteData); msg->addUUIDFast(_PREHASH_MuteID, mute.mID); msg->addString("MuteName", mute.mName); gAgent.sendReliableMessage(); } void notify_automute_callback(const LLUUID& agent_id, const LLAvatarName& full_name, LLMuteList::EAutoReason reason) { std::string notif_name; switch (reason) { default: case LLMuteList::AR_IM: notif_name = "AutoUnmuteByIM"; break; case LLMuteList::AR_INVENTORY: notif_name = "AutoUnmuteByInventory"; break; case LLMuteList::AR_MONEY: notif_name = "AutoUnmuteByMoney"; break; } LLSD args; args["NAME"] = full_name.getUserName(); LLNotificationPtr notif_ptr = LLNotifications::instance().add(notif_name, args, LLSD()); if (notif_ptr) { std::string message = notif_ptr->getMessage(); if (reason == LLMuteList::AR_IM) { LLIMModel::getInstance()->addMessage(agent_id, SYSTEM_FROM, LLUUID::null, message); } } } bool LLMuteList::autoRemove(const LLUUID& agent_id, const EAutoReason reason) { bool removed = false; if (isMuted(agent_id)) { LLMute automute(agent_id, LLStringUtil::null, LLMute::AGENT); removed = true; remove(automute); LLAvatarName av_name; if (LLAvatarNameCache::get(agent_id, &av_name)) { // name in cache, call callback directly notify_automute_callback(agent_id, av_name, reason); } else { // not in cache, lookup name from cache LLAvatarNameCache::get(agent_id, boost::bind(¬ify_automute_callback, _1, _2, reason)); } } return removed; } std::vector LLMuteList::getMutes() const { std::vector mutes; for (mute_set_t::const_iterator it = mMutes.begin(); it != mMutes.end(); ++it) { mutes.push_back(*it); } for (string_set_t::const_iterator it = mLegacyMutes.begin(); it != mLegacyMutes.end(); ++it) { LLMute legacy(LLUUID::null, *it); mutes.push_back(legacy); } std::sort(mutes.begin(), mutes.end(), compare_by_name()); return mutes; } //----------------------------------------------------------------------------- // loadFromFile() //----------------------------------------------------------------------------- bool LLMuteList::loadFromFile(const std::string& filename) { if(!filename.size()) { LL_WARNS() << "Mute List Filename is Empty!" << LL_ENDL; return false; } LLFILE* fp = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ if (!fp) { LL_WARNS() << "Couldn't open mute list " << filename << LL_ENDL; return false; } // *NOTE: Changing the size of these buffers will require changes // in the scanf below. char id_buffer[MAX_STRING]; /*Flawfinder: ignore*/ char name_buffer[MAX_STRING]; /*Flawfinder: ignore*/ char buffer[MAX_STRING]; /*Flawfinder: ignore*/ while (!feof(fp) && fgets(buffer, MAX_STRING, fp)) { id_buffer[0] = '\0'; name_buffer[0] = '\0'; S32 type = 0; U32 flags = 0; sscanf( /* Flawfinder: ignore */ buffer, " %d %254s %254[^|]| %u\n", &type, id_buffer, name_buffer, &flags); LLUUID id = LLUUID(id_buffer); LLMute mute(id, std::string(name_buffer), (LLMute::EType)type, flags); if (mute.mID.isNull() || mute.mType == LLMute::BY_NAME) { mLegacyMutes.insert(mute.mName); } else { mMutes.insert(mute); } } fclose(fp); setLoaded(); // server does not maintain up-to date account names (not display names!) // in this list, so it falls to viewer. pending_names_t::iterator iter = mPendingAgentNameUpdates.begin(); pending_names_t::iterator end = mPendingAgentNameUpdates.end(); while (iter != end) { // this will send updates to server, make sure mIsLoaded is set onAccountNameChanged(iter->first, iter->second); iter++; } mPendingAgentNameUpdates.clear(); return true; } //----------------------------------------------------------------------------- // saveToFile() //----------------------------------------------------------------------------- bool LLMuteList::saveToFile(const std::string& filename) { if(!filename.size()) { LL_WARNS() << "Mute List Filename is Empty!" << LL_ENDL; return false; } LLFILE* fp = LLFile::fopen(filename, "wb"); /*Flawfinder: ignore*/ if (!fp) { LL_WARNS() << "Couldn't open mute list " << filename << LL_ENDL; return false; } // legacy mutes have null uuid std::string id_string; LLUUID::null.toString(id_string); for (string_set_t::iterator it = mLegacyMutes.begin(); it != mLegacyMutes.end(); ++it) { fprintf(fp, "%d %s %s|\n", (S32)LLMute::BY_NAME, id_string.c_str(), it->c_str()); } for (mute_set_t::iterator it = mMutes.begin(); it != mMutes.end(); ++it) { // Don't save external mutes as they are not sent to the server and probably won't //be valid next time anyway. if (it->mType != LLMute::EXTERNAL) { it->mID.toString(id_string); const std::string& name = it->mName; fprintf(fp, "%d %s %s|%u\n", (S32)it->mType, id_string.c_str(), name.c_str(), it->mFlags); } } fclose(fp); return true; } bool LLMuteList::isMuted(const LLUUID& id, const std::string& name, U32 flags) const { // for objects, check for muting on their parent prim LLViewerObject* mute_object = get_object_to_mute_from_id(id); LLUUID id_to_check = (mute_object) ? mute_object->getID() : id; // don't need name or type for lookup LLMute mute(id_to_check); mute_set_t::const_iterator mute_it = mMutes.find(mute); if (mute_it != mMutes.end()) { // If any of the flags the caller passed are set, this item isn't considered muted for this caller. if(flags & mute_it->mFlags) { return false; } return true; } // empty names can't be legacy-muted bool avatar = mute_object && mute_object->isAvatar(); if (name.empty() || avatar) return false; // Look in legacy pile string_set_t::const_iterator legacy_it = mLegacyMutes.find(name); return legacy_it != mLegacyMutes.end(); } bool LLMuteList::isMuted(const std::string& username, U32 flags) const { mute_set_t::const_iterator mute_iter = mMutes.begin(); while(mute_iter != mMutes.end()) { // can't convert "leha.test" into "LeHa TesT" so username comparison is more reliable if (mute_iter->mType == LLMute::AGENT && LLCacheName::buildUsername(mute_iter->mName) == username) { return true; } mute_iter++; } return false; } //----------------------------------------------------------------------------- // requestFromServer() //----------------------------------------------------------------------------- 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"; LLCRC crc; crc.update(filename); LLMessageSystem* msg = gMessageSystem; msg->newMessageFast(_PREHASH_MuteListRequest); msg->nextBlockFast(_PREHASH_AgentData); msg->addUUIDFast(_PREHASH_AgentID, agent_id); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->nextBlockFast(_PREHASH_MuteData); msg->addU32Fast(_PREHASH_MuteCRC, crc.getCRC()); if (gDisconnected) { LL_WARNS() << "Trying to request mute list when disconnected!" << LL_ENDL; return; } if (!gAgent.getRegion()) { LL_WARNS() << "No region for agent yet, skipping mute list request!" << LL_ENDL; return; } // 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); } //----------------------------------------------------------------------------- // cache() //----------------------------------------------------------------------------- void LLMuteList::cache(const LLUUID& agent_id) { // Write to disk even if empty. if(mIsLoaded) { 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"; saveToFile(filename); } } //----------------------------------------------------------------------------- // Static message handlers //----------------------------------------------------------------------------- void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**) { LL_INFOS() << "LLMuteList::processMuteListUpdate()" << LL_ENDL; LLUUID agent_id; 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; return; } std::string unclean_filename; msg->getStringFast(_PREHASH_MuteData, _PREHASH_Filename, unclean_filename); std::string filename = LLDir::getScrubbedFileName(unclean_filename); std::string *local_filename_and_path = new std::string(gDirUtilp->getExpandedFilename( LL_PATH_CACHE, filename )); gXferManager->requestFile(*local_filename_and_path, filename, LL_PATH_CACHE, msg->getSender(), true, // make the remote file temporary. onFileMuteList, (void**)local_filename_and_path, LLXferManager::HIGH_PRIORITY); } 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); } void LLMuteList::onFileMuteList(void** user_data, S32 error_code, LLExtStat ext_status) { LL_INFOS() << "LLMuteList::processMuteListFile()" << LL_ENDL; std::string* local_filename_and_path = (std::string*)user_data; if(local_filename_and_path && !local_filename_and_path->empty() && (error_code == 0)) { LLMuteList::getInstance()->loadFromFile(*local_filename_and_path); LLFile::remove(*local_filename_and_path); } delete local_filename_and_path; } void LLMuteList::onAccountNameChanged(const LLUUID& id, const std::string& username) { if (mIsLoaded) { LLMute mute(id, username, LLMute::AGENT); mute_set_t::iterator mute_it = mMutes.find(mute); if (mute_it != mMutes.end() && mute_it->mName != mute.mName && mute_it->mType == LLMute::AGENT) // just in case, it is std::set, not map { // existing mute, but name changed, copy data mute.mFlags = mute_it->mFlags; // erase old variant mMutes.erase(mute_it); // (re)add the mute entry. { std::pair result = mMutes.insert(mute); if (result.second) { LL_INFOS() << "Muting " << mute.mName << " id " << mute.mID << " flags " << mute.mFlags << LL_ENDL; updateAdd(mute); // Do not notify observers here, observers do not know or need to handle name changes // Example: block list considers notifyObserversDetailed as a selection update; // Various chat/voice statuses care only about id and flags // Since apropriate update time for account names is considered to be in 'hours' it is // fine not to update UI (will be fine after restart or couple other changes) } } } } else { // Delay update until we load file // Ex: Buddies list can arrive too early since we prerequest // names from Buddies list before we load blocklist mPendingAgentNameUpdates[id] = username; } } void LLMuteList::addObserver(LLMuteListObserver* observer) { mObservers.insert(observer); } void LLMuteList::removeObserver(LLMuteListObserver* observer) { mObservers.erase(observer); } void LLMuteList::setLoaded() { mIsLoaded = true; notifyObservers(); } void LLMuteList::notifyObservers() { for (observer_set_t::iterator it = mObservers.begin(); it != mObservers.end(); ) { LLMuteListObserver* observer = *it; observer->onChange(); // In case onChange() deleted an entry. it = mObservers.upper_bound(observer); } } void LLMuteList::notifyObserversDetailed(const LLMute& mute) { for (observer_set_t::iterator it = mObservers.begin(); it != mObservers.end(); ) { LLMuteListObserver* observer = *it; observer->onChangeDetailed(mute); // In case onChange() deleted an entry. it = mObservers.upper_bound(observer); } } LLRenderMuteList::LLRenderMuteList() {} bool LLRenderMuteList::saveToFile() { std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "render_mute_settings.txt"); LLFILE* fp = LLFile::fopen(filename, "wb"); if (!fp) { LL_WARNS() << "Couldn't open render mute list file: " << filename << LL_ENDL; return false; } for (std::map::iterator it = sVisuallyMuteSettingsMap.begin(); it != sVisuallyMuteSettingsMap.end(); ++it) { if (it->second != 0) { std::string id_string; it->first.toString(id_string); fprintf(fp, "%d %s [%d]\n", (S32)it->second, id_string.c_str(), (S32)sVisuallyMuteDateMap[it->first]); } } fclose(fp); return true; } bool LLRenderMuteList::loadFromFile() { std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "render_mute_settings.txt"); LLFILE* fp = LLFile::fopen(filename, "rb"); if (!fp) { LL_WARNS() << "Couldn't open render mute list file: " << filename << LL_ENDL; return false; } char id_buffer[MAX_STRING]; char buffer[MAX_STRING]; while (!feof(fp) && fgets(buffer, MAX_STRING, fp)) { id_buffer[0] = '\0'; S32 setting = 0; S32 time = 0; sscanf(buffer, " %d %254s [%d]\n", &setting, id_buffer, &time); sVisuallyMuteSettingsMap[LLUUID(id_buffer)] = setting; sVisuallyMuteDateMap[LLUUID(id_buffer)] = (time == 0) ? (S32)time_corrected() : time; } fclose(fp); return true; } void LLRenderMuteList::saveVisualMuteSetting(const LLUUID& agent_id, S32 setting) { if(setting == 0) { sVisuallyMuteSettingsMap.erase(agent_id); sVisuallyMuteDateMap.erase(agent_id); } else { sVisuallyMuteSettingsMap[agent_id] = setting; if (sVisuallyMuteDateMap.find(agent_id) == sVisuallyMuteDateMap.end()) { sVisuallyMuteDateMap[agent_id] = (S32)time_corrected(); } } saveToFile(); notifyObservers(); } S32 LLRenderMuteList::getSavedVisualMuteSetting(const LLUUID& agent_id) { std::map::iterator iter = sVisuallyMuteSettingsMap.find(agent_id); if (iter != sVisuallyMuteSettingsMap.end()) { return iter->second; } return 0; } S32 LLRenderMuteList::getVisualMuteDate(const LLUUID& agent_id) { std::map::iterator iter = sVisuallyMuteDateMap.find(agent_id); if (iter != sVisuallyMuteDateMap.end()) { return iter->second; } return 0; } void LLRenderMuteList::addObserver(LLMuteListObserver* observer) { mObservers.insert(observer); } void LLRenderMuteList::removeObserver(LLMuteListObserver* observer) { mObservers.erase(observer); } void LLRenderMuteList::notifyObservers() { for (observer_set_t::iterator it = mObservers.begin(); it != mObservers.end(); ) { LLMuteListObserver* observer = *it; observer->onChange(); // In case onChange() deleted an entry. it = mObservers.upper_bound(observer); } }