aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/llui/llnotifications.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'linden/indra/llui/llnotifications.cpp')
-rw-r--r--linden/indra/llui/llnotifications.cpp1492
1 files changed, 1492 insertions, 0 deletions
diff --git a/linden/indra/llui/llnotifications.cpp b/linden/indra/llui/llnotifications.cpp
new file mode 100644
index 0000000..6b7dd0a
--- /dev/null
+++ b/linden/indra/llui/llnotifications.cpp
@@ -0,0 +1,1492 @@
1/**
2* @file llnotifications.cpp
3* @brief Non-UI queue manager for keeping a prioritized list of notifications
4*
5* $LicenseInfo:firstyear=2008&license=viewergpl$
6*
7* Copyright (c) 2008-2009, Linden Research, Inc.
8*
9* Second Life Viewer Source Code
10* The source code in this file ("Source Code") is provided by Linden Lab
11* to you under the terms of the GNU General Public License, version 2.0
12* ("GPL"), unless you have obtained a separate licensing agreement
13* ("Other License"), formally executed by you and Linden Lab. Terms of
14* the GPL can be found in doc/GPL-license.txt in this distribution, or
15* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
16*
17* There are special exceptions to the terms and conditions of the GPL as
18* it is applied to this Source Code. View the full text of the exception
19* in the file doc/FLOSS-exception.txt in this software distribution, or
20* online at
21* http://secondlifegrid.net/programs/open_source/licensing/flossexception
22*
23* By copying, modifying or distributing this software, you acknowledge
24* that you have read and understood your obligations described above,
25* and agree to abide by those obligations.
26*
27* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
28* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
29* COMPLETENESS OR PERFORMANCE.
30* $/LicenseInfo$
31*/
32
33#include "linden_common.h"
34#include "lluictrlfactory.h"
35#include "lldir.h"
36#include "llsdserialize.h"
37
38#include "llnotifications.h"
39
40#include <algorithm>
41#include <boost/regex.hpp>
42
43
44const std::string NOTIFICATION_PERSIST_VERSION = "0.93";
45
46// local channel for notification history
47class LLNotificationHistoryChannel : public LLNotificationChannel
48{
49 LOG_CLASS(LLNotificationHistoryChannel);
50public:
51 LLNotificationHistoryChannel(const std::string& filename) :
52 LLNotificationChannel("History", "Visible", &historyFilter, LLNotificationComparators::orderByUUID()),
53 mFileName(filename)
54 {
55 connectChanged(boost::bind(&LLNotificationHistoryChannel::historyHandler, this, _1));
56 loadPersistentNotifications();
57 }
58
59private:
60 bool historyHandler(const LLSD& payload)
61 {
62 // we ignore "load" messages, but rewrite the persistence file on any other
63 std::string sigtype = payload["sigtype"];
64 if (sigtype != "load")
65 {
66 savePersistentNotifications();
67 }
68 return false;
69 }
70
71 // The history channel gets all notifications except those that have been cancelled
72 static bool historyFilter(LLNotificationPtr pNotification)
73 {
74 return !pNotification->isCancelled();
75 }
76
77 void savePersistentNotifications()
78 {
79 llinfos << "Saving open notifications to " << mFileName << llendl;
80
81 llofstream notify_file(mFileName.c_str());
82 if (!notify_file.is_open())
83 {
84 llwarns << "Failed to open " << mFileName << llendl;
85 return;
86 }
87
88 LLSD output;
89 output["version"] = NOTIFICATION_PERSIST_VERSION;
90 LLSD& data = output["data"];
91
92 for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it)
93 {
94 if (!LLNotifications::instance().templateExists((*it)->getName())) continue;
95
96 // only store notifications flagged as persisting
97 LLNotificationTemplatePtr templatep = LLNotifications::instance().getTemplate((*it)->getName());
98 if (!templatep->mPersist) continue;
99
100 data.append((*it)->asLLSD());
101 }
102
103 LLPointer<LLSDFormatter> formatter = new LLSDXMLFormatter();
104 formatter->format(output, notify_file, LLSDFormatter::OPTIONS_PRETTY);
105 }
106
107 void loadPersistentNotifications()
108 {
109 llinfos << "Loading open notifications from " << mFileName << llendl;
110
111 llifstream notify_file(mFileName.c_str());
112 if (!notify_file.is_open())
113 {
114 llwarns << "Failed to open " << mFileName << llendl;
115 return;
116 }
117
118 LLSD input;
119 LLPointer<LLSDParser> parser = new LLSDXMLParser();
120 if (parser->parse(notify_file, input, LLSDSerialize::SIZE_UNLIMITED) < 0)
121 {
122 llwarns << "Failed to parse open notifications" << llendl;
123 return;
124 }
125
126 if (input.isUndefined()) return;
127 std::string version = input["version"];
128 if (version != NOTIFICATION_PERSIST_VERSION)
129 {
130 llwarns << "Bad open notifications version: " << version << llendl;
131 return;
132 }
133 LLSD& data = input["data"];
134 if (data.isUndefined()) return;
135
136 LLNotifications& instance = LLNotifications::instance();
137 for (LLSD::array_const_iterator notification_it = data.beginArray();
138 notification_it != data.endArray();
139 ++notification_it)
140 {
141 instance.add(LLNotificationPtr(new LLNotification(*notification_it)));
142 }
143 }
144
145 //virtual
146 void onDelete(LLNotificationPtr pNotification)
147 {
148 // we want to keep deleted notifications in our log
149 mItems.insert(pNotification);
150
151 return;
152 }
153
154private:
155 std::string mFileName;
156};
157
158bool filterIgnoredNotifications(LLNotificationPtr notification)
159{
160 LLNotificationFormPtr form = notification->getForm();
161 // Check to see if the user wants to ignore this alert
162 if (form->getIgnoreType() != LLNotificationForm::IGNORE_NO)
163 {
164 return LLUI::sConfigGroup->getWarning(notification->getName());
165 }
166
167 return true;
168}
169
170bool handleIgnoredNotification(const LLSD& payload)
171{
172 if (payload["sigtype"].asString() == "add")
173 {
174 LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID());
175 if (!pNotif) return false;
176
177 LLNotificationFormPtr form = pNotif->getForm();
178 LLSD response;
179 switch(form->getIgnoreType())
180 {
181 case LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE:
182 response = pNotif->getResponseTemplate(LLNotification::WITH_DEFAULT_BUTTON);
183 break;
184 case LLNotificationForm::IGNORE_WITH_LAST_RESPONSE:
185 response = LLUI::sIgnoresGroup->getLLSD("Default" + pNotif->getName());
186 break;
187 case LLNotificationForm::IGNORE_SHOW_AGAIN:
188 break;
189 default:
190 return false;
191 }
192 pNotif->setIgnored(true);
193 pNotif->respond(response);
194 return true; // don't process this item any further
195 }
196 return false;
197}
198
199namespace LLNotificationFilters
200{
201 // a sample filter
202 bool includeEverything(LLNotificationPtr p)
203 {
204 return true;
205 }
206};
207
208LLNotificationForm::LLNotificationForm()
209: mFormData(LLSD::emptyArray()),
210 mIgnore(IGNORE_NO)
211{
212}
213
214
215LLNotificationForm::LLNotificationForm(const std::string& name, const LLXMLNodePtr xml_node)
216: mFormData(LLSD::emptyArray()),
217 mIgnore(IGNORE_NO)
218{
219 if (!xml_node->hasName("form"))
220 {
221 llwarns << "Bad xml node for form: " << xml_node->getName() << llendl;
222 }
223 LLXMLNodePtr child = xml_node->getFirstChild();
224 while(child)
225 {
226 child = LLNotifications::instance().checkForXMLTemplate(child);
227
228 LLSD item_entry;
229 std::string element_name = child->getName()->mString;
230
231 if (element_name == "ignore")
232 {
233 bool save_option = false;
234 child->getAttribute_bool("save_option", save_option);
235 if (!save_option)
236 {
237 mIgnore = IGNORE_WITH_DEFAULT_RESPONSE;
238 }
239 else
240 {
241 // remember last option chosen by user and automatically respond with that in the future
242 mIgnore = IGNORE_WITH_LAST_RESPONSE;
243 LLUI::sIgnoresGroup->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name));
244 }
245 child->getAttributeString("text", mIgnoreMsg);
246 LLUI::sIgnoresGroup->addWarning(name);
247 }
248 else
249 {
250 // flatten xml form entry into single LLSD map with type==name
251 item_entry["type"] = element_name;
252 const LLXMLAttribList::iterator attrib_end = child->mAttributes.end();
253 for(LLXMLAttribList::iterator attrib_it = child->mAttributes.begin();
254 attrib_it != attrib_end;
255 ++attrib_it)
256 {
257 item_entry[std::string(attrib_it->second->getName()->mString)] = attrib_it->second->getValue();
258 }
259 item_entry["value"] = child->getTextContents();
260 mFormData.append(item_entry);
261 }
262
263 child = child->getNextSibling();
264 }
265}
266
267LLNotificationForm::LLNotificationForm(const LLSD& sd)
268{
269 if (sd.isArray())
270 {
271 mFormData = sd;
272 }
273 else
274 {
275 llwarns << "Invalid form data " << sd << llendl;
276 mFormData = LLSD::emptyArray();
277 }
278}
279
280LLSD LLNotificationForm::asLLSD() const
281{
282 return mFormData;
283}
284
285LLSD LLNotificationForm::getElement(const std::string& element_name)
286{
287 for (LLSD::array_const_iterator it = mFormData.beginArray();
288 it != mFormData.endArray();
289 ++it)
290 {
291 if ((*it)["name"].asString() == element_name) return (*it);
292 }
293 return LLSD();
294}
295
296
297bool LLNotificationForm::hasElement(const std::string& element_name)
298{
299 for (LLSD::array_const_iterator it = mFormData.beginArray();
300 it != mFormData.endArray();
301 ++it)
302 {
303 if ((*it)["name"].asString() == element_name) return true;
304 }
305 return false;
306}
307
308void LLNotificationForm::addElement(const std::string& type, const std::string& name, const LLSD& value)
309{
310 LLSD element;
311 element["type"] = type;
312 element["name"] = name;
313 element["text"] = name;
314 element["value"] = value;
315 element["index"] = mFormData.size();
316 mFormData.append(element);
317}
318
319void LLNotificationForm::append(const LLSD& sub_form)
320{
321 if (sub_form.isArray())
322 {
323 for (LLSD::array_const_iterator it = sub_form.beginArray();
324 it != sub_form.endArray();
325 ++it)
326 {
327 mFormData.append(*it);
328 }
329 }
330}
331
332void LLNotificationForm::formatElements(const LLSD& substitutions)
333{
334 for (LLSD::array_iterator it = mFormData.beginArray();
335 it != mFormData.endArray();
336 ++it)
337 {
338 // format "text" component of each form element
339 if ((*it).has("text"))
340 {
341 std::string text = (*it)["text"].asString();
342 text = LLNotification::format(text, substitutions);
343 (*it)["text"] = text;
344 }
345 if ((*it)["type"].asString() == "text" && (*it).has("value"))
346 {
347 std::string value = (*it)["value"].asString();
348 value = LLNotification::format(value, substitutions);
349 (*it)["value"] = value;
350 }
351 }
352}
353
354std::string LLNotificationForm::getDefaultOption()
355{
356 for (LLSD::array_const_iterator it = mFormData.beginArray();
357 it != mFormData.endArray();
358 ++it)
359 {
360 if ((*it)["default"]) return (*it)["name"].asString();
361 }
362 return "";
363}
364
365LLNotificationTemplate::LLNotificationTemplate() :
366 mExpireSeconds(0),
367 mExpireOption(-1),
368 mURLOption(-1),
369 mUnique(false),
370 mPriority(NOTIFICATION_PRIORITY_NORMAL)
371{
372 mForm = LLNotificationFormPtr(new LLNotificationForm());
373}
374
375LLNotification::LLNotification(const LLNotification::Params& p) :
376 mTimestamp(p.timestamp),
377 mSubstitutions(p.substitutions),
378 mPayload(p.payload),
379 mExpiresAt(0),
380 mResponseFunctorName(p.functor_name),
381 mTemporaryResponder(p.mTemporaryResponder),
382 mRespondedTo(false),
383 mPriority(p.priority),
384 mCancelled(false),
385 mIgnored(false)
386{
387 mId.generate();
388 init(p.name, p.form_elements);
389}
390
391
392LLNotification::LLNotification(const LLSD& sd) :
393 mTemporaryResponder(false),
394 mRespondedTo(false),
395 mCancelled(false),
396 mIgnored(false)
397{
398 mId.generate();
399 mSubstitutions = sd["substitutions"];
400 mPayload = sd["payload"];
401 mTimestamp = sd["time"];
402 mExpiresAt = sd["expiry"];
403 mPriority = (ENotificationPriority)sd["priority"].asInteger();
404 mResponseFunctorName = sd["responseFunctor"].asString();
405 std::string templatename = sd["name"].asString();
406 init(templatename, LLSD());
407 // replace form with serialized version
408 mForm = LLNotificationFormPtr(new LLNotificationForm(sd["form"]));
409}
410
411
412LLSD LLNotification::asLLSD()
413{
414 LLSD output;
415 output["name"] = mTemplatep->mName;
416 output["form"] = getForm()->asLLSD();
417 output["substitutions"] = mSubstitutions;
418 output["payload"] = mPayload;
419 output["time"] = mTimestamp;
420 output["expiry"] = mExpiresAt;
421 output["priority"] = (S32)mPriority;
422 output["responseFunctor"] = mResponseFunctorName;
423 return output;
424}
425
426void LLNotification::update()
427{
428 LLNotifications::instance().update(shared_from_this());
429}
430
431void LLNotification::updateFrom(LLNotificationPtr other)
432{
433 // can only update from the same notification type
434 if (mTemplatep != other->mTemplatep) return;
435
436 // NOTE: do NOT change the ID, since it is the key to
437 // this given instance, just update all the metadata
438 //mId = other->mId;
439
440 mPayload = other->mPayload;
441 mSubstitutions = other->mSubstitutions;
442 mTimestamp = other->mTimestamp;
443 mExpiresAt = other->mExpiresAt;
444 mCancelled = other->mCancelled;
445 mIgnored = other->mIgnored;
446 mPriority = other->mPriority;
447 mForm = other->mForm;
448 mResponseFunctorName = other->mResponseFunctorName;
449 mRespondedTo = other->mRespondedTo;
450 mTemporaryResponder = other->mTemporaryResponder;
451
452 update();
453}
454
455const LLNotificationFormPtr LLNotification::getForm()
456{
457 return mForm;
458}
459
460void LLNotification::cancel()
461{
462 mCancelled = true;
463}
464
465LLSD LLNotification::getResponseTemplate(EResponseTemplateType type)
466{
467 LLSD response = LLSD::emptyMap();
468 for (S32 element_idx = 0;
469 element_idx < mForm->getNumElements();
470 ++element_idx)
471 {
472 LLSD element = mForm->getElement(element_idx);
473 if (element.has("name"))
474 {
475 response[element["name"].asString()] = element["value"];
476 }
477
478 if ((type == WITH_DEFAULT_BUTTON)
479 && element["default"].asBoolean())
480 {
481 response[element["name"].asString()] = true;
482 }
483 }
484 return response;
485}
486
487//static
488S32 LLNotification::getSelectedOption(const LLSD& notification, const LLSD& response)
489{
490 LLNotificationForm form(notification["form"]);
491
492 for (S32 element_idx = 0;
493 element_idx < form.getNumElements();
494 ++element_idx)
495 {
496 LLSD element = form.getElement(element_idx);
497
498 // only look at buttons
499 if (element["type"].asString() == "button"
500 && response[element["name"].asString()].asBoolean())
501 {
502 return element["index"].asInteger();
503 }
504 }
505
506 return -1;
507}
508
509//static
510std::string LLNotification::getSelectedOptionName(const LLSD& response)
511{
512 for (LLSD::map_const_iterator response_it = response.beginMap();
513 response_it != response.endMap();
514 ++response_it)
515 {
516 if (response_it->second.isBoolean() && response_it->second.asBoolean())
517 {
518 return response_it->first;
519 }
520 }
521 return "";
522}
523
524
525void LLNotification::respond(const LLSD& response)
526{
527 mRespondedTo = true;
528 // look up the functor
529 LLNotificationFunctorRegistry::ResponseFunctor functor =
530 LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName);
531 // and then call it
532 functor(asLLSD(), response);
533
534 if (mTemporaryResponder)
535 {
536 LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName);
537 mResponseFunctorName = "";
538 mTemporaryResponder = false;
539 }
540
541 if (mForm->getIgnoreType() != LLNotificationForm::IGNORE_NO)
542 {
543 LLUI::sIgnoresGroup->setWarning(getName(), !mIgnored);
544 if (mIgnored && mForm->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE)
545 {
546 LLUI::sIgnoresGroup->setLLSD("Default" + getName(), response);
547 }
548 }
549
550 update();
551}
552
553void LLNotification::setIgnored(bool ignore)
554{
555 mIgnored = ignore;
556}
557
558void LLNotification::setResponseFunctor(std::string const &responseFunctorName)
559{
560 if (mTemporaryResponder)
561 // get rid of the old one
562 LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName);
563 mResponseFunctorName = responseFunctorName;
564 mTemporaryResponder = false;
565}
566
567bool LLNotification::payloadContainsAll(const std::vector<std::string>& required_fields) const
568{
569 for(std::vector<std::string>::const_iterator required_fields_it = required_fields.begin();
570 required_fields_it != required_fields.end();
571 required_fields_it++)
572 {
573 std::string required_field_name = *required_fields_it;
574 if( ! getPayload().has(required_field_name))
575 {
576 return false; // a required field was not found
577 }
578 }
579 return true; // all required fields were found
580}
581
582bool LLNotification::isEquivalentTo(LLNotificationPtr that) const
583{
584 if (this->mTemplatep->mName != that->mTemplatep->mName)
585 {
586 return false; // must have the same template name or forget it
587 }
588 if (this->mTemplatep->mUnique)
589 {
590 // highlander bit sez there can only be one of these
591 return
592 this->payloadContainsAll(that->mTemplatep->mUniqueContext) &&
593 that->payloadContainsAll(this->mTemplatep->mUniqueContext);
594 }
595 return false;
596}
597
598void LLNotification::init(const std::string& template_name, const LLSD& form_elements)
599{
600 mTemplatep = LLNotifications::instance().getTemplate(template_name);
601 if (!mTemplatep) return;
602
603 // add default substitutions
604 // TODO: change this to read from the translatable strings file!
605 mSubstitutions["SECOND_LIFE"] = "Second Life";
606 mSubstitutions["_URL"] = getURL();
607 mSubstitutions["_NAME"] = template_name;
608 // TODO: something like this so that a missing alert is sensible:
609 //mSubstitutions["_ARGS"] = get_all_arguments_as_text(mSubstitutions);
610
611 mForm = LLNotificationFormPtr(new LLNotificationForm(*mTemplatep->mForm));
612 mForm->append(form_elements);
613
614 // apply substitution to form labels
615 mForm->formatElements(mSubstitutions);
616
617 LLDate rightnow = LLDate::now();
618 if (mTemplatep->mExpireSeconds)
619 {
620 mExpiresAt = LLDate(rightnow.secondsSinceEpoch() + mTemplatep->mExpireSeconds);
621 }
622
623 if (mPriority == NOTIFICATION_PRIORITY_UNSPECIFIED)
624 {
625 mPriority = mTemplatep->mPriority;
626 }
627}
628
629std::string LLNotification::summarize() const
630{
631 std::string s = "Notification(";
632 s += getName();
633 s += ") : ";
634 s += mTemplatep ? mTemplatep->mMessage : "";
635 // should also include timestamp and expiration time (but probably not payload)
636 return s;
637}
638
639//static
640std::string LLNotification::format(const std::string& s, const LLSD& substitutions)
641{
642 if (!substitutions.isMap())
643 {
644 return s;
645 }
646
647 std::ostringstream output;
648 // match strings like [NAME]
649 const boost::regex key("\\[([0-9_A-Z]+)]");
650
651 std::string::const_iterator start = s.begin();
652 std::string::const_iterator end = s.end();
653 boost::smatch match;
654
655 while (boost::regex_search(start, end, match, key, boost::match_default))
656 {
657 bool found_replacement = false;
658 std::string replacement;
659
660 // see if we have a replacement for the bracketed string (without the brackets)
661 // test first using has() because if we just look up with operator[] we get back an
662 // empty string even if the value is missing. We want to distinguish between
663 // missing replacements and deliberately empty replacement strings.
664 if (substitutions.has(std::string(match[1].first, match[1].second)))
665 {
666 replacement = substitutions[std::string(match[1].first, match[1].second)].asString();
667 found_replacement = true;
668 }
669 // if not, see if there's one WITH brackets
670 else if (substitutions.has(std::string(match[0].first, match[0].second)))
671 {
672 replacement = substitutions[std::string(match[0].first, match[0].second)].asString();
673 found_replacement = true;
674 }
675
676 if (found_replacement)
677 {
678 // found a replacement
679 // "hello world" is output
680 output << std::string(start, match[0].first) << replacement;
681 }
682 else
683 {
684 // we had no replacement, so leave the string we searched for so that it gets noticed by QA
685 // "hello [NAME_NOT_FOUND]" is output
686 output << std::string(start, match[0].second);
687 }
688
689 // update search position
690 start = match[0].second;
691 }
692 // send the remainder of the string (with no further matches for bracketed names)
693 output << std::string(start, end);
694 return output.str();
695}
696
697std::string LLNotification::getMessage() const
698{
699 // all our callers cache this result, so it gives us more flexibility
700 // to do the substitution at call time rather than attempting to
701 // cache it in the notification
702 if (!mTemplatep)
703 return std::string();
704 return format(mTemplatep->mMessage, mSubstitutions);
705}
706
707std::string LLNotification::getLabel() const
708{
709 return (mTemplatep ? format(mTemplatep->mLabel, mSubstitutions) : "");
710}
711
712
713
714// =========================================================
715// LLNotificationChannel implementation
716// ---
717void LLNotificationChannelBase::connectChanged(const LLStandardSignal::slot_type& slot)
718{
719 // when someone wants to connect to a channel, we first throw them
720 // all of the notifications that are already in the channel
721 // we use a special signal called "load" in case the channel wants to care
722 // only about new notifications
723 for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it)
724 {
725 slot.get_slot_function()(LLSD().insert("sigtype", "load").insert("id", (*it)->id()));
726 }
727 // and then connect the signal so that all future notifications will also be
728 // forwarded.
729 mChanged.connect(slot);
730}
731
732void LLNotificationChannelBase::connectPassedFilter(const LLStandardSignal::slot_type& slot)
733{
734 // these two filters only fire for notifications added after the current one, because
735 // they don't participate in the hierarchy.
736 mPassedFilter.connect(slot);
737}
738
739void LLNotificationChannelBase::connectFailedFilter(const LLStandardSignal::slot_type& slot)
740{
741 mFailedFilter.connect(slot);
742}
743
744// external call, conforms to our standard signature
745bool LLNotificationChannelBase::updateItem(const LLSD& payload)
746{
747 // first check to see if it's in the master list
748 LLNotificationPtr pNotification = LLNotifications::instance().find(payload["id"]);
749 if (!pNotification)
750 return false; // not found
751
752 return updateItem(payload, pNotification);
753}
754
755
756//FIX QUIT NOT WORKING
757
758
759// internal call, for use in avoiding lookup
760bool LLNotificationChannelBase::updateItem(const LLSD& payload, LLNotificationPtr pNotification)
761{
762 std::string cmd = payload["sigtype"];
763 LLNotificationSet::iterator foundItem = mItems.find(pNotification);
764 bool wasFound = (foundItem != mItems.end());
765 bool passesFilter = mFilter(pNotification);
766
767 // first, we offer the result of the filter test to the simple
768 // signals for pass/fail. One of these is guaranteed to be called.
769 // If either signal returns true, the change processing is NOT performed
770 // (so don't return true unless you know what you're doing!)
771 bool abortProcessing = false;
772 if (passesFilter)
773 {
774 abortProcessing = mPassedFilter(payload);
775 }
776 else
777 {
778 abortProcessing = mFailedFilter(payload);
779 }
780
781 if (abortProcessing)
782 {
783 return true;
784 }
785
786 if (cmd == "load")
787 {
788 // should be no reason we'd ever get a load if we already have it
789 // if passes filter send a load message, else do nothing
790 assert(!wasFound);
791 if (passesFilter)
792 {
793 // not in our list, add it and say so
794 mItems.insert(pNotification);
795 abortProcessing = mChanged(payload);
796 onLoad(pNotification);
797 }
798 }
799 else if (cmd == "change")
800 {
801 // if it passes filter now and was found, we just send a change message
802 // if it passes filter now and wasn't found, we have to add it
803 // if it doesn't pass filter and wasn't found, we do nothing
804 // if it doesn't pass filter and was found, we need to delete it
805 if (passesFilter)
806 {
807 if (wasFound)
808 {
809 // it already existed, so this is a change
810 // since it changed in place, all we have to do is resend the signal
811 abortProcessing = mChanged(payload);
812 onChange(pNotification);
813 }
814 else
815 {
816 // not in our list, add it and say so
817 mItems.insert(pNotification);
818 // our payload is const, so make a copy before changing it
819 LLSD newpayload = payload;
820 newpayload["sigtype"] = "add";
821 abortProcessing = mChanged(newpayload);
822 onChange(pNotification);
823 }
824 }
825 else
826 {
827 if (wasFound)
828 {
829 // it already existed, so this is a delete
830 mItems.erase(pNotification);
831 // our payload is const, so make a copy before changing it
832 LLSD newpayload = payload;
833 newpayload["sigtype"] = "delete";
834 abortProcessing = mChanged(newpayload);
835 onChange(pNotification);
836 }
837 // didn't pass, not on our list, do nothing
838 }
839 }
840 else if (cmd == "add")
841 {
842 // should be no reason we'd ever get an add if we already have it
843 // if passes filter send an add message, else do nothing
844 assert(!wasFound);
845 if (passesFilter)
846 {
847 // not in our list, add it and say so
848 mItems.insert(pNotification);
849 abortProcessing = mChanged(payload);
850 onAdd(pNotification);
851 }
852 }
853 else if (cmd == "delete")
854 {
855 // if we have it in our list, pass on the delete, then delete it, else do nothing
856 if (wasFound)
857 {
858 abortProcessing = mChanged(payload);
859 mItems.erase(pNotification);
860 onDelete(pNotification);
861 }
862 }
863 return abortProcessing;
864}
865
866/* static */
867LLNotificationChannelPtr LLNotificationChannel::buildChannel(const std::string& name,
868 const std::string& parent,
869 LLNotificationFilter filter,
870 LLNotificationComparator comparator)
871{
872 // note: this is not a leak; notifications are self-registering.
873 // This factory helps to prevent excess deletions by making sure all smart
874 // pointers to notification channels come from the same source
875 new LLNotificationChannel(name, parent, filter, comparator);
876 return LLNotifications::instance().getChannel(name);
877}
878
879
880LLNotificationChannel::LLNotificationChannel(const std::string& name,
881 const std::string& parent,
882 LLNotificationFilter filter,
883 LLNotificationComparator comparator) :
884LLNotificationChannelBase(filter, comparator),
885mName(name),
886mParent(parent)
887{
888 // store myself in the channel map
889 LLNotifications::instance().addChannel(LLNotificationChannelPtr(this));
890 // bind to notification broadcast
891 if (parent.empty())
892 {
893 LLNotifications::instance().connectChanged(
894 boost::bind(&LLNotificationChannelBase::updateItem, this, _1));
895 }
896 else
897 {
898 LLNotificationChannelPtr p = LLNotifications::instance().getChannel(parent);
899 LLStandardSignal::slot_type f = boost::bind(&LLNotificationChannelBase::updateItem, this, _1);
900 p->connectChanged(f);
901 }
902}
903
904
905void LLNotificationChannel::setComparator(LLNotificationComparator comparator)
906{
907 mComparator = comparator;
908 LLNotificationSet s2(mComparator);
909 s2.insert(mItems.begin(), mItems.end());
910 mItems.swap(s2);
911
912 // notify clients that we've been resorted
913 mChanged(LLSD().insert("sigtype", "sort"));
914}
915
916bool LLNotificationChannel::isEmpty() const
917{
918 return mItems.empty();
919}
920
921LLNotificationChannel::Iterator LLNotificationChannel::begin()
922{
923 return mItems.begin();
924}
925
926LLNotificationChannel::Iterator LLNotificationChannel::end()
927{
928 return mItems.end();
929}
930
931std::string LLNotificationChannel::summarize()
932{
933 std::string s("Channel '");
934 s += mName;
935 s += "'\n ";
936 for (LLNotificationChannel::Iterator it = begin(); it != end(); ++it)
937 {
938 s += (*it)->summarize();
939 s += "\n ";
940 }
941 return s;
942}
943
944
945// ---
946// END OF LLNotificationChannel implementation
947// =========================================================
948
949
950// =========================================================
951// LLNotifications implementation
952// ---
953LLNotifications::LLNotifications() : LLNotificationChannelBase(LLNotificationFilters::includeEverything,
954 LLNotificationComparators::orderByUUID())
955{
956}
957
958
959// The expiration channel gets all notifications that are cancelled
960bool LLNotifications::expirationFilter(LLNotificationPtr pNotification)
961{
962 return pNotification->isCancelled() || pNotification->isRespondedTo();
963}
964
965bool LLNotifications::expirationHandler(const LLSD& payload)
966{
967 if (payload["sigtype"].asString() != "delete")
968 {
969 // anything added to this channel actually should be deleted from the master
970 cancel(find(payload["id"]));
971 return true; // don't process this item any further
972 }
973 return false;
974}
975
976bool LLNotifications::uniqueFilter(LLNotificationPtr pNotif)
977{
978 if (!pNotif->hasUniquenessConstraints())
979 {
980 return true;
981 }
982
983 // checks against existing unique notifications
984 for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName());
985 existing_it != mUniqueNotifications.end();
986 ++existing_it)
987 {
988 LLNotificationPtr existing_notification = existing_it->second;
989 if (pNotif != existing_notification
990 && pNotif->isEquivalentTo(existing_notification))
991 {
992 return false;
993 }
994 }
995
996 return true;
997}
998
999bool LLNotifications::uniqueHandler(const LLSD& payload)
1000{
1001 LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID());
1002 if (pNotif && pNotif->hasUniquenessConstraints())
1003 {
1004 if (payload["sigtype"].asString() == "add")
1005 {
1006 // not a duplicate according to uniqueness criteria, so we keep it
1007 // and store it for future uniqueness checks
1008 mUniqueNotifications.insert(std::make_pair(pNotif->getName(), pNotif));
1009 }
1010 else if (payload["sigtype"].asString() == "delete")
1011 {
1012 mUniqueNotifications.erase(pNotif->getName());
1013 }
1014 }
1015
1016 return false;
1017}
1018
1019bool LLNotifications::failedUniquenessTest(const LLSD& payload)
1020{
1021 LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID());
1022
1023 if (!pNotif || !pNotif->hasUniquenessConstraints())
1024 {
1025 return false;
1026 }
1027
1028 // checks against existing unique notifications
1029 for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName());
1030 existing_it != mUniqueNotifications.end();
1031 ++existing_it)
1032 {
1033 LLNotificationPtr existing_notification = existing_it->second;
1034 if (pNotif != existing_notification
1035 && pNotif->isEquivalentTo(existing_notification))
1036 {
1037 // copy notification instance data over to oldest instance
1038 // of this unique notification and update it
1039 existing_notification->updateFrom(pNotif);
1040 // then delete the new one
1041 pNotif->cancel();
1042 }
1043 }
1044
1045 return false;
1046}
1047
1048
1049void LLNotifications::addChannel(LLNotificationChannelPtr pChan)
1050{
1051 mChannels[pChan->getName()] = pChan;
1052}
1053
1054LLNotificationChannelPtr LLNotifications::getChannel(const std::string& channelName)
1055{
1056 ChannelMap::iterator p = mChannels.find(channelName);
1057 if(p == mChannels.end())
1058 {
1059 llerrs << "Did not find channel named " << channelName << llendl;
1060 }
1061 return p->second;
1062}
1063
1064
1065// this function is called once at construction time, after the object is constructed.
1066void LLNotifications::initSingleton()
1067{
1068 loadTemplates();
1069 createDefaultChannels();
1070}
1071
1072void LLNotifications::createDefaultChannels()
1073{
1074 // now construct the various channels AFTER loading the notifications,
1075 // because the history channel is going to rewrite the stored notifications file
1076 LLNotificationChannel::buildChannel("Expiration", "",
1077 boost::bind(&LLNotifications::expirationFilter, this, _1));
1078 LLNotificationChannel::buildChannel("Unexpired", "",
1079 !boost::bind(&LLNotifications::expirationFilter, this, _1)); // use negated bind
1080 LLNotificationChannel::buildChannel("Unique", "Unexpired",
1081 boost::bind(&LLNotifications::uniqueFilter, this, _1));
1082 LLNotificationChannel::buildChannel("Ignore", "Unique",
1083 filterIgnoredNotifications);
1084 LLNotificationChannel::buildChannel("Visible", "Ignore",
1085 &LLNotificationFilters::includeEverything);
1086
1087 // create special history channel
1088 //std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_PER_SL_ACCOUNT, "open_notifications.xml" );
1089 // use ^^^ when done debugging notifications serialization
1090 std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_USER_SETTINGS, "open_notifications.xml" );
1091 // this isn't a leak, don't worry about the empty "new"
1092 new LLNotificationHistoryChannel(notifications_log_file);
1093
1094 // connect action methods to these channels
1095 LLNotifications::instance().getChannel("Expiration")->
1096 connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1));
1097 LLNotifications::instance().getChannel("Unique")->
1098 connectChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1));
1099 LLNotifications::instance().getChannel("Unique")->
1100 connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1));
1101 LLNotifications::instance().getChannel("Ignore")->
1102 connectFailedFilter(&handleIgnoredNotification);
1103}
1104
1105static std::string sStringSkipNextTime("Skip this dialog next time");
1106static std::string sStringAlwaysChoose("Always choose this option");
1107
1108bool LLNotifications::addTemplate(const std::string &name,
1109 LLNotificationTemplatePtr theTemplate)
1110{
1111 if (mTemplates.count(name))
1112 {
1113 llwarns << "LLNotifications -- attempted to add template '" << name << "' twice." << llendl;
1114 return false;
1115 }
1116 mTemplates[name] = theTemplate;
1117 return true;
1118}
1119
1120LLNotificationTemplatePtr LLNotifications::getTemplate(const std::string& name)
1121{
1122 if (mTemplates.count(name))
1123 {
1124 return mTemplates[name];
1125 }
1126 else
1127 {
1128 return mTemplates["MissingAlert"];
1129 }
1130}
1131
1132bool LLNotifications::templateExists(const std::string& name)
1133{
1134 return (mTemplates.count(name) != 0);
1135}
1136
1137void LLNotifications::clearTemplates()
1138{
1139 mTemplates.clear();
1140}
1141
1142void LLNotifications::forceResponse(const LLNotification::Params& params, S32 option)
1143{
1144 LLNotificationPtr temp_notify(new LLNotification(params));
1145 LLSD response = temp_notify->getResponseTemplate();
1146 LLSD selected_item = temp_notify->getForm()->getElement(option);
1147
1148 if (selected_item.isUndefined())
1149 {
1150 llwarns << "Invalid option" << option << " for notification " << (std::string)params.name << llendl;
1151 return;
1152 }
1153 response[selected_item["name"].asString()] = true;
1154
1155 temp_notify->respond(response);
1156}
1157
1158LLNotifications::TemplateNames LLNotifications::getTemplateNames() const
1159{
1160 TemplateNames names;
1161 for (TemplateMap::const_iterator it = mTemplates.begin(); it != mTemplates.end(); ++it)
1162 {
1163 names.push_back(it->first);
1164 }
1165 return names;
1166}
1167
1168typedef std::map<std::string, std::string> StringMap;
1169void replaceSubstitutionStrings(LLXMLNodePtr node, StringMap& replacements)
1170{
1171 //llwarns << "replaceSubstitutionStrings" << llendl;
1172 // walk the list of attributes looking for replacements
1173 for (LLXMLAttribList::iterator it=node->mAttributes.begin();
1174 it != node->mAttributes.end(); ++it)
1175 {
1176 std::string value = it->second->getValue();
1177 if (value[0] == '$')
1178 {
1179 value.erase(0, 1); // trim off the $
1180 std::string replacement;
1181 StringMap::const_iterator found = replacements.find(value);
1182 if (found != replacements.end())
1183 {
1184 replacement = found->second;
1185 //llwarns << "replaceSubstituionStrings: value: " << value << " repl: " << replacement << llendl;
1186
1187 it->second->setValue(replacement);
1188 }
1189 else
1190 {
1191 llwarns << "replaceSubstituionStrings FAILURE: value: " << value << " repl: " << replacement << llendl;
1192 }
1193 }
1194 }
1195
1196 // now walk the list of children and call this recursively.
1197 for (LLXMLNodePtr child = node->getFirstChild();
1198 child.notNull(); child = child->getNextSibling())
1199 {
1200 replaceSubstitutionStrings(child, replacements);
1201 }
1202}
1203
1204// private to this file
1205// returns true if the template request was invalid and there's nothing else we
1206// can do with this node, false if you should keep processing (it may have
1207// replaced the contents of the node referred to)
1208LLXMLNodePtr LLNotifications::checkForXMLTemplate(LLXMLNodePtr item)
1209{
1210 if (item->hasName("usetemplate"))
1211 {
1212 std::string replacementName;
1213 if (item->getAttributeString("name", replacementName))
1214 {
1215 StringMap replacements;
1216 for (LLXMLAttribList::const_iterator it=item->mAttributes.begin();
1217 it != item->mAttributes.end(); ++it)
1218 {
1219 replacements[it->second->getName()->mString] = it->second->getValue();
1220 }
1221 if (mXmlTemplates.count(replacementName))
1222 {
1223 item=LLXMLNode::replaceNode(item, mXmlTemplates[replacementName]);
1224
1225 // walk the nodes looking for $(substitution) here and replace
1226 replaceSubstitutionStrings(item, replacements);
1227 }
1228 else
1229 {
1230 llwarns << "XML template lookup failure on '" << replacementName << "' " << llendl;
1231 }
1232 }
1233 }
1234 return item;
1235}
1236
1237bool LLNotifications::loadTemplates()
1238{
1239 const std::string xml_filename = "notifications.xml";
1240 LLXMLNodePtr root;
1241
1242 BOOL success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root);
1243
1244 if (!success || root.isNull() || !root->hasName( "notifications" ))
1245 {
1246 llerrs << "Problem reading UI Notifications file: " << xml_filename << llendl;
1247 return false;
1248 }
1249
1250 clearTemplates();
1251
1252 for (LLXMLNodePtr item = root->getFirstChild();
1253 item.notNull(); item = item->getNextSibling())
1254 {
1255 // we do this FIRST so that item can be changed if we
1256 // encounter a usetemplate -- we just replace the
1257 // current xml node and keep processing
1258 item = checkForXMLTemplate(item);
1259
1260 if (item->hasName("global"))
1261 {
1262 std::string global_name;
1263 if (item->getAttributeString("name", global_name))
1264 {
1265 mGlobalStrings[global_name] = item->getTextContents();
1266 }
1267 continue;
1268 }
1269
1270 if (item->hasName("template"))
1271 {
1272 // store an xml template; templates must have a single node (can contain
1273 // other nodes)
1274 std::string name;
1275 item->getAttributeString("name", name);
1276 LLXMLNodePtr ptr = item->getFirstChild();
1277 mXmlTemplates[name] = ptr;
1278 continue;
1279 }
1280
1281 if (!item->hasName("notification"))
1282 {
1283 llwarns << "Unexpected entity " << item->getName()->mString <<
1284 " found in " << xml_filename << llendl;
1285 continue;
1286 }
1287
1288 // now we know we have a notification entry, so let's build it
1289 LLNotificationTemplatePtr pTemplate(new LLNotificationTemplate());
1290
1291 if (!item->getAttributeString("name", pTemplate->mName))
1292 {
1293 llwarns << "Unable to parse notification with no name" << llendl;
1294 continue;
1295 }
1296
1297 //llinfos << "Parsing " << pTemplate->mName << llendl;
1298
1299 pTemplate->mMessage = item->getTextContents();
1300 pTemplate->mDefaultFunctor = pTemplate->mName;
1301 item->getAttributeString("type", pTemplate->mType);
1302 item->getAttributeString("icon", pTemplate->mIcon);
1303 item->getAttributeString("label", pTemplate->mLabel);
1304 item->getAttributeU32("duration", pTemplate->mExpireSeconds);
1305 item->getAttributeU32("expireOption", pTemplate->mExpireOption);
1306
1307 std::string priority;
1308 item->getAttributeString("priority", priority);
1309 pTemplate->mPriority = NOTIFICATION_PRIORITY_NORMAL;
1310 if (!priority.empty())
1311 {
1312 if (priority == "low") pTemplate->mPriority = NOTIFICATION_PRIORITY_LOW;
1313 if (priority == "normal") pTemplate->mPriority = NOTIFICATION_PRIORITY_NORMAL;
1314 if (priority == "high") pTemplate->mPriority = NOTIFICATION_PRIORITY_HIGH;
1315 if (priority == "critical") pTemplate->mPriority = NOTIFICATION_PRIORITY_CRITICAL;
1316 }
1317
1318 item->getAttributeString("functor", pTemplate->mDefaultFunctor);
1319
1320 BOOL persist = false;
1321 item->getAttributeBOOL("persist", persist);
1322 pTemplate->mPersist = persist;
1323
1324 std::string sound;
1325 item->getAttributeString("sound", sound);
1326 if (!sound.empty())
1327 {
1328 // TODO: test for bad sound effect name / missing effect
1329 pTemplate->mSoundEffect = LLUUID(LLUI::sConfigGroup->getString(sound.c_str()));
1330 }
1331
1332 for (LLXMLNodePtr child = item->getFirstChild();
1333 !child.isNull(); child = child->getNextSibling())
1334 {
1335 child = checkForXMLTemplate(child);
1336
1337 // <url>
1338 if (child->hasName("url"))
1339 {
1340 pTemplate->mURL = child->getTextContents();
1341 child->getAttributeU32("option", pTemplate->mURLOption);
1342 }
1343
1344 if (child->hasName("unique"))
1345 {
1346 pTemplate->mUnique = true;
1347 for (LLXMLNodePtr formitem = child->getFirstChild();
1348 !formitem.isNull(); formitem = formitem->getNextSibling())
1349 {
1350 if (formitem->hasName("context"))
1351 {
1352 std::string key;
1353 formitem->getAttributeString("key", key);
1354 pTemplate->mUniqueContext.push_back(key);
1355 //llwarns << "adding " << key << " to unique context" << llendl;
1356 }
1357 else
1358 {
1359 llwarns << "'unique' has unrecognized subelement "
1360 << formitem->getName()->mString << llendl;
1361 }
1362 }
1363 }
1364
1365 // <form>
1366 if (child->hasName("form"))
1367 {
1368 pTemplate->mForm = LLNotificationFormPtr(new LLNotificationForm(pTemplate->mName, child));
1369 }
1370 }
1371 addTemplate(pTemplate->mName, pTemplate);
1372 }
1373
1374 //std::ostringstream ostream;
1375 //root->writeToOstream(ostream, "\n ");
1376 //llwarns << ostream.str() << llendl;
1377
1378 return true;
1379}
1380
1381// we provide a couple of simple add notification functions so that it's reasonable to create notifications in one line
1382LLNotificationPtr LLNotifications::add(const std::string& name,
1383 const LLSD& substitutions,
1384 const LLSD& payload)
1385{
1386 return add(LLNotification::Params(name).substitutions(substitutions).payload(payload));
1387}
1388
1389LLNotificationPtr LLNotifications::add(const std::string& name,
1390 const LLSD& substitutions,
1391 const LLSD& payload,
1392 const std::string& functor_name)
1393{
1394 return add(LLNotification::Params(name).substitutions(substitutions).payload(payload).functor_name(functor_name));
1395}
1396
1397LLNotificationPtr LLNotifications::add(const std::string& name,
1398 const LLSD& substitutions,
1399 const LLSD& payload,
1400 LLNotificationFunctorRegistry::ResponseFunctor functor)
1401{
1402 return add(LLNotification::Params(name).substitutions(substitutions).payload(payload).functor(functor));
1403}
1404
1405// generalized add function that takes a parameter block object for more complex instantiations
1406LLNotificationPtr LLNotifications::add(const LLNotification::Params& p)
1407{
1408 LLNotificationPtr pNotif(new LLNotification(p));
1409 add(pNotif);
1410 return pNotif;
1411}
1412
1413
1414void LLNotifications::add(const LLNotificationPtr pNotif)
1415{
1416 // first see if we already have it -- if so, that's a problem
1417 LLNotificationSet::iterator it=mItems.find(pNotif);
1418 if (it != mItems.end())
1419 {
1420 llerrs << "Notification added a second time to the master notification channel." << llendl;
1421 }
1422
1423 updateItem(LLSD().insert("sigtype", "add").insert("id", pNotif->id()), pNotif);
1424}
1425
1426void LLNotifications::cancel(LLNotificationPtr pNotif)
1427{
1428 LLNotificationSet::iterator it=mItems.find(pNotif);
1429 if (it == mItems.end())
1430 {
1431 llerrs << "Attempted to delete nonexistent notification " << pNotif->getName() << llendl;
1432 }
1433 updateItem(LLSD().insert("sigtype", "delete").insert("id", pNotif->id()), pNotif);
1434 pNotif->cancel();
1435}
1436
1437void LLNotifications::update(const LLNotificationPtr pNotif)
1438{
1439 LLNotificationSet::iterator it=mItems.find(pNotif);
1440 if (it != mItems.end())
1441 {
1442 updateItem(LLSD().insert("sigtype", "change").insert("id", pNotif->id()), pNotif);
1443 }
1444}
1445
1446
1447LLNotificationPtr LLNotifications::find(LLUUID uuid)
1448{
1449 LLNotificationPtr target = LLNotificationPtr(new LLNotification(uuid));
1450 LLNotificationSet::iterator it=mItems.find(target);
1451 if (it == mItems.end())
1452 {
1453 llwarns << "Tried to dereference uuid '" << uuid << "' as a notification key but didn't find it." << llendl;
1454 return LLNotificationPtr((LLNotification*)NULL);
1455 }
1456 else
1457 {
1458 return *it;
1459 }
1460}
1461
1462void LLNotifications::forEachNotification(NotificationProcess process)
1463{
1464 std::for_each(mItems.begin(), mItems.end(), process);
1465}
1466
1467std::string LLNotifications::getGlobalString(const std::string& key) const
1468{
1469 GlobalStringMap::const_iterator it = mGlobalStrings.find(key);
1470 if (it != mGlobalStrings.end())
1471 {
1472 return it->second;
1473 }
1474 else
1475 {
1476 // if we don't have the key as a global, return the key itself so that the error
1477 // is self-diagnosing.
1478 return key;
1479 }
1480}
1481
1482
1483// ---
1484// END OF LLNotifications implementation
1485// =========================================================
1486
1487std::ostream& operator<<(std::ostream& s, const LLNotification& notification)
1488{
1489 s << notification.summarize();
1490 return s;
1491}
1492