/** * @file llnotifications.cpp * @brief Non-UI queue manager for keeping a prioritized list of notifications * * $LicenseInfo:firstyear=2008&license=viewergpl$ * * Copyright (c) 2008-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "linden_common.h" #include "lluictrlfactory.h" #include "lldir.h" #include "llsdserialize.h" #include "llnotifications.h" #include #include #include "../newview/hippogridmanager.h" const std::string NOTIFICATION_PERSIST_VERSION = "0.93"; // local channel for notification history class LLNotificationHistoryChannel : public LLNotificationChannel { LOG_CLASS(LLNotificationHistoryChannel); public: LLNotificationHistoryChannel(const std::string& filename) : LLNotificationChannel("History", "Visible", &historyFilter, LLNotificationComparators::orderByUUID()), mFileName(filename) { connectChanged(boost::bind(&LLNotificationHistoryChannel::historyHandler, this, _1)); loadPersistentNotifications(); } private: bool historyHandler(const LLSD& payload) { // we ignore "load" messages, but rewrite the persistence file on any other std::string sigtype = payload["sigtype"]; if (sigtype != "load") { savePersistentNotifications(); } return false; } // The history channel gets all notifications except those that have been cancelled static bool historyFilter(LLNotificationPtr pNotification) { return !pNotification->isCancelled(); } void savePersistentNotifications() { LL_DEBUGS("Notifications") << "Saving open notifications to " << mFileName << LL_ENDL; llofstream notify_file(mFileName.c_str()); if (!notify_file.is_open()) { LL_WARNS("Notifications") << "Failed to open " << mFileName << LL_ENDL; return; } LLSD output; output["version"] = NOTIFICATION_PERSIST_VERSION; LLSD& data = output["data"]; for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it) { if (!LLNotifications::instance().templateExists((*it)->getName())) continue; // only store notifications flagged as persisting LLNotificationTemplatePtr templatep = LLNotifications::instance().getTemplate((*it)->getName()); if (!templatep->mPersist) continue; data.append((*it)->asLLSD()); } LLPointer formatter = new LLSDXMLFormatter(); formatter->format(output, notify_file, LLSDFormatter::OPTIONS_PRETTY); } void loadPersistentNotifications() { LL_INFOS("Notifications") << "Loading open notifications from " << mFileName << LL_ENDL; llifstream notify_file(mFileName.c_str()); if (!notify_file.is_open()) { LL_WARNS("Notifications") << "Failed to open " << mFileName << LL_ENDL; return; } LLSD input; LLPointer parser = new LLSDXMLParser(); if (parser->parse(notify_file, input, LLSDSerialize::SIZE_UNLIMITED) < 0) { LL_WARNS("Notifications") << "Failed to parse open notifications" << LL_ENDL; return; } if (input.isUndefined()) return; std::string version = input["version"]; if (version != NOTIFICATION_PERSIST_VERSION) { LL_WARNS("Notifications") << "Bad open notifications version: " << version << LL_ENDL; return; } LLSD& data = input["data"]; if (data.isUndefined()) return; LLNotifications& instance = LLNotifications::instance(); for (LLSD::array_const_iterator notification_it = data.beginArray(); notification_it != data.endArray(); ++notification_it) { instance.add(LLNotificationPtr(new LLNotification(*notification_it))); } } //virtual void onDelete(LLNotificationPtr pNotification) { // we want to keep deleted notifications in our log mItems.insert(pNotification); return; } private: std::string mFileName; }; bool filterIgnoredNotifications(LLNotificationPtr notification) { LLNotificationFormPtr form = notification->getForm(); // Check to see if the user wants to ignore this alert if (form->getIgnoreType() != LLNotificationForm::IGNORE_NO) { return LLUI::sConfigGroup->getWarning(notification->getName()); } return true; } bool handleIgnoredNotification(const LLSD& payload) { if (payload["sigtype"].asString() == "add") { LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); if (!pNotif) return false; LLNotificationFormPtr form = pNotif->getForm(); LLSD response; switch(form->getIgnoreType()) { case LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE: response = pNotif->getResponseTemplate(LLNotification::WITH_DEFAULT_BUTTON); break; case LLNotificationForm::IGNORE_WITH_LAST_RESPONSE: response = LLUI::sIgnoresGroup->getLLSD("Default" + pNotif->getName()); break; case LLNotificationForm::IGNORE_SHOW_AGAIN: break; default: return false; } pNotif->setIgnored(true); pNotif->respond(response); return true; // don't process this item any further } return false; } namespace LLNotificationFilters { // a sample filter bool includeEverything(LLNotificationPtr p) { return true; } }; LLNotificationForm::LLNotificationForm() : mFormData(LLSD::emptyArray()), mIgnore(IGNORE_NO) { } LLNotificationForm::LLNotificationForm(const std::string& name, const LLXMLNodePtr xml_node) : mFormData(LLSD::emptyArray()), mIgnore(IGNORE_NO) { if (!xml_node->hasName("form")) { llwarns << "Bad xml node for form: " << xml_node->getName() << llendl; } LLXMLNodePtr child = xml_node->getFirstChild(); while(child) { child = LLNotifications::instance().checkForXMLTemplate(child); LLSD item_entry; std::string element_name = child->getName()->mString; if (element_name == "ignore") { bool save_option = false; child->getAttribute_bool("save_option", save_option); if (!save_option) { mIgnore = IGNORE_WITH_DEFAULT_RESPONSE; } else { // remember last option chosen by user and automatically respond with that in the future mIgnore = IGNORE_WITH_LAST_RESPONSE; LLUI::sIgnoresGroup->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name)); } child->getAttributeString("text", mIgnoreMsg); LLUI::sIgnoresGroup->addWarning(name); } else { // flatten xml form entry into single LLSD map with type==name item_entry["type"] = element_name; const LLXMLAttribList::iterator attrib_end = child->mAttributes.end(); for(LLXMLAttribList::iterator attrib_it = child->mAttributes.begin(); attrib_it != attrib_end; ++attrib_it) { item_entry[std::string(attrib_it->second->getName()->mString)] = attrib_it->second->getValue(); } item_entry["value"] = child->getTextContents(); mFormData.append(item_entry); } child = child->getNextSibling(); } } LLNotificationForm::LLNotificationForm(const LLSD& sd) { if (sd.isArray()) { mFormData = sd; } else { llwarns << "Invalid form data " << sd << llendl; mFormData = LLSD::emptyArray(); } } LLSD LLNotificationForm::asLLSD() const { return mFormData; } LLSD LLNotificationForm::getElement(const std::string& element_name) { for (LLSD::array_const_iterator it = mFormData.beginArray(); it != mFormData.endArray(); ++it) { if ((*it)["name"].asString() == element_name) return (*it); } return LLSD(); } bool LLNotificationForm::hasElement(const std::string& element_name) { for (LLSD::array_const_iterator it = mFormData.beginArray(); it != mFormData.endArray(); ++it) { if ((*it)["name"].asString() == element_name) return true; } return false; } void LLNotificationForm::addElement(const std::string& type, const std::string& name, const LLSD& value) { LLSD element; element["type"] = type; element["name"] = name; element["text"] = name; element["value"] = value; element["index"] = mFormData.size(); mFormData.append(element); } void LLNotificationForm::append(const LLSD& sub_form) { if (sub_form.isArray()) { for (LLSD::array_const_iterator it = sub_form.beginArray(); it != sub_form.endArray(); ++it) { mFormData.append(*it); } } } void LLNotificationForm::formatElements(const LLSD& substitutions) { for (LLSD::array_iterator it = mFormData.beginArray(); it != mFormData.endArray(); ++it) { // format "text" component of each form element if ((*it).has("text")) { std::string text = (*it)["text"].asString(); text = LLNotification::format(text, substitutions); (*it)["text"] = text; } if ((*it)["type"].asString() == "text" && (*it).has("value")) { std::string value = (*it)["value"].asString(); value = LLNotification::format(value, substitutions); (*it)["value"] = value; } } } std::string LLNotificationForm::getDefaultOption() { for (LLSD::array_const_iterator it = mFormData.beginArray(); it != mFormData.endArray(); ++it) { if ((*it)["default"]) return (*it)["name"].asString(); } return ""; } LLNotificationTemplate::LLNotificationTemplate() : mExpireSeconds(0), mExpireOption(-1), mURLOption(-1), mUnique(false), mPriority(NOTIFICATION_PRIORITY_NORMAL) { mForm = LLNotificationFormPtr(new LLNotificationForm()); } LLNotification::LLNotification(const LLNotification::Params& p) : mTimestamp(p.timestamp), mSubstitutions(p.substitutions), mPayload(p.payload), mExpiresAt(0), mResponseFunctorName(p.functor_name), mTemporaryResponder(p.mTemporaryResponder), mRespondedTo(false), mPriority(p.priority), mCancelled(false), mIgnored(false) { mId.generate(); init(p.name, p.form_elements); } LLNotification::LLNotification(const LLSD& sd) : mTemporaryResponder(false), mRespondedTo(false), mCancelled(false), mIgnored(false) { mId.generate(); mSubstitutions = sd["substitutions"]; mPayload = sd["payload"]; mTimestamp = sd["time"]; mExpiresAt = sd["expiry"]; mPriority = (ENotificationPriority)sd["priority"].asInteger(); mResponseFunctorName = sd["responseFunctor"].asString(); std::string templatename = sd["name"].asString(); init(templatename, LLSD()); // replace form with serialized version mForm = LLNotificationFormPtr(new LLNotificationForm(sd["form"])); } LLSD LLNotification::asLLSD() { LLSD output; output["name"] = mTemplatep->mName; output["form"] = getForm()->asLLSD(); output["substitutions"] = mSubstitutions; output["payload"] = mPayload; output["time"] = mTimestamp; output["expiry"] = mExpiresAt; output["priority"] = (S32)mPriority; output["responseFunctor"] = mResponseFunctorName; return output; } void LLNotification::update() { LLNotifications::instance().update(shared_from_this()); } void LLNotification::updateFrom(LLNotificationPtr other) { // can only update from the same notification type if (mTemplatep != other->mTemplatep) return; // NOTE: do NOT change the ID, since it is the key to // this given instance, just update all the metadata //mId = other->mId; mPayload = other->mPayload; mSubstitutions = other->mSubstitutions; mTimestamp = other->mTimestamp; mExpiresAt = other->mExpiresAt; mCancelled = other->mCancelled; mIgnored = other->mIgnored; mPriority = other->mPriority; mForm = other->mForm; mResponseFunctorName = other->mResponseFunctorName; mRespondedTo = other->mRespondedTo; mTemporaryResponder = other->mTemporaryResponder; update(); } const LLNotificationFormPtr LLNotification::getForm() { return mForm; } void LLNotification::cancel() { mCancelled = true; } LLSD LLNotification::getResponseTemplate(EResponseTemplateType type) { LLSD response = LLSD::emptyMap(); for (S32 element_idx = 0; element_idx < mForm->getNumElements(); ++element_idx) { LLSD element = mForm->getElement(element_idx); if (element.has("name")) { response[element["name"].asString()] = element["value"]; } if ((type == WITH_DEFAULT_BUTTON) && element["default"].asBoolean()) { response[element["name"].asString()] = true; } } return response; } //static S32 LLNotification::getSelectedOption(const LLSD& notification, const LLSD& response) { LLNotificationForm form(notification["form"]); for (S32 element_idx = 0; element_idx < form.getNumElements(); ++element_idx) { LLSD element = form.getElement(element_idx); // only look at buttons if (element["type"].asString() == "button" && response[element["name"].asString()].asBoolean()) { return element["index"].asInteger(); } } return -1; } //static std::string LLNotification::getSelectedOptionName(const LLSD& response) { for (LLSD::map_const_iterator response_it = response.beginMap(); response_it != response.endMap(); ++response_it) { if (response_it->second.isBoolean() && response_it->second.asBoolean()) { return response_it->first; } } return ""; } void LLNotification::respond(const LLSD& response) { mRespondedTo = true; // look up the functor LLNotificationFunctorRegistry::ResponseFunctor functor = LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName); // and then call it functor(asLLSD(), response); if (mTemporaryResponder) { LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName); mResponseFunctorName = ""; mTemporaryResponder = false; } if (mForm->getIgnoreType() != LLNotificationForm::IGNORE_NO) { LLUI::sIgnoresGroup->setWarning(getName(), !mIgnored); if (mIgnored && mForm->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE) { LLUI::sIgnoresGroup->setLLSD("Default" + getName(), response); } } update(); } void LLNotification::setIgnored(bool ignore) { mIgnored = ignore; } void LLNotification::setResponseFunctor(std::string const &responseFunctorName) { if (mTemporaryResponder) // get rid of the old one LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName); mResponseFunctorName = responseFunctorName; mTemporaryResponder = false; } bool LLNotification::payloadContainsAll(const std::vector& required_fields) const { for(std::vector::const_iterator required_fields_it = required_fields.begin(); required_fields_it != required_fields.end(); required_fields_it++) { std::string required_field_name = *required_fields_it; if( ! getPayload().has(required_field_name)) { return false; // a required field was not found } } return true; // all required fields were found } bool LLNotification::isEquivalentTo(LLNotificationPtr that) const { if (this->mTemplatep->mName != that->mTemplatep->mName) { return false; // must have the same template name or forget it } if (this->mTemplatep->mUnique) { // highlander bit sez there can only be one of these return this->payloadContainsAll(that->mTemplatep->mUniqueContext) && that->payloadContainsAll(this->mTemplatep->mUniqueContext); } return false; } void LLNotification::init(const std::string& template_name, const LLSD& form_elements) { mTemplatep = LLNotifications::instance().getTemplate(template_name); if (!mTemplatep) return; // add default substitutions mSubstitutions["[SECOND_LIFE]"] = LLNotifications::instance().getGlobalString("SECOND_LIFE"); mSubstitutions["[VIEWER_NAME]"] = LLNotifications::instance().getGlobalString("VIEWER_NAME"); mSubstitutions["[VIEWER_SITE]"] = LLNotifications::instance().getGlobalString("VIEWER_SITE"); mSubstitutions["[GRID_NAME]"] = gHippoGridManager->getConnectedGrid()->getGridName(); mSubstitutions["[GRID_SITE]"] = gHippoGridManager->getConnectedGrid()->getWebSite(); mSubstitutions["[CURRENCY]"] = gHippoGridManager->getConnectedGrid()->getCurrencySymbol(); mSubstitutions["_URL"] = getURL(); mSubstitutions["_NAME"] = template_name; // TODO: something like this so that a missing alert is sensible: //mSubstitutions["_ARGS"] = get_all_arguments_as_text(mSubstitutions); mForm = LLNotificationFormPtr(new LLNotificationForm(*mTemplatep->mForm)); mForm->append(form_elements); // apply substitution to form labels mForm->formatElements(mSubstitutions); LLDate rightnow = LLDate::now(); if (mTemplatep->mExpireSeconds) { mExpiresAt = LLDate(rightnow.secondsSinceEpoch() + mTemplatep->mExpireSeconds); } if (mPriority == NOTIFICATION_PRIORITY_UNSPECIFIED) { mPriority = mTemplatep->mPriority; } } std::string LLNotification::summarize() const { std::string s = "Notification("; s += getName(); s += ") : "; s += mTemplatep ? mTemplatep->mMessage : ""; // should also include timestamp and expiration time (but probably not payload) return s; } //static std::string LLNotification::format(const std::string& s, const LLSD& substitutions) { if (!substitutions.isMap()) { return s; } std::ostringstream output; // match strings like [NAME] const boost::regex key("\\[([0-9_A-Z]+)]"); std::string::const_iterator start = s.begin(); std::string::const_iterator end = s.end(); boost::smatch match; while (boost::regex_search(start, end, match, key, boost::match_default)) { bool found_replacement = false; std::string replacement; // see if we have a replacement for the bracketed string (without the brackets) // test first using has() because if we just look up with operator[] we get back an // empty string even if the value is missing. We want to distinguish between // missing replacements and deliberately empty replacement strings. if (substitutions.has(std::string(match[1].first, match[1].second))) { replacement = substitutions[std::string(match[1].first, match[1].second)].asString(); found_replacement = true; } // if not, see if there's one WITH brackets else if (substitutions.has(std::string(match[0].first, match[0].second))) { replacement = substitutions[std::string(match[0].first, match[0].second)].asString(); found_replacement = true; } if (found_replacement) { // found a replacement // "hello world" is output output << std::string(start, match[0].first) << replacement; } else { // we had no replacement, so leave the string we searched for so that it gets noticed by QA // "hello [NAME_NOT_FOUND]" is output output << std::string(start, match[0].second); } // update search position start = match[0].second; } // send the remainder of the string (with no further matches for bracketed names) output << std::string(start, end); return output.str(); } std::string LLNotification::getMessage() const { // all our callers cache this result, so it gives us more flexibility // to do the substitution at call time rather than attempting to // cache it in the notification if (!mTemplatep) return std::string(); return format(mTemplatep->mMessage, mSubstitutions); } std::string LLNotification::getLabel() const { return (mTemplatep ? format(mTemplatep->mLabel, mSubstitutions) : ""); } // ========================================================= // LLNotificationChannel implementation // --- void LLNotificationChannelBase::connectChanged(const LLStandardSignal::slot_type& slot) { // when someone wants to connect to a channel, we first throw them // all of the notifications that are already in the channel // we use a special signal called "load" in case the channel wants to care // only about new notifications for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it) { slot.get_slot_function()(LLSD().insert("sigtype", "load").insert("id", (*it)->id())); } // and then connect the signal so that all future notifications will also be // forwarded. mChanged.connect(slot); } void LLNotificationChannelBase::connectPassedFilter(const LLStandardSignal::slot_type& slot) { // these two filters only fire for notifications added after the current one, because // they don't participate in the hierarchy. mPassedFilter.connect(slot); } void LLNotificationChannelBase::connectFailedFilter(const LLStandardSignal::slot_type& slot) { mFailedFilter.connect(slot); } // external call, conforms to our standard signature bool LLNotificationChannelBase::updateItem(const LLSD& payload) { // first check to see if it's in the master list LLNotificationPtr pNotification = LLNotifications::instance().find(payload["id"]); if (!pNotification) return false; // not found return updateItem(payload, pNotification); } //FIX QUIT NOT WORKING // internal call, for use in avoiding lookup bool LLNotificationChannelBase::updateItem(const LLSD& payload, LLNotificationPtr pNotification) { std::string cmd = payload["sigtype"]; LLNotificationSet::iterator foundItem = mItems.find(pNotification); bool wasFound = (foundItem != mItems.end()); bool passesFilter = mFilter(pNotification); // first, we offer the result of the filter test to the simple // signals for pass/fail. One of these is guaranteed to be called. // If either signal returns true, the change processing is NOT performed // (so don't return true unless you know what you're doing!) bool abortProcessing = false; if (passesFilter) { abortProcessing = mPassedFilter(payload); } else { abortProcessing = mFailedFilter(payload); } if (abortProcessing) { return true; } if (cmd == "load") { // should be no reason we'd ever get a load if we already have it // if passes filter send a load message, else do nothing assert(!wasFound); if (passesFilter) { // not in our list, add it and say so mItems.insert(pNotification); abortProcessing = mChanged(payload); onLoad(pNotification); } } else if (cmd == "change") { // if it passes filter now and was found, we just send a change message // if it passes filter now and wasn't found, we have to add it // if it doesn't pass filter and wasn't found, we do nothing // if it doesn't pass filter and was found, we need to delete it if (passesFilter) { if (wasFound) { // it already existed, so this is a change // since it changed in place, all we have to do is resend the signal abortProcessing = mChanged(payload); onChange(pNotification); } else { // not in our list, add it and say so mItems.insert(pNotification); // our payload is const, so make a copy before changing it LLSD newpayload = payload; newpayload["sigtype"] = "add"; abortProcessing = mChanged(newpayload); onChange(pNotification); } } else { if (wasFound) { // it already existed, so this is a delete mItems.erase(pNotification); // our payload is const, so make a copy before changing it LLSD newpayload = payload; newpayload["sigtype"] = "delete"; abortProcessing = mChanged(newpayload); onChange(pNotification); } // didn't pass, not on our list, do nothing } } else if (cmd == "add") { // should be no reason we'd ever get an add if we already have it // if passes filter send an add message, else do nothing assert(!wasFound); if (passesFilter) { // not in our list, add it and say so mItems.insert(pNotification); abortProcessing = mChanged(payload); onAdd(pNotification); } } else if (cmd == "delete") { // if we have it in our list, pass on the delete, then delete it, else do nothing if (wasFound) { abortProcessing = mChanged(payload); mItems.erase(pNotification); onDelete(pNotification); } } return abortProcessing; } /* static */ LLNotificationChannelPtr LLNotificationChannel::buildChannel(const std::string& name, const std::string& parent, LLNotificationFilter filter, LLNotificationComparator comparator) { // note: this is not a leak; notifications are self-registering. // This factory helps to prevent excess deletions by making sure all smart // pointers to notification channels come from the same source new LLNotificationChannel(name, parent, filter, comparator); return LLNotifications::instance().getChannel(name); } LLNotificationChannel::LLNotificationChannel(const std::string& name, const std::string& parent, LLNotificationFilter filter, LLNotificationComparator comparator) : LLNotificationChannelBase(filter, comparator), mName(name), mParent(parent) { // store myself in the channel map LLNotifications::instance().addChannel(LLNotificationChannelPtr(this)); // bind to notification broadcast if (parent.empty()) { LLNotifications::instance().connectChanged( boost::bind(&LLNotificationChannelBase::updateItem, this, _1)); } else { LLNotificationChannelPtr p = LLNotifications::instance().getChannel(parent); LLStandardSignal::slot_type f = boost::bind(&LLNotificationChannelBase::updateItem, this, _1); p->connectChanged(f); } } void LLNotificationChannel::setComparator(LLNotificationComparator comparator) { mComparator = comparator; LLNotificationSet s2(mComparator); s2.insert(mItems.begin(), mItems.end()); mItems.swap(s2); // notify clients that we've been resorted mChanged(LLSD().insert("sigtype", "sort")); } bool LLNotificationChannel::isEmpty() const { return mItems.empty(); } LLNotificationChannel::Iterator LLNotificationChannel::begin() { return mItems.begin(); } LLNotificationChannel::Iterator LLNotificationChannel::end() { return mItems.end(); } std::string LLNotificationChannel::summarize() { std::string s("Channel '"); s += mName; s += "'\n "; for (LLNotificationChannel::Iterator it = begin(); it != end(); ++it) { s += (*it)->summarize(); s += "\n "; } return s; } // --- // END OF LLNotificationChannel implementation // ========================================================= // ========================================================= // LLNotifications implementation // --- LLNotifications::LLNotifications() : LLNotificationChannelBase(LLNotificationFilters::includeEverything, LLNotificationComparators::orderByUUID()) { } // The expiration channel gets all notifications that are cancelled bool LLNotifications::expirationFilter(LLNotificationPtr pNotification) { return pNotification->isCancelled() || pNotification->isRespondedTo(); } bool LLNotifications::expirationHandler(const LLSD& payload) { if (payload["sigtype"].asString() != "delete") { // anything added to this channel actually should be deleted from the master cancel(find(payload["id"])); return true; // don't process this item any further } return false; } bool LLNotifications::uniqueFilter(LLNotificationPtr pNotif) { if (!pNotif->hasUniquenessConstraints()) { return true; } // checks against existing unique notifications for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName()); existing_it != mUniqueNotifications.end(); ++existing_it) { LLNotificationPtr existing_notification = existing_it->second; if (pNotif != existing_notification && pNotif->isEquivalentTo(existing_notification)) { return false; } } return true; } bool LLNotifications::uniqueHandler(const LLSD& payload) { LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); if (pNotif && pNotif->hasUniquenessConstraints()) { if (payload["sigtype"].asString() == "add") { // not a duplicate according to uniqueness criteria, so we keep it // and store it for future uniqueness checks mUniqueNotifications.insert(std::make_pair(pNotif->getName(), pNotif)); } else if (payload["sigtype"].asString() == "delete") { mUniqueNotifications.erase(pNotif->getName()); } } return false; } bool LLNotifications::failedUniquenessTest(const LLSD& payload) { LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); if (!pNotif || !pNotif->hasUniquenessConstraints()) { return false; } // checks against existing unique notifications for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName()); existing_it != mUniqueNotifications.end(); ++existing_it) { LLNotificationPtr existing_notification = existing_it->second; if (pNotif != existing_notification && pNotif->isEquivalentTo(existing_notification)) { // copy notification instance data over to oldest instance // of this unique notification and update it existing_notification->updateFrom(pNotif); // then delete the new one pNotif->cancel(); } } return false; } void LLNotifications::addChannel(LLNotificationChannelPtr pChan) { mChannels[pChan->getName()] = pChan; } LLNotificationChannelPtr LLNotifications::getChannel(const std::string& channelName) { ChannelMap::iterator p = mChannels.find(channelName); if(p == mChannels.end()) { llerrs << "Did not find channel named " << channelName << llendl; } return p->second; } // this function is called once at construction time, after the object is constructed. void LLNotifications::initSingleton() { loadTemplates(); createDefaultChannels(); } void LLNotifications::createDefaultChannels() { // now construct the various channels AFTER loading the notifications, // because the history channel is going to rewrite the stored notifications file LLNotificationChannel::buildChannel("Expiration", "", boost::bind(&LLNotifications::expirationFilter, this, _1)); LLNotificationChannel::buildChannel("Unexpired", "", !boost::bind(&LLNotifications::expirationFilter, this, _1)); // use negated bind LLNotificationChannel::buildChannel("Unique", "Unexpired", boost::bind(&LLNotifications::uniqueFilter, this, _1)); LLNotificationChannel::buildChannel("Ignore", "Unique", filterIgnoredNotifications); LLNotificationChannel::buildChannel("Visible", "Ignore", &LLNotificationFilters::includeEverything); // create special history channel //std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_PER_SL_ACCOUNT, "open_notifications.xml" ); // use ^^^ when done debugging notifications serialization std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_USER_SETTINGS, "open_notifications.xml" ); // this isn't a leak, don't worry about the empty "new" new LLNotificationHistoryChannel(notifications_log_file); // connect action methods to these channels LLNotifications::instance().getChannel("Expiration")-> connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1)); LLNotifications::instance().getChannel("Unique")-> connectChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1)); LLNotifications::instance().getChannel("Unique")-> connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); LLNotifications::instance().getChannel("Ignore")-> connectFailedFilter(&handleIgnoredNotification); } static std::string sStringSkipNextTime("Skip this dialog next time"); static std::string sStringAlwaysChoose("Always choose this option"); bool LLNotifications::addTemplate(const std::string &name, LLNotificationTemplatePtr theTemplate) { if (mTemplates.count(name)) { llwarns << "LLNotifications -- attempted to add template '" << name << "' twice." << llendl; return false; } mTemplates[name] = theTemplate; return true; } LLNotificationTemplatePtr LLNotifications::getTemplate(const std::string& name) { if (mTemplates.count(name)) { return mTemplates[name]; } else { return mTemplates["MissingAlert"]; } } bool LLNotifications::templateExists(const std::string& name) { return (mTemplates.count(name) != 0); } void LLNotifications::clearTemplates() { mTemplates.clear(); } void LLNotifications::forceResponse(const LLNotification::Params& params, S32 option) { LLNotificationPtr temp_notify(new LLNotification(params)); LLSD response = temp_notify->getResponseTemplate(); LLSD selected_item = temp_notify->getForm()->getElement(option); if (selected_item.isUndefined()) { llwarns << "Invalid option" << option << " for notification " << (std::string)params.name << llendl; return; } response[selected_item["name"].asString()] = true; temp_notify->respond(response); } LLNotifications::TemplateNames LLNotifications::getTemplateNames() const { TemplateNames names; for (TemplateMap::const_iterator it = mTemplates.begin(); it != mTemplates.end(); ++it) { names.push_back(it->first); } return names; } typedef std::map StringMap; void replaceSubstitutionStrings(LLXMLNodePtr node, StringMap& replacements) { //llwarns << "replaceSubstitutionStrings" << llendl; // walk the list of attributes looking for replacements for (LLXMLAttribList::iterator it=node->mAttributes.begin(); it != node->mAttributes.end(); ++it) { std::string value = it->second->getValue(); if (value[0] == '$') { value.erase(0, 1); // trim off the $ std::string replacement; StringMap::const_iterator found = replacements.find(value); if (found != replacements.end()) { replacement = found->second; //llinfos << "replaceSubstitutionStrings: value: \"" << value << "\" repl: \"" << replacement << "\"." << llendl; it->second->setValue(replacement); } else { llwarns << "replaceSubstitutionStrings FAILURE: could not find replacement \"" << value << "\"." << llendl; } } } // now walk the list of children and call this recursively. for (LLXMLNodePtr child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) { replaceSubstitutionStrings(child, replacements); } } // private to this file // returns true if the template request was invalid and there's nothing else we // can do with this node, false if you should keep processing (it may have // replaced the contents of the node referred to) LLXMLNodePtr LLNotifications::checkForXMLTemplate(LLXMLNodePtr item) { if (item->hasName("usetemplate")) { std::string replacementName; if (item->getAttributeString("name", replacementName)) { StringMap replacements; for (LLXMLAttribList::const_iterator it=item->mAttributes.begin(); it != item->mAttributes.end(); ++it) { replacements[it->second->getName()->mString] = it->second->getValue(); } if (mXmlTemplates.count(replacementName)) { item=LLXMLNode::replaceNode(item, mXmlTemplates[replacementName]); // walk the nodes looking for $(substitution) here and replace replaceSubstitutionStrings(item, replacements); } else { llwarns << "XML template lookup failure on '" << replacementName << "' " << llendl; } } } return item; } bool LLNotifications::loadTemplates() { const std::string xml_filename = "notifications.xml"; LLXMLNodePtr root; BOOL success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); if (!success || root.isNull() || !root->hasName( "notifications" )) { llerrs << "Problem reading UI Notifications file: " << xml_filename << llendl; return false; } clearTemplates(); for (LLXMLNodePtr item = root->getFirstChild(); item.notNull(); item = item->getNextSibling()) { // we do this FIRST so that item can be changed if we // encounter a usetemplate -- we just replace the // current xml node and keep processing item = checkForXMLTemplate(item); if (item->hasName("global")) { std::string global_name; if (item->getAttributeString("name", global_name)) { mGlobalStrings[global_name] = item->getTextContents(); } continue; } if (item->hasName("template")) { // store an xml template; templates must have a single node (can contain // other nodes) std::string name; item->getAttributeString("name", name); LLXMLNodePtr ptr = item->getFirstChild(); mXmlTemplates[name] = ptr; continue; } if (!item->hasName("notification")) { llwarns << "Unexpected entity " << item->getName()->mString << " found in " << xml_filename << llendl; continue; } // now we know we have a notification entry, so let's build it LLNotificationTemplatePtr pTemplate(new LLNotificationTemplate()); if (!item->getAttributeString("name", pTemplate->mName)) { llwarns << "Unable to parse notification with no name" << llendl; continue; } LL_DEBUGS("Notifications") << "Parsing " << pTemplate->mName << LL_ENDL; pTemplate->mMessage = item->getTextContents(); pTemplate->mDefaultFunctor = pTemplate->mName; item->getAttributeString("type", pTemplate->mType); item->getAttributeString("icon", pTemplate->mIcon); item->getAttributeString("label", pTemplate->mLabel); item->getAttributeU32("duration", pTemplate->mExpireSeconds); item->getAttributeU32("expireOption", pTemplate->mExpireOption); std::string priority; item->getAttributeString("priority", priority); pTemplate->mPriority = NOTIFICATION_PRIORITY_NORMAL; if (!priority.empty()) { if (priority == "low") pTemplate->mPriority = NOTIFICATION_PRIORITY_LOW; if (priority == "normal") pTemplate->mPriority = NOTIFICATION_PRIORITY_NORMAL; if (priority == "high") pTemplate->mPriority = NOTIFICATION_PRIORITY_HIGH; if (priority == "critical") pTemplate->mPriority = NOTIFICATION_PRIORITY_CRITICAL; } item->getAttributeString("functor", pTemplate->mDefaultFunctor); BOOL persist = false; item->getAttributeBOOL("persist", persist); pTemplate->mPersist = persist; std::string sound; item->getAttributeString("sound", sound); if (!sound.empty()) { // TODO: test for bad sound effect name / missing effect pTemplate->mSoundEffect = LLUUID(LLUI::sConfigGroup->getString(sound.c_str())); } for (LLXMLNodePtr child = item->getFirstChild(); !child.isNull(); child = child->getNextSibling()) { child = checkForXMLTemplate(child); // if (child->hasName("url")) { pTemplate->mURL = child->getTextContents(); child->getAttributeU32("option", pTemplate->mURLOption); } if (child->hasName("unique")) { pTemplate->mUnique = true; for (LLXMLNodePtr formitem = child->getFirstChild(); !formitem.isNull(); formitem = formitem->getNextSibling()) { if (formitem->hasName("context")) { std::string key; formitem->getAttributeString("key", key); pTemplate->mUniqueContext.push_back(key); //llwarns << "adding " << key << " to unique context" << llendl; } else { llwarns << "'unique' has unrecognized subelement " << formitem->getName()->mString << llendl; } } } //
if (child->hasName("form")) { pTemplate->mForm = LLNotificationFormPtr(new LLNotificationForm(pTemplate->mName, child)); } } addTemplate(pTemplate->mName, pTemplate); } //std::ostringstream ostream; //root->writeToOstream(ostream, "\n "); //llwarns << ostream.str() << llendl; return true; } // we provide a couple of simple add notification functions so that it's reasonable to create notifications in one line LLNotificationPtr LLNotifications::add(const std::string& name, const LLSD& substitutions, const LLSD& payload) { return add(LLNotification::Params(name).substitutions(substitutions).payload(payload)); } LLNotificationPtr LLNotifications::add(const std::string& name, const LLSD& substitutions, const LLSD& payload, const std::string& functor_name) { return add(LLNotification::Params(name).substitutions(substitutions).payload(payload).functor_name(functor_name)); } LLNotificationPtr LLNotifications::add(const std::string& name, const LLSD& substitutions, const LLSD& payload, LLNotificationFunctorRegistry::ResponseFunctor functor) { return add(LLNotification::Params(name).substitutions(substitutions).payload(payload).functor(functor)); } // generalized add function that takes a parameter block object for more complex instantiations LLNotificationPtr LLNotifications::add(const LLNotification::Params& p) { LLNotificationPtr pNotif(new LLNotification(p)); add(pNotif); return pNotif; } void LLNotifications::add(const LLNotificationPtr pNotif) { // first see if we already have it -- if so, that's a problem LLNotificationSet::iterator it=mItems.find(pNotif); if (it != mItems.end()) { llerrs << "Notification added a second time to the master notification channel." << llendl; } updateItem(LLSD().insert("sigtype", "add").insert("id", pNotif->id()), pNotif); } void LLNotifications::cancel(LLNotificationPtr pNotif) { LLNotificationSet::iterator it=mItems.find(pNotif); if (it == mItems.end()) { llerrs << "Attempted to delete nonexistent notification " << pNotif->getName() << llendl; } updateItem(LLSD().insert("sigtype", "delete").insert("id", pNotif->id()), pNotif); pNotif->cancel(); } void LLNotifications::update(const LLNotificationPtr pNotif) { LLNotificationSet::iterator it=mItems.find(pNotif); if (it != mItems.end()) { updateItem(LLSD().insert("sigtype", "change").insert("id", pNotif->id()), pNotif); } } LLNotificationPtr LLNotifications::find(LLUUID uuid) { LLNotificationPtr target = LLNotificationPtr(new LLNotification(uuid)); LLNotificationSet::iterator it=mItems.find(target); if (it == mItems.end()) { llwarns << "Tried to dereference uuid '" << uuid << "' as a notification key but didn't find it." << llendl; return LLNotificationPtr((LLNotification*)NULL); } else { return *it; } } void LLNotifications::forEachNotification(NotificationProcess process) { std::for_each(mItems.begin(), mItems.end(), process); } std::string LLNotifications::getGlobalString(const std::string& key) const { GlobalStringMap::const_iterator it = mGlobalStrings.find(key); if (it != mGlobalStrings.end()) { return it->second; } else { // if we don't have the key as a global, return the key itself so that the error // is self-diagnosing. return key; } } // --- // END OF LLNotifications implementation // ========================================================= std::ostream& operator<<(std::ostream& s, const LLNotification& notification) { s << notification.summarize(); return s; }