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
parent
cf39274b64
commit
0c8fac147d
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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) */
|
||||
Loading…
Reference in New Issue