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 /indra/llcommon | |
| 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.
Diffstat (limited to 'indra/llcommon')
| -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) */ | 
