Files
SingularityViewer/indra/llui/llnotifications.cpp
Aleric Inglewood f2199a504b More various small, old patches.
Updated source tree to include everything I did
up till 2010-11-09.

Still to add: IMP-590, IMP-701, IMP-734, IMP-735 and IMP-702.
2011-05-04 19:48:16 +02:00

1505 lines
42 KiB
C++

/**
* @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 "../newview/hippogridmanager.h"
#include <algorithm>
#if LL_MSVC
#pragma warning( disable : 4265 ) // "class has virtual functions, but destructor is not virtual"
#endif
#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)
{
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<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
// TODO: change this to read from the translatable strings file!
mSubstitutions["[SECOND_LIFE]"] = "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().with("sigtype", "load").with("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().with("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<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;
//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;
}
//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())
{
// 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);
// <url>
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;
}
}
}
// <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;
}
// 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().with("sigtype", "add").with("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().with("sigtype", "delete").with("id", pNotif->id()), pNotif);
pNotif->cancel();
}
void LLNotifications::update(const LLNotificationPtr pNotif)
{
LLNotificationSet::iterator it=mItems.find(pNotif);
if (it != mItems.end())
{
updateItem(LLSD().with("sigtype", "change").with("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;
}