summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2012-03-16 15:34:21 -0400
committerNat Goodspeed <nat@lindenlab.com>2012-03-16 15:34:21 -0400
commit0c8fac147d2baed8d8ef0e8c9bdcc47cb3082854 (patch)
tree8d447aaa16853e64de83c8f9366608898cb176f4
parentcf39274b640e983a5fcc2d03e4c47947a2b36732 (diff)
Introduce LLLeapListener, associating one with each LLLeap object.
Every LEAP plugin gets its own LLLeapListener, managing its own collection of listeners to various LLEventPumps. LLLeapListener's command LLEventPump now has a UUID for a name, both for uniqueness and to make it tough for a plugin to mess with any other.
-rw-r--r--indra/llcommon/CMakeLists.txt2
-rw-r--r--indra/llcommon/llleap.cpp38
-rw-r--r--indra/llcommon/llleaplistener.cpp287
-rw-r--r--indra/llcommon/llleaplistener.h73
4 files changed, 391 insertions, 9 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 47a8aa96aa..eec5250a23 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -64,6 +64,7 @@ set(llcommon_SOURCE_FILES
llinitparam.cpp
llinstancetracker.cpp
llleap.cpp
+ llleaplistener.cpp
llliveappconfig.cpp
lllivefile.cpp
lllog.cpp
@@ -182,6 +183,7 @@ set(llcommon_HEADER_FILES
llkeythrottle.h
lllazy.h
llleap.h
+ llleaplistener.h
lllistenerwrapper.h
lllinkedqueue.h
llliveappconfig.h
diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp
index 07880bd818..0a57ef1c48 100644
--- a/indra/llcommon/llleap.cpp
+++ b/indra/llcommon/llleap.cpp
@@ -31,6 +31,12 @@
#include "llsdserialize.h"
#include "llerrorcontrol.h"
#include "lltimer.h"
+#include "lluuid.h"
+#include "llleaplistener.h"
+
+#if LL_MSVC
+#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
+#endif
LLLeap::LLLeap() {}
LLLeap::~LLLeap() {}
@@ -52,7 +58,13 @@ public:
// pump name -- so it should NOT need tweaking for uniqueness.
mReplyPump(LLUUID::generateNewID().asString()),
mExpect(0),
- mPrevFatalFunction(LLError::getFatalFunction())
+ mPrevFatalFunction(LLError::getFatalFunction()),
+ // Instantiate a distinct LLLeapListener for this plugin. (Every
+ // plugin will want its own collection of managed listeners, etc.)
+ // Pass it a callback to our connect() method, so it can send events
+ // from a particular LLEventPump to the plugin without having to know
+ // this class or method name.
+ mListener(new LLLeapListener(boost::bind(&LLLeapImpl::connect, this, _1, _2)))
{
// Rule out empty vector
if (plugin.empty())
@@ -115,11 +127,8 @@ public:
childout.setLimit(20);
childerr.setLimit(20);
- // Serialize any event received on mReplyPump to our child's stdin,
- // suitably enriched with the pump name on which it was received.
- mStdinConnection = mReplyPump
- .listen("LLLeap",
- boost::bind(&LLLeapImpl::wstdin, this, mReplyPump.getName(), _1));
+ // Serialize any event received on mReplyPump to our child's stdin.
+ mStdinConnection = connect(mReplyPump, "LLLeap");
// Listening on stdout is stateful. In general, we're either waiting
// for the length prefix or waiting for the specified length of data.
@@ -144,13 +153,12 @@ public:
// Send child a preliminary event reporting our own reply-pump name --
// which would otherwise be pretty tricky to guess!
-// TODO TODO inject name of command pump here.
wstdin(mReplyPump.getName(),
LLSDMap
- ("command", LLSD())
+ ("command", mListener->getName())
// Include LLLeap features -- this may be important for child to
// construct (or recognize) current protocol.
- ("features", LLSD::emptyMap()));
+ ("features", LLLeapListener::getFeatures()));
}
// Normally we'd expect to arrive here only via done()
@@ -397,6 +405,17 @@ public:
}
private:
+ /// We always want to listen on mReplyPump with wstdin(); under some
+ /// circumstances we'll also echo other LLEventPumps to the plugin.
+ LLBoundListener connect(LLEventPump& pump, const std::string& listener)
+ {
+ // Serialize any event received on the specified LLEventPump to our
+ // child's stdin, suitably enriched with the pump name on which it was
+ // received.
+ return pump.listen(listener,
+ boost::bind(&LLLeapImpl::wstdin, this, pump.getName(), _1));
+ }
+
std::string mDesc;
LLEventStream mDonePump;
LLEventStream mReplyPump;
@@ -406,6 +425,7 @@ private:
boost::scoped_ptr<LLEventPump::Blocker> mBlocker;
LLProcess::ReadPipe::size_type mExpect;
LLError::FatalFunction mPrevFatalFunction;
+ boost::scoped_ptr<LLLeapListener> mListener;
};
// This must follow the declaration of LLLeapImpl, so it may as well be last.
diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp
new file mode 100644
index 0000000000..fa5730f112
--- /dev/null
+++ b/indra/llcommon/llleaplistener.cpp
@@ -0,0 +1,287 @@
+/**
+ * @file llleaplistener.cpp
+ * @author Nat Goodspeed
+ * @date 2012-03-16
+ * @brief Implementation for llleaplistener.
+ *
+ * $LicenseInfo:firstyear=2012&license=viewerlgpl$
+ * Copyright (c) 2012, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llleaplistener.h"
+// STL headers
+// std headers
+// external library headers
+#include <boost/foreach.hpp>
+// other Linden headers
+#include "lluuid.h"
+#include "llsdutil.h"
+#include "stringize.h"
+
+/*****************************************************************************
+* LEAP FEATURE STRINGS
+*****************************************************************************/
+/**
+ * Implement "getFeatures" command. The LLSD map thus obtained is intended to
+ * be machine-readable (read: easily-parsed, if parsing be necessary) and to
+ * highlight the differences between this version of the LEAP protocol and
+ * the baseline version. A client may thus determine whether or not the
+ * running viewer supports some recent feature of interest.
+ *
+ * This method is defined at the top of this implementation file so it's easy
+ * to find, easy to spot, easy to update as we enhance the LEAP protocol.
+ */
+/*static*/ LLSD LLLeapListener::getFeatures()
+{
+ static LLSD features;
+ if (features.isUndefined())
+ {
+ features = LLSD::emptyMap();
+
+ // This initial implementation IS the baseline LEAP protocol; thus the
+ // set of differences is empty; thus features is initially empty.
+// features["featurename"] = "value";
+ }
+
+ return features;
+}
+
+LLLeapListener::LLLeapListener(const ConnectFunc& connect):
+ // Each LEAP plugin has an instance of this listener. Make the command
+ // pump name difficult for other such plugins to guess.
+ LLEventAPI(LLUUID::generateNewID().asString(),
+ "Operations relating to the LLSD Event API Plugin (LEAP) protocol"),
+ mConnect(connect)
+{
+ LLSD need_name(LLSDMap("name", LLSD()));
+ add("newpump",
+ "Instantiate a new LLEventPump named like [\"name\"] and listen to it.\n"
+ "If [\"type\"] == \"LLEventQueue\", make LLEventQueue, else LLEventStream.\n"
+ "Events sent through new LLEventPump will be decorated with [\"pump\"]=name.\n"
+ "Returns actual name in [\"name\"] (may be different if collision).",
+ &LLLeapListener::newpump,
+ need_name);
+ add("killpump",
+ "Delete LLEventPump [\"name\"] created by \"newpump\".\n"
+ "Returns [\"status\"] boolean indicating whether such a pump existed.",
+ &LLLeapListener::killpump,
+ need_name);
+ LLSD need_source_listener(LLSDMap("source", LLSD())("listener", LLSD()));
+ add("listen",
+ "Listen to an existing LLEventPump named [\"source\"], with listener name\n"
+ "[\"listener\"].\n"
+ "By default, send events on [\"source\"] to the plugin, decorated\n"
+ "with [\"pump\"]=[\"source\"].\n"
+ "If [\"dest\"] specified, send undecorated events on [\"source\"] to the\n"
+ "LLEventPump named [\"dest\"].\n"
+ "Returns [\"status\"] boolean indicating whether the connection was made.",
+ &LLLeapListener::listen,
+ need_source_listener);
+ add("stoplistening",
+ "Disconnect a connection previously established by \"listen\".\n"
+ "Pass same [\"source\"] and [\"listener\"] arguments.\n"
+ "Returns [\"status\"] boolean indicating whether such a listener existed.",
+ &LLLeapListener::stoplistening,
+ need_source_listener);
+ add("ping",
+ "No arguments, just a round-trip sanity check.",
+ &LLLeapListener::ping);
+ add("getAPIs",
+ "Enumerate all LLEventAPI instances by name and description.",
+ &LLLeapListener::getAPIs);
+ add("getAPI",
+ "Get name, description, dispatch key and operations for LLEventAPI [\"api\"].",
+ &LLLeapListener::getAPI,
+ LLSD().with("api", LLSD()));
+ add("getFeatures",
+ "Return an LLSD map of feature strings (deltas from baseline LEAP protocol)",
+ static_cast<void (LLLeapListener::*)(const LLSD&) const>(&LLLeapListener::getFeatures));
+ add("getFeature",
+ "Return the feature value with key [\"feature\"]",
+ &LLLeapListener::getFeature,
+ LLSD().with("feature", LLSD()));
+}
+
+LLLeapListener::~LLLeapListener()
+{
+ // We'd have stored a map of LLTempBoundListener instances, save that the
+ // operation of inserting into a std::map necessarily copies the
+ // value_type, and Bad Things would happen if you copied an
+ // LLTempBoundListener. (Destruction of the original would disconnect the
+ // listener, invalidating every stored connection.)
+ BOOST_FOREACH(ListenersMap::value_type& pair, mListeners)
+ {
+ pair.second.disconnect();
+ }
+}
+
+void LLLeapListener::newpump(const LLSD& request)
+{
+ Response reply(LLSD(), request);
+
+ std::string name = request["name"];
+ LLSD const & type = request["type"];
+
+ LLEventPump * new_pump = NULL;
+ if (type.asString() == "LLEventQueue")
+ {
+ new_pump = new LLEventQueue(name, true); // tweak name for uniqueness
+ }
+ else
+ {
+ if (! (type.isUndefined() || type.asString() == "LLEventStream"))
+ {
+ reply.warn(STRINGIZE("unknown 'type' " << type << ", using LLEventStream"));
+ }
+ new_pump = new LLEventStream(name, true); // tweak name for uniqueness
+ }
+
+ name = new_pump->getName();
+
+ mEventPumps.insert(name, new_pump);
+
+ // Now listen on this new pump with our plugin listener
+ std::string myname("llleap");
+ saveListener(name, myname, mConnect(*new_pump, myname));
+
+ reply["name"] = name;
+}
+
+void LLLeapListener::killpump(const LLSD& request)
+{
+ Response reply(LLSD(), request);
+
+ std::string name = request["name"];
+ // success == (nonzero number of entries were erased)
+ reply["status"] = bool(mEventPumps.erase(name));
+}
+
+void LLLeapListener::listen(const LLSD& request)
+{
+ Response reply(LLSD(), request);
+
+ std::string source_name = request["source"];
+ std::string dest_name = request["dest"];
+ std::string listener_name = request["listener"];
+
+ LLEventPump & source = LLEventPumps::instance().obtain(source_name);
+
+ reply["status"] = false;
+ if (mListeners.find(ListenersMap::key_type(source_name, listener_name)) == mListeners.end())
+ {
+ try
+ {
+ if (request["dest"].isDefined())
+ {
+ // If we're asked to connect the "source" pump to a
+ // specific "dest" pump, find dest pump and connect it.
+ LLEventPump & dest = LLEventPumps::instance().obtain(dest_name);
+ saveListener(source_name, listener_name,
+ source.listen(listener_name,
+ boost::bind(&LLEventPump::post, &dest, _1)));
+ }
+ else
+ {
+ // "dest" unspecified means to direct events on "source"
+ // to our plugin listener.
+ saveListener(source_name, listener_name, mConnect(source, listener_name));
+ }
+ reply["status"] = true;
+ }
+ catch (const LLEventPump::DupListenerName &)
+ {
+ // pass - status already set to false
+ }
+ }
+}
+
+void LLLeapListener::stoplistening(const LLSD& request)
+{
+ Response reply(LLSD(), request);
+
+ std::string source_name = request["source"];
+ std::string listener_name = request["listener"];
+
+ ListenersMap::iterator finder =
+ mListeners.find(ListenersMap::key_type(source_name, listener_name));
+
+ reply["status"] = false;
+ if(finder != mListeners.end())
+ {
+ reply["status"] = true;
+ finder->second.disconnect();
+ mListeners.erase(finder);
+ }
+}
+
+void LLLeapListener::ping(const LLSD& request) const
+{
+ // do nothing, default reply suffices
+ Response(LLSD(), request);
+}
+
+void LLLeapListener::getAPIs(const LLSD& request) const
+{
+ Response reply(LLSD(), request);
+
+ for (LLEventAPI::instance_iter eai(LLEventAPI::beginInstances()),
+ eaend(LLEventAPI::endInstances());
+ eai != eaend; ++eai)
+ {
+ LLSD info;
+ info["desc"] = eai->getDesc();
+ reply[eai->getName()] = info;
+ }
+}
+
+void LLLeapListener::getAPI(const LLSD& request) const
+{
+ Response reply(LLSD(), request);
+
+ LLEventAPI* found = LLEventAPI::getInstance(request["api"]);
+ if (found)
+ {
+ reply["name"] = found->getName();
+ reply["desc"] = found->getDesc();
+ reply["key"] = found->getDispatchKey();
+ LLSD ops;
+ for (LLEventAPI::const_iterator oi(found->begin()), oend(found->end());
+ oi != oend; ++oi)
+ {
+ ops.append(found->getMetadata(oi->first));
+ }
+ reply["ops"] = ops;
+ }
+}
+
+void LLLeapListener::getFeatures(const LLSD& request) const
+{
+ // Merely constructing and destroying a Response object suffices here.
+ // Giving it a name would only produce fatal 'unreferenced variable'
+ // warnings.
+ Response(getFeatures(), request);
+}
+
+void LLLeapListener::getFeature(const LLSD& request) const
+{
+ Response reply(LLSD(), request);
+
+ LLSD::String feature_name(request["feature"]);
+ LLSD features(getFeatures());
+ if (features[feature_name].isDefined())
+ {
+ reply["feature"] = features[feature_name];
+ }
+}
+
+void LLLeapListener::saveListener(const std::string& pump_name,
+ const std::string& listener_name,
+ const LLBoundListener& listener)
+{
+ mListeners.insert(ListenersMap::value_type(ListenersMap::key_type(pump_name, listener_name),
+ listener));
+}
diff --git a/indra/llcommon/llleaplistener.h b/indra/llcommon/llleaplistener.h
new file mode 100644
index 0000000000..2193d81b9e
--- /dev/null
+++ b/indra/llcommon/llleaplistener.h
@@ -0,0 +1,73 @@
+/**
+ * @file llleaplistener.h
+ * @author Nat Goodspeed
+ * @date 2012-03-16
+ * @brief LLEventAPI supporting LEAP plugins
+ *
+ * $LicenseInfo:firstyear=2012&license=viewerlgpl$
+ * Copyright (c) 2012, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLLEAPLISTENER_H)
+#define LL_LLLEAPLISTENER_H
+
+#include "lleventapi.h"
+#include <map>
+#include <string>
+#include <boost/function.hpp>
+#include <boost/ptr_container/ptr_map.hpp>
+
+/// Listener class implementing LLLeap query/control operations.
+/// See https://jira.lindenlab.com/jira/browse/DEV-31978.
+class LLLeapListener: public LLEventAPI
+{
+public:
+ /**
+ * Decouple LLLeap by dependency injection. Certain LLLeapListener
+ * operations must be able to cause LLLeap to listen on a specified
+ * LLEventPump with the LLLeap listener that wraps incoming events in an
+ * outer (pump=, data=) map and forwards them to the plugin. Very well,
+ * define the signature for a function that will perform that, and make
+ * our constructor accept such a function.
+ */
+ typedef boost::function<LLBoundListener(LLEventPump&, const std::string& listener)>
+ ConnectFunc;
+ LLLeapListener(const ConnectFunc& connect);
+ ~LLLeapListener();
+
+ static LLSD getFeatures();
+
+private:
+ void newpump(const LLSD&);
+ void killpump(const LLSD&);
+ void listen(const LLSD&);
+ void stoplistening(const LLSD&);
+ void ping(const LLSD&) const;
+ void getAPIs(const LLSD&) const;
+ void getAPI(const LLSD&) const;
+ void getFeatures(const LLSD&) const;
+ void getFeature(const LLSD&) const;
+
+ void saveListener(const std::string& pump_name, const std::string& listener_name,
+ const LLBoundListener& listener);
+
+ ConnectFunc mConnect;
+
+ // In theory, listen() could simply call the relevant LLEventPump's
+ // listen() method, stoplistening() likewise. Lifespan issues make us
+ // capture the LLBoundListener objects: when this object goes away, all
+ // those listeners should be disconnected. But what if the client listens,
+ // stops, listens again on the same LLEventPump with the same listener
+ // name? Merely collecting LLBoundListeners wouldn't adequately track
+ // that. So capture the latest LLBoundListener for this LLEventPump name
+ // and listener name.
+ typedef std::map<std::pair<std::string, std::string>, LLBoundListener> ListenersMap;
+ ListenersMap mListeners;
+ // Similar lifespan reasoning applies to LLEventPumps instantiated by
+ // newpump() operations.
+ typedef boost::ptr_map<std::string, LLEventPump> EventPumpsMap;
+ EventPumpsMap mEventPumps;
+};
+
+#endif /* ! defined(LL_LLLEAPLISTENER_H) */