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.
master
Nat Goodspeed 2012-03-16 15:34:21 -04:00
parent cf39274b64
commit 0c8fac147d
4 changed files with 391 additions and 9 deletions

View File

@ -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

View File

@ -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.

View File

@ -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));
}

View File

@ -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) */