diff options
| author | Edwin Mons <edwin.mons@isode.com> | 2014-05-23 09:01:23 (GMT) | 
|---|---|---|
| committer | Swift Review <review@swift.im> | 2014-06-22 12:35:26 (GMT) | 
| commit | bd7f30aec53fc776be678577dbe4f9afec5898a6 (patch) | |
| tree | 66afad4382dc16f7405a856dd0b5abc38db51653 /Sluift | |
| parent | 1eb14b6bde145ca54ac9b981df339fb8c56d3930 (diff) | |
| download | swift-contrib-bd7f30aec53fc776be678577dbe4f9afec5898a6.zip swift-contrib-bd7f30aec53fc776be678577dbe4f9afec5898a6.tar.bz2 | |
Sluift component support
Change-Id: Ib8af01c04c866e198c04d35236dea4da464c9116
Diffstat (limited to 'Sluift')
| -rw-r--r-- | Sluift/ClientHelpers.h | 18 | ||||
| -rw-r--r-- | Sluift/Examples/Component.lua | 55 | ||||
| -rw-r--r-- | Sluift/Helpers.cpp (renamed from Sluift/ClientHelpers.cpp) | 32 | ||||
| -rw-r--r-- | Sluift/Helpers.h | 21 | ||||
| -rw-r--r-- | Sluift/SConscript | 4 | ||||
| -rw-r--r-- | Sluift/SluiftClient.cpp | 6 | ||||
| -rw-r--r-- | Sluift/SluiftComponent.cpp | 145 | ||||
| -rw-r--r-- | Sluift/SluiftComponent.h | 108 | ||||
| -rw-r--r-- | Sluift/component.cpp | 467 | ||||
| -rw-r--r-- | Sluift/core.lua | 202 | ||||
| -rw-r--r-- | Sluift/sluift.cpp | 37 | 
11 files changed, 1063 insertions, 32 deletions
| diff --git a/Sluift/ClientHelpers.h b/Sluift/ClientHelpers.h deleted file mode 100644 index eb78ba6..0000000 --- a/Sluift/ClientHelpers.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2013 Remko Tronçon - * Licensed under the GNU General Public License. - * See the COPYING file for more information. - */ - -#pragma once - -#include <Swiften/Base/Override.h> -#include <Swiften/Base/API.h> - -#include <string> - -namespace Swift { -	class ClientError; - -	std::string getClientErrorString(const ClientError& error); -} diff --git a/Sluift/Examples/Component.lua b/Sluift/Examples/Component.lua new file mode 100644 index 0000000..b5d6539 --- /dev/null +++ b/Sluift/Examples/Component.lua @@ -0,0 +1,55 @@ +--[[ +	Copyright (c) 2014 Edwin Mons and Remko Tronçon +	Licensed under the GNU General Public License v3. +	See Documentation/Licenses/GPLv3.txt for more information. +--]] + +--[[ +	 +	Component example. + +	This script connects to an XMPP server as a component, and listens to +	messages received. +	 +	The following environment variables are used: +	* SLUIFT_COMP_DOMAIN: Component domain name +	* SLUIFT_COMP_SECRET: Component secret +	* SLUIFT_COMP_HOST: XMPP server host name +	* SLUIFT_COMP_PORT: XMPP server component port +	* SLUIFT_JID: Recipient of presence and initial message +	* SLUIFT_DEBUG: Sets whether debugging should be turned on + +--]] + +require "sluift" + +sluift.debug = os.getenv("SLUIFT_DEBUG") or false + +config = { +	domain = os.getenv('SLUIFT_COMP_DOMAIN'), +	secret = os.getenv('SLUIFT_COMP_SECRET'), +	host = os.getenv('SLUIFT_COMP_HOST'), +	port = os.getenv('SLUIFT_COMP_PORT'), +	jid = os.getenv('SLUIFT_JID') +} + +-- Create the component, and connect +comp = sluift.new_component(config.domain, config.secret); +comp:connect(config) + +-- Send initial presence and message +-- Assumes the remote client already has this component user on his roster +comp:send_presence{from='root@' .. config.domain, to=config.jid} +comp:send_message{from='root@' .. config.domain, to=config.jid, body='Component active'} + +-- Listen for messages, and reply if one is received +for message in comp:messages() do +	print("Received a message from " .. message.from) +	comp:send_message{to=message.from, from=message.to, body='I received: ' .. message['body']} + +	-- Send out a ping to demonstrate we can do more than just send messages +	comp:get{to=message.from, query='<ping xmlns="urn:xmpp:ping"/>'} +end + +comp:disconnect() + diff --git a/Sluift/ClientHelpers.cpp b/Sluift/Helpers.cpp index 8e07112..29e2b04 100644 --- a/Sluift/ClientHelpers.cpp +++ b/Sluift/Helpers.cpp @@ -1,28 +1,36 @@  /* - * Copyright (c) 2013 Remko Tronçon + * Copyright (c) 2013-2014 Kevin Smith and Remko Tronçon   * Licensed under the GNU General Public License.   * See the COPYING file for more information.   */ -#include <Sluift/ClientHelpers.h> +#include <Sluift/Helpers.h>  #include <Swiften/Client/ClientError.h> +#include <Swiften/Component/ComponentError.h>  using namespace Swift; -std::string Swift::getClientErrorString(const ClientError& error) { +template<class T> std::string Swift::getCommonErrorString(T& error) {  	std::string reason = "Disconnected: ";  	switch(error.getType()) { -		case ClientError::UnknownError: reason += "Unknown Error"; break; +		case T::UnknownError: reason += "Unknown Error"; break; +		case T::ConnectionError: reason += "Error connecting to server"; break; +		case T::ConnectionReadError: reason += "Error while receiving server data"; break; +		case T::ConnectionWriteError: reason += "Error while sending data to the server"; break; +		case T::XMLError: reason += "Error parsing server data"; break; +		case T::AuthenticationFailedError: reason += "Login/password invalid"; break; +		case T::UnexpectedElementError: reason += "Unexpected response"; break; +	} +	return reason; +} + +std::string Swift::getErrorString(const ClientError& error) { +	std::string reason = getCommonErrorString(error); +	switch(error.getType()) {  		case ClientError::DomainNameResolveError: reason += "Unable to find server"; break; -		case ClientError::ConnectionError: reason += "Error connecting to server"; break; -		case ClientError::ConnectionReadError: reason += "Error while receiving server data"; break; -		case ClientError::ConnectionWriteError: reason += "Error while sending data to the server"; break; -		case ClientError::XMLError: reason += "Error parsing server data"; break; -		case ClientError::AuthenticationFailedError: reason += "Login/password invalid"; break;  		case ClientError::CompressionFailedError: reason += "Error while compressing stream"; break;  		case ClientError::ServerVerificationFailedError: reason += "Server verification failed"; break;  		case ClientError::NoSupportedAuthMechanismsError: reason += "Authentication mechanisms not supported"; break; -		case ClientError::UnexpectedElementError: reason += "Unexpected response"; break;  		case ClientError::ResourceBindError: reason += "Error binding resource"; break;  		case ClientError::RevokedError: reason += "Certificate got revoked"; break; @@ -49,2 +57,6 @@ std::string Swift::getClientErrorString(const ClientError& error) {  } +std::string Swift::getErrorString(const ComponentError& error) { +	return getCommonErrorString(error); +} + diff --git a/Sluift/Helpers.h b/Sluift/Helpers.h new file mode 100644 index 0000000..d04a610 --- /dev/null +++ b/Sluift/Helpers.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2013-2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License. + * See the COPYING file for more information. + */ + +#pragma once + +#include <Swiften/Base/Override.h> +#include <Swiften/Base/API.h> + +#include <string> + +namespace Swift { +	class ClientError; +	class ComponentError; + +	template<typename T> std::string getCommonErrorString(T& error); +	std::string getErrorString(const ClientError& error); +	std::string getErrorString(const ComponentError& error); +} diff --git a/Sluift/SConscript b/Sluift/SConscript index 3cc1f29..5e0a030 100644 --- a/Sluift/SConscript +++ b/Sluift/SConscript @@ -34,9 +34,11 @@ elif env["SCONS_STAGE"] == "build" :  		"ElementConvertors/StatusShowConvertor.cpp",  		"ElementConvertors/DelayConvertor.cpp", -		"ClientHelpers.cpp", +		"Helpers.cpp",  		"SluiftClient.cpp", +		"SluiftComponent.cpp",  		"Watchdog.cpp",  		"core.c",  		"client.cpp", +		"component.cpp",  		"sluift.cpp"  	] diff --git a/Sluift/SluiftClient.cpp b/Sluift/SluiftClient.cpp index 9ff9d18..69472b8 100644 --- a/Sluift/SluiftClient.cpp +++ b/Sluift/SluiftClient.cpp @@ -1,4 +1,4 @@  /* - * Copyright (c) 2013-2014 Remko Tronçon + * Copyright (c) 2013-2014 Kevin Smith and Remko Tronçon   * Licensed under the GNU General Public License.   * See the COPYING file for more information. @@ -17,5 +17,5 @@  #include <Swiften/Elements/PubSubEvent.h>  #include <Swiften/Queries/RawRequest.h> -#include <Sluift/ClientHelpers.h> +#include <Sluift/Helpers.h>  #include <Swiften/Elements/Presence.h> @@ -78,5 +78,5 @@ void SluiftClient::waitConnected(int timeout) {  	}  	if (disconnectedError) { -		throw Lua::Exception(getClientErrorString(*disconnectedError)); +		throw Lua::Exception(getErrorString(*disconnectedError));  	}  } diff --git a/Sluift/SluiftComponent.cpp b/Sluift/SluiftComponent.cpp new file mode 100644 index 0000000..c08a103 --- /dev/null +++ b/Sluift/SluiftComponent.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License. + * See the COPYING file for more information. + */ + +#include <Sluift/SluiftComponent.h> + +#include <boost/numeric/conversion/cast.hpp> + +#include <Swiften/Component/ComponentXMLTracer.h> +#include <Swiften/Component/Component.h> +#include <Swiften/Roster/XMPPRoster.h> +#include <Sluift/SluiftGlobals.h> +#include <Sluift/Lua/Exception.h> +#include <Swiften/Elements/Message.h> +#include <Swiften/Queries/RawRequest.h> +#include <Sluift/Helpers.h> +#include <Swiften/Elements/Presence.h> + +using namespace Swift; + +SluiftComponent::SluiftComponent( +		const JID& jid,  +		const std::string& password,  +		NetworkFactories* networkFactories,  +		SimpleEventLoop* eventLoop): +			networkFactories(networkFactories),  +			eventLoop(eventLoop), +			tracer(NULL) { +	component = new Component(jid, password, networkFactories); +	component->onError.connect(boost::bind(&SluiftComponent::handleError, this, _1)); +	component->onMessageReceived.connect(boost::bind(&SluiftComponent::handleIncomingMessage, this, _1)); +	component->onPresenceReceived.connect(boost::bind(&SluiftComponent::handleIncomingPresence, this, _1)); +} + +SluiftComponent::~SluiftComponent() { +	delete tracer; +	delete component; +} + +void SluiftComponent::connect(const std::string& host, int port) { +	disconnectedError = boost::optional<ComponentError>(); +	component->connect(host, port); +} + +void SluiftComponent::setTraceEnabled(bool b) { +	if (b && !tracer) { +		tracer = new ComponentXMLTracer(component); +	} +	else if (!b && tracer) { +		delete tracer; +		tracer = NULL; +	} +} + +void SluiftComponent::waitConnected(int timeout) { +	Watchdog watchdog(timeout, networkFactories->getTimerFactory()); +	while (!watchdog.getTimedOut() && !disconnectedError && !component->isAvailable()) { +		eventLoop->runUntilEvents(); +	} +	if (watchdog.getTimedOut()) { +		component->disconnect(); +		throw Lua::Exception("Timeout while connecting"); +	} +	if (disconnectedError) { +		throw Lua::Exception(getErrorString(*disconnectedError)); +	} +} + +bool SluiftComponent::isConnected() const { +	return component->isAvailable(); +} + +void SluiftComponent::disconnect() { +	component->disconnect(); +	while (component->isAvailable()) { +		eventLoop->runUntilEvents(); +	} +} + +void SluiftComponent::setSoftwareVersion(const std::string& name, const std::string& version, const std::string& os) { +	component->setSoftwareVersion(name, version); +} + +boost::optional<SluiftComponent::Event> SluiftComponent::getNextEvent( +		int timeout, boost::function<bool (const Event&)> condition) { +	Watchdog watchdog(timeout, networkFactories->getTimerFactory()); +	size_t currentIndex = 0; +	while (true) { +		// Look for pending events in the queue +		while (currentIndex < pendingEvents.size()) { +			Event event = pendingEvents[currentIndex]; +			if (!condition || condition(event)) { +				pendingEvents.erase( +						pendingEvents.begin()  +						+ boost::numeric_cast<int>(currentIndex)); +				return event; +			} +			++currentIndex; +		} + +		// Wait for new events +		while (!watchdog.getTimedOut() && currentIndex >= pendingEvents.size() && component->isAvailable()) { +			eventLoop->runUntilEvents(); +		} + +		// Finish if we're disconnected or timed out +		if (watchdog.getTimedOut() || !component->isAvailable()) { +			return boost::optional<Event>(); +		} +	} +} + +void SluiftComponent::handleIncomingMessage(boost::shared_ptr<Message> stanza) { +	pendingEvents.push_back(Event(stanza)); +} + +void SluiftComponent::handleIncomingPresence(boost::shared_ptr<Presence> stanza) { +	pendingEvents.push_back(Event(stanza)); +} + +void SluiftComponent::handleRequestResponse(boost::shared_ptr<Payload> response, boost::shared_ptr<ErrorPayload> error) { +	requestResponse = response; +	requestError = error; +	requestResponseReceived = true; +} + +void SluiftComponent::handleError(const boost::optional<ComponentError>& error) { +	disconnectedError = error; +} + +Sluift::Response SluiftComponent::doSendRequest(boost::shared_ptr<Request> request, int timeout) { +	requestResponse.reset(); +	requestError.reset(); +	requestResponseReceived = false; +	request->send(); + +	Watchdog watchdog(timeout, networkFactories->getTimerFactory()); +	while (!watchdog.getTimedOut() && !requestResponseReceived) { +		eventLoop->runUntilEvents(); +	} +	return Sluift::Response(requestResponse, watchdog.getTimedOut() ?  +			boost::make_shared<ErrorPayload>(ErrorPayload::RemoteServerTimeout) : requestError); +} diff --git a/Sluift/SluiftComponent.h b/Sluift/SluiftComponent.h new file mode 100644 index 0000000..3d5792b --- /dev/null +++ b/Sluift/SluiftComponent.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License. + * See the COPYING file for more information. + */ + +#pragma once + +#include <deque> +#include <boost/optional.hpp> +#include <boost/bind.hpp> +#include <boost/function.hpp> + +#include <Swiften/Client/ClientOptions.h> +#include <Swiften/Elements/IQ.h> +#include <Swiften/Elements/Message.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Queries/GenericRequest.h> +#include <Swiften/Roster/XMPPRosterItem.h> +#include <Swiften/Component/ComponentError.h> +#include <Swiften/Network/NetworkFactories.h> +#include <Swiften/Component/Component.h> +#include <Swiften/EventLoop/SimpleEventLoop.h> +#include <Sluift/Watchdog.h> +#include <Sluift/Response.h> + +namespace Swift { +	struct SluiftGlobals; +	class ComponentXMLTracer; +	class Component; +	class Stanza; +	class Payload; +	class ErrorPayload; +	class JID; + +	class SluiftComponent { +		public: +			struct Event { +				enum Type { +					MessageType, +					PresenceType +				}; + +				Event(boost::shared_ptr<Message> stanza) : type(MessageType), stanza(stanza) {} +				Event(boost::shared_ptr<Presence> stanza) : type(PresenceType), stanza(stanza) {} + +				Type type; + +				// Message & Presence +				boost::shared_ptr<Stanza> stanza; +			}; + +			SluiftComponent( +					const JID& jid,  +					const std::string& password,  +					NetworkFactories* networkFactories,  +					SimpleEventLoop* eventLoop); +			~SluiftComponent(); + +			Component* getComponent() { +				return component; +			} + +			void connect(const std::string& host, int port); +			void waitConnected(int timeout); +			bool isConnected() const; +			void setTraceEnabled(bool b); + +			template<typename REQUEST_TYPE> +			Sluift::Response sendRequest(REQUEST_TYPE request, int timeout) { +				boost::signals::scoped_connection c = request->onResponse.connect( +						boost::bind(&SluiftComponent::handleRequestResponse, this, _1, _2)); +				return doSendRequest(request, timeout); +			} + +			template<typename REQUEST_TYPE> +			Sluift::Response sendVoidRequest(REQUEST_TYPE request, int timeout) { +				boost::signals::scoped_connection c = request->onResponse.connect( +						boost::bind(&SluiftComponent::handleRequestResponse, this, boost::shared_ptr<Payload>(), _1)); +				return doSendRequest(request, timeout); +			} + +			void disconnect(); +			void setSoftwareVersion(const std::string& name, const std::string& version, const std::string& os); +			boost::optional<SluiftComponent::Event> getNextEvent(int timeout,  +					boost::function<bool (const Event&)> condition = 0); + +		private: +			Sluift::Response doSendRequest(boost::shared_ptr<Request> request, int timeout); + +			void handleIncomingMessage(boost::shared_ptr<Message> stanza); +			void handleIncomingPresence(boost::shared_ptr<Presence> stanza); +			void handleRequestResponse(boost::shared_ptr<Payload> response, boost::shared_ptr<ErrorPayload> error); +			void handleError(const boost::optional<ComponentError>& error); +		 +		private: +			NetworkFactories* networkFactories; +			SimpleEventLoop* eventLoop; +			Component* component; +			ComponentXMLTracer* tracer; +			bool rosterReceived; +			std::deque<Event> pendingEvents; +			boost::optional<ComponentError> disconnectedError; +			bool requestResponseReceived; +			boost::shared_ptr<Payload> requestResponse; +			boost::shared_ptr<ErrorPayload> requestError; +	}; +} diff --git a/Sluift/component.cpp b/Sluift/component.cpp new file mode 100644 index 0000000..0c400b3 --- /dev/null +++ b/Sluift/component.cpp @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License. + * See the COPYING file for more information. + */ + +#include <boost/lambda/lambda.hpp> +#include <boost/lambda/bind.hpp> +#include <boost/assign/list_of.hpp> +#include <iostream> + +#include <Sluift/SluiftComponent.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/SoftwareVersion.h> +#include <Swiften/Elements/Message.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/Elements/RawXMLPayload.h> +#include <Swiften/Elements/RosterItemPayload.h> +#include <Swiften/Elements/RosterPayload.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Elements/MAMQuery.h> +#include <Swiften/Queries/GenericRequest.h> +#include <Swiften/Presence/PresenceSender.h> +#include <Swiften/Roster/XMPPRoster.h> +#include <Swiften/Roster/SetRosterRequest.h> +#include <Swiften/Presence/SubscriptionManager.h> +#include <Swiften/Roster/XMPPRosterItem.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/Queries/Requests/GetSoftwareVersionRequest.h> +#include <Sluift/Lua/FunctionRegistration.h> +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/IDGenerator.h> +#include <Sluift/Lua/Check.h> +#include <Sluift/Lua/Value.h> +#include <Sluift/Lua/Exception.h> +#include <Sluift/Lua/LuaUtils.h> +#include <Sluift/globals.h> +#include <Sluift/ElementConvertors/StanzaConvertor.h> +#include <Sluift/ElementConvertors/IQConvertor.h> +#include <Sluift/ElementConvertors/PresenceConvertor.h> +#include <Sluift/ElementConvertors/MessageConvertor.h> + +using namespace Swift; +namespace lambda = boost::lambda; + +static inline SluiftComponent* getComponent(lua_State* L) { +	return *Lua::checkUserData<SluiftComponent>(L, 1); +} + +static inline int getGlobalTimeout(lua_State* L) { +	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.moduleLibIndex); +	lua_getfield(L, -1, "timeout"); +	int result = boost::numeric_cast<int>(lua_tointeger(L, -1)); +	lua_pop(L, 2); +	return result; +} + +static void addPayloadsToTable(lua_State* L, const std::vector<boost::shared_ptr<Payload> >& payloads) { +	if (!payloads.empty()) { +		lua_createtable(L, boost::numeric_cast<int>(payloads.size()), 0); +		for (size_t i = 0; i < payloads.size(); ++i) { +			Sluift::globals.elementConvertor.convertToLua(L, payloads[i]); +			lua_rawseti(L, -2, boost::numeric_cast<int>(i+1)); +		} +		Lua::registerGetByTypeIndex(L, -1); +		lua_setfield(L, -2, "payloads"); +	} +} + +static boost::shared_ptr<Payload> getPayload(lua_State* L, int index) { +	if (lua_type(L, index) == LUA_TTABLE) { +		return boost::dynamic_pointer_cast<Payload>(Sluift::globals.elementConvertor.convertFromLua(L, index)); +	} +	else if (lua_type(L, index) == LUA_TSTRING) { +		return boost::make_shared<RawXMLPayload>(Lua::checkString(L, index)); +	} +	else { +		return boost::shared_ptr<Payload>(); +	} +} + +static std::vector< boost::shared_ptr<Payload> > getPayloadsFromTable(lua_State* L, int index) { +	index = Lua::absoluteOffset(L, index); +	std::vector< boost::shared_ptr<Payload> > result; +	lua_getfield(L, index, "payloads"); +	if (lua_istable(L, -1)) { +		for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { +			boost::shared_ptr<Payload> payload = getPayload(L, -1); +			if (payload) { +				result.push_back(payload); +			} +		} +	} +	lua_pop(L, 1); +	return result; +} + +SLUIFT_LUA_FUNCTION(Component, async_connect) { +	SluiftComponent* component = getComponent(L); + +	std::string host; +	int port = 0; +	if (lua_istable(L, 2)) { +		if (boost::optional<std::string> hostString = Lua::getStringField(L, 2, "host")) { +			host = *hostString; +		} +		if (boost::optional<int> portInt = Lua::getIntField(L, 2, "port")) { +			port = *portInt; +		} +	} +	component->connect(host, port); +	return 0; +} + +SLUIFT_LUA_FUNCTION_WITH_HELP( +		Component, set_trace_enabled, +		"Enable/disable tracing of the data sent/received.\n\n.", +		"self\n" +		"enable  a boolean specifying whether to enable/disable tracing", +		"" +) { +	getComponent(L)->setTraceEnabled(lua_toboolean(L, 1)); +	return 0; +} + +SLUIFT_LUA_FUNCTION_WITH_HELP( +		Component, wait_connected, +		"Block until the component is connected.\n\nThis is useful after an `async_connect`.", +		"self", +		"" +) { +	getComponent(L)->waitConnected(getGlobalTimeout(L)); +	return 0; +} + +SLUIFT_LUA_FUNCTION_WITH_HELP( +		Component, is_connected, +		"Checks whether this component is still connected.\n\nReturns a boolean.", +		"self\n", +		"" +) { +	lua_pushboolean(L, getComponent(L)->isConnected()); +	return 1; +} + +SLUIFT_LUA_FUNCTION_WITH_HELP( +		Component, disconnect, +		"Disconnect from the server", +		"self\n", +		"" +) { +	Sluift::globals.eventLoop.runOnce(); +	getComponent(L)->disconnect(); +	return 0; +} + +SLUIFT_LUA_FUNCTION_WITH_HELP( +		Component, set_version, + +		"Sets the published version of this component.", + +		"self", + +		"name  the name of the component software\n" +		"version  the version identifier of this component\n" +		"os  the OS this component is running on\n" +) { +	Sluift::globals.eventLoop.runOnce(); +	SluiftComponent* component = getComponent(L); +	if (boost::shared_ptr<SoftwareVersion> version = boost::dynamic_pointer_cast<SoftwareVersion>(Sluift::globals.elementConvertor.convertFromLuaUntyped(L, 2, "software_version"))) { +		component->setSoftwareVersion(version->getName(), version->getVersion(), version->getOS()); +	} +	return 0; +} + +SLUIFT_LUA_FUNCTION_WITH_HELP( +		Component, send_message, +		"Send a message.", +		"self\n" +		"to  the JID to send the message to\n" +		"body  the body of the message. Can alternatively be specified using the `body` option\n", + +		"to  the JID to send the message to\n" +		"body  the body of the message\n" +		"subject  the subject of the MUC room to set\n" +		"type  the type of message to send (`normal`, `chat`, `error`, `groupchat`, `headline`)\n" +		"payloads  payloads to add to the message\n" +) { +	Sluift::globals.eventLoop.runOnce(); +	JID to; +	boost::optional<std::string> from; +	boost::optional<std::string> body; +	boost::optional<std::string> subject; +	std::vector<boost::shared_ptr<Payload> > payloads; +	int index = 2; +	Message::Type type = Message::Chat; +	if (lua_isstring(L, index)) { +		to = std::string(lua_tostring(L, index)); +		++index; +		if (lua_isstring(L, index)) { +			body = lua_tostring(L, index); +			++index; +		} +	} +	if (lua_istable(L, index)) { +		if (boost::optional<std::string> value = Lua::getStringField(L, index, "to")) { +			to = *value; +		} + +		if (boost::optional<std::string> value = Lua::getStringField(L, index, "from")) { +			from = value; +		} + +		if (boost::optional<std::string> value = Lua::getStringField(L, index, "body")) { +			body = value; +		} + +		if (boost::optional<std::string> value = Lua::getStringField(L, index, "type")) { +			type = MessageConvertor::convertMessageTypeFromString(*value); +		} + +		if (boost::optional<std::string> value = Lua::getStringField(L, index, "subject")) { +			subject = value; +		} + +		payloads = getPayloadsFromTable(L, index); +	} + +	if (!to.isValid()) { +		throw Lua::Exception("Missing 'to'"); +	} +	if ((!body || body->empty()) && !subject && payloads.empty()) { +		throw Lua::Exception("Missing any of 'body', 'subject' or 'payloads'"); +	} +	Message::ref message = boost::make_shared<Message>(); +	message->setTo(to); +	if (from && !from->empty()) { +		message->setFrom(*from); +	} +	if (body && !body->empty()) { +		message->setBody(*body); +	} +	if (subject) { +		message->setSubject(*subject); +	} +	message->addPayloads(payloads.begin(), payloads.end()); +	message->setType(type); +	getComponent(L)->getComponent()->sendMessage(message); +	return 0; +} + +SLUIFT_LUA_FUNCTION_WITH_HELP( +		Component, send_presence, +		"Send presence.", + +		"self\n" +		"body  the text of the presence. Can alternatively be specified using the `status` option\n", + +		"to  the JID to send the message to\n" +		"from  the JID to send the message from\n" +		"status  the text of the presence\n" +		"priority  the priority of the presence\n" +		"type  the type of message to send (`available`, `error`, `probe`, `subscribe`, `subscribed`, `unavailable`, `unsubscribe`, `unsubscribed`)\n" +		"payloads  payloads to add to the presence\n" +) { +	Sluift::globals.eventLoop.runOnce(); +	boost::shared_ptr<Presence> presence = boost::make_shared<Presence>(); +	 +	int index = 2; +	if (lua_isstring(L, index)) { +		presence->setStatus(lua_tostring(L, index)); +		++index; +	} +	if (lua_istable(L, index)) { +		if (boost::optional<std::string> value = Lua::getStringField(L, index, "to")) { +			presence->setTo(*value); +		} +		if (boost::optional<std::string> value = Lua::getStringField(L, index, "from")) { +			presence->setFrom(*value); +		} +		if (boost::optional<std::string> value = Lua::getStringField(L, index, "status")) { +			presence->setStatus(*value); +		} +		if (boost::optional<int> value = Lua::getIntField(L, index, "priority")) { +			presence->setPriority(*value); +		} +		if (boost::optional<std::string> value = Lua::getStringField(L, index, "type")) { +			presence->setType(PresenceConvertor::convertPresenceTypeFromString(*value)); +		} +		std::vector< boost::shared_ptr<Payload> > payloads = getPayloadsFromTable(L, index); +		presence->addPayloads(payloads.begin(), payloads.end()); +	} + +	getComponent(L)->getComponent()->sendPresence(presence); +	lua_pushvalue(L, 1); +	return 0; +} + +static int sendQuery(lua_State* L, IQ::Type type) { +	SluiftComponent* component = getComponent(L); + +	JID to; +	if (boost::optional<std::string> toString = Lua::getStringField(L, 2, "to")) { +		to = JID(*toString); +	} + +	JID from; +	if (boost::optional<std::string> fromString = Lua::getStringField(L, 2, "from")) { +		from = JID(*fromString); +	} + +	int timeout = getGlobalTimeout(L); +	if (boost::optional<int> timeoutInt = Lua::getIntField(L, 2, "timeout")) { +		timeout = *timeoutInt; +	} + +	boost::shared_ptr<Payload> payload; +	lua_getfield(L, 2, "query"); +	payload = getPayload(L, -1); +	lua_pop(L, 1); + +	return component->sendRequest( +		boost::make_shared< GenericRequest<Payload> >(type, from, to, payload, component->getComponent()->getIQRouter()), timeout).convertToLuaResult(L); +} + +#define DISPATCH_PUBSUB_PAYLOAD(payloadType, container, response) \ +	else if (boost::shared_ptr<payloadType> p = boost::dynamic_pointer_cast<payloadType>(payload)) { \ +		return component->sendPubSubRequest(type, to, p, timeout).convertToLuaResult(L); \ +	} + +SLUIFT_LUA_FUNCTION(Component, get) { +	return sendQuery(L, IQ::Get); +} + +SLUIFT_LUA_FUNCTION(Component, set) { +	return sendQuery(L, IQ::Set); +} + +SLUIFT_LUA_FUNCTION_WITH_HELP( +		Component, send, +		"Sends a raw string", + +		"self\n" +		"data  the string to send\n", + +		"" +) { +	Sluift::globals.eventLoop.runOnce(); + +	getComponent(L)->getComponent()->sendData(std::string(Lua::checkString(L, 2))); +	lua_pushvalue(L, 1); +	return 0; +} + +static void pushEvent(lua_State* L, const SluiftComponent::Event& event) { +	switch (event.type) { +		case SluiftComponent::Event::MessageType: { +			Message::ref message = boost::dynamic_pointer_cast<Message>(event.stanza); +			Lua::Table result = boost::assign::map_list_of +				("type", boost::make_shared<Lua::Value>(std::string("message"))) +				("from", boost::make_shared<Lua::Value>(message->getFrom().toString())) +				("to", boost::make_shared<Lua::Value>(message->getTo().toString())) +				("body", boost::make_shared<Lua::Value>(message->getBody())) +				("message_type", boost::make_shared<Lua::Value>(MessageConvertor::convertMessageTypeToString(message->getType()))); +			Lua::pushValue(L, result); +			addPayloadsToTable(L, message->getPayloads()); +			Lua::registerTableToString(L, -1); +			break; +		} +		case SluiftComponent::Event::PresenceType: { +			Presence::ref presence = boost::dynamic_pointer_cast<Presence>(event.stanza); +			Lua::Table result = boost::assign::map_list_of +				("type", boost::make_shared<Lua::Value>(std::string("presence"))) +				("from", boost::make_shared<Lua::Value>(presence->getFrom().toString())) +				("to", boost::make_shared<Lua::Value>(presence->getTo().toString())) +				("status", boost::make_shared<Lua::Value>(presence->getStatus())) +				("presence_type", boost::make_shared<Lua::Value>(PresenceConvertor::convertPresenceTypeToString(presence->getType()))); +			Lua::pushValue(L, result); +			addPayloadsToTable(L, presence->getPayloads()); +			Lua::registerTableToString(L, -1); +			break; +		} +	} +} + +struct CallUnaryLuaPredicateOnEvent { +	CallUnaryLuaPredicateOnEvent(lua_State* L, int index) : L(L), index(index) { +	} + +	bool operator()(const SluiftComponent::Event& event) { +		lua_pushvalue(L, index); +		pushEvent(L, event); +		if (lua_pcall(L, 1, 1, 0) != 0) { +			throw Lua::Exception(lua_tostring(L, -1)); +		} +		bool result = lua_toboolean(L, -1); +		lua_pop(L, 1); +		return result; +	} + +	lua_State* L; +	int index; +}; + + +SLUIFT_LUA_FUNCTION(Component, get_next_event) { +	Sluift::globals.eventLoop.runOnce(); +	SluiftComponent* component = getComponent(L); + +	int timeout = getGlobalTimeout(L); +	boost::optional<SluiftComponent::Event::Type> type; +	int condition = 0; +	if (lua_istable(L, 2)) { +		if (boost::optional<std::string> typeString = Lua::getStringField(L, 2, "type")) { +			if (*typeString == "message") { +				type = SluiftComponent::Event::MessageType; +			} +			else if (*typeString == "presence") { +				type = SluiftComponent::Event::PresenceType; +			} +		} +		if (boost::optional<int> timeoutInt = Lua::getIntField(L, 2, "timeout")) { +			timeout = *timeoutInt; +		} +		lua_getfield(L, 2, "if"); +		if (lua_isfunction(L, -1)) { +			condition = Lua::absoluteOffset(L, -1); +		} +	} + +	boost::optional<SluiftComponent::Event> event; +	if (condition) { +		event = component->getNextEvent(timeout, CallUnaryLuaPredicateOnEvent(L, condition)); +	} +	else if (type) { +		event = component->getNextEvent( +				timeout, lambda::bind(&SluiftComponent::Event::type, lambda::_1) == *type); +	} +	else { +		event = component->getNextEvent(timeout); +	} + +	if (event) { +		pushEvent(L, *event); +	} +	else { +		lua_pushnil(L); +	} +	return 1; +} + +SLUIFT_LUA_FUNCTION_WITH_HELP( +		Component, jid, +		"Returns the JID of this component", +		"self\n", +		"" +) { +	SluiftComponent* component = getComponent(L); +	lua_pushstring(L, component->getComponent()->getJID().toString().c_str()); +	return 1; +} + +SLUIFT_LUA_FUNCTION(Component, __gc) { +	SluiftComponent* component = getComponent(L); +	delete component; +	return 0; +} diff --git a/Sluift/core.lua b/Sluift/core.lua index 7487de1..ffbb5f9 100644 --- a/Sluift/core.lua +++ b/Sluift/core.lua @@ -145,4 +145,5 @@ end  -- Contains help for native methods that we want access to from here  local extra_help = {} +local component_extra_help = {}  local help_data = {}  local help_classes = {} @@ -482,4 +483,13 @@ Client.__index = Client  register_class_table_help(Client, "Client") +_H = { +	[[ Component interface ]] +} +local Component = { +	_with_prompt = function(component) return component:jid() end +} +Component.__index = Component +register_class_table_help(Component, "Component") +  _H = { @@ -784,4 +794,194 @@ end  register_help(Client.pubsub) + +-------------------------------------------------------------------------------- +-- Component +-------------------------------------------------------------------------------- + +component_extra_help = { +	["Component.get_next_event"] = { +		[[ Returns the next event. ]], +		parameters = { "self" }, +		options = { +			type = "The type of event to return (`message`, `presence`). When omitted, all event types are returned.", +			timeout = "The amount of time to wait for events.", +			["if"] = "A function to filter events. When this function, called with the event as a parameter, returns true, the event will be returned" +		} +	}, +	["Component.get"] = { +		[[ Sends a `get` query. ]], +		parameters = { "self" }, +		options = { +			to = "The JID of the target to send the query to", +			query = "The query to send", +			timeout = "The amount of time to wait for the query to finish", +		} +	}, +	["Component.set"] = { +		[[ Sends a `set` query. ]], +		parameters = { "self" }, +		options = { +			to = "The JID of the target to send the query to", +			query = "The query to send.", +			timeout = "The amount of time to wait for the query to finish.", +		} +	}, +	["Component.async_connect"] = { +		[[  +			Connect to the server asynchronously. +			 +			This method immediately returns. +		]], +		parameters = { "self" }, +		options = { +			host = "The host to connect to.", +			port = "The port to connect to." +		} +	} +} + +_H = { +	[[ +		Connect to the server. + +		This method blocks until the connection has been established. +	]], +	parameters = { "self" }, +	options = component_extra_help["Component.async_connect"].options +} +function Component:connect (...) +	local options = parse_options({}, ...) +	local f = options.f +	self:async_connect(options) +	self:wait_connected() +	if f then +		return call {function() return f(self) end, finally = function() self:disconnect() end} +	end +	return true +end +register_help(Component.connect) + + +_H = { +	[[ +		Returns an iterator over all events. + +		This function blocks until `timeout` is reached (or blocks forever if it is omitted). +	]], +	parameters = { "self" }, +	options = component_extra_help["Component.get_next_event"].options +} +function Component:events (options) +	local function component_events_iterator(s) +		return s['component']:get_next_event(s['options']) +	end +	return component_events_iterator, {component = self, options = options} +end +register_help(Component.events) + + +_H = { +	[[ +		Calls `f` for each event. +	]], +	parameters = { "self" }, +	options = merge_tables(get_help(Component.events).options, { +		f = "The functor to call with each event. Required." +	}) +} +function Component:for_each_event (...) +	local options = parse_options({}, ...) +	if not type(options.f) == 'function' then error('Expected function') end +	for event in self:events(options) do +		local result = options.f(event) +		if result then +			return result +		end +	end +end +register_help(Component.for_each_event) + +for method, event_type in pairs({message = 'message', presence = 'presence'}) do +	_H = { +		"Call `f` for all events of type `" .. event_type .. "`.", +		parameters = { "self" }, +		options = remove_help_parameters("type", get_help(Component.for_each_event).options) +	} +	Component['for_each_' .. method] = function (component, ...) +		local options = parse_options({}, ...) +		options['type'] = event_type +		return component:for_each_event (options) +	end +	register_help(Component['for_each_' .. method]) + +	_H = { +		"Get the next event of type `" .. event_type .. "`.", +		parameters = { "self" }, +		options = remove_help_parameters("type", component_extra_help["Component.get_next_event"].options) +	} +	Component['get_next_' .. method] = function (component, ...) +		local options = parse_options({}, ...) +		options['type'] = event_type +		return component:get_next_event(options) +	end +	register_help(Component['get_next_' .. method]) +end + +for method, event_type in pairs({messages = 'message'}) do +	_H = { +		"Returns an iterator over all events of type `" .. event_type .. "`.", +		parameters = { "self" }, +		options = remove_help_parameters("type", get_help(Component.for_each_event).options) +	} +	Component[method] = function (component, ...) +		local options = parse_options({}, ...) +		options['type'] = event_type +		return component:events (options) +	end +	register_help(Component[method]) +end + +_H = { +	[[  +		Process all pending events +	]], +	parameters = { "self" } +} +function Component:process_events () +	for event in self:events{timeout=0} do end +end +register_help(Component.process_events) + + +-- +-- Register get_* and set_* convenience methods for some type of queries +-- +-- Example usages: +--	component:get_software_version{to = 'alice@wonderland.lit'} +--	component:set_command{to = 'alice@wonderland.lit', command = { type = 'execute', node = 'uptime' }} +-- +local get_set_shortcuts = { +	get = {'software_version', 'disco_items', 'xml', 'dom', 'vcard'}, +	set = {'command'} +} +for query_action, query_types in pairs(get_set_shortcuts) do +	for _, query_type in ipairs(query_types) do +		_H = { +			"Sends a `" .. query_action .. "` query of type `" .. query_type .. "`.\n" .. +			"Apart from the options below, all top level elements of `" .. query_type .. "` can be passed.", +			parameters = { "self" }, +			options = remove_help_parameters({"query", "type"}, component_extra_help["Component.get"].options), +		} +		local method = query_action .. '_' .. query_type +		Component[method] = function (component, options) +			options = options or {} +			if type(options) ~= 'table' then error('Invalid options: ' .. options) end  +			options['query'] = merge_tables({_type = query_type}, options[query_type] or {}) +			return component[query_action](component, options) +		end +		register_help(Component[method]) +	end +end +  --------------------------------------------------------------------------------  -- PubSub @@ -1024,4 +1224,5 @@ extra_help['sluift'] = {  return {  	Client = Client, +	Component = Component,  	register_help = register_help,  	register_class_help = register_class_help, @@ -1036,4 +1237,5 @@ return {  	help = help,  	extra_help = extra_help, +	component_extra_help = component_extra_help,  	copy = copy,  	with = with, diff --git a/Sluift/sluift.cpp b/Sluift/sluift.cpp index b55649b..2fd1e50 100644 --- a/Sluift/sluift.cpp +++ b/Sluift/sluift.cpp @@ -17,4 +17,5 @@  #include <Sluift/Lua/Check.h>  #include <Sluift/SluiftClient.h> +#include <Sluift/SluiftComponent.h>  #include <Sluift/globals.h>  #include <Sluift/Lua/Exception.h> @@ -89,4 +90,30 @@ SLUIFT_LUA_FUNCTION_WITH_HELP(  SLUIFT_LUA_FUNCTION_WITH_HELP( +		Sluift, new_component, + +		"Creates a new component.\n\nReturns a @{Component} object.\n", + +		"jid  The JID to connect as\n" +		"passphrase  The passphrase to use\n", + +		"" +) { +	Lua::checkString(L, 1); +	JID jid(std::string(Lua::checkString(L, 1))); +	std::string password(Lua::checkString(L, 2)); + +	SluiftComponent** component = reinterpret_cast<SluiftComponent**>(lua_newuserdata(L, sizeof(SluiftComponent*))); + +	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.coreLibIndex); +	lua_getfield(L, -1, "Component"); +	lua_setmetatable(L, -3); +	lua_pop(L, 1); + +	*component = new SluiftComponent(jid, password, &Sluift::globals.networkFactories, &Sluift::globals.eventLoop); +	(*component)->setTraceEnabled(getGlobalDebug(L)); +	return 1; +} + +SLUIFT_LUA_FUNCTION_WITH_HELP(  		Sluift, sha1,  		"Compute the SHA-1 hash of given data", @@ -409,4 +436,14 @@ SLUIFT_API int luaopen_sluift(lua_State* L) {  	lua_pop(L, 1); +	// Load component metatable +	lua_rawgeti(L, LUA_REGISTRYINDEX, Sluift::globals.coreLibIndex); +	std::vector<std::string> comp_tables = boost::assign::list_of("Component"); +	foreach(const std::string& table, comp_tables) { +		lua_getfield(L, -1, table.c_str()); +		Lua::FunctionRegistry::getInstance().addFunctionsToTable(L, table); +		lua_pop(L, 1); +	} +	lua_pop(L, 1); +  	// Register documentation for all elements  	foreach (boost::shared_ptr<LuaElementConvertor> convertor, Sluift::globals.elementConvertor.getConvertors()) { | 
 Swift
 Swift