From 33f9dc5337f1a59932b7ac117b0dea56e1dafe1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Remko=20Tron=C3=A7on?= Date: Fri, 20 Sep 2013 22:32:57 +0200 Subject: Sluift: Add support for ad-hoc commands Change-Id: I4ac2d0b07841b03086d9dbd9fa06d1f030f4e1ca diff --git a/Sluift/ElementConvertors/CommandConvertor.cpp b/Sluift/ElementConvertors/CommandConvertor.cpp new file mode 100644 index 0000000..7fb7b22 --- /dev/null +++ b/Sluift/ElementConvertors/CommandConvertor.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2013 Remko Tronçon + * Licensed under the GNU General Public License. + * See the COPYING file for more information. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +using namespace Swift; + +static Command::Action convertActionFromString(const std::string& action) { + if (action == "cancel") { return Command::Cancel; } + else if (action == "execute") { return Command::Execute; } + else if (action == "complete") { return Command::Complete; } + else if (action == "prev") { return Command::Prev; } + else if (action == "next") { return Command::Next; } + return Command::NoAction; +} + +static std::string convertActionToString(Command::Action action) { + switch (action) { + case Command::Cancel: return "cancel"; + case Command::Execute: return "execute"; + case Command::Complete: return "complete"; + case Command::Prev: return "prev"; + case Command::Next: return "next"; + case Command::NoAction: assert(false); return ""; + } + assert(false); + return ""; +} + +CommandConvertor::CommandConvertor(LuaElementConvertors* convertors) : + GenericLuaElementConvertor("command"), + convertors(convertors) { +} + +CommandConvertor::~CommandConvertor() { +} + +boost::shared_ptr CommandConvertor::doConvertFromLua(lua_State* L) { + boost::shared_ptr result = boost::make_shared(); + + lua_getfield(L, -1, "node"); + if (!lua_isnil(L, -1)) { + result->setNode(std::string(Lua::checkString(L, -1))); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "session_id"); + if (!lua_isnil(L, -1)) { + result->setSessionID(std::string(Lua::checkString(L, -1))); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "status"); + if (!lua_isnil(L, -1)) { + std::string statusText = Lua::checkString(L, -1); + Command::Status status = Command::NoStatus; + if (statusText == "executing") { status = Command::Executing; } + else if (statusText == "completed") { status = Command::Completed; } + else if (statusText == "canceled") { status = Command::Canceled; } + result->setStatus(status); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "action"); + if (!lua_isnil(L, -1)) { + result->setAction(convertActionFromString(Lua::checkString(L, -1))); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "execute_action"); + if (!lua_isnil(L, -1)) { + result->setExecuteAction(convertActionFromString(Lua::checkString(L, -1))); + } + lua_pop(L, 1); + + lua_getfield(L, -1, "available_actions"); + if (!lua_isnil(L, -1)) { + Lua::checkType(L, -1, LUA_TTABLE); + lua_pushnil(L); + for (lua_pushnil(L); lua_next(L, -2) != 0; ) { + result->addAvailableAction(convertActionFromString(Lua::checkString(L, -1))); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + lua_getfield(L, -1, "notes"); + if (!lua_isnil(L, -1)) { + Lua::checkType(L, -1, LUA_TTABLE); + lua_pushnil(L); + for (lua_pushnil(L); lua_next(L, -2) != 0; ) { + Lua::checkType(L, -1, LUA_TTABLE); + std::string note; + lua_getfield(L, -1, "note"); + if (!lua_isnil(L, -1)) { + note = Lua::checkString(L, -1); + } + lua_pop(L, 1); + + Command::Note::Type noteType = Command::Note::Info; + lua_getfield(L, -1, "type"); + if (!lua_isnil(L, -1)) { + std::string type = Lua::checkString(L, -1); + if (type == "info") { noteType = Command::Note::Info; } + else if (type == "warn") { noteType = Command::Note::Warn; } + else if (type == "error") { noteType = Command::Note::Error; } + } + lua_pop(L, 1); + + result->addNote(Command::Note(note, noteType)); + lua_pop(L, 1); + } + } + lua_pop(L, 1); + + lua_getfield(L, -1, "form"); + if (!lua_isnil(L, -1)) { + if (boost::shared_ptr
form = boost::dynamic_pointer_cast(convertors->convertFromLuaUntyped(L, -1, "form"))) { + result->setForm(form); + } + } + lua_pop(L, 1); + + return result; +} + +void CommandConvertor::doConvertToLua(lua_State* L, boost::shared_ptr payload) { + Lua::Table result; + if (!payload->getNode().empty()) { + result["node"] = Lua::valueRef(payload->getNode()); + } + if (!payload->getSessionID().empty()) { + result["session_id"] = Lua::valueRef(payload->getSessionID()); + } + switch (payload->getStatus()) { + case Command::Executing: result["status"] = Lua::valueRef("executing"); break; + case Command::Completed: result["status"] = Lua::valueRef("completed"); break; + case Command::Canceled: result["status"] = Lua::valueRef("canceled"); break; + case Command::NoStatus: break; + } + + if (!payload->getNotes().empty()) { + std::vector notes; + foreach (const Command::Note& note, payload->getNotes()) { + Lua::Table noteTable; + if (!note.note.empty()) { + noteTable["note"] = Lua::valueRef(note.note); + } + switch (note.type) { + case Command::Note::Info: noteTable["type"] = Lua::valueRef("info"); break; + case Command::Note::Warn: noteTable["type"] = Lua::valueRef("warn"); break; + case Command::Note::Error: noteTable["type"] = Lua::valueRef("error"); break; + } + notes.push_back(noteTable); + } + result["notes"] = Lua::valueRef(notes); + } + + if (payload->getAction() != Command::NoAction) { + result["action"] = Lua::valueRef(convertActionToString(payload->getAction())); + } + + if (payload->getExecuteAction() != Command::NoAction) { + result["execute_action"] = Lua::valueRef(convertActionToString(payload->getAction())); + } + + if (!payload->getAvailableActions().empty()) { + std::vector availableActions; + foreach (const Command::Action& action, payload->getAvailableActions()) { + if (action != Command::NoAction) { + availableActions.push_back(convertActionToString(action)); + } + } + result["available_actions"] = Lua::valueRef(availableActions); + } + + Lua::pushValue(L, result); + + if (payload->getForm()) { + convertors->convertToLuaUntyped(L, payload->getForm()); + lua_setfield(L, -2, "form"); + } +} diff --git a/Sluift/ElementConvertors/CommandConvertor.h b/Sluift/ElementConvertors/CommandConvertor.h new file mode 100644 index 0000000..7008b50 --- /dev/null +++ b/Sluift/ElementConvertors/CommandConvertor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2013 Remko Tronçon + * Licensed under the GNU General Public License. + * See the COPYING file for more information. + */ + +#pragma once + +#include + +#include +#include + +namespace Swift { + class LuaElementConvertors; + + class CommandConvertor : public GenericLuaElementConvertor { + public: + CommandConvertor(LuaElementConvertors* convertors); + virtual ~CommandConvertor(); + + virtual boost::shared_ptr doConvertFromLua(lua_State*) SWIFTEN_OVERRIDE; + virtual void doConvertToLua(lua_State*, boost::shared_ptr) SWIFTEN_OVERRIDE; + + private: + LuaElementConvertors* convertors; + }; +} diff --git a/Sluift/Examples/AdHocCommands.lua b/Sluift/Examples/AdHocCommands.lua new file mode 100644 index 0000000..9e83f0c --- /dev/null +++ b/Sluift/Examples/AdHocCommands.lua @@ -0,0 +1,48 @@ +--[[ + Copyright (c) 2013 Remko Tronçon + Licensed under the GNU General Public License v3. + See Documentation/Licenses/GPLv3.txt for more information. +--]] + +--[[ + + Ad-hoc command example. + + This script logs into an XMPP server, and executes a + multi-step ad-hoc command. + + The following environment variables are used: + * SLUIFT_JID, SWIFT_PASS: JID and password to log in with + * SLUIFT_DEBUG: Sets whether debugging should be turned on + +--]] + +require "sluift" + +sluift.debug = os.getenv("SLUIFT_DEBUG") or false + +c = sluift.new_client(os.getenv("SLUIFT_JID"), os.getenv("SLUIFT_PASS")) +c:connect(function () + to = sluift.jid.domain(os.getenv("SLUIFT_JID")) + + -- Get the list of commands + commands = assert(c:get_disco_items{ to = to, disco_items = { + node = sluift.disco.features.COMMANDS }}) + print(commands) + + -- Get the initial form + result = assert(c:set_command{ to = to, command = { + action = 'execute', node = 'http://jabber.org/protocol/admin#get-user-roster'}}) + + -- Fill in the form + submission = result.form:create_submission() + submission.accountjid = os.getenv("SLUIFT_JID") + + -- Submit the form + result = assert(c:set_command{ to = to, command = { + action = 'complete', node = 'http://jabber.org/protocol/admin#get-user-roster', + session_id = result.session_id, form = submission}}) + for _, v in ipairs(result.form.roster.values) do + print(v) + end +end) diff --git a/Sluift/LuaElementConvertors.cpp b/Sluift/LuaElementConvertors.cpp index cadfbc4..71957c1 100644 --- a/Sluift/LuaElementConvertors.cpp +++ b/Sluift/LuaElementConvertors.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,7 @@ using namespace Swift; LuaElementConvertors::LuaElementConvertors() { registerConvertors(); + convertors.push_back(boost::make_shared(this)); convertors.push_back(boost::make_shared(this)); convertors.push_back(boost::make_shared()); convertors.push_back(boost::make_shared()); diff --git a/Sluift/SConscript b/Sluift/SConscript index 5d2242e..1a29e43 100644 --- a/Sluift/SConscript +++ b/Sluift/SConscript @@ -29,6 +29,7 @@ elif env["SCONS_STAGE"] == "build" : "ElementConvertors/FormConvertor.cpp", "ElementConvertors/SoftwareVersionConvertor.cpp", "ElementConvertors/VCardConvertor.cpp", + "ElementConvertors/CommandConvertor.cpp", "ClientHelpers.cpp", "SluiftClient.cpp", "boot.c", diff --git a/Sluift/boot.lua b/Sluift/boot.lua index e81257a..2505736 100644 --- a/Sluift/boot.lua +++ b/Sluift/boot.lua @@ -173,13 +173,25 @@ for method, event_type in pairs({messages = 'message', pubsub_events = 'pubsub'} end end --- Register get_* convenience methods for some type of queries -for _, query_type in ipairs({'software_version', 'disco_items', 'xml', 'dom', 'vcard'}) do - Client['get_' .. query_type] = function (client, options) - options = options or {} - if type(options) ~= 'table' then error('Invalid options: ' .. options) end - options['query'] = merge_tables({_type = query_type}, options['query'] or {}) - return client:get(options) +-- +-- Register get_* and set_* convenience methods for some type of queries +-- +-- Example usages: +-- client:get_software_version{to = 'alice@wonderland.lit'} +-- client: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 + Client[query_action .. '_' .. query_type] = function (client, 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 client[query_action](client, options) + end end end @@ -403,6 +415,7 @@ end local disco = { features = { DISCO_INFO = 'http://jabber.org/protocol/disco#info', + COMMANDS = 'http://jabber.org/protocol/commands', USER_LOCATION = 'http://jabber.org/protocol/geoloc', USER_TUNE = 'http://jabber.org/protocol/tune', USER_AVATAR_METADATA = 'urn:xmpp:avatar:metadata', -- cgit v0.10.2-6-g49f6