From 0c8fac147d2baed8d8ef0e8c9bdcc47cb3082854 Mon Sep 17 00:00:00 2001
From: Nat Goodspeed <nat@lindenlab.com>
Date: Fri, 16 Mar 2012 15:34:21 -0400
Subject: [PATCH] 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.

---
 indra/llcommon/CMakeLists.txt     |   2 +
 indra/llcommon/llleap.cpp         |  38 +++-
 indra/llcommon/llleaplistener.cpp | 287 ++++++++++++++++++++++++++++++
 indra/llcommon/llleaplistener.h   |  73 ++++++++
 4 files changed, 391 insertions(+), 9 deletions(-)
 create mode 100644 indra/llcommon/llleaplistener.cpp
 create mode 100644 indra/llcommon/llleaplistener.h

diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index 47a8aa96aae..eec5250a231 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 07880bd8188..0a57ef1c48c 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 @@ class LLLeapImpl: public LLLeap
         // 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 @@ class LLLeapImpl: public LLLeap
         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 @@ class LLLeapImpl: public LLLeap
 
         // 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 @@ class LLLeapImpl: public LLLeap
     }
 
 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 @@ class LLLeapImpl: public LLLeap
     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 00000000000..fa5730f112e
--- /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 00000000000..2193d81b9ea
--- /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) */
-- 
GitLab