summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPatrick Ohly <patrick.ohly@intel.com>2009-09-27 22:48:04 +0200
committerPatrick Ohly <patrick.ohly@intel.com>2009-10-07 18:15:45 +0200
commit98f83c5b3c863ce880e23a86a7deff88f05d79fd (patch)
tree3f496463c33246d2c3d0100a789e1b77cb51c10f
parenta9181786b5cc03ac93dbca90cdfb8ca22a2c9876 (diff)
syncevo-dbus-server/syncevolution-http-server.py: SyncML HTTP server
This uses the new combined client/server Synthesis engine. When building shared modules, the engine is opened dynamically only while needed, thus reducing overall memory consumption. The HTTP server is implemented in Python, using the the 'twisted' server framework because it can use the same glib event loop as the D-Bus binding. This allows us to keep the same event loop running forever and react to both kinds of events. The server takes a base url (including host name and port) and a default configuration as name. The host name is currently ignored. It could be used to bind to a specific interface. The path is what a client has to use as part of his sync URL to synchronize against the default configuration. In addition the client can add another path to select that as his server configuration. For example, if the script is called with http://localhost:9000/syncevolution default then syncURL = http://localhost:9000/syncevolution will synchronize against the configuration called "default". With syncURL = http://localhost:9000/syncevolution/my_server, it will synchronize against "my_server".
-rw-r--r--HACKING4
-rw-r--r--configure-pre.in41
-rw-r--r--src/Makefile-gen.am2
-rw-r--r--src/syncclient_sample_config.xml21
-rw-r--r--src/syncevo-dbus-server.cpp75
-rw-r--r--src/syncevo/Makefile.am1
-rw-r--r--src/syncevo/SyncContext.cpp342
-rw-r--r--src/syncevo/SyncContext.h25
-rw-r--r--src/syncevo/SynthesisEngine.cpp5
-rw-r--r--src/syncevo/SynthesisEngine.h2
-rwxr-xr-xtest/syncevo-http-server.py191
11 files changed, 546 insertions, 163 deletions
diff --git a/HACKING b/HACKING
index b9f57916..af25ee7e 100644
--- a/HACKING
+++ b/HACKING
@@ -30,11 +30,15 @@ For doing development work the recommended configure line is:
CXXFLAGS="-g -Wall -Werror -Wno-unknown-pragmas" \
--enable-unit-tests \
--enable-libcurl \
+ --disable-shared \
--enable-developer-mode
Enabling libcurl explicitly ensures that it gets built even when not
the default.
+--disable-shared results in easier to debug executables (no shell
+wrapper scripts, all symbols available before the program runs).
+
Backend libraries are dynamically scannned and loaded into syncevolution, the
library path defaults to $prefix/syncevolution/backends. When developer-mode is
enabled, it will scan libraries in current build directory instead.
diff --git a/configure-pre.in b/configure-pre.in
index 31880ad0..5ca07096 100644
--- a/configure-pre.in
+++ b/configure-pre.in
@@ -30,6 +30,15 @@ AC_ARG_WITH(synthesis-src,
SYNTHESISSRC_REPO otherwise.]),
[SYNTHESISSRC="$withval"], [SYNTHESISSRC="$SYNTHESISSRC_DEF"; REVISION="SYNTHESISSRC_REVISION"])
+AC_ARG_WITH(syncml-engines,
+ AS_HELP_STRING([--with-syncml-engines=client|server|both],
+ [Determines which kind of support for SyncML is compiled and linked into SyncEvolution. Default is both. Currently has no effect.]),
+ [SYNCML_ENGINES="$withval"], SYNCML_ENGINES=both)
+
+case $SYNCML_ENGINES in both|client) AC_DEFINE(ENABLE_SYNCML_CLIENT, 1, [SyncML client support available]);; esac
+case $SYNCML_ENGINES in both|server) AC_DEFINE(ENABLE_SYNCML_SERVER, 1, [SyncML server support available]);; esac
+case $SYNCML_ENGINES in both|server|client) true;; *) AC_ERROR([Invalid value for --with-syncml-engines: $SYNCML_ENGINES]);; esac
+
AC_ARG_WITH(synthesis-username,
AS_HELP_STRING([--with-synthesis-username=<svn username>],
[username to use when checking out --with-synthesis-src sources from Subversion, default 'guest']),
@@ -108,13 +117,6 @@ if test $enable_static_cxx == "yes"; then
fi
AC_SUBST(CORE_LDADD_DEP)
-# preserve src/synthesis by default,
-# always
-CLEAN_SYNTHESIS_SRC=
-SYNTHESIS_LIB=$PWD/src/build-synthesis/src/libsynthesissdk.la
-
-AC_SUBST(CLEAN_CLIENT_SRC)
-
# Check for transport layer.
# Both curl and libsoup can be enabled and disabled explicitly.
# The default is to use libsoup if available, otherwise curl.
@@ -411,8 +413,14 @@ elif test "$SYNTHESISSRC" != "none" && test -d $srcdir/src/synthesis; then
/*) SYNTHESIS_SRC="$srcdir/src/synthesis";;
*) SYNTHESIS_SRC="$PWD/$srcdir/src/synthesis";;
esac
-else
+elif test "$enable_shared" = "no"; then
+ # link against engine
PKG_CHECK_MODULES(SYNTHESIS, "synthesis")
+ SYNTHESIS_ENGINE="$SYNTHESIS_LIBS -lsynthesis"
+else
+ # link against SDK alone, except in client-test
+ PKG_CHECK_MODULES(SYNTHESIS, "synthesis-sdk")
+ SYNTHESIS_ENGINE="`echo $SYNTHESIS_LIBS | sed -e 's/-lsynthesisstubs/-lsynthesis/'`"
fi
if test $SYNTHESIS_SRC != "no-synthesis-source"; then
@@ -424,10 +432,20 @@ if test $SYNTHESIS_SRC != "no-synthesis-source"; then
# use local copy of the sources, with dependencies
# to trigger building the synthesis library
SYNTHESIS_SUBDIR=$PWD/src/build-synthesis
- SYNTHESIS_DEP=$PWD/src/build-synthesis/src/libsynthesissdk.la
-
SYNTHESIS_CFLAGS="-I$SYNTHESIS_SUBDIR/src"
- SYNTHESIS_LIBS="-L$SYNTHESIS_SUBDIR/src -lsynthesissdk -lsynthesis"
+ SYNTHESIS_LIBS="$SYNTHESIS_SUBDIR/src/libsynthesissdk.la"
+
+ if test "$enable_shared" = "no"; then
+ # link against the engines that were enabled
+ case $SYNCML_ENGINES in both|client|server) SYNTHESIS_LIBS="$SYNTHESIS_LIBS $SYNTHESIS_SUBDIR/src/libsynthesis.la";; esac
+ AC_DEFINE(ENABLE_SYNCML_LINKED, 1, [SyncML engines are linked directly])
+ else
+ SYNTHESIS_LIBS="$SYNTHESIS_LIBS $SYNTHESIS_SUBDIR/src/libsynthesisstubs.la"
+ fi
+ SYNTHESIS_DEP=$SYNTHESIS_LIBS
+
+ # for linking client-test
+ SYNTHESIS_ENGINE="$SYNTHESIS_SUBDIR/src/libsynthesis.la"
AC_MSG_NOTICE( [configuring the Synthesis library] )
if (set -x; mkdir -p $SYNTHESIS_SUBDIR && cd $SYNTHESIS_SUBDIR && eval "\$SHELL \"\$SYNTHESIS_CONFIGURE\" $ac_configure_args \"--srcdir=\$SYNTHESIS_SRC\" " ); then true; else
@@ -440,6 +458,7 @@ AC_SUBST(SYNTHESIS_LIBS)
AC_SUBST(SYNTHESIS)
AC_SUBST(SYNTHESIS_SUBDIR)
AC_SUBST(SYNTHESIS_DEP)
+AC_SUBST(SYNTHESIS_ENGINE)
AC_SUBST(SYNTHESIS_LIB)
AC_SUBST(SYNTHESISSRC)
diff --git a/src/Makefile-gen.am b/src/Makefile-gen.am
index fd4d3654..65b80ebc 100644
--- a/src/Makefile-gen.am
+++ b/src/Makefile-gen.am
@@ -199,7 +199,7 @@ CLIENT_LIB_TEST_FILES += $(TEST_FILES_GENERATED)
client_test_CPPFLAGS = -DHAVE_CONFIG_H -DENABLE_INTEGRATION_TESTS -DENABLE_UNIT_TESTS $(AM_CPPFLAGS)
client_test_CXXFLAGS = `cppunit-config --cflags` $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS)
client_test_LDFLAGS = `cppunit-config --libs` `nm synccevo/.libs/libsyncevolution.a | grep funambolAutoRegisterRegistry | sed -e 's/.* /-u /'` $(CORE_LD_FLAGS)
-client_test_LDADD = $(CORE_LDADD)
+client_test_LDADD = $(CORE_LDADD) $(SYNTHESIS_ENGINE)
# These dependencies are intentionally a bit too broad:
# they ensure that all files are in place to *run* client-test.
diff --git a/src/syncclient_sample_config.xml b/src/syncclient_sample_config.xml
index 1b3f0970..444f05cd 100644
--- a/src/syncclient_sample_config.xml
+++ b/src/syncclient_sample_config.xml
@@ -1312,25 +1312,6 @@
<datatypes/>
</datatypes>
-
- <client type="plugin">
- <binfilespath>$(binfilepath)</binfilespath>
- <defaultauth/>
-
- <!-- SyncEvolution has traditionally not folded long lines in
- vCard. Testing showed that servers still have problems with
- it, so avoid it by default -->
- <donotfoldcontent>yes</donotfoldcontent>
-
- <fakedeviceid/>
-
- <datastore/>
-
- <remoterule name="EVOLUTION">
- <deviceid>none - this rule is activated via its name in MAKE/PARSETEXTWITHPROFILE() macro calls</deviceid>
- </remoterule>
-
- <remoterules/>
- </client>
+ <clientorserver/>
</sysync_config>
diff --git a/src/syncevo-dbus-server.cpp b/src/syncevo-dbus-server.cpp
index b4eb9e97..5aba705d 100644
--- a/src/syncevo-dbus-server.cpp
+++ b/src/syncevo-dbus-server.cpp
@@ -75,10 +75,10 @@ public:
ReadOperations(const std::string &config_name);
/** the double dictionary used to represent configurations */
- typedef std::map< std::string, std::map<std::string, std::string> > Config_t;
+ typedef std::map< std::string, StringMap > Config_t;
/** the array of reports filled by getReports() */
- typedef std::vector< std::map<std::string, std::string> > Reports_t;
+ typedef std::vector< StringMap > Reports_t;
/** implementation of D-Bus GetConfig() for m_configName as server configuration */
void getConfig(bool getTemplate,
@@ -161,7 +161,7 @@ class DBusServer : public DBusObjectHelper
void connect(const Caller_t &caller,
const boost::shared_ptr<Watch> &watch,
- const std::map<std::string, std::string> &peer,
+ const StringMap &peer,
bool must_authenticate,
const std::string &session,
DBusObject_t &object);
@@ -426,6 +426,12 @@ class Session : public DBusObjectHelper,
private boost::noncopyable
{
DBusServer &m_server;
+ const std::string m_sessionID;
+
+ bool m_serverMode;
+ SharedBuffer m_initialMessage;
+ string m_initialMessageType;
+
boost::weak_ptr<Connection> m_connection;
std::string m_connectionError;
bool m_useConnection;
@@ -517,6 +523,7 @@ public:
void setPriority(int priority) { m_priority = priority; }
int getPriority() const { return m_priority; }
+ void initServer(SharedBuffer data, const std::string &messageType);
void setConnection(const boost::shared_ptr<Connection> c) { m_connection = c; m_useConnection = c; }
boost::weak_ptr<Connection> getConnection() { return m_connection; }
bool useConnection() { return m_useConnection; }
@@ -567,7 +574,7 @@ public:
SyncSource &source,
int32_t extra1, int32_t extra2, int32_t extra3);
- typedef std::map<std::string, std::string> SourceModes_t;
+ typedef StringMap SourceModes_t;
void sync(const std::string &mode, const SourceModes_t &source_modes);
void abort();
void suspend();
@@ -589,7 +596,7 @@ public:
class Connection : public DBusObjectHelper, public Resource
{
DBusServer &m_server;
- std::map<std::string, std::string> m_peer;
+ StringMap m_peer;
bool m_mustAuthenticate;
enum {
SETUP, /**< ready for first message */
@@ -626,7 +633,7 @@ class Connection : public DBusObjectHelper, public Resource
/**
* returns "<description> (<ID> via <transport> <transport_description>)"
*/
- static std::string buildDescription(const std::map<std::string, std::string> &peer);
+ static std::string buildDescription(const StringMap &peer);
void process(const Caller_t &caller,
const std::pair<size_t, const uint8_t *> &message,
@@ -637,7 +644,7 @@ class Connection : public DBusObjectHelper, public Resource
EmitSignal0 abort;
EmitSignal5<const std::pair<size_t, const uint8_t *> &,
const std::string &,
- const std::map<std::string, std::string> &,
+ const StringMap &,
bool,
const std::string &> reply;
@@ -649,7 +656,7 @@ public:
Connection(DBusServer &server,
const DBusConnectionPtr &conn,
const std::string &session_num,
- const std::map<std::string, std::string> &peer,
+ const StringMap &peer,
bool must_authenticate);
~Connection();
@@ -794,6 +801,13 @@ void Session::setConfig(bool update, bool clear, bool temporary,
throw std::runtime_error("not implemented yet");
}
+void Session::initServer(SharedBuffer data, const std::string &messageType)
+{
+ m_serverMode = true;
+ m_initialMessage = data;
+ m_initialMessageType = messageType;
+}
+
void Session::sync(const std::string &mode, const SourceModes_t &source_modes)
{
if (!m_active) {
@@ -804,6 +818,11 @@ void Session::sync(const std::string &mode, const SourceModes_t &source_modes)
}
m_sync.reset(new DBusSync(m_configName, *this));
+ if (m_serverMode) {
+ m_sync->initServer(m_sessionID,
+ m_initialMessage,
+ m_initialMessageType);
+ }
// Apply temporary config filters. The parameters of this function
// override the source filters, if set.
@@ -917,6 +936,8 @@ Session::Session(DBusServer &server,
"org.syncevolution.Session"),
ReadOperations(config_name),
m_server(server),
+ m_sessionID(session),
+ m_serverMode(false),
m_useConnection(false),
m_active(false),
m_done(false),
@@ -1055,9 +1076,9 @@ void Connection::failed(const std::string &reason)
m_state = FAILED;
}
-std::string Connection::buildDescription(const std::map<std::string, std::string> &peer)
+std::string Connection::buildDescription(const StringMap &peer)
{
- std::map<std::string, std::string>::const_iterator
+ StringMap::const_iterator
desc = peer.find("description"),
id = peer.find("id"),
trans = peer.find("transport"),
@@ -1113,6 +1134,7 @@ void Connection::process(const Caller_t &caller,
switch (m_state) {
case SETUP: {
std::string config;
+ bool serverMode = false;
// check message type, determine whether we act
// as client or server, choose config
if (message_type == "HTTP Config") {
@@ -1139,6 +1161,14 @@ void Connection::process(const Caller_t &caller,
}
// TODO: use the session ID set by the server if non-null
+ } else if (message_type == TransportAgent::m_contentTypeSyncML ||
+ message_type == TransportAgent::m_contentTypeSyncWBXML) {
+ // run a new SyncML session as server
+ serverMode = true;
+ if (m_peer.find("config") == m_peer.end()) {
+ throw runtime_error("must choose config in Server.Connect()");
+ }
+ config = m_peer["config"];
} else {
throw runtime_error("message type not supported for starting a sync");
}
@@ -1148,6 +1178,11 @@ void Connection::process(const Caller_t &caller,
m_session.reset(new Session(m_server,
config,
m_sessionID));
+ if (serverMode) {
+ m_session->initServer(SharedBuffer(reinterpret_cast<const char *>(message.second),
+ message.first),
+ message_type);
+ }
m_session->setPriority(Session::PRI_CONNECTION);
m_session->setConnection(myself);
// this will be reset only when the connection shuts down okay
@@ -1172,8 +1207,15 @@ void Connection::process(const Caller_t &caller,
}
break;
case FINAL:
- case DONE:
+ if (m_loop) {
+ g_main_loop_quit(m_loop);
+ m_loop = NULL;
+ }
+ m_state = FAILED;
+ m_session->setConnectionError("connection closed unexpectedly due to incoming message while expecing Close()");
throw std::runtime_error("protocol error: final reply sent, no further message processing possible");
+ case DONE:
+ throw std::runtime_error("protocol error: connection closed, no further message processing possible");
break;
case FAILED:
throw std::runtime_error(m_failure);
@@ -1226,7 +1268,7 @@ void Connection::shutdown()
Connection::Connection(DBusServer &server,
const DBusConnectionPtr &conn,
const std::string &sessionID,
- const std::map<std::string, std::string> &peer,
+ const StringMap &peer,
bool must_authenticate) :
DBusObjectHelper(conn.get(),
std::string("/org/syncevolution/Connection/") + sessionID,
@@ -1342,7 +1384,7 @@ void DBusTransportAgent::send(const char *data, size_t len)
connection->m_incomingMsg = SharedBuffer();
// TODO: turn D-Bus exceptions into transport exceptions
- std::map<std::string, std::string> meta;
+ StringMap meta;
meta["URL"] = m_url;
connection->reply(std::make_pair(len, reinterpret_cast<const uint8_t *>(data)),
m_type, meta, false, connection->m_sessionID);
@@ -1360,7 +1402,7 @@ void DBusTransportAgent::shutdown()
// send final, empty message and wait for close
connection->m_state = Connection::FINAL;
connection->reply(std::pair<size_t, const uint8_t *>(0, 0),
- "", std::map<std::string, std::string>(),
+ "", StringMap(),
true, connection->m_sessionID);
}
@@ -1482,7 +1524,7 @@ void DBusServer::detachClient(const Caller_t &caller)
void DBusServer::connect(const Caller_t &caller,
const boost::shared_ptr<Watch> &watch,
- const std::map<std::string, std::string> &peer,
+ const StringMap &peer,
bool must_authenticate,
const std::string &session,
DBusObject_t &object)
@@ -1523,6 +1565,7 @@ void DBusServer::startSession(const Caller_t &caller,
boost::shared_ptr<Session> session(new Session(*this,
server,
new_session));
+ // TODO: how do we decide whether this is a client or server session?
client->attach(session);
session->activate();
enqueue(session);
@@ -1568,7 +1611,7 @@ void DBusServer::activate()
makeMethodEntry<DBusServer,
const Caller_t &,
const boost::shared_ptr<Watch> &,
- const std::map<std::string, std::string> &,
+ const StringMap &,
bool,
const std::string &,
DBusObject_t &,
diff --git a/src/syncevo/Makefile.am b/src/syncevo/Makefile.am
index 847ad8e7..912455a3 100644
--- a/src/syncevo/Makefile.am
+++ b/src/syncevo/Makefile.am
@@ -108,6 +108,7 @@ libsyncevolution_la_SOURCES = $(SYNCEVOLUTION_SOURCES)
libsyncevolution_la_LIBADD = @EPACKAGE_LIBS@ @GLIB_LIBS@ $(SYNTHESIS_LIBS) $(TRANSPORT_LIBS) @LIBS@ $(SYNCEVOLUTION_LDADD)
libsyncevolution_la_CXXFLAGS = $(TRANSPORT_CFLAGS) $(SYNCEVOLUTION_CXXFLAGS) $(SYNTHESIS_CFLAGS)
libsyncevolution_la_CPPFLAGS = $(AM_CPPFLAGS) -DTEMPLATE_DIR=\""$(sysconfdir)/default/syncevolution"\" -DLIBDIR=\""$(libdir)"\"
+libsyncevolution_la_DEPENDENCIES = $(SYNTHESIS_DEP)
if ENABLE_MODULES
libsyncevolution_la_LDFLAGS =
diff --git a/src/syncevo/SyncContext.cpp b/src/syncevo/SyncContext.cpp
index edddc582..2fc69894 100644
--- a/src/syncevo/SyncContext.cpp
+++ b/src/syncevo/SyncContext.cpp
@@ -107,7 +107,8 @@ SyncContext::SyncContext(const string &server,
m_server(server),
m_doLogging(doLogging),
m_quiet(false),
- m_dryrun(false)
+ m_dryrun(false),
+ m_serverMode(false)
{
}
@@ -1299,6 +1300,56 @@ void SyncContext::getConfigXML(string &xml, string &configname)
size_t index;
unsigned long hash = 0;
+ substTag(xml,
+ "clientorserver",
+ m_serverMode ?
+ // TODO: authentication handling.
+ //
+ // Not required: <plugin_sessionauth>no, <requested/requiredauth>none
+ // Specific password: <plugin_sessionauth>no, <simpleauthuser/passwd>
+ string(
+ " <server type='plugin'>\n"
+ " <plugin_module>[SDK_textdb]</plugin_module>\n"
+ " <plugin_sessionauth>yes</plugin_sessionauth>\n"
+ " <plugin_deviceadmin>yes</plugin_deviceadmin>\n"
+ " <plugin_params>\n"
+ " <datafilepath>") + getSynthesisDatadir() + "</datafilepath>\n"
+ " </plugin_params>\n"
+ "\n"
+ " <sessioninitscript><![CDATA[\n"
+ " // these variables are possibly modified by rule scripts\n"
+ " TIMESTAMP mindate; // earliest date remote party can handle\n"
+ " INTEGER retransfer_body; // if set to true, body is re-sent to client when message is moved from outbox to sent\n"
+ " mindate=EMPTY; // no limit by default\n"
+ " retransfer_body=FALSE; // normally, do not retransfer email body (and attachments) when moving items to sent box\n"
+ " ]]></sessioninitscript>\n"
+ " <sessiontimeout>300</sessiontimeout>\n"
+ "\n"
+ " <defaultauth/>\n"
+ "\n"
+ " <datastore/>\n"
+ "\n"
+ " <remoterules/>\n"
+ " </server>\n"
+ :
+ " <client type='plugin'>\n"
+ " <binfilespath>$(binfilepath)</binfilespath>\n"
+ " <defaultauth/>\n"
+ "\n"
+ // SyncEvolution has traditionally not folded long lines in
+ // vCard. Testing showed that servers still have problems with
+ // it, so avoid it by default
+ " <donotfoldcontent>yes</donotfoldcontent>\n"
+ "\n"
+ " <fakedeviceid/>\n"
+ "\n"
+ " <datastore/>\n"
+ "\n"
+ " <remoterules/>\n"
+ " </client>\n",
+ true
+ );
+
tag = "<debug/>";
index = xml.find(tag);
if (index != xml.npos) {
@@ -1388,9 +1439,16 @@ void SyncContext::getConfigXML(string &xml, string &configname)
substTag(xml, "fieldlists", fragments.m_fieldlists.join(), true);
substTag(xml, "profiles", fragments.m_profiles.join(), true);
substTag(xml, "datatypes", fragments.m_datatypes.join(), true);
- substTag(xml, "remoterules", fragments.m_remoterules.join(), true);
+ substTag(xml, "remoterules",
+ string("<remoterule name='EVOLUTION'><deviceid>none - this rule is activated via its name in MAKE/PARSETEXTWITHPROFILE() macro calls</deviceid></remoterule>\n") +
+ fragments.m_remoterules.join(),
+ true);
- substTag(xml, "fakedeviceid", getDevID());
+ if (m_serverMode) {
+ // TODO: set the device ID for an OBEX server
+ } else {
+ substTag(xml, "fakedeviceid", getDevID());
+ }
substTag(xml, "model", getMod());
substTag(xml, "manufacturer", getMan());
substTag(xml, "hardwareversion", getHwv());
@@ -1399,7 +1457,17 @@ void SyncContext::getConfigXML(string &xml, string &configname)
substTag(xml, "devicetype", getDevType());
substTag(xml, "maxmsgsize", std::max(getMaxMsgSize(), 10000ul));
substTag(xml, "maxobjsize", std::max(getMaxObjSize(), 1024u));
- substTag(xml, "defaultauth", getClientAuthType());
+ if (m_serverMode) {
+ substTag(xml, "defaultauth",
+ "<requestedauth>md5</requestedauth>\n"
+ "<requiredauth>md5</requiredauth>\n"
+ "<autononce>yes</autononce>\n"
+ "<simpleauthuser>test</simpleauthuser>\n"
+ "<simpleauthpw>test</simpleauthpw>\n",
+ true);
+ } else {
+ substTag(xml, "defaultauth", getClientAuthType());
+ }
// if the hash code is changed, that means the content of the
// config has changed, save the new hash and regen the configdate
@@ -1416,14 +1484,18 @@ SharedEngine SyncContext::createEngine()
{
SharedEngine engine(new sysync::TEngineModuleBridge);
- // Use libsynthesis that we were linked against. The name of a
- // .so could be given here, too, to use that instead. This
- // instance of the engine is used outside of the sync session
- // itself. doSync() then creates another engine for the sync
- // itself. That is necessary because the engine shutdown depends
- // on the context of the sync (in particular instantiated sync
- // sources).
- engine.Connect("[]", 0,
+ // This instance of the engine is used outside of the sync session
+ // itself for logging. doSync() then reinitializes it with a full
+ // datastore configuration.
+ engine.Connect(m_serverMode ?
+#ifdef ENABLE_SYNCML_LINKED
+ // use Synthesis client or server engine that we were linked against
+ "[server:]" : "[]",
+#else
+ // load engine dynamically
+ "server:libsynthesis.so.0" : "libsynthesis.so.0",
+#endif
+ 0,
sysync::DBG_PLUGIN_NONE|
sysync::DBG_PLUGIN_INT|
sysync::DBG_PLUGIN_DB|
@@ -1434,7 +1506,7 @@ SharedEngine SyncContext::createEngine()
engine.SetStrValue(configvars, "defout_path",
logdir.size() ? logdir : "/dev/null");
engine.SetStrValue(configvars, "conferrpath", "console");
- engine.SetStrValue(configvars, "binfilepath", getRootPath() + "/.synthesis");
+ engine.SetStrValue(configvars, "binfilepath", getSynthesisDatadir().c_str());
configvars.reset();
return engine;
@@ -1447,6 +1519,17 @@ namespace {
}
}
+void SyncContext::initServer(const std::string &sessionID,
+ SharedBuffer data,
+ const std::string &messageType)
+{
+ m_serverMode = true;
+ m_sessionID = sessionID;
+ m_initialMessage = data;
+ m_initialMessageType = messageType;
+
+}
+
SyncMLStatus SyncContext::sync(SyncReport *report)
{
SyncMLStatus status = STATUS_OK;
@@ -1605,77 +1688,98 @@ SyncMLStatus SyncContext::doSync()
m_engine.InitEngineXML(xml.c_str());
SE_LOG_DEV(NULL, NULL, "Full XML configuration:\n%s", xml.c_str());
- // check the settings status (MUST BE DONE TO MAKE SETTINGS READY)
- SharedKey profiles = m_engine.OpenKeyByPath(SharedKey(), "/profiles");
- m_engine.GetStrValue(profiles, "settingsstatus");
- // allow creating new settings when existing settings are not up/downgradeable
- m_engine.SetStrValue(profiles, "overwrite", "1");
- // check status again
- m_engine.GetStrValue(profiles, "settingsstatus");
+ SharedKey targets;
+ SharedKey target;
+ if (m_serverMode) {
+ // Server engine has no profiles. All settings have be done
+ // via the XML configuration or function parameters (session ID
+ // in OpenSession()).
+ } else {
+ // check the settings status (MUST BE DONE TO MAKE SETTINGS READY)
+ SharedKey profiles = m_engine.OpenKeyByPath(SharedKey(), "/profiles");
+ m_engine.GetStrValue(profiles, "settingsstatus");
+ // allow creating new settings when existing settings are not up/downgradeable
+ m_engine.SetStrValue(profiles, "overwrite", "1");
+ // check status again
+ m_engine.GetStrValue(profiles, "settingsstatus");
- // open first profile
- SharedKey profile;
- try {
- profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_FIRST);
- } catch (NoSuchKey error) {
- // no profile exists yet, create default profile
- profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_NEW_DEFAULT);
- }
+ // open first profile
+ SharedKey profile;
+ try {
+ profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_FIRST);
+ } catch (NoSuchKey error) {
+ // no profile exists yet, create default profile
+ profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_NEW_DEFAULT);
+ }
- m_engine.SetStrValue(profile, "serverURI", getSyncURL());
- m_engine.SetStrValue(profile, "serverUser", getUsername());
- m_engine.SetStrValue(profile, "serverPassword", getPassword());
- m_engine.SetInt32Value(profile, "encoding",
- getWBXML() ? 1 /* WBXML */ : 2 /* XML */);
-
- // Iterate over all data stores in the XML config
- // and match them with sync sources.
- // TODO: let sync sources provide their own
- // XML snippets (inside <client> and inside <datatypes>).
- SharedKey targets = m_engine.OpenKeyByPath(profile, "targets");
- SharedKey target;
+ m_engine.SetStrValue(profile, "serverURI", getSyncURL());
+ m_engine.SetStrValue(profile, "serverUser", getUsername());
+ m_engine.SetStrValue(profile, "serverPassword", getPassword());
+ m_engine.SetInt32Value(profile, "encoding",
+ getWBXML() ? 1 /* WBXML */ : 2 /* XML */);
+
+ // Iterate over all data stores in the XML config
+ // and match them with sync sources.
+ // TODO: let sync sources provide their own
+ // XML snippets (inside <client> and inside <datatypes>).
+ targets = m_engine.OpenKeyByPath(profile, "targets");
- try {
- target = m_engine.OpenSubkey(targets, sysync::KEYVAL_ID_FIRST);
- while (true) {
- s = m_engine.GetStrValue(target, "dbname");
- SyncSource *source = (*m_sourceListPtr)[s];
- if (source) {
- m_engine.SetInt32Value(target, "enabled", 1);
- int slow = 0;
- int direction = 0;
- string mode = source->getSync();
- if (!strcasecmp(mode.c_str(), "slow")) {
- slow = 1;
- direction = 0;
- } else if (!strcasecmp(mode.c_str(), "two-way")) {
- slow = 0;
- direction = 0;
- } else if (!strcasecmp(mode.c_str(), "refresh-from-server")) {
- slow = 1;
- direction = 1;
- } else if (!strcasecmp(mode.c_str(), "refresh-from-client")) {
- slow = 1;
- direction = 2;
- } else if (!strcasecmp(mode.c_str(), "one-way-from-server")) {
- slow = 0;
- direction = 1;
- } else if (!strcasecmp(mode.c_str(), "one-way-from-client")) {
- slow = 0;
- direction = 2;
+ try {
+ target = m_engine.OpenSubkey(targets, sysync::KEYVAL_ID_FIRST);
+ while (true) {
+ s = m_engine.GetStrValue(target, "dbname");
+ SyncSource *source = (*m_sourceListPtr)[s];
+ if (source) {
+ m_engine.SetInt32Value(target, "enabled", 1);
+ int slow = 0;
+ int direction = 0;
+ string mode = source->getSync();
+ if (!strcasecmp(mode.c_str(), "slow")) {
+ slow = 1;
+ direction = 0;
+ } else if (!strcasecmp(mode.c_str(), "two-way")) {
+ slow = 0;
+ direction = 0;
+ } else if (!strcasecmp(mode.c_str(), "refresh-from-server")) {
+ slow = 1;
+ direction = 1;
+ } else if (!strcasecmp(mode.c_str(), "refresh-from-client")) {
+ slow = 1;
+ direction = 2;
+ } else if (!strcasecmp(mode.c_str(), "one-way-from-server")) {
+ slow = 0;
+ direction = 1;
+ } else if (!strcasecmp(mode.c_str(), "one-way-from-client")) {
+ slow = 0;
+ direction = 2;
+ } else {
+ source->throwError(string("invalid sync mode: ") + mode);
+ }
+ m_engine.SetInt32Value(target, "forceslow", slow);
+ m_engine.SetInt32Value(target, "syncmode", direction);
+
+ m_engine.SetStrValue(target, "remotepath", source->getURI());
} else {
- source->throwError(string("invalid sync mode: ") + mode);
+ m_engine.SetInt32Value(target, "enabled", 0);
}
- m_engine.SetInt32Value(target, "forceslow", slow);
- m_engine.SetInt32Value(target, "syncmode", direction);
-
- m_engine.SetStrValue(target, "remotepath", source->getURI());
- } else {
- m_engine.SetInt32Value(target, "enabled", 0);
+ target = m_engine.OpenSubkey(targets, sysync::KEYVAL_ID_NEXT);
}
- target = m_engine.OpenSubkey(targets, sysync::KEYVAL_ID_NEXT);
+ } catch (NoSuchKey error) {
}
- } catch (NoSuchKey error) {
+
+ // Close all keys so that engine can flush the modified config.
+ // Otherwise the session reads the unmodified values from the
+ // created files while the updated values are still in memory.
+ target.reset();
+ targets.reset();
+ profile.reset();
+ profiles.reset();
+
+ // reopen profile keys
+ profiles = m_engine.OpenKeyByPath(SharedKey(), "/profiles");
+ m_engine.GetStrValue(profiles, "settingsstatus");
+ profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_FIRST);
+ targets = m_engine.OpenKeyByPath(profile, "targets");
}
m_retryInterval = getRetryInterval();
@@ -1685,26 +1789,26 @@ SyncMLStatus SyncContext::doSync()
// run an HTTP client sync session
boost::shared_ptr<TransportAgent> agent(createTransportAgent());
- // Close all keys so that engine can flush the modified config.
- // Otherwise the session reads the unmodified values from the
- // created files while the updated values are still in memory.
- target.reset();
- targets.reset();
- profile.reset();
- profiles.reset();
-
- // reopen profile keys
- profiles = m_engine.OpenKeyByPath(SharedKey(), "/profiles");
- m_engine.GetStrValue(profiles, "settingsstatus");
- profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_FIRST);
- targets = m_engine.OpenKeyByPath(profile, "targets");
-
sysync::TEngineProgressInfo progressInfo;
- sysync::uInt16 stepCmd = sysync::STEPCMD_CLIENTSTART; // first step
- SharedSession session = m_engine.OpenSession();
+ sysync::uInt16 stepCmd =
+ m_serverMode ?
+ sysync::STEPCMD_GOTDATA :
+ sysync::STEPCMD_CLIENTSTART;
+ SharedSession session = m_engine.OpenSession(m_sessionID);
SharedBuffer sendBuffer;
SessionSentinel sessionSentinel(*this, session);
+ if (m_serverMode) {
+ m_engine.WriteSyncMLBuffer(session,
+ m_initialMessage.get(),
+ m_initialMessage.size());
+ SharedKey sessionKey = m_engine.OpenSessionKey(session);
+ m_engine.SetStrValue(sessionKey,
+ "contenttype",
+ m_initialMessageType);
+ m_initialMessage.reset();
+ }
+
// Sync main loop: runs until SessionStep() signals end or error.
// Exceptions are caught and lead to a call of SessionStep() with
// parameter STEPCMD_ABORT -> abort session as soon as possible.
@@ -1796,21 +1900,23 @@ SyncMLStatus SyncContext::doSync()
progressInfo.extra3);
break;
default:
- // specific for a certain sync source:
- // find it...
- target = m_engine.OpenSubkey(targets, progressInfo.targetID);
- s = m_engine.GetStrValue(target, "dbname");
- SyncSource *source = (*m_sourceListPtr)[s];
- if (source) {
- displaySourceProgress(sysync::TProgressEventEnum(progressInfo.eventtype),
- *source,
- progressInfo.extra1,
- progressInfo.extra2,
- progressInfo.extra3);
- } else {
- throwError(std::string("unknown target ") + s);
+ if (!m_serverMode) {
+ // specific for a certain sync source:
+ // find it...
+ target = m_engine.OpenSubkey(targets, progressInfo.targetID);
+ s = m_engine.GetStrValue(target, "dbname");
+ SyncSource *source = (*m_sourceListPtr)[s];
+ if (source) {
+ displaySourceProgress(sysync::TProgressEventEnum(progressInfo.eventtype),
+ *source,
+ progressInfo.extra1,
+ progressInfo.extra2,
+ progressInfo.extra3);
+ } else {
+ throwError(std::string("unknown target ") + s);
+ }
+ target.reset();
}
- target.reset();
break;
}
}
@@ -1830,12 +1936,16 @@ SyncMLStatus SyncContext::doSync()
case sysync::STEPCMD_SENDDATA: {
// send data to remote
- // use OpenSessionKey() and GetValue() to retrieve "connectURI"
- // and "contenttype" to be used to send data to the server
SharedKey sessionKey = m_engine.OpenSessionKey(session);
- s = m_engine.GetStrValue(sessionKey,
- "connectURI");
- agent->setURL(s);
+ if (m_serverMode) {
+ agent->setURL("");
+ } else {
+ // use OpenSessionKey() and GetValue() to retrieve "connectURI"
+ // and "contenttype" to be used to send data to the server
+ s = m_engine.GetStrValue(sessionKey,
+ "connectURI");
+ agent->setURL(s);
+ }
s = m_engine.GetStrValue(sessionKey,
"contenttype");
agent->setContentType(s);
@@ -1899,6 +2009,12 @@ SyncMLStatus SyncContext::doSync()
m_engine.WriteSyncMLBuffer(session,
reply,
replylen);
+ if (m_serverMode) {
+ SharedKey sessionKey = m_engine.OpenSessionKey(session);
+ m_engine.SetStrValue(sessionKey,
+ "contenttype",
+ contentType);
+ }
stepCmd = sysync::STEPCMD_GOTDATA; // we have received response data
break;
} else {
@@ -1986,7 +2102,9 @@ SyncMLStatus SyncContext::doSync()
if (!status) {
try {
agent->shutdown();
+ // TODO: implement timeout for peers which fail to respond
while (agent->wait(true) == TransportAgent::ACTIVE) {
+ // TODO: allow aborting the sync here
}
} catch (...) {
status = handleException();
diff --git a/src/syncevo/SyncContext.h b/src/syncevo/SyncContext.h
index 8f638d37..18320006 100644
--- a/src/syncevo/SyncContext.h
+++ b/src/syncevo/SyncContext.h
@@ -73,6 +73,11 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
bool m_quiet;
bool m_dryrun;
+ bool m_serverMode;
+ std::string m_sessionID;
+ SharedBuffer m_initialMessage;
+ string m_initialMessageType;
+
/**
* flags for suspend and abort
*/
@@ -130,6 +135,19 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
static SuspendFlags& getSuspendFlags() {return s_flags;}
/**
+ * Initializes the session so that it runs as SyncML server once
+ * sync() is called. For this to work the first client message
+ * must be available already.
+ *
+ * @param sessionID session ID to be used by server
+ * @param data content of initial message sent by the client
+ * @param messageType content type set by the client
+ */
+ void initServer(const std::string &sessionID,
+ SharedBuffer data,
+ const std::string &messageType);
+
+ /**
* Executes the sync, throws an exception in case of failure.
* Handles automatic backups and report generation.
*
@@ -501,6 +519,13 @@ class SyncContext : public SyncConfig, public ConfigUserInterface {
*/
SyncMLStatus doSync();
+ /**
+ * directory for Synthesis client binfiles or
+ * Synthesis server textdb files, unique for each
+ * peer
+ */
+ string getSynthesisDatadir() { return getRootPath() + "/.synthesis"; }
+
// total retry duration
int m_retryDuration;
// message resend interval
diff --git a/src/syncevo/SynthesisEngine.cpp b/src/syncevo/SynthesisEngine.cpp
index 0293eec2..e2330626 100644
--- a/src/syncevo/SynthesisEngine.cpp
+++ b/src/syncevo/SynthesisEngine.cpp
@@ -66,10 +66,11 @@ public:
};
-SharedSession SharedEngine::OpenSession()
+SharedSession SharedEngine::OpenSession(const string &aSessionID)
{
sysync::SessionH sessionH = NULL;
- sysync::TSyError err = m_engine->OpenSession(sessionH);
+ sysync::TSyError err = m_engine->OpenSession(sessionH, 0,
+ aSessionID.empty() ? NULL : aSessionID.c_str());
if (err) {
throw BadSynthesisResult("opening session failed", static_cast<sysync::TSyErrorEnum>(err));
}
diff --git a/src/syncevo/SynthesisEngine.h b/src/syncevo/SynthesisEngine.h
index 6a0ada61..b4a9670d 100644
--- a/src/syncevo/SynthesisEngine.h
+++ b/src/syncevo/SynthesisEngine.h
@@ -93,7 +93,7 @@ class SharedEngine {
void Disconnect();
void InitEngineXML(const string &aConfigXML);
- SharedSession OpenSession();
+ SharedSession OpenSession(const string &aSessionID);
SharedKey OpenSessionKey(SharedSession &aSessionH);
void SessionStep(const SharedSession &aSessionH,
diff --git a/test/syncevo-http-server.py b/test/syncevo-http-server.py
new file mode 100755
index 00000000..a1b931fd
--- /dev/null
+++ b/test/syncevo-http-server.py
@@ -0,0 +1,191 @@
+#! /usr/bin/python
+
+'''Usage: syncevo-http-server.py <URL> <default config>
+Runs a SyncML HTTP server under the given base URL,
+using one specific configuration.'''
+
+# use the same glib main loop in D-Bus and twisted
+from dbus.mainloop.glib import DBusGMainLoop
+from twisted.internet import glib2reactor # for non-GUI apps
+DBusGMainLoop(set_as_default=True)
+glib2reactor.install()
+
+import dbus
+import gobject
+import sys
+import urlparse
+
+import twisted.web
+from twisted.web import server, resource, http
+from twisted.internet import reactor
+
+bus = dbus.SessionBus()
+loop = gobject.MainLoop()
+
+def session_changed(object, ready):
+ print "SessionChanged:", object, ready
+
+bus.add_signal_receiver(session_changed,
+ 'SessionChanged',
+ 'org.syncevolution.Server',
+ 'org.syncevolution',
+ None,
+ byte_arrays=True)
+
+class SyncMLSession:
+ sessions = []
+
+ def __init__(self):
+ self.sessionid = None
+ self.request = None
+ self.conpath = None
+ self.connection = None
+
+ def destruct(self, code, message=""):
+ '''Tell both HTTP client and D-Bus server that we are shutting down,
+ then remove the session'''
+ if self.request:
+ self.request.setResponseCode(code, message)
+ self.request.finish()
+ self.request = None
+ if self.connection:
+ self.connection.Close(False, message)
+ self.connection = None
+ if self in SyncMLSession.sessions:
+ SyncMLSession.sessions.remove(self)
+
+ def abort(self):
+ '''D-Bus server requests to close connection, so cancel everything'''
+ print "connection", self.conpath, "went down"
+ self.destruct(http.INTERNAL_SERVER_ERROR, "lost connection to SyncEvolution")
+
+ def reply(self, data, type, meta, final, session):
+ '''sent reply to HTTP client and/or close down normally'''
+ print "reply session", session, "final", final, "data len", len(data), meta
+ # When the D-Bus server sends an empty array, Python binding
+ # puts the four chars in 'None' into the data array?!
+ if data and len(data) > 0 and data != 'None':
+ request = self.request
+ self.request = None
+ if request:
+ request.setHeader('Content-Type', type)
+ request.setResponseCode(http.OK)
+ request.write(data)
+ request.finish()
+ self.sessionid = session
+ else:
+ self.connection.Close(False, "could not deliver reply")
+ self.connection = None
+ if final:
+ print "closing connection for connection", self.conpath, "session", session
+ if self.connection:
+ self.connection.Close(True, "")
+ self.connection = None
+ self.destruct(http.GONE, "D-Bus server done")
+
+ def done(self, error):
+ '''lost connection to HTTP client, either normally or in error'''
+ if error and self.connection:
+ self.connection.Close(False, error)
+ self.connection = None
+
+ def start(self, request, config, url):
+ '''start a new session based on the incoming message'''
+ print "requesting new session"
+ self.object = dbus.Interface(bus.get_object('org.syncevolution',
+ '/org/syncevolution/Server'),
+ 'org.syncevolution.Server')
+ deferred = request.notifyFinish()
+ deferred.addCallback(self.done)
+ self.conpath = self.object.Connect({'description': 'syncevo-server-http.py',
+ 'transport': 'HTTP',
+ 'config': config,
+ 'URL': url},
+ False,
+ '')
+ self.connection = dbus.Interface(bus.get_object('org.syncevolution',
+ self.conpath),
+ 'org.syncevolution.Connection')
+
+ bus.add_signal_receiver(self.abort,
+ 'Abort',
+ 'org.syncevolution.Connection',
+ 'org.syncevolution',
+ self.conpath,
+ utf8_strings=True,
+ byte_arrays=True)
+ bus.add_signal_receiver(self.reply,
+ 'Reply',
+ 'org.syncevolution.Connection',
+ 'org.syncevolution',
+ self.conpath,
+ utf8_strings=True,
+ byte_arrays=True)
+
+ # feed new data into SyncEvolution and wait for reply
+ request.content.seek(0, 0)
+ self.connection.Process(request.content.read(),
+ request.getHeader('content-type'))
+ self.request = request
+ SyncMLSession.sessions.append(self)
+
+ def process(self, request):
+ '''process next message by client in running session'''
+ if self.request:
+ # message resend?! Ignore old request.
+ self.request.finish()
+ self.request = None
+ deferred = request.notifyFinish()
+ deferred.addCallback(self.done)
+ self.connection.Process(request.content.read(),
+ request.getHeader('content-type'))
+ self.request = request
+
+class SyncMLPost(resource.Resource):
+ isLeaf = True
+
+ def __init__(self, url, defaultconfig):
+ self.url = url
+ self.defaultconfig = defaultconfig
+
+ def render_GET(self, request):
+ return "<html>SyncEvolution SyncML Server</html>"
+
+ def render_POST(self, request):
+ config = request.postpath
+ if config:
+ config = config[0]
+ else:
+ config = self.defaultconfig
+ type = request.getHeader('content-type')
+ len = request.getHeader('content-length')
+ sessionid = request.args.get('sessionid')
+ if sessionid:
+ sessionid = sessionid[0]
+ print "POST from", request.getClientIP(), "config", config, "type", type, "session", sessionid, "args", request.args
+ # TODO: detect that a client is asking for a new session while
+ # an old one is still running and then abort the old session
+ if not sessionid:
+ session = SyncMLSession()
+ session.start(request, config,
+ urlparse.urljoin(self.url.geturl(), request.path))
+ return server.NOT_DONE_YET
+ else:
+ for session in SyncMLSession.sessions:
+ if session.sessionid == sessionid:
+ session.process(request)
+ return server.NOT_DONE_YET
+ raise twisted.web.Error(http.NOT_FOUND)
+
+
+def main():
+ url = urlparse.urlparse(sys.argv[1])
+ defaultconfig = sys.argv[2]
+ root = resource.Resource()
+ root.putChild(url.path[1:], SyncMLPost(url, defaultconfig))
+ site = server.Site(root)
+ reactor.listenTCP(url.port, site)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()