diff options
Diffstat (limited to 'indra/llui/llnotifications.cpp')
-rw-r--r-- | indra/llui/llnotifications.cpp | 3033 |
1 files changed, 1517 insertions, 1516 deletions
diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index a68b9cae57..ef222bad60 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -1,1516 +1,1517 @@ -/**
-* @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 "llnotifications.h"
-
-#include "lluictrl.h"
-#include "lluictrlfactory.h"
-#include "lldir.h"
-#include "llsdserialize.h"
-#include "lltrans.h"
-#include "llnotificationslistener.h"
-
-#include <algorithm>
-#include <boost/regex.hpp>
-
-
-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()
- {
- llinfos << "Saving open notifications to " << mFileName << llendl;
-
- llofstream notify_file(mFileName.c_str());
- if (!notify_file.is_open())
- {
- llwarns << "Failed to open " << mFileName << llendl;
- 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<LLSDFormatter> formatter = new LLSDXMLFormatter();
- formatter->format(output, notify_file, LLSDFormatter::OPTIONS_PRETTY);
- }
-
- void loadPersistentNotifications()
- {
- llinfos << "Loading open notifications from " << mFileName << llendl;
-
- llifstream notify_file(mFileName.c_str());
- if (!notify_file.is_open())
- {
- llwarns << "Failed to open " << mFileName << llendl;
- return;
- }
-
- LLSD input;
- LLPointer<LLSDParser> parser = new LLSDXMLParser();
- if (parser->parse(notify_file, input, LLSDSerialize::SIZE_UNLIMITED) < 0)
- {
- llwarns << "Failed to parse open notifications" << llendl;
- return;
- }
-
- if (input.isUndefined()) return;
- std::string version = input["version"];
- if (version != NOTIFICATION_PERSIST_VERSION)
- {
- llwarns << "Bad open notifications version: " << version << llendl;
- 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)
-{
- // filter everything if we are to ignore ALL
- if(LLNotifications::instance().getIgnoreAllNotifications())
- {
- return false;
- }
-
- LLNotificationFormPtr form = notification->getForm();
- // Check to see if the user wants to ignore this alert
- if (form->getIgnoreType() != LLNotificationForm::IGNORE_NO)
- {
- return LLUI::sSettingGroups["ignores"]->getBOOL(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::sSettingGroups["ignores"]->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::sSettingGroups["ignores"]->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name));
- }
- child->getAttributeString("text", mIgnoreMsg);
- BOOL show_notification = TRUE;
- LLUI::sSettingGroups["ignores"]->declareBOOL(name, show_notification, "Ignore notification with this name", TRUE);
- }
- 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();
- LLStringUtil::format(text, substitutions);
- (*it)["text"] = text;
- }
- if ((*it)["type"].asString() == "text" && (*it).has("value"))
- {
- std::string value = (*it)["value"].asString();
- LLStringUtil::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),
- mURLOpenExternally(-1),
- mUnique(false),
- mPriority(NOTIFICATION_PRIORITY_NORMAL)
-{
- mForm = LLNotificationFormPtr(new LLNotificationForm());
-}
-
-LLNotification::LLNotification(const LLNotification::Params& p) :
- mTimestamp(p.time_stamp),
- mSubstitutions(p.substitutions),
- mPayload(p.payload),
- mExpiresAt(0),
- mTemporaryResponder(false),
- mRespondedTo(false),
- mPriority(p.priority),
- mCancelled(false),
- mIgnored(false)
-{
- if (p.functor.name.isChosen())
- {
- mResponseFunctorName = p.functor.name;
- }
- else if (p.functor.function.isChosen())
- {
- mResponseFunctorName = LLUUID::generateNewID().asString();
- LLNotificationFunctorRegistry::instance().registerFunctor(mResponseFunctorName, p.functor.function());
-
- mTemporaryResponder = true;
- }
-
- 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)
- {
- BOOL show_notification = mIgnored ? FALSE : TRUE;
- LLUI::sSettingGroups["ignores"]->setBOOL(getName(), show_notification);
- if (mIgnored && mForm->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE)
- {
- LLUI::sSettingGroups["ignores"]->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<std::string>& required_fields) const
-{
- for(std::vector<std::string>::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
- const LLStringUtil::format_map_t& default_args = LLTrans::getDefaultArgs();
- for (LLStringUtil::format_map_t::const_iterator iter = default_args.begin();
- iter != default_args.end(); ++iter)
- {
- mSubstitutions[iter->first] = iter->second;
- }
- 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;
-}
-
-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();
-
- std::string message = mTemplatep->mMessage;
- LLStringUtil::format(message, mSubstitutions);
- return message;
-}
-
-std::string LLNotification::getLabel() const
-{
- std::string label = mTemplatep->mLabel;
- LLStringUtil::format(label, mSubstitutions);
- return (mTemplatep ? label : "");
-}
-
-std::string LLNotification::getURL() const
-{
- if (!mTemplatep)
- return std::string();
- std::string url = mTemplatep->mURL;
- LLStringUtil::format(url, mSubstitutions);
- return (mTemplatep ? url : "");
-}
-
-// =========================================================
-// LLNotificationChannel implementation
-// ---
-LLBoundListener LLNotificationChannelBase::connectChangedImpl(const LLEventListener& 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(LLSD().insert("sigtype", "load").insert("id", (*it)->id()));
- }
- // and then connect the signal so that all future notifications will also be
- // forwarded.
- return mChanged.connect(slot);
-}
-
-LLBoundListener LLNotificationChannelBase::connectAtFrontChangedImpl(const LLEventListener& slot)
-{
- for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it)
- {
- slot(LLSD().insert("sigtype", "load").insert("id", (*it)->id()));
- }
- return mChanged.connect(slot, boost::signals2::at_front);
-}
-
-LLBoundListener LLNotificationChannelBase::connectPassedFilterImpl(const LLEventListener& slot)
-{
- // these two filters only fire for notifications added after the current one, because
- // they don't participate in the hierarchy.
- return mPassedFilter.connect(slot);
-}
-
-LLBoundListener LLNotificationChannelBase::connectFailedFilterImpl(const LLEventListener& slot)
-{
- return 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);
- p->connectChanged(boost::bind(&LLNotificationChannelBase::updateItem, this, _1));
- }
-}
-
-
-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()),
- mIgnoreAllNotifications(false)
-{
- LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Notification.Show", boost::bind(&LLNotifications::addFromCallback, this, _2));
-
- mListener.reset(new LLNotificationsListener(*this));
-}
-
-
-// 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));
- // uniqueHandler slot should be added as first slot of the signal due to
- // usage LLStopWhenHandled combiner in LLStandardSignal
- LLNotifications::instance().getChannel("Unique")->
- connectAtFrontChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1));
-// failedUniquenessTest slot isn't necessary
-// LLNotifications::instance().getChannel("Unique")->
-// connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1));
- LLNotifications::instance().getChannel("Ignore")->
- connectFailedFilter(&handleIgnoredNotification);
-}
-
-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<std::string, std::string> 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;
- //llwarns << "replaceSubstituionStrings: value: " << value << " repl: " << replacement << llendl;
-
- it->second->setValue(replacement);
- }
- else
- {
- llwarns << "replaceSubstituionStrings FAILURE: value: " << value << " repl: " << replacement << 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;
- }
-
- //llinfos << "Parsing " << pTemplate->mName << llendl;
-
- 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())
- {
- // test for bad sound effect name / missing effect
- if (LLUI::sSettingGroups["config"]->controlExists(sound))
- {
- pTemplate->mSoundEffect =
- LLUUID(LLUI::sSettingGroups["config"]->getString(sound));
- }
- else
- {
- llwarns << "Unknown sound effect control name " << sound
- << llendl;
- }
- }
-
- for (LLXMLNodePtr child = item->getFirstChild();
- !child.isNull(); child = child->getNextSibling())
- {
- child = checkForXMLTemplate(child);
-
- // <url>
- if (child->hasName("url"))
- {
- pTemplate->mURL = child->getTextContents();
- child->getAttributeU32("option", pTemplate->mURLOption);
- child->getAttributeU32("openexternally", pTemplate->mURLOpenExternally);
- }
-
- 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;
- }
- }
- }
-
- // <form>
- 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;
-}
-
-// Add a simple notification (from XUI)
-void LLNotifications::addFromCallback(const LLSD& name)
-{
- add(LLNotification::Params().name(name.asString()));
-}
-
-// 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)
-{
- LLNotification::Params::Functor functor_p;
- functor_p.name = name;
- return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p));
-}
-
-LLNotificationPtr LLNotifications::add(const std::string& name,
- const LLSD& substitutions,
- const LLSD& payload,
- const std::string& functor_name)
-{
- LLNotification::Params::Functor functor_p;
- functor_p.name = functor_name;
- return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p));
-}
-
-LLNotificationPtr LLNotifications::add(const std::string& name,
- const LLSD& substitutions,
- const LLSD& payload,
- LLNotificationFunctorRegistry::ResponseFunctor functor)
-{
- LLNotification::Params::Functor functor_p;
- functor_p.function = functor;
- return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p));
-}
-
-// 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;
- }
- pNotif->cancel();
- updateItem(LLSD().insert("sigtype", "delete").insert("id", pNotif->id()), pNotif);
-}
-
-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;
- }
-}
-
-void LLNotifications::setIgnoreAllNotifications(bool setting)
-{
- mIgnoreAllNotifications = setting;
-}
-bool LLNotifications::getIgnoreAllNotifications()
-{
- return mIgnoreAllNotifications;
-}
-
-// ---
-// END OF LLNotifications implementation
-// =========================================================
-
-std::ostream& operator<<(std::ostream& s, const LLNotification& notification)
-{
- s << notification.summarize();
- return s;
-}
-
+/** +* @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 "llnotifications.h" + +#include "llxmlnode.h" +#include "lluictrl.h" +#include "lluictrlfactory.h" +#include "lldir.h" +#include "llsdserialize.h" +#include "lltrans.h" +#include "llnotificationslistener.h" + +#include <algorithm> +#include <boost/regex.hpp> + + +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() + { + llinfos << "Saving open notifications to " << mFileName << llendl; + + llofstream notify_file(mFileName.c_str()); + if (!notify_file.is_open()) + { + llwarns << "Failed to open " << mFileName << llendl; + 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<LLSDFormatter> formatter = new LLSDXMLFormatter(); + formatter->format(output, notify_file, LLSDFormatter::OPTIONS_PRETTY); + } + + void loadPersistentNotifications() + { + llinfos << "Loading open notifications from " << mFileName << llendl; + + llifstream notify_file(mFileName.c_str()); + if (!notify_file.is_open()) + { + llwarns << "Failed to open " << mFileName << llendl; + return; + } + + LLSD input; + LLPointer<LLSDParser> parser = new LLSDXMLParser(); + if (parser->parse(notify_file, input, LLSDSerialize::SIZE_UNLIMITED) < 0) + { + llwarns << "Failed to parse open notifications" << llendl; + return; + } + + if (input.isUndefined()) return; + std::string version = input["version"]; + if (version != NOTIFICATION_PERSIST_VERSION) + { + llwarns << "Bad open notifications version: " << version << llendl; + 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) +{ + // filter everything if we are to ignore ALL + if(LLNotifications::instance().getIgnoreAllNotifications()) + { + return false; + } + + LLNotificationFormPtr form = notification->getForm(); + // Check to see if the user wants to ignore this alert + if (form->getIgnoreType() != LLNotificationForm::IGNORE_NO) + { + return LLUI::sSettingGroups["ignores"]->getBOOL(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::sSettingGroups["ignores"]->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::sSettingGroups["ignores"]->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name)); + } + child->getAttributeString("text", mIgnoreMsg); + BOOL show_notification = TRUE; + LLUI::sSettingGroups["ignores"]->declareBOOL(name, show_notification, "Ignore notification with this name", TRUE); + } + 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(); + LLStringUtil::format(text, substitutions); + (*it)["text"] = text; + } + if ((*it)["type"].asString() == "text" && (*it).has("value")) + { + std::string value = (*it)["value"].asString(); + LLStringUtil::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), + mURLOpenExternally(-1), + mUnique(false), + mPriority(NOTIFICATION_PRIORITY_NORMAL) +{ + mForm = LLNotificationFormPtr(new LLNotificationForm()); +} + +LLNotification::LLNotification(const LLNotification::Params& p) : + mTimestamp(p.time_stamp), + mSubstitutions(p.substitutions), + mPayload(p.payload), + mExpiresAt(0), + mTemporaryResponder(false), + mRespondedTo(false), + mPriority(p.priority), + mCancelled(false), + mIgnored(false) +{ + if (p.functor.name.isChosen()) + { + mResponseFunctorName = p.functor.name; + } + else if (p.functor.function.isChosen()) + { + mResponseFunctorName = LLUUID::generateNewID().asString(); + LLNotificationFunctorRegistry::instance().registerFunctor(mResponseFunctorName, p.functor.function()); + + mTemporaryResponder = true; + } + + 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) + { + BOOL show_notification = mIgnored ? FALSE : TRUE; + LLUI::sSettingGroups["ignores"]->setBOOL(getName(), show_notification); + if (mIgnored && mForm->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE) + { + LLUI::sSettingGroups["ignores"]->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<std::string>& required_fields) const +{ + for(std::vector<std::string>::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 + const LLStringUtil::format_map_t& default_args = LLTrans::getDefaultArgs(); + for (LLStringUtil::format_map_t::const_iterator iter = default_args.begin(); + iter != default_args.end(); ++iter) + { + mSubstitutions[iter->first] = iter->second; + } + 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; +} + +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(); + + std::string message = mTemplatep->mMessage; + LLStringUtil::format(message, mSubstitutions); + return message; +} + +std::string LLNotification::getLabel() const +{ + std::string label = mTemplatep->mLabel; + LLStringUtil::format(label, mSubstitutions); + return (mTemplatep ? label : ""); +} + +std::string LLNotification::getURL() const +{ + if (!mTemplatep) + return std::string(); + std::string url = mTemplatep->mURL; + LLStringUtil::format(url, mSubstitutions); + return (mTemplatep ? url : ""); +} + +// ========================================================= +// LLNotificationChannel implementation +// --- +LLBoundListener LLNotificationChannelBase::connectChangedImpl(const LLEventListener& 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(LLSD().insert("sigtype", "load").insert("id", (*it)->id())); + } + // and then connect the signal so that all future notifications will also be + // forwarded. + return mChanged.connect(slot); +} + +LLBoundListener LLNotificationChannelBase::connectAtFrontChangedImpl(const LLEventListener& slot) +{ + for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it) + { + slot(LLSD().insert("sigtype", "load").insert("id", (*it)->id())); + } + return mChanged.connect(slot, boost::signals2::at_front); +} + +LLBoundListener LLNotificationChannelBase::connectPassedFilterImpl(const LLEventListener& slot) +{ + // these two filters only fire for notifications added after the current one, because + // they don't participate in the hierarchy. + return mPassedFilter.connect(slot); +} + +LLBoundListener LLNotificationChannelBase::connectFailedFilterImpl(const LLEventListener& slot) +{ + return 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); + p->connectChanged(boost::bind(&LLNotificationChannelBase::updateItem, this, _1)); + } +} + + +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()), + mIgnoreAllNotifications(false) +{ + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Notification.Show", boost::bind(&LLNotifications::addFromCallback, this, _2)); + + mListener.reset(new LLNotificationsListener(*this)); +} + + +// 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)); + // uniqueHandler slot should be added as first slot of the signal due to + // usage LLStopWhenHandled combiner in LLStandardSignal + LLNotifications::instance().getChannel("Unique")-> + connectAtFrontChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1)); +// failedUniquenessTest slot isn't necessary +// LLNotifications::instance().getChannel("Unique")-> +// connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); + LLNotifications::instance().getChannel("Ignore")-> + connectFailedFilter(&handleIgnoredNotification); +} + +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<std::string, std::string> 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; + //llwarns << "replaceSubstituionStrings: value: " << value << " repl: " << replacement << llendl; + + it->second->setValue(replacement); + } + else + { + llwarns << "replaceSubstituionStrings FAILURE: value: " << value << " repl: " << replacement << 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; + } + + //llinfos << "Parsing " << pTemplate->mName << llendl; + + 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()) + { + // test for bad sound effect name / missing effect + if (LLUI::sSettingGroups["config"]->controlExists(sound)) + { + pTemplate->mSoundEffect = + LLUUID(LLUI::sSettingGroups["config"]->getString(sound)); + } + else + { + llwarns << "Unknown sound effect control name " << sound + << llendl; + } + } + + for (LLXMLNodePtr child = item->getFirstChild(); + !child.isNull(); child = child->getNextSibling()) + { + child = checkForXMLTemplate(child); + + // <url> + if (child->hasName("url")) + { + pTemplate->mURL = child->getTextContents(); + child->getAttributeU32("option", pTemplate->mURLOption); + child->getAttributeU32("openexternally", pTemplate->mURLOpenExternally); + } + + 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; + } + } + } + + // <form> + 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; +} + +// Add a simple notification (from XUI) +void LLNotifications::addFromCallback(const LLSD& name) +{ + add(LLNotification::Params().name(name.asString())); +} + +// 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) +{ + LLNotification::Params::Functor functor_p; + functor_p.name = name; + return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); +} + +LLNotificationPtr LLNotifications::add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload, + const std::string& functor_name) +{ + LLNotification::Params::Functor functor_p; + functor_p.name = functor_name; + return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); +} + +LLNotificationPtr LLNotifications::add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload, + LLNotificationFunctorRegistry::ResponseFunctor functor) +{ + LLNotification::Params::Functor functor_p; + functor_p.function = functor; + return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); +} + +// 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; + } + pNotif->cancel(); + updateItem(LLSD().insert("sigtype", "delete").insert("id", pNotif->id()), pNotif); +} + +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; + } +} + +void LLNotifications::setIgnoreAllNotifications(bool setting) +{ + mIgnoreAllNotifications = setting; +} +bool LLNotifications::getIgnoreAllNotifications() +{ + return mIgnoreAllNotifications; +} + +// --- +// END OF LLNotifications implementation +// ========================================================= + +std::ostream& operator<<(std::ostream& s, const LLNotification& notification) +{ + s << notification.summarize(); + return s; +} + |