syncevolution/test/dbus-client-server.cpp

339 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>
SyncEvo::GMainLoopCXX loop;
// closes child connection
std::unique_ptr<GDBusCXX::DBusObject> guard;
class Test
{
GDBusCXX::DBusObjectHelper m_server;
// GDBusCXX::DBusRemoteObject m_dbusAPI;
// GDBusCXX::SignalWatch<> 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,
// nullptr,
// nullptr);
}
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::DBusClientCall<std::string> m_hello;
GDBusCXX::DBusClientCall<> 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::DBusClientCall<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::DBusClientCall<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::DBusClientCall<std::string, int, std::string> getmixed(proxy, "GetMixed");
auto result = getmixed();
std::cout << "Got tuple: " << std::get<0>(result) << " " << std::get<1>(result) << " " << std::get<2>(result) << std::endl;
std::cout << "calling server" << std::endl;
proxy.m_hello.start(boost::bind(helloCB, loop.get(), boost::placeholders::_1, boost::placeholders::_2), std::string("world"));
// 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", nullptr },
{ "forkexec", 'e', 0, G_OPTION_ARG_NONE, &opt_fork_exec, "Use fork+exec to start the client (implies --server)", nullptr },
{ "forkfailure", 'f', 0, G_OPTION_ARG_NONE, &opt_fork_exec_failure, "Fork /bin/false to simulate a failure in the child (implies )", nullptr },
{ "forkkill", 'a', 0, G_OPTION_ARG_STRING, &opt_kill, "'child/parent' call peer which kills itself before replying (implies --forkexec)", nullptr },
{ "address", 'a', 0, G_OPTION_ARG_STRING, &opt_address, "D-Bus address to use when connecting to server", nullptr },
// { "allow-anonymous", 'n', 0, G_OPTION_ARG_NONE, &opt_allow_anonymous, "Allow anonymous authentication", nullptr },
{ nullptr}
};
#if !GLIB_CHECK_VERSION(2,36,0)
g_type_init();
#endif
opt_address = nullptr;
opt_kill = nullptr;
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, nullptr);
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 (nullptr, 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;
auto forkexec = SyncEvo::make_weak_shared::make<SyncEvo::ForkExecParent>(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, boost::placeholders::_1, boost::ref(testptr)) :
boost::bind(onChildConnectKill, boost::placeholders::_1, boost::ref(testptr)));
forkexec->m_onQuit.connect(onQuit);
forkexec->m_onFailure.connect(boost::bind(onFailure, boost::placeholders::_2));
forkexec->start();
g_main_loop_run(loop.get());
} else if (opt_server) {
boost::scoped_ptr<Test> testptr;
std::shared_ptr<GDBusCXX::DBusServerCXX> server =
GDBusCXX::DBusServerCXX::listen(boost::bind(newClientConnection, boost::placeholders::_1, boost::placeholders::_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()) {
auto forkexec = SyncEvo::make_weak_shared::make<SyncEvo::ForkExecChild>();
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, boost::placeholders::_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;
}