From 4f1274bcdd2af1a38b7de0b3ef4c18d6cd83c4a3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be>
Date: Sun, 1 Sep 2013 08:12:18 +0200
Subject: Sluift: More PubSub convenience methods & use cases.

- Convenience iterators to PubSub and PubSubNode.
- Retrieving X most recent items
- Retrieving a single item
- Fixed GeoLocation serializer

Change-Id: Ib4ecde225fb274b21163fcc9b52e19b0d3431860

diff --git a/.gitignore b/.gitignore
index c89f9bf..b67b8e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,6 +25,7 @@
 *.moc
 *.patch
 *.dSYM
+*.ld_*
 .gdb_history
 .clang_complete
 *~
diff --git a/Sluift/ElementConvertors/BodyConvertor.cpp b/Sluift/ElementConvertors/BodyConvertor.cpp
new file mode 100644
index 0000000..a7abc1e
--- /dev/null
+++ b/Sluift/ElementConvertors/BodyConvertor.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2013 Remko Tronçon
+ * Licensed under the GNU General Public License.
+ * See the COPYING file for more information.
+ */
+
+#include <Sluift/ElementConvertors/BodyConvertor.h>
+
+#include <lua.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+#include <Sluift/Lua/LuaUtils.h>
+
+using namespace Swift;
+
+BodyConvertor::BodyConvertor() : GenericLuaElementConvertor<Body>("body") {
+}
+
+BodyConvertor::~BodyConvertor() {
+}
+
+boost::shared_ptr<Body> BodyConvertor::doConvertFromLua(lua_State* L) {
+	boost::shared_ptr<Body> result = boost::make_shared<Body>();
+	if (boost::optional<std::string> value = Lua::getStringField(L, -1, "text")) {
+		result->setText(*value);
+	}
+	return result;
+}
+
+void BodyConvertor::doConvertToLua(lua_State* L, boost::shared_ptr<Body> payload) {
+	lua_createtable(L, 0, 0);
+	if (!payload->getText().empty()) {
+		lua_pushstring(L, payload->getText().c_str());
+		lua_setfield(L, -2, "text");
+	}
+}
diff --git a/Sluift/ElementConvertors/BodyConvertor.h b/Sluift/ElementConvertors/BodyConvertor.h
new file mode 100644
index 0000000..b1cd494
--- /dev/null
+++ b/Sluift/ElementConvertors/BodyConvertor.h
@@ -0,0 +1,25 @@
+/*
+ * 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 <Sluift/GenericLuaElementConvertor.h>
+#include <Swiften/Elements/Body.h>
+
+namespace Swift {
+	class LuaElementConvertors;
+
+	class BodyConvertor : public GenericLuaElementConvertor<Body> {
+		public:
+			BodyConvertor();
+			virtual ~BodyConvertor();
+
+			virtual boost::shared_ptr<Body> doConvertFromLua(lua_State*) SWIFTEN_OVERRIDE;
+			virtual void doConvertToLua(lua_State*, boost::shared_ptr<Body>) SWIFTEN_OVERRIDE;
+	};
+}
diff --git a/Sluift/ElementConvertors/DiscoItemsConvertor.cpp b/Sluift/ElementConvertors/DiscoItemsConvertor.cpp
index 6c39aea..a38e766 100644
--- a/Sluift/ElementConvertors/DiscoItemsConvertor.cpp
+++ b/Sluift/ElementConvertors/DiscoItemsConvertor.cpp
@@ -49,12 +49,18 @@ void DiscoItemsConvertor::doConvertToLua(lua_State* L, boost::shared_ptr<DiscoIt
 		lua_createtable(L, boost::numeric_cast<int>(items.size()), 0);
 		for (size_t i = 0; i < items.size(); ++i) {
 			lua_createtable(L, 0, 0);
-			lua_pushstring(L, items[i].getName().c_str());
-			lua_setfield(L, -2, "name");
-			lua_pushstring(L, items[i].getNode().c_str());
-			lua_setfield(L, -2, "node");
-			lua_pushstring(L, items[i].getJID().toString().c_str());
-			lua_setfield(L, -2, "jid");
+			if (!items[i].getName().empty()) {
+				lua_pushstring(L, items[i].getName().c_str());
+				lua_setfield(L, -2, "name");
+			}
+			if (!items[i].getNode().empty()) {
+				lua_pushstring(L, items[i].getNode().c_str());
+				lua_setfield(L, -2, "node");
+			}
+			if (!items[i].getJID().isValid()) {
+				lua_pushstring(L, items[i].getJID().toString().c_str());
+				lua_setfield(L, -2, "jid");
+			}
 			lua_rawseti(L, -2, boost::numeric_cast<int>(i+1));
 		}
 		lua_setfield(L, -2, "items");
diff --git a/Sluift/Examples/Wonderland.lua b/Sluift/Examples/Wonderland.lua
index 235d8d0..426ec07 100755
--- a/Sluift/Examples/Wonderland.lua
+++ b/Sluift/Examples/Wonderland.lua
@@ -8,45 +8,97 @@
 --
 
 require "sluift"
---sluift.debug = true
+sluift.debug = true
 
 characters = {
-	{jid = "alice@wonderland.lit", name = "Alice", groups = {}, presence = "<presence/>"},
-	{jid = "hatter@wonderland.lit", name = "Mad Hatter", groups = {}, presence = "<presence><show>away</show><status>At the Tea Party</status></presence>"},
-	{jid ="queen@wonderland.lit", name = "Queen of Hearts", groups = {}, presence = "<presence><show>dnd</show><status>Executing</status></presence>"},
-	{jid = "rabbit@wonderland.lit", name = "White Rabbit", groups = {"Animals"}, presence = "<presence><status>Oh dear!</status></presence>"},
-	{jid = "turtle@wonderland.lit", name = "Mock Turtle", groups = {"Animals"}, presence = "<presence/>"},
+	["Alice"] = {
+		jid = "alice@wonderland.lit", groups = {}, presence = "<presence/>"
+	},
+	["Mad Hatter"] = {
+		jid = "hatter@wonderland.lit", groups = {}, 
+		presence = "<presence><show>away</show><status>At the Tea Party</status></presence>"
+	},
+	["Queen of Hearts"] = {
+		jid ="queen@wonderland.lit", groups = {}, 
+		presence = "<presence><show>dnd</show><status>Executing</status></presence>"
+	},
+	["White Rabbit"] = {
+		jid = "rabbit@wonderland.lit", groups = {"Animals"}, 
+		presence = "<presence><status>Oh dear!</status></presence>"},
+	["Mock Turtle"] = {
+		jid = "turtle@wonderland.lit", groups = {"Animals"}, 
+		presence = "<presence/>"
+	},
 }
 
-clients = {}
-for _, character in ipairs(characters) do
-	print("Connecting " .. character["name"] .. "...")
-	client = sluift.new_client(character["jid"], os.getenv("SLUIFT_PASS"))
+for name, character in pairs(characters) do
+	print("Connecting " .. name .. "...")
+	local client = sluift.new_client(character.jid, os.getenv("SLUIFT_PASS"))
 	client:set_options({compress = false, tls = false})
 	client:connect()
 	client:get_contacts()
-	client:send(character["presence"])
-	table.insert(clients, client)
-	for _, contact in ipairs(characters) do
-		if contact["jid"] ~= character["jid"] then
+	client:send(character.presence)
+	for contact_name, contact in pairs(characters) do
+		if contact.jid ~= character.jid then
 			client:add_contact(contact)
 		end
 	end
+	character.client = client
 end
 
 print("Confirming subscriptions")
-for _, client in ipairs(clients) do
-	for _, contact in ipairs(characters) do
-		client:confirm_subscription(contact["jid"])
+for _, character in pairs(characters) do
+	for _, contact in pairs(characters) do
+		character.client:confirm_subscription(contact.jid)
 	end
 end
 
+print("Setting up PubSub nodes")
+local hatters_riddles = characters["Mad Hatter"].client:pubsub("pubsub.wonderland.lit"):node("hatters_riddles")
+hatters_riddles:delete()
+assert(hatters_riddles:create())
+
+local queen_quotes = characters["Queen of Hearts"].client:pubsub("pubsub.wonderland.lit"):node("queen_quotes")
+queen_quotes:delete()
+assert(queen_quotes:create())
+queen_quotes:publish{id = 'quote1', item = {_type = 'body', text = 'Off with his head!'}}
+queen_quotes:publish{id = 'quote2', item = {_type = 'body', text = 'Off with her head!'}}
+queen_quotes:publish{id = 'quote3', item = {_type = 'body', text = 'Off with their heads!'}}
+
+characters['Mad Hatter'].client:pubsub():node('http://jabber.org/protocol/geoloc'):publish{
+	item = {_type = 'user_location', latitude = 50.376739, longitude = -4.200709}}
+characters['Queen of Hearts'].client:pubsub():node('http://jabber.org/protocol/geoloc'):publish{
+	item = {_type = 'user_location', latitude = 50.551123, longitude = -4.141654}}
+characters['Mock Turtle'].client:pubsub():node('http://jabber.org/protocol/geoloc'):publish{
+	item = {_type = 'user_location', latitude = 50.366630, longitude = -4.134518}}
+characters['White Rabbit'].client:pubsub():node('http://jabber.org/protocol/geoloc'):publish{
+	item = {_type = 'user_location', latitude = 50.332907, longitude = -4.759194}}
+
+
+
+print("Disconnecting alice")
+characters['Alice'].client:disconnect()
+
 print("Done. Waiting ...")
 while true do
-	for _, client in ipairs(clients) do
-		for message in client:messages {timeout = 1000} do
-			client:send_message{to = e["from"], body = "Off with their heads!"}
+	for name, character in pairs(characters) do
+		if name == 'Queen of Hearts' then
+			for message in character.client:messages{timeout = 1000} do
+				if message.body == 'publish' then
+					queen_quotes:publish{item = {_type = 'body', text = 'Off with her head!'}}
+					queen_quotes:publish{item = {_type = 'body', text = 'Off with his head!'}}
+				else
+					character.client:send_message{to = e["from"], body = "Off with their heads!"}
+				end
+			end
+		elseif name == "Mad Hatter" then
+			for message in character.client:messages{timeout = 1000} do
+				if message.body == 'publish' then
+					hatters_riddles:publish{item = {_type = 'body', text = 'Why is a raven like a writing desk?'}}
+				end
+			end
+		else
+			for message in character.client:messages{timeout = 100} do end
 		end
 	end
-	sluift.sleep(1000)
 end
diff --git a/Sluift/LuaElementConvertors.cpp b/Sluift/LuaElementConvertors.cpp
index 5913858..cadfbc4 100644
--- a/Sluift/LuaElementConvertors.cpp
+++ b/Sluift/LuaElementConvertors.cpp
@@ -20,6 +20,7 @@
 #include <Sluift/ElementConvertors/SoftwareVersionConvertor.h>
 #include <Sluift/ElementConvertors/VCardUpdateConvertor.h>
 #include <Sluift/ElementConvertors/VCardConvertor.h>
+#include <Sluift/ElementConvertors/BodyConvertor.h>
 #include <Sluift/Lua/LuaUtils.h>
 #include <Sluift/Lua/Exception.h>
 
@@ -28,6 +29,7 @@ using namespace Swift;
 LuaElementConvertors::LuaElementConvertors() {
 	registerConvertors();
 	convertors.push_back(boost::make_shared<PubSubEventConvertor>(this));
+	convertors.push_back(boost::make_shared<BodyConvertor>());
 	convertors.push_back(boost::make_shared<VCardConvertor>());
 	convertors.push_back(boost::make_shared<VCardUpdateConvertor>());
 	convertors.push_back(boost::make_shared<FormConvertor>());
diff --git a/Sluift/SConscript b/Sluift/SConscript
index fcc39fc..c4d3b28 100644
--- a/Sluift/SConscript
+++ b/Sluift/SConscript
@@ -18,6 +18,7 @@ elif env["SCONS_STAGE"] == "build" :
 		"LuaElementConvertors.cpp",
 		"LuaElementConvertor.cpp",
 		"Response.cpp",
+		"ElementConvertors/BodyConvertor.cpp",
 		"ElementConvertors/VCardUpdateConvertor.cpp",
 		"ElementConvertors/PubSubEventConvertor.cpp",
 		"ElementConvertors/RawXMLElementConvertor.cpp",
diff --git a/Sluift/SluiftClient.cpp b/Sluift/SluiftClient.cpp
index 726a683..8a8d772 100644
--- a/Sluift/SluiftClient.cpp
+++ b/Sluift/SluiftClient.cpp
@@ -87,14 +87,15 @@ void SluiftClient::setSoftwareVersion(const std::string& name, const std::string
 	client->setSoftwareVersion(name, version, os);
 }
 
-boost::optional<SluiftClient::Event> SluiftClient::getNextEvent(boost::optional<Event::Type> type, int timeout) {
+boost::optional<SluiftClient::Event> SluiftClient::getNextEvent(
+		int timeout, boost::function<bool (const Event&)> condition) {
 	Watchdog watchdog(timeout, networkFactories->getTimerFactory());
 	while (true) {
 		// Look for pending events in the queue
 		while (!pendingEvents.empty()) {
 			Event event = pendingEvents.front();
 			pendingEvents.pop_front();
-			if (!type || *type == event.type) {
+			if (!condition || condition(event)) {
 				return event;
 			}
 		}
diff --git a/Sluift/SluiftClient.h b/Sluift/SluiftClient.h
index 6515b7d..bedd6ba 100644
--- a/Sluift/SluiftClient.h
+++ b/Sluift/SluiftClient.h
@@ -9,6 +9,7 @@
 #include <deque>
 #include <boost/optional.hpp>
 #include <boost/bind.hpp>
+#include <boost/function.hpp>
 
 #include <Swiften/Client/ClientOptions.h>
 #include <Sluift/globals.h>
@@ -101,7 +102,8 @@ namespace Swift {
 
 			void disconnect();
 			void setSoftwareVersion(const std::string& name, const std::string& version, const std::string& os);
-			boost::optional<SluiftClient::Event> getNextEvent(boost::optional<Event::Type> type, int timeout);
+			boost::optional<SluiftClient::Event> getNextEvent(int timeout, 
+					boost::function<bool (const Event&)> condition = 0);
 			std::vector<XMPPRosterItem> getRoster();
 
 		private:
diff --git a/Sluift/boot.lua b/Sluift/boot.lua
index ae8cc41..e81257a 100644
--- a/Sluift/boot.lua
+++ b/Sluift/boot.lua
@@ -157,6 +157,12 @@ for method, event_type in pairs({message = 'message', presence = 'presence', pub
 		options['type'] = event_type
 		return client:for_each_event (options)
 	end
+
+	Client['get_next_' .. method] = function (client, ...)
+		local options = parse_options({}, ...)
+		options['type'] = event_type
+		return client:get_next_event(options)
+	end
 end
 
 for method, event_type in pairs({messages = 'message', pubsub_events = 'pubsub'}) do
@@ -222,6 +228,16 @@ for method, query_type in pairs(simple_pubsub_queries) do
 	end
 end
 
+for _, method in ipairs({'events', 'get_next_event', 'for_each_event'}) do
+	PubSub[method] = function (node, ...)
+		local options = parse_options({}, ...)
+		options['if'] = function (event) 
+			return event.type == 'pubsub' and event.from == node.jid and event.node == node
+		end
+		return node.client[method](node.client, options)
+ end
+end
+
 --------------------------------------------------------------------------------
 -- PubSubNode
 --------------------------------------------------------------------------------
@@ -247,7 +263,6 @@ local simple_pubsub_node_queries = {
 	get_configuration = 'pubsub_owner_configure',
 	get_subscriptions = 'pubsub_subscriptions',
 	get_affiliations = 'pubsub_affiliations',
-	get_items = 'pubsub_items',
 	get_default_subscription_options = 'pubsub_default',
 }
 for method, query_type in pairs(simple_pubsub_node_queries) do
@@ -259,6 +274,23 @@ for method, query_type in pairs(simple_pubsub_node_queries) do
 	end
 end
 
+function PubSubNode.get_items (node, ...)
+	local options = parse_options({}, ...)
+	local items = options.items or {}
+	if options.maximum_items then
+		items = merge_tables({maximum_items = options.maximum_items}, items)
+	end
+	items = merge_tables({_type = 'pubsub_items', node = node.node}, items)
+	return node.client:query_pubsub(merge_tables({ 
+		type = 'get', to = node.jid, query = items}, options))
+end
+
+function PubSubNode.get_item (node, ...)
+	local options = parse_options({}, ...)
+	if not type(options.id) == 'string' then error('Expected ID') end
+	return PubSubNode.get_items(node, {items = {{id = options.id}}})
+end
+
 function PubSubNode.create (node, options)
 	options = options or {}
 	local configure
@@ -291,11 +323,12 @@ function PubSubNode.set_configuration(node, options)
 		}, options))
 end
 
-function PubSubNode.subscribe(node, options)
-	options = options or {}
+function PubSubNode.subscribe(node, ...)
+	local options = parse_options(...)
+	local jid = options.jid or sluift.jid.to_bare(node.client:jid())
 	return node.client:query_pubsub(merge_tables(
 		{ type = 'set', to = node.jid, query = { 
-				_type = 'pubsub_subscribe', node = node.node, jid = options['jid'] }
+				_type = 'pubsub_subscribe', node = node.node, jid = jid }
 		}, options))
 end
 
@@ -332,13 +365,11 @@ function PubSubNode.publish(node, ...)
 		}, options))
 end
 
-function PubSubNode.retract(node, options)
-	options = options or {}
-	local item_ids = options['items']
-	item_ids = item_ids or { options['item'] }
-	local items = {}
-	for _, item_id in ipairs(item_ids) do
-		items[#items+1] = { id = item_id }
+function PubSubNode.retract(node, ...)
+	local options = parse_options({}, ...)
+	local items = options.items
+	if options.id then
+		items = {{id = options.id}}
 	end
 	return node.client:query_pubsub(merge_tables(
 		{ type = 'set', to = node.jid, query = { 
@@ -346,14 +377,25 @@ function PubSubNode.retract(node, options)
 		}}, options))
 end
 
-function PubSubNode.purge(node, options)
-	options = options or {}
+function PubSubNode.purge(node, ...)
+	local options = parse_options({}, ...)
 	return node.client:query_pubsub(merge_tables(
 		{ type = 'set', to = node.jid, query = { 
 				_type = 'pubsub_owner_purge', node = node.node
 		}}, options))
 end
 
+-- Iterators over events
+for _, method in ipairs({'events', 'get_next_event', 'for_each_event'}) do
+	PubSubNode[method] = function (node, ...)
+		local options = parse_options({}, ...)
+		options['if'] = function (event) 
+			return event.type == 'pubsub' and event.from == node.jid and event.node == node.node
+		end
+		return node.client[method](node.client, options)
+	end
+end
+
 --------------------------------------------------------------------------------
 -- Service discovery
 --------------------------------------------------------------------------------
diff --git a/Sluift/client.cpp b/Sluift/client.cpp
index 6d8bee8..df43075 100644
--- a/Sluift/client.cpp
+++ b/Sluift/client.cpp
@@ -4,6 +4,11 @@
  * 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/SluiftClient.h>
 #include <Swiften/JID/JID.h>
 #include <Swiften/Elements/SoftwareVersion.h>
@@ -20,7 +25,6 @@
 #include <Swiften/Roster/SetRosterRequest.h>
 #include <Swiften/Presence/SubscriptionManager.h>
 #include <Swiften/Roster/XMPPRosterItem.h>
-#include <boost/assign/list_of.hpp>
 #include <Sluift/Watchdog.h>
 #include <Swiften/Queries/Requests/GetSoftwareVersionRequest.h>
 #include <Sluift/Lua/FunctionRegistration.h>
@@ -30,9 +34,9 @@
 #include <Sluift/Lua/Exception.h>
 #include <Sluift/Lua/LuaUtils.h>
 #include <Sluift/globals.h>
-#include <iostream>
 
 using namespace Swift;
+namespace lambda = boost::lambda;
 
 static const std::string SLUIFT_CLIENT = Lua::FunctionRegistry::getMetaTableNameForType("Client");
 
@@ -376,12 +380,33 @@ static void pushEvent(lua_State* L, const SluiftClient::Event& event) {
 	}
 }
 
+struct CallUnaryLuaPredicateOnEvent {
+	CallUnaryLuaPredicateOnEvent(lua_State* L, int index) : L(L), index(index) {
+	}
+
+	bool operator()(const SluiftClient::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(Client, get_next_event) {
 	Sluift::globals.eventLoop.runOnce();
 	SluiftClient* client = getClient(L);
 
 	int timeout = Sluift::globals.timeout;
 	boost::optional<SluiftClient::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") {
@@ -397,9 +422,25 @@ SLUIFT_LUA_FUNCTION(Client, get_next_event) {
 		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);
+		}
 	}
 
-	if (boost::optional<SluiftClient::Event> event = client->getNextEvent(type, timeout)) {
+	boost::optional<SluiftClient::Event> event;
+	if (condition) {
+		event = client->getNextEvent(timeout, CallUnaryLuaPredicateOnEvent(L, condition));
+	}
+	else if (type) {
+		event = client->getNextEvent(
+				timeout, lambda::bind(&SluiftClient::Event::type, lambda::_1) == *type);
+	}
+	else {
+		event = client->getNextEvent(timeout);
+	}
+
+	if (event) {
 		pushEvent(L, *event);
 	}
 	else {
@@ -510,6 +551,12 @@ SLUIFT_LUA_FUNCTION(Client, set_caps_node) {
 	return 0;
 }
 
+SLUIFT_LUA_FUNCTION(Client, jid) {
+	SluiftClient* client = getClient(L);
+	lua_pushstring(L, client->getClient()->getJID().toString().c_str());
+	return 1;
+}
+
 SLUIFT_LUA_FUNCTION(Client, __gc) {
 	SluiftClient* client = getClient(L);
 	delete client;
diff --git a/Swiften/QA/ScriptedTests/PubSub.lua b/Swiften/QA/ScriptedTests/PubSub.lua
index a9c0fe8..cb4679a 100644
--- a/Swiften/QA/ScriptedTests/PubSub.lua
+++ b/Swiften/QA/ScriptedTests/PubSub.lua
@@ -22,6 +22,7 @@ local subscriber_node
 
 local publish_item = {id = 'item_id', data = {{ _type = 'software_version', name = 'MyTest', os = 'Lua' }} }
 local publish_item2 = {id = 'item_id2', data = {{ _type = 'software_version', name = 'MyTest2', os = 'Lua' }} }
+local publish_item3 = {id = 'item_id3', data = {{ _type = 'software_version', name = 'MyTest3', os = 'Lua' }} }
 
 --------------------------------------------------------------------------------
 -- Helper methods
@@ -109,11 +110,40 @@ function test_subscriber_use_cases()
 	
 	-- 6.5 Retrieve items of a node
 	assert(node:create())
-	assert(node:publish {item = publish_item})
+	assert(node:set_configuration{configuration = {['pubsub#max_items'] = '10'}})
+	assert(node:publish{item = publish_item})
+	assert(node:publish{item = publish_item2})
 	local items = assert(subscriber_node:get_items())
-	assert(#items == 1)
+	assert(#items == 2)
 	assert(items[1].id == 'item_id')
 	assert(items[1].data[1].name == 'MyTest')
+	assert(items[2].id == 'item_id2')
+	assert(items[2].data[1].name == 'MyTest2')
+	assert(node:delete())
+
+	-- 6.5.7 Requesting most recent items
+	assert(node:create())
+	assert(node:set_configuration{configuration = {['pubsub#max_items'] = '10'}})
+	assert(node:publish{item = publish_item})
+	assert(node:publish{item = publish_item2})
+	assert(node:publish{item = publish_item3})
+	local items = assert(subscriber_node:get_items{maximum_items = 2})
+	assert(#items == 2)
+	assert(items[1].id == 'item_id2')
+	assert(items[1].data[1].name == 'MyTest2')
+	assert(items[2].id == 'item_id3')
+	assert(items[2].data[1].name == 'MyTest3')
+	assert(node:delete())
+	
+	-- 6.5.8 requesting specific item
+	assert(node:create())
+	assert(node:set_configuration{configuration = {['pubsub#max_items'] = '10'}})
+	assert(node:publish{item = publish_item})
+	assert(node:publish{item = publish_item2})
+	local items = assert(subscriber_node:get_item{id = 'item_id2'})
+	assert(#items == 1)
+	assert(items[1].id == 'item_id2')
+	assert(items[1].data[1].name == 'MyTest2')
 	assert(node:delete())
 end
 
@@ -141,7 +171,7 @@ function test_publisher_use_cases()
 	assert(node:create())
 	assert(subscriber_node:subscribe({ jid = subscriber_jid }))
 	assert(node:publish {items = {publish_item}})
-	assert(node:retract { item = 'item_id', notify = true })
+	assert(node:retract { id = 'item_id', notify = true })
 	assert(node:delete())
 
 	-- 7.2.2.1 Delete and notify
@@ -149,7 +179,7 @@ function test_publisher_use_cases()
 	assert(subscriber_node:subscribe({ jid = subscriber_jid }))
 	assert(node:publish {items = {publish_item}})
 	purge_pubsub_events(subscriber)
-	assert(node:retract { item = 'item_id', notify = true })
+	assert(node:retract { id = 'item_id', notify = true })
 	local event = assert(subscriber:get_next_event { type = 'pubsub' })
 	assert(event._type == 'pubsub_event_items')
 	assert(event.retracts[1].id == 'item_id')
diff --git a/Swiften/Serializer/PayloadSerializers/UserLocationSerializer.cpp b/Swiften/Serializer/PayloadSerializers/UserLocationSerializer.cpp
index 373276b..e257654 100644
--- a/Swiften/Serializer/PayloadSerializers/UserLocationSerializer.cpp
+++ b/Swiften/Serializer/PayloadSerializers/UserLocationSerializer.cpp
@@ -25,67 +25,67 @@ std::string UserLocationSerializer::serializePayload(
 	if (boost::optional<std::string> value = payload->getArea()) {
 		result.addNode(boost::make_shared<XMLElement>("area", "", *value));
 	}
-	else if (boost::optional<float> value = payload->getAltitude()) {
+	if (boost::optional<float> value = payload->getAltitude()) {
 		result.addNode(boost::make_shared<XMLElement>("alt", "", boost::lexical_cast<std::string>(*value)));
 	}
-	else if (boost::optional<std::string> value = payload->getLocality()) {
+	if (boost::optional<std::string> value = payload->getLocality()) {
 		result.addNode(boost::make_shared<XMLElement>("locality", "", *value));
 	}
-	else if (boost::optional<float> value = payload->getLatitude()) {
+	if (boost::optional<float> value = payload->getLatitude()) {
 		result.addNode(boost::make_shared<XMLElement>("lat", "", boost::lexical_cast<std::string>(*value)));
 	}
-	else if (boost::optional<float> value = payload->getAccuracy()) {
-		result.addNode(boost::make_shared<XMLElement>("lon", "", boost::lexical_cast<std::string>(*value)));
+	if (boost::optional<float> value = payload->getAccuracy()) {
+		result.addNode(boost::make_shared<XMLElement>("accuracy", "", boost::lexical_cast<std::string>(*value)));
 	}
-	else if (boost::optional<std::string> value = payload->getDescription()) {
+	if (boost::optional<std::string> value = payload->getDescription()) {
 		result.addNode(boost::make_shared<XMLElement>("description", "", *value));
 	}
-	else if (boost::optional<std::string> value = payload->getCountryCode()) {
+	if (boost::optional<std::string> value = payload->getCountryCode()) {
 		result.addNode(boost::make_shared<XMLElement>("countrycode", "", *value));
 	}
-	else if (boost::optional<boost::posix_time::ptime> value = payload->getTimestamp()) {
+	if (boost::optional<boost::posix_time::ptime> value = payload->getTimestamp()) {
 		result.addNode(boost::make_shared<XMLElement>("timestamp", "", dateTimeToString(*value)));
 	}
-	else if (boost::optional<std::string> value = payload->getFloor()) {
+	if (boost::optional<std::string> value = payload->getFloor()) {
 		result.addNode(boost::make_shared<XMLElement>("floor", "", *value));
 	}
-	else if (boost::optional<std::string> value = payload->getBuilding()) {
+	if (boost::optional<std::string> value = payload->getBuilding()) {
 		result.addNode(boost::make_shared<XMLElement>("building", "", *value));
 	}
-	else if (boost::optional<std::string> value = payload->getRoom()) {
+	if (boost::optional<std::string> value = payload->getRoom()) {
 		result.addNode(boost::make_shared<XMLElement>("room", "", *value));
 	}
-	else if (boost::optional<std::string> value = payload->getCountry()) {
+	if (boost::optional<std::string> value = payload->getCountry()) {
 		result.addNode(boost::make_shared<XMLElement>("country", "", *value));
 	}
-	else if (boost::optional<std::string> value = payload->getRegion()) {
+	if (boost::optional<std::string> value = payload->getRegion()) {
 		result.addNode(boost::make_shared<XMLElement>("region", "", *value));
 	}
-	else if (boost::optional<std::string> value = payload->getURI()) {
+	if (boost::optional<std::string> value = payload->getURI()) {
 		result.addNode(boost::make_shared<XMLElement>("uri", "", *value));
 	}
-	else if (boost::optional<float> value = payload->getLongitude()) {
+	if (boost::optional<float> value = payload->getLongitude()) {
 		result.addNode(boost::make_shared<XMLElement>("lon", "", boost::lexical_cast<std::string>(*value)));
 	}
-	else if (boost::optional<float> value = payload->getError()) {
+	if (boost::optional<float> value = payload->getError()) {
 		result.addNode(boost::make_shared<XMLElement>("error", "", boost::lexical_cast<std::string>(*value)));
 	}
-	else if (boost::optional<std::string> value = payload->getPostalCode()) {
+	if (boost::optional<std::string> value = payload->getPostalCode()) {
 		result.addNode(boost::make_shared<XMLElement>("postalcode", "", *value));
 	}
-	else if (boost::optional<float> value = payload->getBearing()) {
+	if (boost::optional<float> value = payload->getBearing()) {
 		result.addNode(boost::make_shared<XMLElement>("bearing", "", boost::lexical_cast<std::string>(*value)));
 	}
-	else if (boost::optional<std::string> value = payload->getText()) {
+	if (boost::optional<std::string> value = payload->getText()) {
 		result.addNode(boost::make_shared<XMLElement>("text", "", *value));
 	}
-	else if (boost::optional<std::string> value = payload->getDatum()) {
+	if (boost::optional<std::string> value = payload->getDatum()) {
 		result.addNode(boost::make_shared<XMLElement>("datum", "", *value));
 	}
-	else if (boost::optional<std::string> value = payload->getStreet()) {
+	if (boost::optional<std::string> value = payload->getStreet()) {
 		result.addNode(boost::make_shared<XMLElement>("street", "", *value));
 	}
-	else if (boost::optional<float> value = payload->getSpeed()) {
+	if (boost::optional<float> value = payload->getSpeed()) {
 		result.addNode(boost::make_shared<XMLElement>("speed", "", boost::lexical_cast<std::string>(*value)));
 	}
 	return result.serialize();
-- 
cgit v0.10.2-6-g49f6