syncevolution/test/dbus-client-server.cpp
Patrick Ohly de8461f802 code restructing: Exception, throwError()
Raising exceptions via throwError() looses the original source code
location information. Fixing that by explicitly passing that
information as additional parameter, created with the preprocessor
macro SE_HERE.

Several files included the complex SyncContext.h only because needed
throwError(). A better place for the revised implementation is the
Exception class which used to be in util.h, now Exception.h.

Simplifying the include statements exposed indirect include
dependencies and removed "using namespace std" from the compilation of
some source files which happened to rely on it. We want to get rid of
that name space polution, so fix the code instead of adding it back.
2014-05-02 16:43:52 +02:00

342 lines
12 KiB
C++

#include "gdbus-cxx-bridge.h"
#include <syncevo/GLibSupport.h>
#include <syncevo/SmartPtr.h>
#include <syncevo/ForkExec.h>
#include <iostream>
#include <signal.h>
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/tuple/tuple_io.hpp>
SyncEvo::GMainLoopCXX loop;
// closes child connection
boost::scoped_ptr<GDBusCXX::DBusObject> guard;
class Test
{
GDBusCXX::DBusObjectHelper m_server;
// GDBusCXX::DBusRemoteObject m_dbusAPI;
// GDBusCXX::SignalWatch0 m_disconnected;
public:
Test(const GDBusCXX::DBusConnectionPtr &conn) :
// will close connection
m_server(conn, "/test", "org.example.Test", GDBusCXX::DBusObjectHelper::Callback_t(), true)
// m_dbusAPI(conn, DBUS_PATH_LOCAL, DBUS_INTERFACE_LOCAL, "" /* sender? */),
// m_disconnected(m_dbusAPI, "Disconnected")
{
m_server.add(this, &Test::hello, "Hello");
m_server.add(this, &Test::getstrings, "GetStrings");
m_server.add(this, &Test::getmixed, "GetMixed");
m_server.add(this, &Test::kill, "Kill");
}
void activate()
{
m_server.activate();
// fails with an unspecific error inside libdbus, don't rely on "Disconnected"
// m_disconnected.activate(boost::bind(&Test::disconnected, this));
// not implemented either
// b_dbus_set_disconnect_function(m_server.getConnection(),
// staticDisconnected,
// NULL,
// NULL);
}
std::string hello(const std::string &in)
{
std::cout << "hello() called with " << in << std::endl;
return "world";
}
void getstrings(std::string &first, std::string &second)
{
first = "hello";
second = "world";
}
void getmixed(std::string &first, int &second, std::string &third)
{
first = "hello";
second = 1;
third = "world";
}
void kill()
{
std::cout << "killing myself as requested" << std::endl;
abort();
}
void disconnected()
{
std::cout << "connection disconnected";
}
// static void staticDisconnected(DBusConnection*conn, void *data)
// {
// std::cout << "connection disconnected";
// }
};
static void newClientConnection(GDBusCXX::DBusServerCXX &server, GDBusCXX::DBusConnectionPtr &conn,
boost::scoped_ptr<Test> &testptr)
{
std::cout << "new connection" << std::endl;
testptr.reset(new Test(conn.get()));
testptr->activate();
}
static void onChildConnect(const GDBusCXX::DBusConnectionPtr &conn,
boost::scoped_ptr<Test> &testptr)
{
std::cout << "child is ready" << std::endl;
testptr.reset(new Test(conn.get()));
testptr->activate();
}
static void onQuit(int status)
{
std::cout << "child has quit, status " << status << std::endl;
// always quit the process, not just on failure
g_main_loop_quit(loop.get());
}
static void onFailure(const std::string &error)
{
std::cout << "failure, quitting now: " << error << std::endl;
g_main_loop_quit(loop.get());
}
class TestProxy : public GDBusCXX::DBusRemoteObject
{
public:
TestProxy(const GDBusCXX::DBusConnectionPtr &conn) :
GDBusCXX::DBusRemoteObject(conn.get(), "/test", "org.example.Test", "direct.peer"),
m_hello(*this, "Hello"),
m_kill(*this, "Kill")
{
}
GDBusCXX::DBusClientCall1<std::string> m_hello;
GDBusCXX::DBusClientCall0 m_kill;
};
static void onChildConnectKill(const GDBusCXX::DBusConnectionPtr &conn,
boost::scoped_ptr<Test> &testptr)
{
std::cout << "child is ready, kill it" << std::endl;
testptr.reset(new Test(conn.get()));
testptr->activate();
// process messages already before returning from this onConnect callback
dbus_bus_connection_undelay(conn);
TestProxy proxy(conn);
try {
proxy.m_kill();
} catch (const std::runtime_error &ex) {
std::cout << "caught exception, as expected: " << ex.what() << std::endl;
std::cout << "aborting..." << std::endl;
abort();
}
std::cout << "did not get the expected exception" << std::endl;
abort();
}
static void helloCB(GMainLoop *loop, const std::string &res, const std::string &error)
{
if (!error.empty()) {
std::cout << "call failed: " << error << std::endl;
} else {
std::cout << "hello('hello') = " << res << std::endl;
}
g_main_loop_quit(loop);
}
static void callServer(const GDBusCXX::DBusConnectionPtr &conn)
{
TestProxy proxy(conn);
Test test(conn.get());
test.activate();
// process messages already before returning from this onConnect callback
dbus_bus_connection_undelay(conn);
std::cout << "blocking call to server without callback" << std::endl;
std::cout << proxy.m_hello(std::string("blocking world, II")) << std::endl;
try {
GDBusCXX::DBusClientCall1<std::string> nosuchcall(proxy, "nosuchcall");
std::cout << nosuchcall(std::string("ignoreme")) << std::endl;
} catch (const std::runtime_error &ex) {
std::cout << "caught exception, as expected: " << ex.what() << std::endl;
}
GDBusCXX::DBusClientCall2<std::string, std::string> getstrings(proxy, "GetStrings");
std::pair<std::string, std::string> r = getstrings();
std::cout << "Got pair: (" << r.first << ", " << r.second << ")" << std::endl;
GDBusCXX::DBusClientCall3<std::string, int, std::string> getmixed(proxy, "GetMixed");
std::cout << "Got tuple: " << getmixed() << std::endl;
std::cout << "calling server" << std::endl;
proxy.m_hello.start(std::string("world"), boost::bind(helloCB, loop.get(), _1, _2));
// keep connection open until child quits
guard.reset(new GDBusCXX::DBusObject(conn, "foo", "bar", true));
}
static void killServer(const GDBusCXX::DBusConnectionPtr &conn)
{
TestProxy proxy(conn);
// process messages already before returning from this onConnect callback
dbus_bus_connection_undelay(conn);
try {
proxy.m_kill();
} catch (const std::runtime_error &ex) {
std::cout << "caught exception, as expected: " << ex.what() << std::endl;
std::cout << "aborting..." << std::endl;
abort();
}
std::cout << "did not get the expected exception" << std::endl;
abort();
}
static void calledByServer(const GDBusCXX::DBusConnectionPtr &conn)
{
// run until Test::kill() is invoked by server
Test test(conn.get());
test.activate();
dbus_bus_connection_undelay(conn);
g_main_loop_run(loop.get());
}
void signalHandler (int sig)
{
if (loop) {
g_main_loop_quit(loop.get());
}
}
int main(int argc, char **argv)
{
int ret = 0;
signal (SIGABRT, &signalHandler);
signal (SIGTERM, &signalHandler);
signal (SIGINT, &signalHandler);
try {
gboolean opt_server;
gboolean opt_fork_exec;
gboolean opt_fork_exec_failure;
gchar *opt_address;
gchar *opt_kill;
GOptionContext *opt_context;
// gboolean opt_allow_anonymous;
SyncEvo::GErrorCXX gerror;
GDBusCXX::DBusErrorCXX dbusError;
GOptionEntry opt_entries[] = {
{ "server", 's', 0, G_OPTION_ARG_NONE, &opt_server, "Start a server instead of a client", NULL },
{ "forkexec", 'e', 0, G_OPTION_ARG_NONE, &opt_fork_exec, "Use fork+exec to start the client (implies --server)", NULL },
{ "forkfailure", 'f', 0, G_OPTION_ARG_NONE, &opt_fork_exec_failure, "Fork /bin/false to simulate a failure in the child (implies )", NULL },
{ "forkkill", 'a', 0, G_OPTION_ARG_STRING, &opt_kill, "'child/parent' call peer which kills itself before replying (implies --forkexec)", NULL },
{ "address", 'a', 0, G_OPTION_ARG_STRING, &opt_address, "D-Bus address to use when connecting to server", NULL },
// { "allow-anonymous", 'n', 0, G_OPTION_ARG_NONE, &opt_allow_anonymous, "Allow anonymous authentication", NULL },
{ NULL}
};
g_type_init();
opt_address = NULL;
opt_kill = NULL;
opt_server = FALSE;
opt_fork_exec = FALSE;
opt_fork_exec_failure = FALSE;
// opt_allow_anonymous = FALSE;
opt_context = g_option_context_new("peer-to-peer example");
g_option_context_add_main_entries(opt_context, opt_entries, NULL);
bool success = g_option_context_parse(opt_context, &argc, &argv, gerror);
g_option_context_free(opt_context);
if (!success) {
gerror.throwError(SE_HERE, "parsing command line options");
}
// if (!opt_server && opt_allow_anonymous) {
// throw stdruntime_error("The --allow-anonymous option only makes sense when used with --server.");
// }
loop = SyncEvo::GMainLoopStealCXX(g_main_loop_new (NULL, FALSE));
if (!loop) {
throw std::runtime_error("could not allocate main loop");
}
if (opt_fork_exec || opt_fork_exec_failure) {
boost::scoped_ptr<Test> testptr;
boost::shared_ptr<SyncEvo::ForkExecParent> forkexec =
SyncEvo::ForkExecParent::create(opt_fork_exec_failure ? "/bin/false" : argv[0]);
if (opt_kill) {
forkexec->addEnvVar("DBUS_CLIENT_SERVER_KILL", opt_kill);
}
forkexec->m_onConnect.connect(g_strcmp0(opt_kill, "child") ?
boost::bind(onChildConnect, _1, boost::ref(testptr)) :
boost::bind(onChildConnectKill, _1, boost::ref(testptr)));
forkexec->m_onQuit.connect(onQuit);
forkexec->m_onFailure.connect(boost::bind(onFailure, _2));
forkexec->start();
g_main_loop_run(loop.get());
} else if (opt_server) {
boost::scoped_ptr<Test> testptr;
boost::shared_ptr<GDBusCXX::DBusServerCXX> server =
GDBusCXX::DBusServerCXX::listen(boost::bind(newClientConnection, _1, _2, boost::ref(testptr)),
&dbusError);
if (!server) {
dbusError.throwFailure("starting server");
}
std::cout << "Server is listening at: " << server->getAddress() << std::endl;
g_main_loop_run(loop.get());
} else if (SyncEvo::ForkExecChild::wasForked()) {
boost::shared_ptr<SyncEvo::ForkExecChild> forkexec =
SyncEvo::ForkExecChild::create();
forkexec->m_onConnect.connect(!g_strcmp0(getenv("DBUS_CLIENT_SERVER_KILL"), "child") ? calledByServer :
!g_strcmp0(getenv("DBUS_CLIENT_SERVER_KILL"), "server") ? killServer :
callServer);
forkexec->m_onFailure.connect(boost::bind(onFailure, _2));
forkexec->connect();
g_main_loop_run(loop.get());
} else {
if (!opt_address) {
throw std::runtime_error("need server address");
}
GDBusCXX::DBusConnectionPtr conn = dbus_get_bus_connection(opt_address,
&dbusError);
if (!conn) {
dbusError.throwFailure("connecting to server");
}
// closes connection
callServer(conn);
g_main_loop_run(loop.get());
}
loop.reset();
} catch (const std::exception &ex) {
std::cout << ex.what() << std::endl;
ret = 1;
loop.reset();
}
std::cout << "server done" << std::endl;
return ret;
}