/** * @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 #include // std::find_if #include #include #include // std headers // external library headers // other Linden headers #include "lazyeventapi.h" #include "llsdutil.h" #include "lluuid.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 std::string_view& caller, const Callback& callback): // 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"), mCaller(caller), mCallback(callback), // Troubling thought: what if one plugin intentionally messes with // another plugin? LLEventPump names are in a single global namespace. // Try to make that more difficult by generating a UUID for the reply- // pump name -- so it should NOT need tweaking for uniqueness. mReplyPump(LLUUID::generateNewID().asString()), mReplyConn(connect(mReplyPump, mCaller)) { LLSD need_name(LLSDMap("name", LLSD())); add("newpump", R"-(Instantiate a new LLEventPump named like ["name"] and listen to it. ["type"] == "LLEventStream", "LLEventMailDrop" et al. Events sent through new LLEventPump will be decorated with ["pump"]=name. Returns actual name in ["name"] (may be different if collision).)-", &LLLeapListener::newpump, need_name); LLSD need_source_listener(LLSDMap("source", LLSD())("listener", LLSD())); add("listen", R"-(Listen to an existing LLEventPump named ["source"], with listener name ["listener"]. If ["tweak"] is specified as true, tweak listener name for uniqueness. By default, send events on ["source"] to the plugin, decorated with ["pump"]=["source"]. If ["dest"] specified, send undecorated events on ["source"] to the LLEventPump named ["dest"]. Returns ["status"] boolean indicating whether the connection was made, plus ["listener"] reporting (possibly tweaked) listener name.)-", &LLLeapListener::listen, need_source_listener); add("stoplistening", R"-(Disconnect a connection previously established by "listen". Pass same ["source"] and ["listener"] arguments. 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", R"-(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(&LLLeapListener::getFeatures)); add("getFeature", R"-(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.) LL_DEBUGS("LLLeapListener") << "~LLLeapListener(\"" << mCaller << "\")" << LL_ENDL; for (ListenersMap::value_type& pair : mListeners) { pair.second.disconnect(); } } void LLLeapListener::newpump(const LLSD& request) { Response reply(LLSD(), request); std::string name = request["name"]; std::string type = request["type"]; try { // tweak name for uniqueness LLEventPump& new_pump(LLEventPumps::instance().make(name, true, type)); name = new_pump.getName(); reply["name"] = name; // Now listen on this new pump with our plugin listener saveListener(name, mCaller, connect(new_pump, mCaller)); } catch (const LLEventPumps::BadType& error) { reply.error(error.what()); } } 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"]; if (request["tweak"].asBoolean()) { listener_name = LLEventPump::inventName(listener_name); } reply["listener"] = listener_name; 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, connect(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); // first, traverse existing LLEventAPI instances std::set instances; for (auto& ea : LLEventAPI::instance_snapshot()) { // remember which APIs are actually instantiated instances.insert(ea.getName()); reply[ea.getName()] = llsd::map("desc", ea.getDesc()); } // supplement that with *potential* instances: that is, instances of // LazyEventAPI that can each instantiate an LLEventAPI on demand for (const auto& lea : LL::LazyEventAPIBase::instance_snapshot()) { // skip any LazyEventAPI that's already instantiated its LLEventAPI if (instances.find(lea.getName()) == instances.end()) { reply[lea.getName()] = llsd::map("desc", lea.getDesc()); } } } // Because LazyEventAPI deliberately mimics LLEventAPI's query API, this // function can be passed either -- even though they're unrelated types. template void reportAPI(LLEventAPI::Response& reply, const API& api) { reply["name"] = api.getName(); reply["desc"] = api.getDesc(); reply["key"] = api.getDispatchKey(); LLSD ops; for (const auto& namedesc : api) { ops.append(api.getMetadata(namedesc.first)); } reply["ops"] = ops; } void LLLeapListener::getAPI(const LLSD& request) const { Response reply(LLSD(), request); // check first among existing LLEventAPI instances auto foundea = LLEventAPI::getInstance(request["api"]); if (foundea) { reportAPI(reply, *foundea); } else { // Here the requested LLEventAPI doesn't yet exist, but do we have a // registered LazyEventAPI for it? LL::LazyEventAPIBase::instance_snapshot snap; auto foundlea = std::find_if(snap.begin(), snap.end(), [api = request["api"].asString()] (const auto& lea) { return (lea.getName() == api); }); if (foundlea != snap.end()) { reportAPI(reply, *foundlea); } } } 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]; } } LLBoundListener LLLeapListener::connect(LLEventPump& pump, const std::string& listener) { // Connect to source pump with an adapter that calls our callback with the // pump name as well as the event data. return pump.listen( listener, [callback=mCallback, pump=pump.getName()] (const LLSD& data) { return callback(pump, data); }); } void LLLeapListener::saveListener(const std::string& pump_name, const std::string& listener_name, const LLBoundListener& listener) { // Don't use insert() or emplace() because, if this (pump_name, // listener_name) pair is already in mListeners, we *want* to overwrite it. auto& listener_entry{ mListeners[ListenersMap::key_type(pump_name, listener_name)] }; // If we already stored a connection for this pump and listener name, // disconnect it before overwriting it. But if this entry was newly // created, disconnect() will be a no-op. listener_entry.disconnect(); listener_entry = listener; }