From c5bb67eab6f97ae0f5f7e673ff0ba9b1111191f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Remko=20Tron=C3=A7on?= <git@el-tramo.be> Date: Sat, 9 Oct 2010 11:52:51 +0200 Subject: Added EchoBot walkthrough example. diff --git a/BuildTools/Copyrighter.py b/BuildTools/Copyrighter.py index 7f4d9f9..b83c3d6 100755 --- a/BuildTools/Copyrighter.py +++ b/BuildTools/Copyrighter.py @@ -136,7 +136,7 @@ elif sys.argv[1] == "check-all-copyrights" : for (path, dirs, files) in os.walk(".") : if "3rdParty" in path or ".sconf" in path or "Swift.app" in path : continue - for filename in [os.path.join(path, file) for file in files if (file.endswith(".cpp") or file.endswith(".h")) and not "ui_" in file and not "moc_" in file and not "qrc_" in file and not "BuildVersion.h" in file] : + for filename in [os.path.join(path, file) for file in files if (file.endswith(".cpp") or file.endswith(".h")) and not "ui_" in file and not "moc_" in file and not "qrc_" in file and not "BuildVersion.h" in file and not "Swiften.h" in file ] : ok &= check_copyright(filename) if not ok : sys.exit(-1) diff --git a/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/.gitignore b/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/.gitignore new file mode 100644 index 0000000..28275ad --- /dev/null +++ b/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/.gitignore @@ -0,0 +1,2 @@ +EchoBot? +*.cpp.xml diff --git a/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot1.cpp b/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot1.cpp new file mode 100644 index 0000000..584ec9d --- /dev/null +++ b/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot1.cpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swiften/Swiften.h" + +using namespace Swift; + +int main(int, char*) { + SimpleEventLoop eventLoop; + + Client client(JID("echobot@wonderland.lit"), "mypass"); + client.connect(); + + eventLoop.run(); + + return 0; +} diff --git a/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot2.cpp b/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot2.cpp new file mode 100644 index 0000000..a03c3db --- /dev/null +++ b/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot2.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <iostream> +#include <boost/bind.hpp> + +#include "Swiften/Swiften.h" + +using namespace Swift; +using namespace boost; + +Client* client; + +void handleConnected() { + std::cout << "Connected" << std::endl; +} + +void handleMessageReceived(Message::ref message) { + // Echo back the incoming message + message->setTo(message->getFrom()); + message->setFrom(JID()); + client->sendMessage(message); +} + +int main(int, char*) { + SimpleEventLoop eventLoop; + + client = new Client(JID("echobot@wonderland.lit"), "mypass"); + client->onConnected.connect(&handleConnected); + client->onMessageReceived.connect(bind(&handleMessageReceived, _1)); + client->connect(); + + eventLoop.run(); + + delete client; + return 0; +} diff --git a/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot3.cpp b/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot3.cpp new file mode 100644 index 0000000..a9c611f --- /dev/null +++ b/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot3.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <iostream> +#include <boost/bind.hpp> + +#include "Swiften/Swiften.h" + +using namespace Swift; +using namespace boost; + +class EchoBot { + public: + EchoBot() { + client = new Client(JID("echobot@wonderland.lit"), "mypass"); + client->onConnected.connect(bind(&EchoBot::handleConnected, this)); + client->onMessageReceived.connect( + bind(&EchoBot::handleMessageReceived, this, _1)); + tracer = new ClientXMLTracer(client); + client->connect(); + } + + ~EchoBot() { + delete tracer; + delete client; + } + + private: + void handleConnected() { + std::cout << "Connected" << std::endl; + } + + void handleMessageReceived(Message::ref message) { + // Echo back the incoming message + message->setTo(message->getFrom()); + message->setFrom(JID()); + client->sendMessage(message); + } + + private: + Client* client; + ClientXMLTracer* tracer; +}; + +int main(int, char*) { + SimpleEventLoop eventLoop; + EchoBot bot; + eventLoop.run(); + return 0; +} diff --git a/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot4.cpp b/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot4.cpp new file mode 100644 index 0000000..b01fecd --- /dev/null +++ b/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot4.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +//... +#include <iostream> +#include <boost/bind.hpp> + +#include "Swiften/Swiften.h" + +using namespace Swift; +using namespace boost; +//... +class EchoBot { + public: + EchoBot() { + //... + client = new Client(JID("echobot@wonderland.lit"), "mypass"); + client->onConnected.connect(bind(&EchoBot::handleConnected, this)); + client->onMessageReceived.connect( + bind(&EchoBot::handleMessageReceived, this, _1)); + //... + client->onPresenceReceived.connect( + bind(&EchoBot::handlePresenceReceived, this, _1)); + //... + tracer = new ClientXMLTracer(client); + client->connect(); + //... + } + + //... + ~EchoBot() { + delete tracer; + delete client; + } + + private: + //... + void handlePresenceReceived(Presence::ref presence) { + // Automatically approve subscription requests + if (presence->getType() == Presence::Subscribe) { + Presence::ref response = Presence::create(); + response->setTo(presence->getFrom()); + response->setType(Presence::Subscribed); + client->sendPresence(response); + } + } + + void handleConnected() { + // Request the roster + GetRosterRequest::ref rosterRequest = + GetRosterRequest::create(client->getIQRouter()); + rosterRequest->onResponse.connect( + bind(&EchoBot::handleRosterReceived, this, _2)); + rosterRequest->send(); + } + + void handleRosterReceived(const optional<ErrorPayload>& error) { + if (error) { + std::cerr << "Error receiving roster. Continuing anyway."; + } + // Send initial available presence + client->sendPresence(Presence::create("Send me a message")); + } + //... + + void handleMessageReceived(Message::ref message) { + // Echo back the incoming message + message->setTo(message->getFrom()); + message->setFrom(JID()); + client->sendMessage(message); + } + + private: + Client* client; + ClientXMLTracer* tracer; + //... +}; +//... + +int main(int, char*) { + SimpleEventLoop eventLoop; + EchoBot bot; + eventLoop.run(); + return 0; +} +//... diff --git a/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot5.cpp b/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot5.cpp new file mode 100644 index 0000000..72d0eb1 --- /dev/null +++ b/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/EchoBot5.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +//... +#include <iostream> +#include <boost/bind.hpp> + +#include "Swiften/Swiften.h" + +using namespace Swift; +using namespace boost; +//... +class EchoBot { + public: + EchoBot() { + //... + client = new Client(JID("echobot@wonderland.lit"), "mypass"); + client->onConnected.connect(bind(&EchoBot::handleConnected, this)); + client->onMessageReceived.connect( + bind(&EchoBot::handleMessageReceived, this, _1)); + client->onPresenceReceived.connect( + bind(&EchoBot::handlePresenceReceived, this, _1)); + tracer = new ClientXMLTracer(client); + //... + softwareVersionResponder = new SoftwareVersionResponder( + "EchoBot", "1.0", client->getIQRouter()); + //... + client->connect(); + //... + } + + ~EchoBot() { + delete softwareVersionResponder; + //... + delete tracer; + delete client; + //... + } + //... + + private: + void handlePresenceReceived(Presence::ref presence) { + // Automatically approve subscription requests + if (presence->getType() == Presence::Subscribe) { + Presence::ref response = Presence::create(); + response->setTo(presence->getFrom()); + response->setType(Presence::Subscribed); + client->sendPresence(response); + } + } + + void handleConnected() { + // Request the roster + GetRosterRequest::ref rosterRequest = + GetRosterRequest::create(client->getIQRouter()); + rosterRequest->onResponse.connect( + bind(&EchoBot::handleRosterReceived, this, _2)); + rosterRequest->send(); + } + + void handleRosterReceived(const optional<ErrorPayload>& error) { + if (error) { + std::cerr << "Error receiving roster. Continuing anyway."; + } + // Send initial available presence + client->sendPresence(Presence::create("Send me a message")); + } + + void handleMessageReceived(Message::ref message) { + // Echo back the incoming message + message->setTo(message->getFrom()); + message->setFrom(JID()); + client->sendMessage(message); + } + + //... + private: + //... + Client* client; + ClientXMLTracer* tracer; + //... + SoftwareVersionResponder* softwareVersionResponder; +}; +//... + +int main(int, char*) { + SimpleEventLoop eventLoop; + EchoBot bot; + eventLoop.run(); + return 0; +} +//... diff --git a/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/SConscript b/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/SConscript new file mode 100644 index 0000000..ceead6b --- /dev/null +++ b/Documentation/SwiftenDevelopersGuide/Examples/EchoBot/SConscript @@ -0,0 +1,15 @@ +Import("env") + +example_env = env.Clone() +example_env.MergeFlags(example_env["SWIFTEN_FLAGS"]) +example_env.MergeFlags(example_env["LIBIDN_FLAGS"]) +example_env.MergeFlags(example_env["BOOST_FLAGS"]) +example_env.MergeFlags(example_env.get("SQLITE_FLAGS", {})) +example_env.MergeFlags(example_env["ZLIB_FLAGS"]) +example_env.MergeFlags(example_env["OPENSSL_FLAGS"]) +example_env.MergeFlags(example_env.get("LIBXML_FLAGS", "")) +example_env.MergeFlags(example_env.get("EXPAT_FLAGS", "")) +example_env.MergeFlags(example_env["PLATFORM_FLAGS"]) + +for i in range(1,6) : + example_env.Program("EchoBot" + str(i), ["EchoBot" + str(i) + ".cpp"]) diff --git a/Documentation/SwiftenDevelopersGuide/Examples/SConscript b/Documentation/SwiftenDevelopersGuide/Examples/SConscript new file mode 100644 index 0000000..1284b34 --- /dev/null +++ b/Documentation/SwiftenDevelopersGuide/Examples/SConscript @@ -0,0 +1 @@ +SConscript(dirs = ["EchoBot"]) diff --git a/Documentation/SwiftenDevelopersGuide/SConscript b/Documentation/SwiftenDevelopersGuide/SConscript index 145663f..fb08a8f 100644 --- a/Documentation/SwiftenDevelopersGuide/SConscript +++ b/Documentation/SwiftenDevelopersGuide/SConscript @@ -2,4 +2,89 @@ Import("env") env.Tool("DocBook", toolpath = ["#/BuildTools/DocBook/SCons"]) +################################################################################ +# Code generation helper +################################################################################ + +import sys, re, os.path + +def generateDocBookCode(env, target, source) : + # Strips empty lines from the beginning & end of a program + def stripEmptyLines(program) : + programLines = program.split('\n') + newProgramLines = [] + inProgram = False + for line in programLines : + if not re.match("^\s*$", line) or inProgram : + inProgram = True + newProgramLines.append(line) + return '\n'.join(newProgramLines).rstrip() + + def createCallouts(program, calloutPrefix) : + newProgramLines = [] + calloutLines = [] + nextID = 0 + for line in program.split("\n") : + # FIXME: Takes the largest match + m = re.match(".*\/* \(\*\) (.*) \*/.*", line) + if m : + cobID = "cob-" + calloutPrefix + "-" + str(nextID) + coID = "co-" + calloutPrefix + "-" + str(nextID) + nextID += 1 + line = re.sub("/\*.*\*/", "]]><co id=\"%(cobID)s\" linkends=\"%(coID)s\"/><![CDATA[" % {"cobID" : cobID, "coID" : coID}, line) + calloutLines.append("<callout arearefs=\"%(cobID)s\" id=\"%(coID)s\"><para>%(text)s</para></callout>" % {"cobID": cobID, "coID": coID, "text": m.group(1)}) + newProgramLines.append(line) + callouts = "<calloutlist>" + "\n".join(calloutLines) + "</calloutlist>" if len(calloutLines) > 0 else "" + return ("\n".join(newProgramLines), callouts) + + # Parse program + filename = source[0].abspath + filenameBase = os.path.basename(filename).replace(".cpp", "") + inputfile = open(filename) + program = "" + programs = {} + programName = "" + inEllipsis = False + for line in inputfile.readlines() : + if inEllipsis : + if "//..." in line : + inEllipsis = False + else : + if line.startswith("/*") or line.startswith(" *") : + continue + if "//..." in line : + inEllipsis = True + line = line.replace("//...", "]]>…<![CDATA[") + else : + m = re.match("^/// (.*)", line) + if m : + if programName : + programs[programName] = program + program = "" + programName = m.group(1).strip().replace(" ", "") + continue + line = re.sub("\t", " ", line) + program += line + programs[programName] = program + inputfile.close() + + for programName, program in programs.items() : + program = stripEmptyLines(program) + (program, callouts) = createCallouts(program, filenameBase + "-" + programName) + + document = "<foo><programlisting><![CDATA[" + program + "]]></programlisting>" + callouts + "</foo>" + + # Generate code + output = open(target[0].abspath, 'w') + output.write(document) + output.close() + +################################################################################ + env.DocBook("Swiften Developers Guide.xml") + +for i in range(1, 6) : + source = "Examples/EchoBot/EchoBot" + str(i) + ".cpp" + env.Command(source + ".xml", source, Action(generateDocBookCode, cmdstr = "$GENCOMSTR")) + +SConscript(dirs = ["Examples"]) diff --git a/Documentation/SwiftenDevelopersGuide/Swiften Developers Guide.xml b/Documentation/SwiftenDevelopersGuide/Swiften Developers Guide.xml index e0a63ae..1df3781 100644 --- a/Documentation/SwiftenDevelopersGuide/Swiften Developers Guide.xml +++ b/Documentation/SwiftenDevelopersGuide/Swiften Developers Guide.xml @@ -6,44 +6,322 @@ <chapter> <title>Introduction</title> + <section> + <title>Prerequisites</title> + <para> + We assume that the reader is familiar with the basics of the + XMPP protocol. For an overview of the XMPP protocol and its + workings, see + <citetitle>XMPP: The Definitive Guide</citetitle> + <citation><biblioref linkend="XMPP-TDG"/></citation> + </para> + </section> - <para> - This is an introduction - </para> - - <sect1> - <title>Section</title> - + <section> + <title>Boost</title> <para> - This is a section + Swiften makes heavy use of <emphasis>Boost</emphasis> (<ulink url="http://boost.org"><uri>http://boost.org</uri></ulink>) libraries, including <emphasis>Signal</emphasis>, + <emphasis>Bind</emphasis>, <emphasis>Optional</emphasis>, and + <emphasis>Smart Pointers</emphasis>. We + introduce the basic usage of these libraries in our API throughout + this manual. For detailed documentation, we refer to the Boost + website. </para> - </sect1> + </section> </chapter> <chapter> <title>Tutorial: Writing an Echo Bot</title> <para> - In this chapter, we build a simple echo bot, illustrating how - to use and extend Swiften for your own purposes. + In this chapter, we guide you through the Swiften API by building an + example XMPP application: an EchoBot. This example program, taken from + <citetitle>XMPP: The Definitive Guide</citetitle> + <citation><biblioref linkend="XMPP-TDG"/></citation>, connects to + an XMPP server, logs in, and responds to all incoming messages with + the exact same message. We build up our application using Swiften's + basic building blocks for XMPP development, to help get a good + understanding of how Swiften fundamental classes work and can be + extended. In the last stage of this example, we + introduce some of Swiften's convenience classes for standard + XMPP tasks such as roster management. </para> <sect1> - <title>Setting up the build environment</title> + <title>Connecting to a server: Clients & event loops</title> + <para> + As a first step, we create an application that connects to a server. + The code can be seen in <xref linkend="Example-EchoBot1"/>. + </para> + <example id="Example-EchoBot1"> + <title>Connecting to a server</title> + <include xmlns="http://www.w3.org/2001/XInclude" href="Examples/EchoBot/EchoBot1.cpp.xml" xpointer="xpointer(//programlisting|//calloutlist)"/> + </example> + <para> + The first thing this program does is construct an + <emphasis>Event Loop</emphasis>. An event loop is a seemingly infinite + loop that waits for external events (e.g. incoming network packets, + timers being activated, input happening) to happen; when such an event + comes in, it notifies interested parties of this event, and then + continues listening for the next event. Since many application + frameworks (such as Qt, GLib, Cocoa) use their own event loop, + Swiften comes prepackaged with classes that integrate with these + event loops. These classes can be found in + <literal>Swiften/EventLoop</literal>. + In this example, however, we don't use such a framework, + so we use Swiften's own <literal>SimpleEventLoop</literal>. This + class is used by simply instantiating it at the beginning of the + application, and calling <literal>run()</literal> after everything + is set up, which will go into an infinite loop. Apart from constructing + and (if necessary) starting the event loop, you will probably have + no other contact with it in the rest of the application. + </para> + + <para> + Swiften's central class for implementing XMPP applications is + <literal>Client</literal>. This class handles all the interaction + with the XMPP network. After constructing it with the JID and + password with which we want to connect, we call + <literal>connect()</literal> to instruct the client to connect to + the XMPP server with the given credentials. Note that this call returns + immediately; it is only when starting the event loop that network + the actual connection process will start. + </para> + </sect1> + + <sect1> + <title>Building EchoBot</title> + <remark>TODO: Explain linking against the static library.</remark> + </sect1> + + <sect1> + <title>Reacting to events: Signals, Slots & Bind</title> + <para> + Up to this point, our client doesn't do anything useful. In this + section, we make the client react to XMPP events. The code can + be seen in <xref linkend="Example-EchoBot2"/>. + </para> + <example id="Example-EchoBot2"> + <title>Reacting to events: Notify whenever the client is connected to the network, and echo back incoming messages</title> + <include xmlns="http://www.w3.org/2001/XInclude" href="Examples/EchoBot/EchoBot2.cpp.xml" xpointer="xpointer(//programlisting|//calloutlist)"/> + </example> + + <para> + A first thing we want to do is print out a message when the client + is connected to the server. Swiften uses the + <emphasis>signal/slot</emphasis> paradigm for notifying interested + parties of events. A <emphasis>signal</emphasis> is an object + representing a type of event. For example, <literal>Client</literal> + has an <literal>onConnected</literal> signal for notifying whenever + the client is connected to the network. If you are interested in + a particular signal, you connect a <emphasis>slot</emphasis> to the + signal. A slot represents a callback that will be called whenever a + signal is emitted. Since we want to print out a message whenever + we're connected to the network, we connect to the client's signal, + and tell it to call <literal>handleConnected</literal> (which prints + out a message): + <programlisting>client->onConnected.connect(&handleConnected)</programlisting> + </para> + + <para> + Another event we're interested in is whenever a message comes in. + For this purpose, <literal>Client</literal> provides a signal called + <literal>onMessageReceived</literal>. The major difference with the + previous <literal>onConnected</literal> signal is that this signal + also can provide extra information to the callback: the actual + message received. A signal can provide this extra information through + one or more arguments, which will be passed to the slot's parameters. + To be able to handle parameters to slots, there needs to be a more + general representation of callbacks than just function pointers. This + is where Boost's <literal>bind</literal> comes in: <literal>bind</literal> + provides a way to construct <emphasis>functors</emphasis> (callbacks, slots, …), by combining function pointers and parameter values. + For example, to connect the signal to our slot, we call: + <programlisting>client->onMessageReceived.connect(bind(&handleMessageReceived, _1))</programlisting> + This is essentially saying: when the + <literal>onMessageReceived</literal> signal is emitted, call + <literal>handleMessageReceived</literal>, and pass it the first + parameter provided by the slot (which, in this case, is the actual + message received). + </para> + <para> + The implementation of <literal>handleMessageReceived</literal> should be straightforward: put the <emphasis>To</emphasis> address in place of the <emphasis>From</emphasis> address, and send the message to the server. One + thing to note is that <literal>Message::ref</literal> represents a + <emphasis>shared pointer</emphasis> to a <literal>Message</literal> + stanza. Shared pointers behave the same as regular pointers, except that, + when the last copy of the pointer goes away, the object it points to is + deleted as well. <literal>Message::ref</literal> is in fact a + typedef for <literal>boost::shared_ptr<Message></literal>. + Although Swiften tends to prefer the use of the <literal>::ref</literal> + notation, you will see both forms used intermixed. + </para> + + <para> + Before moving on to the next step, we are going to rearrange our + code a bit, to make it a bit cleaner. Instead of using global + variables, we are going to create an <literal>EchoBot</literal> + class with the current code in it. The resulting code can be found + in <xref linkend="Example-EchoBot3"/>. + </para> + <example id="Example-EchoBot3"> + <title>Creating an EchoBot class</title> + <include xmlns="http://www.w3.org/2001/XInclude" href="Examples/EchoBot/EchoBot3.cpp.xml" xpointer="xpointer(//programlisting|//calloutlist)"/> + </example> + <para> + The important thing to consider in this step are the changes to the signal + connections. Since we are now passing member variables of a class + to the signal, we need to use <literal>bind</literal> to pass + in the actual object on which this member variable is called as + the first parameter. + </para> + <para> + The only thing we added to this version is the + <literal>ClientXMLTracer</literal>. This class will dump all + incoming and outgoing XMPP messages to the console, which can be handy + for debugging our bot. </para> </sect1> <sect1> - <title>Connecting to the network</title> + <title>Presence Management: Requests</title> <para> + The current version of our EchoBot does what it is supposed to do: + it answers all incoming messages. However, although users can add + the bot to their contact list, they will not see when it is + online, since the bot doesn't do any presence handling yet. In + this section, we explain the different steps involved in adding + presence management, resulting in the code in + <xref linkend="Example-EchoBot4"/>. + </para> + <example id="Example-EchoBot4"> + <title>Adding presence management: Requesting the initial roster, and auto-approving incoming subscription requests.</title> + <include xmlns="http://www.w3.org/2001/XInclude" href="Examples/EchoBot/EchoBot4.cpp.xml" xpointer="xpointer(//programlisting|//calloutlist)"/> + </example> + <para> + First of all, our bot needs to listen to incoming subscription requests + from users who want to add it to their roster, and automatically + approve them. This is done by connecting to + the <literal>onPresenceReceived</literal> signal, checking whether + the incoming presence is a subscription request, and if so, + respond to it with an approval (in <literal>handlePresenceReceived</literal>). + </para> + + <para> + The first version of the XMPP protocol states that a client will not + get any presence + subscriptions until it requests the roster. To make sure we handle + this, we want to make sure our bot requests the roster at login. + After getting the <literal>onConnected</literal> signal, we + therefore send a + <emphasis>request</emphasis> to retrieve the roster. Swiften's + <literal>Request</literal> classes correspond to XMPP IQ Get or + Set actions. Swiften provides a set of built-in request classes for + the most common tasks in <literal>Swiften/Queries/Requests</literal>, + and can be easily extended to use add your own (see <xref linkend="Section-Extending"/>). Requests have an <literal>onResponse</literal> signal, + which is emitted when a response comes in. This signal has 2 parameters: + the actual response data (the <emphasis>Payload</emphasis>), and an + optional error payload in case there was an error executing the + request. To use a <literal>Request</literal> class, you construct + it with the correct parameters, connect to the + <literal>onResponse</literal> signal, and then send the request by + calling <literal>send()</literal> on it. In this case, we're not + interested in the actual payload of the response (passed as the + first parameter), so we pass it a slot with only the second parameter + (the error payload). When we get the roster back, we send initial + presence to all our subscribers, announcing them we're online. </para> </sect1> <sect1> - <title>T</title> + <title>Publishing version information: Responders</title> <para> + Most XMPP clients have support for querying software version information + of a client through + <citation><biblioref linkend="XEP-0092"/></citation>. These clients + send out an IQ-Get request to an entity, which responds with the + requested information. We would like our bot to listen to these + requests, and respond with the correct information. Swiften uses + <literal>Responder</literal> classes for the purpose of responding + to IQ requests, and are therefore the dual of the + <literal>Request</literal> clients discussed in the previous section. </para> + + <example id="Example-EchoBot5"> + <title>Adding presence management: Requesting the initial roster, and auto-approving incoming subscription requests.</title> + <include xmlns="http://www.w3.org/2001/XInclude" href="Examples/EchoBot/EchoBot5.cpp.xml" xpointer="xpointer(//programlisting|//calloutlist)"/> + </example> + + <para> + Using <literal>SoftwareVersionResponder</literal> is pretty + straightforward, as can be seen in <xref linkend="Example-EchoBot5"/>: + simply construct the responder with the correct + parameters, and it will automatically respond to the incoming + requests. Other <literal>Responder</literal> classes may provide + signals to notify of incoming requests, or may have some other + behavior. For a detailed explanation of responders, see + <xref linkend="Section-CustomQueries"/>. + </para> + </sect1> + + <sect1 id="Section-Extending"> + <title>Extending Swiften with new payloads: Payloads, Parsers, and Serializers</title> + <para> + Swiften uses abstract datastructures for all the data that is received + and sent over the XMPP network. The declaration of these datastructures + can all be found in <literal>Swiften/Elements</literal>. For + representing the XMPP stanzas, Swiften uses the + <literal>Message</literal>, <literal>Presence</literal>, and + <literal>IQ</literal> classes. Each stanza can have an arbitrary + amount of child <emphasis>payloads</emphasis>, represented by the + <literal>Payload</literal> class. A payload typically corresponds + to a (namespaced) child XML element of a stanza; for example, the + <literal><query xmlns="jabber:iq:roster"/></literal> + element used for managing the roster is represented as a + <literal>RosterPayload</literal>. + </para> + + <remark>TODO</remark> + </sect1> + + <sect1 id="Section-CustomQueries"> + <title>Extending Swiften with new queries and responders</title> + <remark>TODO</remark> + </sect1> + + <sect1> + <title>Using Swiften's convenience classes</title> + <remark>TODO</remark> </sect1> </chapter> + + <bibliography> + <title>Bibliography</title> + + <biblioentry id="XMPP-TDG"> + <abbrev>XMPP-TDG</abbrev> + <title><ulink url="http://oreilly.com/catalog/9780596157197/">XMPP: The + Definitive Guide</ulink></title> + <author> + <firstname>Peter</firstname> + <surname>Saint-Andre</surname> + </author> + <author> + <firstname>Kevin</firstname> + <surname>Smith</surname> + </author> + <author> + <firstname>Remko</firstname> + <surname>Tronçon</surname> + </author> + </biblioentry> + + <biblioentry id='XEP-0092'> + <abbrev>XEP-0092</abbrev> + <title><ulink url='http://www.xmpp.org/extensions/xep-0092.html'>Software Version</ulink></title> + <author> + <firstname>Peter</firstname> + <surname>Saint-Andre</surname> + </author> + </biblioentry> + </bibliography> </book> diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp index 9619722..de74150 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.cpp +++ b/Swift/Controllers/Chat/ChatControllerBase.cpp @@ -62,7 +62,7 @@ void ChatControllerBase::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::SecurityLabels)) { //chatWindow_->setSecurityLabelsEnabled(true); //chatWindow_->setSecurityLabelsError(); - boost::shared_ptr<GetSecurityLabelsCatalogRequest> request(new GetSecurityLabelsCatalogRequest(JID(toJID_.toBare()), iqRouter_)); + GetSecurityLabelsCatalogRequest::ref request = GetSecurityLabelsCatalogRequest::create(JID(toJID_.toBare()), iqRouter_); request->onResponse.connect(boost::bind(&ChatControllerBase::handleSecurityLabelsCatalogResponse, this, _1, _2)); request->send(); //labelsEnabled_ = true; diff --git a/Swift/Controllers/Chat/MUCSearchController.cpp b/Swift/Controllers/Chat/MUCSearchController.cpp index 304de85..dc81031 100644 --- a/Swift/Controllers/Chat/MUCSearchController.cpp +++ b/Swift/Controllers/Chat/MUCSearchController.cpp @@ -63,7 +63,7 @@ void MUCSearchController::handleAddService(const JID& jid, bool userTriggered) { //Set Window to say error this isn't valid return; } - boost::shared_ptr<GetDiscoInfoRequest> discoInfoRequest(new GetDiscoInfoRequest(jid, iqRouter_)); + GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(jid, iqRouter_); discoInfoRequest->onResponse.connect(boost::bind(&MUCSearchController::handleDiscoInfoResponse, this, _1, _2, jid)); discoInfoRequest->send(); } @@ -79,7 +79,7 @@ void MUCSearchController::handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> i handleDiscoError(jid, error.get()); return; } - boost::shared_ptr<GetDiscoItemsRequest> discoItemsRequest(new GetDiscoItemsRequest(jid, iqRouter_)); + GetDiscoItemsRequest::ref discoItemsRequest = GetDiscoItemsRequest::create(jid, iqRouter_); bool mucService = false; bool couldContainServices = false; String name; diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 06b231c..ddf9848 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -306,7 +306,7 @@ void MainController::handleConnected() { xmppRosterController_->requestRoster(); - boost::shared_ptr<GetDiscoInfoRequest> discoInfoRequest(new GetDiscoInfoRequest(JID(), client_->getIQRouter())); + GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(JID(), client_->getIQRouter()); discoInfoRequest->onResponse.connect(boost::bind(&MainController::handleServerDiscoInfoResponse, this, _1, _2)); discoInfoRequest->send(); diff --git a/Swift/Controllers/RosterController.cpp b/Swift/Controllers/RosterController.cpp index 15ed6a4..e79fad1 100644 --- a/Swift/Controllers/RosterController.cpp +++ b/Swift/Controllers/RosterController.cpp @@ -162,7 +162,7 @@ void RosterController::handleUIEvent(boost::shared_ptr<UIEvent> event) { item.setJID(addContactEvent->getJID()); boost::shared_ptr<RosterPayload> roster(new RosterPayload()); roster->addItem(item); - boost::shared_ptr<SetRosterRequest> request(new SetRosterRequest(roster, iqRouter_)); + SetRosterRequest::ref request = SetRosterRequest::create(roster, iqRouter_); request->onResponse.connect(boost::bind(&RosterController::handleRosterSetError, this, _1, roster)); request->send(); presenceSender_->requestSubscription(addContactEvent->getJID()); @@ -173,7 +173,7 @@ void RosterController::handleUIEvent(boost::shared_ptr<UIEvent> event) { RosterItemPayload item(removeEvent->getJID(), "", RosterItemPayload::Remove); boost::shared_ptr<RosterPayload> roster(new RosterPayload()); roster->addItem(item); - boost::shared_ptr<SetRosterRequest> request(new SetRosterRequest(roster, iqRouter_)); + SetRosterRequest::ref request = SetRosterRequest::create(roster, iqRouter_); request->onResponse.connect(boost::bind(&RosterController::handleRosterSetError, this, _1, roster)); request->send(); @@ -186,7 +186,7 @@ void RosterController::handleUIEvent(boost::shared_ptr<UIEvent> event) { item.setGroups(xmppRoster_->getGroupsForJID(contact)); boost::shared_ptr<RosterPayload> roster(new RosterPayload()); roster->addItem(item); - boost::shared_ptr<SetRosterRequest> request(new SetRosterRequest(roster, iqRouter_)); + SetRosterRequest::ref request = SetRosterRequest::create(roster, iqRouter_); request->onResponse.connect(boost::bind(&RosterController::handleRosterSetError, this, _1, roster)); request->send(); return; @@ -210,7 +210,7 @@ void RosterController::handleUIEvent(boost::shared_ptr<UIEvent> event) { item.setGroups(newGroups); boost::shared_ptr<RosterPayload> roster(new RosterPayload()); roster->addItem(item); - boost::shared_ptr<SetRosterRequest> request(new SetRosterRequest(roster, iqRouter_)); + SetRosterRequest::ref request = SetRosterRequest::create(roster, iqRouter_); request->onResponse.connect(boost::bind(&RosterController::handleRosterSetError, this, _1, roster)); request->send(); return; diff --git a/Swiften/.gitignore b/Swiften/.gitignore index 9eca6c8..c21d6aa 100644 --- a/Swiften/.gitignore +++ b/Swiften/.gitignore @@ -1,2 +1,3 @@ *.a *.o +Swiften.h diff --git a/Swiften/Client/Client.h b/Swiften/Client/Client.h index 7e55289..5432920 100644 --- a/Swiften/Client/Client.h +++ b/Swiften/Client/Client.h @@ -22,6 +22,7 @@ #include "Swiften/Client/StanzaChannel.h" #include "Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h" #include "Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h" +#include "Swiften/Base/Shared.h" namespace Swift { class IQRouter; diff --git a/Swiften/Client/ClientXMLTracer.h b/Swiften/Client/ClientXMLTracer.h index 8bcb6e7..f3cfd08 100644 --- a/Swiften/Client/ClientXMLTracer.h +++ b/Swiften/Client/ClientXMLTracer.h @@ -6,6 +6,8 @@ #pragma once +#include <boost/bind.hpp> + #include "Swiften/Client/Client.h" namespace Swift { diff --git a/Swiften/Disco/CapsManager.cpp b/Swiften/Disco/CapsManager.cpp index a5023d3..a9920a2 100644 --- a/Swiften/Disco/CapsManager.cpp +++ b/Swiften/Disco/CapsManager.cpp @@ -69,7 +69,7 @@ void CapsManager::handleDiscoInfoReceived(const JID& from, const String& hash, D } void CapsManager::requestDiscoInfo(const JID& jid, const String& node, const String& hash) { - boost::shared_ptr<GetDiscoInfoRequest> request(new GetDiscoInfoRequest(jid, node + "#" + hash, iqRouter)); + GetDiscoInfoRequest::ref request = GetDiscoInfoRequest::create(jid, node + "#" + hash, iqRouter); request->onResponse.connect(boost::bind(&CapsManager::handleDiscoInfoReceived, this, jid, hash, _1, _2)); requestedDiscoInfos.insert(hash); request->send(); diff --git a/Swiften/Elements/Presence.h b/Swiften/Elements/Presence.h index 7297339..45638b9 100644 --- a/Swiften/Elements/Presence.h +++ b/Swiften/Elements/Presence.h @@ -22,6 +22,18 @@ namespace Swift { setStatus(status); } + static ref create() { + return ref(new Presence()); + } + + static ref create(const String& status) { + return ref(new Presence(status)); + } + + static ref create(Presence::ref presence) { + return ref(new Presence(*presence)); + } + Type getType() const { return type_; } void setType(Type type) { type_ = type; } diff --git a/Swiften/Elements/RosterPayload.h b/Swiften/Elements/RosterPayload.h index 58c5726..0c987c4 100644 --- a/Swiften/Elements/RosterPayload.h +++ b/Swiften/Elements/RosterPayload.h @@ -4,17 +4,17 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#ifndef SWIFTEN_RosterPayload_H -#define SWIFTEN_RosterPayload_H +#pragma once #include <vector> #include <boost/optional.hpp> #include "Swiften/Elements/RosterItemPayload.h" #include "Swiften/Elements/Payload.h" +#include "Swiften/Base/Shared.h" namespace Swift { - class RosterPayload : public Payload { + class RosterPayload : public Payload, public Shared<RosterPayload> { public: typedef std::vector<RosterItemPayload> RosterItemPayloads; @@ -35,5 +35,3 @@ namespace Swift { RosterItemPayloads items_; }; } - -#endif diff --git a/Swiften/Examples/ConnectivityTest/ConnectivityTest.cpp b/Swiften/Examples/ConnectivityTest/ConnectivityTest.cpp index af08a9c..3d814a8 100644 --- a/Swiften/Examples/ConnectivityTest/ConnectivityTest.cpp +++ b/Swiften/Examples/ConnectivityTest/ConnectivityTest.cpp @@ -41,7 +41,7 @@ void handleServerDiscoInfoResponse(boost::shared_ptr<DiscoInfo> /*info*/, const void handleConnected() { exitCode = NO_RESPONSE; - boost::shared_ptr<GetDiscoInfoRequest> discoInfoRequest(new GetDiscoInfoRequest(JID(), client->getIQRouter())); + GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(JID(), client->getIQRouter()); discoInfoRequest->onResponse.connect(handleServerDiscoInfoResponse); discoInfoRequest->send(); } diff --git a/Swiften/Examples/EchoBot/.gitignore b/Swiften/Examples/EchoBot/.gitignore deleted file mode 100644 index 9200f42..0000000 --- a/Swiften/Examples/EchoBot/.gitignore +++ /dev/null @@ -1 +0,0 @@ -EchoBot diff --git a/Swiften/Examples/EchoBot/EchoBot.cpp b/Swiften/Examples/EchoBot/EchoBot.cpp deleted file mode 100644 index 0474287..0000000 --- a/Swiften/Examples/EchoBot/EchoBot.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2010 Remko Tronçon - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. - */ - -#include <boost/bind.hpp> -#include <iostream> - -#include "Swiften/Client/Client.h" -#include "Swiften/EventLoop/SimpleEventLoop.h" -#include "Swiften/Queries/Requests/GetRosterRequest.h" - -using namespace Swift; -using namespace boost; - -class EchoBot { - public: - EchoBot(const JID& jid, const String& pass) { - client = new Client(jid, pass); - client->onConnected.connect(bind(&EchoBot::handleConnected, this)); - client->onMessageReceived.connect(bind(&EchoBot::handleMessageReceived, this, _1)); - client->connect(); - } - - ~EchoBot() { - delete client; - } - - private: - void handleConnected() { - shared_ptr<GetRosterRequest> rosterRequest(new GetRosterRequest(client->getIQRouter())); - rosterRequest->onResponse.connect(bind(&EchoBot::handleRosterReceived, this, _2)); - rosterRequest->send(); - } - - void handleRosterReceived(const optional<ErrorPayload>& error) { - if (error) { - std::cerr << "Error receiving roster. Continuing anyway."; - } - client->sendPresence(shared_ptr<Presence>(new Presence("Send me a message"))); - } - - void handleMessageReceived(shared_ptr<Message> message) { - message->setTo(message->getFrom()); - message->setFrom(JID()); - client->sendMessage(message); - } - - private: - Client* client; -}; - -int main(int argc, char* argv[]) { - if (argc != 3) { - std::cerr << "Usage: " << argv[0] << " <jid> <pass>" << std::endl; - return -1; - } - SimpleEventLoop eventLoop; - EchoBot bot(JID(argv[1]), argv[2]); - eventLoop.run(); - return 0; -} diff --git a/Swiften/Examples/EchoBot/SConscript b/Swiften/Examples/EchoBot/SConscript deleted file mode 100644 index fb7749d..0000000 --- a/Swiften/Examples/EchoBot/SConscript +++ /dev/null @@ -1,14 +0,0 @@ -Import("env") - -myenv = env.Clone() -myenv.MergeFlags(myenv["SWIFTEN_FLAGS"]) -myenv.MergeFlags(myenv["LIBIDN_FLAGS"]) -myenv.MergeFlags(myenv["BOOST_FLAGS"]) -myenv.MergeFlags(myenv.get("SQLITE_FLAGS", {})) -myenv.MergeFlags(myenv["ZLIB_FLAGS"]) -myenv.MergeFlags(myenv["OPENSSL_FLAGS"]) -myenv.MergeFlags(myenv.get("LIBXML_FLAGS", "")) -myenv.MergeFlags(myenv.get("EXPAT_FLAGS", "")) -myenv.MergeFlags(myenv["PLATFORM_FLAGS"]) - -myenv.Program("EchoBot", ["EchoBot.cpp"]) diff --git a/Swiften/Examples/SConscript b/Swiften/Examples/SConscript index 64d1859..61bedfb 100644 --- a/Swiften/Examples/SConscript +++ b/Swiften/Examples/SConscript @@ -6,6 +6,5 @@ SConscript(dirs = [ "SendMessage", "ConnectivityTest", "LinkLocalTool", - "EchoBot", "ParserTester", ]) diff --git a/Swiften/MUC/MUCBookmarkManager.cpp b/Swiften/MUC/MUCBookmarkManager.cpp index 64615e4..0f5f3bb 100644 --- a/Swiften/MUC/MUCBookmarkManager.cpp +++ b/Swiften/MUC/MUCBookmarkManager.cpp @@ -19,7 +19,7 @@ namespace Swift { MUCBookmarkManager::MUCBookmarkManager(IQRouter* iqRouter) { iqRouter_ = iqRouter; ready_ = false; - boost::shared_ptr<GetPrivateStorageRequest<Storage> > request(new GetPrivateStorageRequest<Storage>(iqRouter_)); + GetPrivateStorageRequest<Storage>::ref request = GetPrivateStorageRequest<Storage>::create(iqRouter_); request->onResponse.connect(boost::bind(&MUCBookmarkManager::handleBookmarksReceived, this, _1, _2)); request->send(); } @@ -106,7 +106,7 @@ void MUCBookmarkManager::flush() { } // Send an iq to save the storage element - boost::shared_ptr<SetPrivateStorageRequest<Storage> > request(new SetPrivateStorageRequest<Storage>(storage, iqRouter_)); + SetPrivateStorageRequest<Storage>::ref request = SetPrivateStorageRequest<Storage>::create(storage, iqRouter_); // FIXME: We should care about the result //request->onResponse.connect(boost::bind(&MUCBookmarkManager::handleBookmarksSet, this, _1, _2)); request->send(); diff --git a/Swiften/Queries/Request.h b/Swiften/Queries/Request.h index 450e311..9184dea 100644 --- a/Swiften/Queries/Request.h +++ b/Swiften/Queries/Request.h @@ -21,6 +21,9 @@ namespace Swift { class Request : public IQHandler, public boost::enable_shared_from_this<Request> { public: + void send(); + + protected: Request( IQ::Type type, const JID& receiver, @@ -31,9 +34,6 @@ namespace Swift { const JID& receiver, IQRouter* router); - void send(); - - protected: virtual void setPayload(boost::shared_ptr<Payload> p) { payload_ = p; } diff --git a/Swiften/Queries/Requests/GetDiscoInfoRequest.h b/Swiften/Queries/Requests/GetDiscoInfoRequest.h index 9ec1050..d1ed279 100644 --- a/Swiften/Queries/Requests/GetDiscoInfoRequest.h +++ b/Swiften/Queries/Requests/GetDiscoInfoRequest.h @@ -10,8 +10,17 @@ #include "Swiften/Elements/DiscoInfo.h" namespace Swift { - class GetDiscoInfoRequest : public GenericRequest<DiscoInfo> { + class GetDiscoInfoRequest : public GenericRequest<DiscoInfo>, public Shared<GetDiscoInfoRequest> { public: + static ref create(const JID& jid, IQRouter* router) { + return ref(new GetDiscoInfoRequest(jid, router)); + } + + static ref create(const JID& jid, const String& node, IQRouter* router) { + return ref(new GetDiscoInfoRequest(jid, node, router)); + } + + private: GetDiscoInfoRequest(const JID& jid, IQRouter* router) : GenericRequest<DiscoInfo>(IQ::Get, jid, boost::shared_ptr<DiscoInfo>(new DiscoInfo()), router) { } diff --git a/Swiften/Queries/Requests/GetDiscoItemsRequest.h b/Swiften/Queries/Requests/GetDiscoItemsRequest.h index 453eab4..ed565ac 100644 --- a/Swiften/Queries/Requests/GetDiscoItemsRequest.h +++ b/Swiften/Queries/Requests/GetDiscoItemsRequest.h @@ -10,8 +10,13 @@ #include "Swiften/Elements/DiscoItems.h" namespace Swift { - class GetDiscoItemsRequest : public GenericRequest<DiscoItems> { + class GetDiscoItemsRequest : public GenericRequest<DiscoItems>, public Shared<GetDiscoItemsRequest> { public: + static ref create(const JID& jid, IQRouter* router) { + return ref(new GetDiscoItemsRequest(jid, router)); + } + + private: GetDiscoItemsRequest(const JID& jid, IQRouter* router) : GenericRequest<DiscoItems>(IQ::Get, jid, boost::shared_ptr<DiscoItems>(new DiscoItems()), router) { } diff --git a/Swiften/Queries/Requests/GetPrivateStorageRequest.h b/Swiften/Queries/Requests/GetPrivateStorageRequest.h index d44d5d8..b593495 100644 --- a/Swiften/Queries/Requests/GetPrivateStorageRequest.h +++ b/Swiften/Queries/Requests/GetPrivateStorageRequest.h @@ -17,6 +17,13 @@ namespace Swift { template<typename PAYLOAD_TYPE> class GetPrivateStorageRequest : public Request { public: + typedef boost::shared_ptr<GetPrivateStorageRequest<PAYLOAD_TYPE> > ref; + + static ref create(IQRouter* router) { + return ref(new GetPrivateStorageRequest(router)); + } + + private: GetPrivateStorageRequest(IQRouter* router) : Request(IQ::Get, JID(), boost::shared_ptr<PrivateStorage>(new PrivateStorage(boost::shared_ptr<Payload>(new PAYLOAD_TYPE()))), router) { } diff --git a/Swiften/Queries/Requests/GetRosterRequest.h b/Swiften/Queries/Requests/GetRosterRequest.h index 59cefe4..271b2fb 100644 --- a/Swiften/Queries/Requests/GetRosterRequest.h +++ b/Swiften/Queries/Requests/GetRosterRequest.h @@ -8,10 +8,16 @@ #include "Swiften/Queries/GenericRequest.h" #include "Swiften/Elements/RosterPayload.h" +#include "Swiften/Base/Shared.h" namespace Swift { - class GetRosterRequest : public GenericRequest<RosterPayload> { + class GetRosterRequest : public GenericRequest<RosterPayload>, public Shared<GetRosterRequest> { public: + static ref create(IQRouter* router) { + return ref(new GetRosterRequest(router)); + } + + private: GetRosterRequest(IQRouter* router) : GenericRequest<RosterPayload>(IQ::Get, JID(), boost::shared_ptr<Payload>(new RosterPayload()), router) { } diff --git a/Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h b/Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h index 41225fb..ec04f80 100644 --- a/Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h +++ b/Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h @@ -8,10 +8,16 @@ #include "Swiften/Queries/GenericRequest.h" #include "Swiften/Elements/SecurityLabelsCatalog.h" +#include "Swiften/Base/Shared.h" namespace Swift { - class GetSecurityLabelsCatalogRequest : public GenericRequest<SecurityLabelsCatalog> { + class GetSecurityLabelsCatalogRequest : public GenericRequest<SecurityLabelsCatalog>, public Shared<GetSecurityLabelsCatalogRequest> { public: + static ref create(const JID& recipient, IQRouter* router) { + return ref(new GetSecurityLabelsCatalogRequest(recipient, router)); + } + + private: GetSecurityLabelsCatalogRequest( const JID& recipient, IQRouter* router) : diff --git a/Swiften/Queries/Requests/GetVCardRequest.h b/Swiften/Queries/Requests/GetVCardRequest.h index f369cdc..2c40cd1 100644 --- a/Swiften/Queries/Requests/GetVCardRequest.h +++ b/Swiften/Queries/Requests/GetVCardRequest.h @@ -13,6 +13,11 @@ namespace Swift { class GetVCardRequest : public GenericRequest<VCard>, public Shared<GetVCardRequest> { public: + static ref create(const JID& jid, IQRouter* router) { + return ref(new GetVCardRequest(jid, router)); + } + + private: GetVCardRequest(const JID& jid, IQRouter* router) : GenericRequest<VCard>(IQ::Get, jid, boost::shared_ptr<Payload>(new VCard()), router) { } }; diff --git a/Swiften/Queries/Requests/SetPrivateStorageRequest.h b/Swiften/Queries/Requests/SetPrivateStorageRequest.h index ebdfca8..1931f3a 100644 --- a/Swiften/Queries/Requests/SetPrivateStorageRequest.h +++ b/Swiften/Queries/Requests/SetPrivateStorageRequest.h @@ -17,6 +17,13 @@ namespace Swift { template<typename PAYLOAD_TYPE> class SetPrivateStorageRequest : public Request { public: + typedef boost::shared_ptr<SetPrivateStorageRequest<PAYLOAD_TYPE> > ref; + + static ref create(boost::shared_ptr<PAYLOAD_TYPE> payload, IQRouter* router) { + return ref(new SetPrivateStorageRequest<PAYLOAD_TYPE>(payload, router)); + } + + private: SetPrivateStorageRequest(boost::shared_ptr<PAYLOAD_TYPE> payload, IQRouter* router) : Request(IQ::Set, JID(), boost::shared_ptr<PrivateStorage>(new PrivateStorage(payload)), router) { } diff --git a/Swiften/Queries/Requests/SetRosterRequest.h b/Swiften/Queries/Requests/SetRosterRequest.h index 1f47cfe..7b1bf8c 100644 --- a/Swiften/Queries/Requests/SetRosterRequest.h +++ b/Swiften/Queries/Requests/SetRosterRequest.h @@ -11,10 +11,16 @@ #include "Swiften/Queries/Request.h" #include "Swiften/Elements/RosterPayload.h" +#include "Swiften/Base/Shared.h" namespace Swift { - class SetRosterRequest : public Request { + class SetRosterRequest : public Request, public Shared<SetRosterRequest> { public: + static ref create(RosterPayload::ref payload, IQRouter* router) { + return ref(new SetRosterRequest(payload, router)); + } + + private: SetRosterRequest(boost::shared_ptr<RosterPayload> payload, IQRouter* router) : Request(IQ::Set, JID(), boost::shared_ptr<RosterPayload>(payload), router) { } diff --git a/Swiften/Queries/Requests/UnitTest/GetPrivateStorageRequestTest.cpp b/Swiften/Queries/Requests/UnitTest/GetPrivateStorageRequestTest.cpp index d90a114..fe8b0a0 100644 --- a/Swiften/Queries/Requests/UnitTest/GetPrivateStorageRequestTest.cpp +++ b/Swiften/Queries/Requests/UnitTest/GetPrivateStorageRequestTest.cpp @@ -45,8 +45,8 @@ class GetPrivateStorageRequestTest : public CppUnit::TestFixture } void testSend() { - GetPrivateStorageRequest<MyPayload> request(router); - request.send(); + GetPrivateStorageRequest<MyPayload>::ref request = GetPrivateStorageRequest<MyPayload>::create(router); + request->send(); CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(channel->iqs_.size())); CPPUNIT_ASSERT_EQUAL(JID(), channel->iqs_[0]->getTo()); @@ -58,9 +58,9 @@ class GetPrivateStorageRequestTest : public CppUnit::TestFixture } void testHandleResponse() { - GetPrivateStorageRequest<MyPayload> testling(router); - testling.onResponse.connect(boost::bind(&GetPrivateStorageRequestTest::handleResponse, this, _1, _2)); - testling.send(); + GetPrivateStorageRequest<MyPayload>::ref testling = GetPrivateStorageRequest<MyPayload>::create(router); + testling->onResponse.connect(boost::bind(&GetPrivateStorageRequestTest::handleResponse, this, _1, _2)); + testling->send(); channel->onIQReceived(createResponse("test-id", "foo")); CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(responses.size())); @@ -68,9 +68,9 @@ class GetPrivateStorageRequestTest : public CppUnit::TestFixture } void testHandleResponse_Error() { - GetPrivateStorageRequest<MyPayload> testling(router); - testling.onResponse.connect(boost::bind(&GetPrivateStorageRequestTest::handleResponse, this, _1, _2)); - testling.send(); + GetPrivateStorageRequest<MyPayload>::ref testling = GetPrivateStorageRequest<MyPayload>::create(router); + testling->onResponse.connect(boost::bind(&GetPrivateStorageRequestTest::handleResponse, this, _1, _2)); + testling->send(); channel->onIQReceived(createError("test-id")); CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(responses.size())); diff --git a/Swiften/Roster/XMPPRosterController.cpp b/Swiften/Roster/XMPPRosterController.cpp index dca74c0..7743ec8 100644 --- a/Swiften/Roster/XMPPRosterController.cpp +++ b/Swiften/Roster/XMPPRosterController.cpp @@ -29,7 +29,7 @@ XMPPRosterController::XMPPRosterController(IQRouter* iqRouter, XMPPRoster* xmppR void XMPPRosterController::requestRoster() { xmppRoster_->clear(); - boost::shared_ptr<GetRosterRequest> rosterRequest(new GetRosterRequest(iqRouter_)); + GetRosterRequest::ref rosterRequest = GetRosterRequest::create(iqRouter_); rosterRequest->onResponse.connect(boost::bind(&XMPPRosterController::handleRosterReceived, this, _1)); rosterRequest->send(); } diff --git a/Swiften/SConscript b/Swiften/SConscript index c5ad9a6..7665fb0 100644 --- a/Swiften/SConscript +++ b/Swiften/SConscript @@ -1,3 +1,5 @@ +import os + Import("env") ################################################################################ @@ -244,3 +246,19 @@ if env["SCONS_STAGE"] == "build" : File("StringCodecs/UnitTest/PBKDF2Test.cpp"), File("VCards/UnitTest/VCardManagerTest.cpp"), ]) + + # Generate the Swiften header + swiften_header = "#pragma once\n" + top_path = env.Dir("..").abspath + public_dirs = ["Queries", "Client", "Elements"] + for public_dir in public_dirs : + for root, dirs, files in os.walk(env.Dir(public_dir).abspath) : + if root.endswith("UnitTest") : + continue + for file in files : + if not file.endswith(".h") : + continue + swiften_header += "#include \"" + os.path.relpath(os.path.join(root, file), top_path) + "\"\n" + for file in ["EventLoop/SimpleEventLoop.h"] : + swiften_header += "#include \"Swiften/" + file + "\"\n" + swiften_env.WriteVal("Swiften.h", swiften_env.Value(swiften_header)) \ No newline at end of file diff --git a/Swiften/VCards/VCardManager.cpp b/Swiften/VCards/VCardManager.cpp index 673b937..c189717 100644 --- a/Swiften/VCards/VCardManager.cpp +++ b/Swiften/VCards/VCardManager.cpp @@ -34,7 +34,7 @@ void VCardManager::requestVCard(const JID& requestedJID) { if (requestedVCards.find(jid) != requestedVCards.end()) { return; } - GetVCardRequest::ref request(new GetVCardRequest(jid, iqRouter)); + GetVCardRequest::ref request = GetVCardRequest::create(jid, iqRouter); request->onResponse.connect(boost::bind(&VCardManager::handleVCardReceived, this, jid, _1, _2)); request->send(); requestedVCards.insert(jid); -- cgit v0.10.2-6-g49f6