diff options
author | Nat Goodspeed <nat@lindenlab.com> | 2012-03-16 15:34:21 -0400 |
---|---|---|
committer | Nat Goodspeed <nat@lindenlab.com> | 2012-03-16 15:34:21 -0400 |
commit | 0c8fac147d2baed8d8ef0e8c9bdcc47cb3082854 (patch) | |
tree | 8d447aaa16853e64de83c8f9366608898cb176f4 | |
parent | cf39274b640e983a5fcc2d03e4c47947a2b36732 (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.txt | 2 | ||||
-rw-r--r-- | indra/llcommon/llleap.cpp | 38 | ||||
-rw-r--r-- | indra/llcommon/llleaplistener.cpp | 287 | ||||
-rw-r--r-- | indra/llcommon/llleaplistener.h | 73 |
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) */ |