Skip to content
Snippets Groups Projects
Commit 0c8fac14 authored by Nat Goodspeed's avatar Nat Goodspeed
Browse files

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.
parent cf39274b
No related branches found
No related tags found
No related merge requests found
...@@ -64,6 +64,7 @@ set(llcommon_SOURCE_FILES ...@@ -64,6 +64,7 @@ set(llcommon_SOURCE_FILES
llinitparam.cpp llinitparam.cpp
llinstancetracker.cpp llinstancetracker.cpp
llleap.cpp llleap.cpp
llleaplistener.cpp
llliveappconfig.cpp llliveappconfig.cpp
lllivefile.cpp lllivefile.cpp
lllog.cpp lllog.cpp
...@@ -182,6 +183,7 @@ set(llcommon_HEADER_FILES ...@@ -182,6 +183,7 @@ set(llcommon_HEADER_FILES
llkeythrottle.h llkeythrottle.h
lllazy.h lllazy.h
llleap.h llleap.h
llleaplistener.h
lllistenerwrapper.h lllistenerwrapper.h
lllinkedqueue.h lllinkedqueue.h
llliveappconfig.h llliveappconfig.h
......
...@@ -31,6 +31,12 @@ ...@@ -31,6 +31,12 @@
#include "llsdserialize.h" #include "llsdserialize.h"
#include "llerrorcontrol.h" #include "llerrorcontrol.h"
#include "lltimer.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() {}
LLLeap::~LLLeap() {} LLLeap::~LLLeap() {}
...@@ -52,7 +58,13 @@ class LLLeapImpl: public LLLeap ...@@ -52,7 +58,13 @@ class LLLeapImpl: public LLLeap
// pump name -- so it should NOT need tweaking for uniqueness. // pump name -- so it should NOT need tweaking for uniqueness.
mReplyPump(LLUUID::generateNewID().asString()), mReplyPump(LLUUID::generateNewID().asString()),
mExpect(0), 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 // Rule out empty vector
if (plugin.empty()) if (plugin.empty())
...@@ -115,11 +127,8 @@ class LLLeapImpl: public LLLeap ...@@ -115,11 +127,8 @@ class LLLeapImpl: public LLLeap
childout.setLimit(20); childout.setLimit(20);
childerr.setLimit(20); childerr.setLimit(20);
// Serialize any event received on mReplyPump to our child's stdin, // Serialize any event received on mReplyPump to our child's stdin.
// suitably enriched with the pump name on which it was received. mStdinConnection = connect(mReplyPump, "LLLeap");
mStdinConnection = mReplyPump
.listen("LLLeap",
boost::bind(&LLLeapImpl::wstdin, this, mReplyPump.getName(), _1));
// Listening on stdout is stateful. In general, we're either waiting // Listening on stdout is stateful. In general, we're either waiting
// for the length prefix or waiting for the specified length of data. // for the length prefix or waiting for the specified length of data.
...@@ -144,13 +153,12 @@ class LLLeapImpl: public LLLeap ...@@ -144,13 +153,12 @@ class LLLeapImpl: public LLLeap
// Send child a preliminary event reporting our own reply-pump name -- // Send child a preliminary event reporting our own reply-pump name --
// which would otherwise be pretty tricky to guess! // which would otherwise be pretty tricky to guess!
// TODO TODO inject name of command pump here.
wstdin(mReplyPump.getName(), wstdin(mReplyPump.getName(),
LLSDMap LLSDMap
("command", LLSD()) ("command", mListener->getName())
// Include LLLeap features -- this may be important for child to // Include LLLeap features -- this may be important for child to
// construct (or recognize) current protocol. // construct (or recognize) current protocol.
("features", LLSD::emptyMap())); ("features", LLLeapListener::getFeatures()));
} }
// Normally we'd expect to arrive here only via done() // Normally we'd expect to arrive here only via done()
...@@ -397,6 +405,17 @@ class LLLeapImpl: public LLLeap ...@@ -397,6 +405,17 @@ class LLLeapImpl: public LLLeap
} }
private: 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; std::string mDesc;
LLEventStream mDonePump; LLEventStream mDonePump;
LLEventStream mReplyPump; LLEventStream mReplyPump;
...@@ -406,6 +425,7 @@ class LLLeapImpl: public LLLeap ...@@ -406,6 +425,7 @@ class LLLeapImpl: public LLLeap
boost::scoped_ptr<LLEventPump::Blocker> mBlocker; boost::scoped_ptr<LLEventPump::Blocker> mBlocker;
LLProcess::ReadPipe::size_type mExpect; LLProcess::ReadPipe::size_type mExpect;
LLError::FatalFunction mPrevFatalFunction; LLError::FatalFunction mPrevFatalFunction;
boost::scoped_ptr<LLLeapListener> mListener;
}; };
// This must follow the declaration of LLLeapImpl, so it may as well be last. // This must follow the declaration of LLLeapImpl, so it may as well be last.
......
/**
* @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));
}
/**
* @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) */
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment