syncevolution/src/dbus/server/session.cpp

1496 lines
57 KiB
C++

/*
* Copyright (C) 2011 Intel Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include "session.h"
#include "connection.h"
#include "server.h"
#include "client.h"
#include "restart.h"
#include "info-req.h"
#include "session-common.h"
#include "dbus-callbacks.h"
#include "presence-status.h"
#include <syncevo/ForkExec.h>
#include <syncevo/SyncContext.h>
#include <syncevo/BoostHelper.h>
#ifdef USE_DLT
#include <syncevo/LogDLT.h>
#endif
#include <memory>
#include <boost/foreach.hpp>
using namespace GDBusCXX;
SE_BEGIN_CXX
/** A Proxy to the remote session. */
class SessionProxy : public GDBusCXX::DBusRemoteObject
{
public:
SessionProxy(const GDBusCXX::DBusConnectionPtr &conn, const std::string &instance) :
GDBusCXX::DBusRemoteObject(conn.get(),
std::string(SessionCommon::HELPER_PATH) + "/" + instance,
SessionCommon::HELPER_IFACE,
SessionCommon::HELPER_DESTINATION,
true), // This is a one-to-one connection. Close it.
/* m_getNamedConfig (*this, "GetNamedConfig"), */
/* m_setNamedConfig (*this, "SetNamedConfig"), */
/* m_getReports (*this, "GetReports"), */
/* m_checkSource (*this, "CheckSource"), */
/* m_getDatabases (*this, "GetDatabases"), */
m_sync(*this, "Sync"),
m_setFreeze(*this, "SetFreeze"),
m_restore(*this, "Restore"),
m_execute(*this, "Execute"),
m_passwordResponse(*this, "PasswordResponse"),
m_storeMessage(*this, "StoreMessage"),
m_connectionState(*this, "ConnectionState"),
/* m_abort (*this, "Abort"), */
/* m_suspend (*this, "Suspend"), */
/* m_getStatus (*this, "GetStatus"), */
/* m_getProgress (*this, "GetProgress"), */
/* m_restore (*this, "Restore"), */
/* m_execute (*this, "Execute"), */
/* m_serverShutdown (*this, "ServerShutdown"), */
/* m_passwordResponse (*this, "PasswordResponse"), */
/* m_setActive (*this, "SetActive"), */
/* m_statusChanged (*this, "StatusChanged", false), */
/* m_progressChanged (*this, "ProgressChanged", false), */
m_logOutput(*this, "LogOutput", false),
m_syncProgress(*this, "SyncProgress", false),
m_sourceProgress(*this, "SourceProgress", false),
m_sourceSynced(*this, "SourceSynced", false),
m_waiting(*this, "Waiting", false),
m_syncSuccessStart(*this, "SyncSuccessStart", false),
m_configChanged(*this, "ConfigChanged", false),
m_passwordRequest(*this, "PasswordRequest", false),
m_sendMessage(*this, "Message", false),
m_shutdownConnection(*this, "Shutdown", false)
{}
/* GDBusCXX::DBusClientCall1<ReadOperations::Config_t> m_getNamedConfig; */
/* GDBusCXX::DBusClientCall1<bool> m_setNamedConfig; */
/* GDBusCXX::DBusClientCall1<std::vector<StringMap> > m_getReports; */
/* GDBusCXX::DBusClientCall0 m_checkSource; */
/* GDBusCXX::DBusClientCall1<ReadOperations::SourceDatabases_t> m_getDatabases; */
GDBusCXX::DBusClientCall2<bool, SyncReport> m_sync;
GDBusCXX::DBusClientCall1<bool> m_setFreeze;
GDBusCXX::DBusClientCall1<bool> m_restore;
GDBusCXX::DBusClientCall1<bool> m_execute;
/* GDBusCXX::DBusClientCall0 m_serverShutdown; */
GDBusCXX::DBusClientCall0 m_passwordResponse;
GDBusCXX::DBusClientCall0 m_storeMessage;
GDBusCXX::DBusClientCall0 m_connectionState;
/* GDBusCXX::DBusClientCall0 m_setActive; */
/* GDBusCXX::SignalWatch3<std::string, uint32_t, */
/* SessionCommon::SourceStatuses_t> m_statusChanged; */
GDBusCXX::SignalWatch3<std::string, std::string, std::string> m_logOutput;
GDBusCXX::SignalWatch4<sysync::TProgressEventEnum,
int32_t, int32_t, int32_t> m_syncProgress;
GDBusCXX::SignalWatch6<sysync::TProgressEventEnum,
std::string, SyncMode,
int32_t, int32_t, int32_t> m_sourceProgress;
GDBusCXX::SignalWatch2<std::string, SyncSourceReport> m_sourceSynced;
GDBusCXX::SignalWatch1<bool> m_waiting;
GDBusCXX::SignalWatch0 m_syncSuccessStart;
GDBusCXX::SignalWatch0 m_configChanged;
GDBusCXX::SignalWatch2<std::string, ConfigPasswordKey> m_passwordRequest;
GDBusCXX::SignalWatch3<DBusArray<uint8_t>, std::string, std::string> m_sendMessage;
GDBusCXX::SignalWatch0 m_shutdownConnection;
};
void Session::attach(const Caller_t &caller)
{
boost::shared_ptr<Client> client(m_server.findClient(caller));
if (!client) {
throw runtime_error("unknown client");
}
boost::shared_ptr<Session> me = m_me.lock();
if (!me) {
throw runtime_error("session already deleted?!");
}
client->attach(me);
}
void Session::detach(const Caller_t &caller)
{
boost::shared_ptr<Client> client(m_server.findClient(caller));
if (!client) {
throw runtime_error("unknown client");
}
client->detach(this);
}
/**
* validate key/value property and copy it to the filter
* if okay
*/
static void copyProperty(const StringPair &keyvalue,
ConfigPropertyRegistry &registry,
FilterConfigNode::ConfigFilter &filter)
{
const std::string &name = keyvalue.first;
const std::string &value = keyvalue.second;
const ConfigProperty *prop = registry.find(name);
if (!prop) {
SE_THROW_EXCEPTION(InvalidCall, StringPrintf("unknown property '%s'", name.c_str()));
}
std::string error;
if (!prop->checkValue(value, error)) {
SE_THROW_EXCEPTION(InvalidCall, StringPrintf("invalid value '%s' for property '%s': '%s'",
value.c_str(), name.c_str(), error.c_str()));
}
filter.insert(std::make_pair(keyvalue.first, InitStateString(keyvalue.second, true)));
}
static void setSyncFilters(const ReadOperations::Config_t &config,FilterConfigNode::ConfigFilter &syncFilter,std::map<std::string, FilterConfigNode::ConfigFilter> &sourceFilters)
{
ReadOperations::Config_t::const_iterator it;
for (it = config.begin(); it != config.end(); ++it) {
map<string, string>::const_iterator sit;
string name = it->first;
if (name.empty()) {
ConfigPropertyRegistry &registry = SyncConfig::getRegistry();
for (sit = it->second.begin(); sit != it->second.end(); ++sit) {
// read-only properties can (and have to be) ignored
static const char *init[] = {
"configName",
"description",
"score",
"deviceName",
"hardwareName",
"templateName",
"fingerprint"
};
static const set< std::string, Nocase<std::string> >
special(init,
init + (sizeof(init) / sizeof(*init)));
if (special.find(sit->first) == special.end()) {
copyProperty(*sit, registry, syncFilter);
}
}
} else if (boost::starts_with(name, "source/")) {
name = name.substr(strlen("source/"));
FilterConfigNode::ConfigFilter &sourceFilter = sourceFilters[name];
ConfigPropertyRegistry &registry = SyncSourceConfig::getRegistry();
for (sit = it->second.begin(); sit != it->second.end(); ++sit) {
copyProperty(*sit, registry, sourceFilter);
}
} else {
SE_THROW_EXCEPTION(InvalidCall, StringPrintf("invalid config entry '%s'", name.c_str()));
}
}
}
void Session::setConfig(bool update, bool temporary,
const ReadOperations::Config_t &config)
{
setNamedConfig(m_configName, update, temporary, config);
}
void Session::setNamedConfig(const std::string &configName,
bool update, bool temporary,
const ReadOperations::Config_t &config)
{
PushLogger<Logger> guard(m_me);
if (m_runOperation != SessionCommon::OP_NULL) {
string msg = StringPrintf("%s started, cannot change configuration at this time", runOpToString(m_runOperation).c_str());
SE_THROW_EXCEPTION(InvalidCall, msg);
}
if (m_status != SESSION_ACTIVE) {
SE_THROW_EXCEPTION(InvalidCall, "session is not active, call not allowed at this time");
}
// avoid the check if effect is the same as setConfig()
if (m_configName != configName) {
bool found = false;
BOOST_FOREACH(const std::string &flag, m_flags) {
if (boost::iequals(flag, "all-configs")) {
found = true;
break;
}
}
if (!found) {
SE_THROW_EXCEPTION(InvalidCall,
"SetNameConfig() only allowed in 'all-configs' sessions");
}
if (temporary) {
SE_THROW_EXCEPTION(InvalidCall,
"SetNameConfig() with temporary config change only supported for config named when starting the session");
}
}
m_server.getPresenceStatus().updateConfigPeers (configName, config);
/** check whether we need remove the entire configuration */
if(!update && !temporary && config.empty()) {
boost::shared_ptr<SyncConfig> syncConfig(new SyncConfig(configName));
if(syncConfig.get()) {
syncConfig->remove();
m_setConfig = true;
}
return;
}
/*
* validate input config and convert to filters;
* if validation fails, no harm was done at this point yet
*/
FilterConfigNode::ConfigFilter syncFilter;
SourceFilters_t sourceFilters;
setSyncFilters(config, syncFilter, sourceFilters);
if (temporary) {
/* save temporary configs in session filters, either erasing old
temporary settings or adding to them */
if (update) {
m_syncFilter.insert(syncFilter.begin(), syncFilter.end());
BOOST_FOREACH(SourceFilters_t::value_type &source, sourceFilters) {
SourceFilters_t::iterator it = m_sourceFilters.find(source.first);
if (it != m_sourceFilters.end()) {
// add to existing source filter
it->second.insert(source.second.begin(), source.second.end());
} else {
// add source filter
m_sourceFilters.insert(source);
}
}
} else {
m_syncFilter = syncFilter;
m_sourceFilters = sourceFilters;
}
m_tempConfig = true;
} else {
/* need to save configurations */
boost::shared_ptr<SyncConfig> from(new SyncConfig(configName));
/* if it is not clear mode and config does not exist, an error throws */
if(update && !from->exists()) {
SE_THROW_EXCEPTION(NoSuchConfig, "The configuration '" + configName + "' doesn't exist" );
}
if(!update) {
list<string> sources = from->getSyncSources();
list<string>::iterator it;
for(it = sources.begin(); it != sources.end(); ++it) {
string source = "source/";
source += *it;
ReadOperations::Config_t::const_iterator configIt = config.find(source);
if(configIt == config.end()) {
/** if no config for this source, we remove it */
from->removeSyncSource(*it);
} else {
/** just clear visiable properties, remove them and their values */
from->clearSyncSourceProperties(*it);
}
}
from->clearSyncProperties();
}
/** generate new sources in the config map */
for (ReadOperations::Config_t::const_iterator it = config.begin(); it != config.end(); ++it) {
string sourceName = it->first;
if(sourceName.find("source/") == 0) {
sourceName = sourceName.substr(7); ///> 7 is the length of "source/"
from->getSyncSourceNodes(sourceName);
}
}
/* apply user settings */
from->setConfigFilter(true, "", syncFilter);
map<string, FilterConfigNode::ConfigFilter>::iterator it;
for (it = sourceFilters.begin(); it != sourceFilters.end(); ++it) {
from->setConfigFilter(false, it->first, it->second);
}
// We need no interactive user interface, but we do need to handle
// storing passwords in a keyring here.
boost::shared_ptr<SyncContext> syncConfig(new SyncContext(configName));
syncConfig->prepareConfigForWrite();
syncConfig->copy(*from, NULL);
class KeyringUI : public UserInterface {
InitStateString m_keyring;
public:
KeyringUI(const InitStateString &keyring) :
m_keyring(keyring)
{}
// Implement UserInterface.
virtual bool savePassword(const std::string &passwordName,
const std::string &password,
const ConfigPasswordKey &key)
{
return GetSavePasswordSignal()(m_keyring, passwordName, password, key);
}
virtual void readStdin(std::string &content) { SE_THROW("not implemented"); }
virtual std::string askPassword(const std::string &passwordName,
const std::string &descr,
const ConfigPasswordKey &key)
{
SE_THROW("not implemented");
return "";
}
} ui(syncConfig->getKeyring());
syncConfig->preFlush(ui);
syncConfig->flush();
m_setConfig = true;
}
}
void Session::initServer(SharedBuffer data, const std::string &messageType)
{
PushLogger<Logger> guard(m_me);
m_serverMode = true;
m_initialMessage = data;
m_initialMessageType = messageType;
}
void Session::syncExtended(const std::string &mode, const SessionCommon::SourceModes_t &sourceModes,
const StringMap &env)
{
PushLogger<Logger> guard(m_me);
if (m_runOperation == SessionCommon::OP_SYNC) {
string msg = StringPrintf("%s started, cannot start again", runOpToString(m_runOperation).c_str());
SE_THROW_EXCEPTION(InvalidCall, msg);
} else if (m_runOperation != SessionCommon::OP_NULL) {
string msg = StringPrintf("%s started, cannot start sync", runOpToString(m_runOperation).c_str());
SE_THROW_EXCEPTION(InvalidCall, msg);
}
if (m_status != SESSION_ACTIVE) {
SE_THROW_EXCEPTION(InvalidCall, "session is not active, call not allowed at this time");
}
m_syncMode = mode;
m_syncEnv = env;
// Turn session into "running sync" now, before returning to
// caller. Starting the helper (if needed) and making it
// execute the sync is part of "running sync".
runOperationAsync(SessionCommon::OP_SYNC,
boost::bind(&Session::sync2, this, mode, sourceModes),
env);
}
void Session::sync2(const std::string &mode, const SessionCommon::SourceModes_t &sourceModes)
{
PushLogger<Logger> guard(m_me);
if (!m_forkExecParent || !m_helper) {
SE_THROW("syncing cannot continue, helper died");
}
// helper is ready, tell it what to do
SyncParams params;
params.m_config = m_configName;
params.m_mode = mode;
params.m_sourceModes = sourceModes;
params.m_serverMode = m_serverMode;
params.m_serverAlerted = m_serverAlerted;
params.m_remoteInitiated = m_remoteInitiated;
params.m_sessionID = m_sessionID;
params.m_initialMessage = m_initialMessage;
params.m_initialMessageType = m_initialMessageType;
params.m_syncFilter = m_syncFilter;
params.m_sourceFilter = m_sourceFilter;
params.m_sourceFilters = m_sourceFilters;
boost::shared_ptr<Connection> c = m_connection.lock();
if (c && !c->mustAuthenticate()) {
// unsetting username/password disables checking them
params.m_syncFilter["password"] = InitStateString("", true);
params.m_syncFilter["username"] = InitStateString("", true);
}
// Relay messages between connection and helper.If the
// connection goes away, we need to tell the helper, because
// otherwise it will never know that its message went into nirvana
// and that it is waiting for a reply that will never come.
//
// We also need to send responses to the helper asynchronously
// and ignore failures -> do it in our code instead of connection
// signals directly.
//
// Session might quit before connection, so use instance
// tracking.
m_helper->m_sendMessage.activate(boost::bind(&Session::sendViaConnection,
this,
_1, _2, _3));
m_helper->m_shutdownConnection.activate(boost::bind(&Session::shutdownConnection,
this));
boost::shared_ptr<Connection> connection = m_connection.lock();
if (connection) {
connection->m_messageSignal.connect(Connection::MessageSignal_t::slot_type(&Session::storeMessage,
this,
_1, _2).track(m_me));
connection->m_statusSignal.connect(Connection::StatusSignal_t::slot_type(&Session::connectionState,
this,
_1));
}
// Helper implements Sync() asynchronously. If it completes
// normally, dbusResultCb() will call doneCb() directly. Otherwise
// the error is recorded before ending the session. Premature
// exits by the helper are handled by D-Bus, which then will abort
// the pending method call.
m_helper->m_sync.start(params, boost::bind(&Session::dbusResultCb, m_me, "sync()", _1, _2, _3));
}
void Session::abort()
{
PushLogger<Logger> guard(m_me);
if (m_runOperation != SessionCommon::OP_SYNC && m_runOperation != SessionCommon::OP_CMDLINE) {
SE_THROW_EXCEPTION(InvalidCall, "sync not started, cannot abort at this time");
}
if (m_forkExecParent) {
// Tell helper to abort via SIGTERM. The signal might get
// delivered so soon that the helper quits immediately.
// Treat that as "aborted by user" instead of failure
// in m_onQuit.
m_wasAborted = true;
m_forkExecParent->stop(SIGTERM);
}
if (m_syncStatus == SYNC_RUNNING ||
m_syncStatus == SYNC_SUSPEND) {
m_syncStatus = SYNC_ABORT;
fireStatus(true);
}
}
void Session::setFreezeAsync(bool freeze, const Result<void (bool)> &result)
{
PushLogger<Logger> guard(m_me);
SE_LOG_DEBUG(NULL, "session %s: SetFreeze(%s), %s",
getPath(),
freeze ? "freeze" : "thaw",
m_forkExecParent ? "send to helper" : "no effect, because no helper");
if (m_forkExecParent) {
m_helper->m_setFreeze.start(freeze,
boost::bind(&Session::setFreezeDone,
m_me,
_1, _2,
freeze,
result));
} else {
// Had no effect.
result.done(false);
}
}
void Session::setFreezeDone(bool changed, const std::string &error,
bool freeze,
const Result<void (bool)> &result)
{
PushLogger<Logger> guard(m_me);
try {
SE_LOG_DEBUG(NULL, "session %s: SetFreeze(%s) returned from helper %s, error %s",
getPath(),
freeze ? "freeze" : "thaw",
changed ? "changed freeze state" : "no effect",
error.c_str());
if (!error.empty()) {
Exception::tryRethrowDBus(error);
}
if (changed) {
m_freeze = freeze;
}
result.done(changed);
} catch (...) {
result.failed();
}
}
void Session::suspend()
{
PushLogger<Logger> guard(m_me);
if (m_runOperation != SessionCommon::OP_SYNC && m_runOperation != SessionCommon::OP_CMDLINE) {
SE_THROW_EXCEPTION(InvalidCall, "sync not started, cannot suspend at this time");
}
if (m_forkExecParent) {
// same as abort(), except that we use SIGINT
m_wasAborted = true;
m_forkExecParent->stop(SIGINT);
}
if (m_syncStatus == SYNC_RUNNING) {
m_syncStatus = SYNC_SUSPEND;
fireStatus(true);
}
}
void Session::abortAsync(const SimpleResult &result)
{
PushLogger<Logger> guard(m_me);
if (!m_forkExecParent) {
result.done();
} else {
// Tell helper to quit, if necessary by aborting a running sync.
// Once it is dead we know that the session no longer runs.
// This must succeed; there is no timeout or failure mode.
// TODO: kill helper after a certain amount of time?!
m_forkExecParent->stop(SIGTERM);
m_forkExecParent->m_onQuit.connect(boost::bind(&SimpleResult::done, result));
}
}
void Session::getStatus(std::string &status,
uint32_t &error,
SourceStatuses_t &sources)
{
PushLogger<Logger> guard(m_me);
status = syncStatusToString(m_syncStatus);
if (m_stepIsWaiting) {
status += ";waiting";
}
error = m_error;
sources = m_sourceStatus;
}
void Session::getAPIProgress(int32_t &progress,
APISourceProgresses_t &sources)
{
PushLogger<Logger> guard(m_me);
progress = m_progData.getProgress();
sources = m_sourceProgress;
}
void Session::getProgress(int32_t &progress,
SourceProgresses_t &sources)
{
PushLogger<Logger> guard(m_me);
progress = m_progData.getProgress();
sources = m_sourceProgress;
}
bool Session::getSyncSourceReport(const std::string &sourceName, SyncSourceReport &report) const
{
SyncSourceReports::const_iterator it = m_syncSourceReports.find(sourceName);
if (it != m_syncSourceReports.end()) {
report = it->second;
return true;
} else {
return false;
}
}
void Session::fireStatus(bool flush)
{
PushLogger<Logger> guard(m_me);
std::string status;
uint32_t error;
SourceStatuses_t sources;
/** not force flushing and not timeout, return */
if(!flush && !m_statusTimer.timeout()) {
return;
}
m_statusTimer.reset();
getStatus(status, error, sources);
m_statusSignal(status, error, sources);
}
void Session::fireProgress(bool flush)
{
PushLogger<Logger> guard(m_me);
int32_t progress;
SourceProgresses_t sources;
/** not force flushing and not timeout, return */
if(!flush && !m_progressTimer.timeout()) {
return;
}
m_progressTimer.reset();
getProgress(progress, sources);
m_progressSignal(progress, sources);
}
boost::shared_ptr<Session> Session::createSession(Server &server,
const std::string &peerDeviceID,
const std::string &config_name,
const std::string &session,
const std::vector<std::string> &flags)
{
boost::shared_ptr<Session> me(new Session(server, peerDeviceID, config_name, session, flags));
me->m_me = me;
return me;
}
static void SetProgress(Session::SourceProgresses_t &to, const Session::SourceProgresses_t &from)
{
to = from;
}
Session::Session(Server &server,
const std::string &peerDeviceID,
const std::string &config_name,
const std::string &session,
const std::vector<std::string> &flags) :
DBusObjectHelper(server.getConnection(),
std::string("/org/syncevolution/Session/") + session,
"org.syncevolution.Session",
boost::bind(&Server::autoTermCallback, &server)),
ReadOperations(config_name, server),
m_flags(flags),
m_sessionID(session),
m_peerDeviceID(peerDeviceID),
m_serverMode(false),
m_serverAlerted(false),
m_useConnection(false),
m_tempConfig(false),
m_setConfig(false),
m_status(SESSION_IDLE),
m_wasAborted(false),
m_remoteInitiated(false),
m_syncStatus(SYNC_QUEUEING),
m_stepIsWaiting(false),
m_priority(PRI_DEFAULT),
m_error(0),
m_lastProgressTimestamp(Timespec::monotonic()),
m_freeze(false),
m_statusTimer(100),
m_progressTimer(50),
m_restoreSrcTotal(0),
m_restoreSrcEnd(0),
m_runOperation(SessionCommon::OP_NULL),
m_cmdlineOp(SessionCommon::OP_CMDLINE),
emitStatus(*this, "StatusChanged"),
emitProgress(*this, "ProgressChanged")
{
add(this, &Session::attach, "Attach");
add(this, &Session::detach, "Detach");
add(this, &Session::getFlags, "GetFlags");
add(this, &Session::getNormalConfigName, "GetConfigName");
add(static_cast<ReadOperations *>(this), &ReadOperations::getConfigs, "GetConfigs");
add(static_cast<ReadOperations *>(this), &ReadOperations::getConfig, "GetConfig");
add(static_cast<ReadOperations *>(this), &ReadOperations::getNamedConfig, "GetNamedConfig");
add(this, &Session::setConfig, "SetConfig");
add(this, &Session::setNamedConfig, "SetNamedConfig");
add(static_cast<ReadOperations *>(this), &ReadOperations::getReports, "GetReports");
add(static_cast<ReadOperations *>(this), &ReadOperations::checkSource, "CheckSource");
add(static_cast<ReadOperations *>(this), &ReadOperations::getDatabases, "GetDatabases");
add(this, &Session::sync, "Sync");
add(this, &Session::abort, "Abort");
add(this, &Session::suspend, "Suspend");
add(this, &Session::getStatus, "GetStatus");
add(this, &Session::getAPIProgress, "GetProgress");
add(this, &Session::restore, "Restore");
add(this, &Session::checkPresence, "CheckPresence");
add(this, &Session::execute, "Execute");
add(emitStatus);
add(emitProgress);
m_statusSignal.connect(boost::bind(boost::ref(emitStatus), _1, _2, _3));
m_progressSignal.connect(boost::bind(&Timespec::resetMonotonic, &m_lastProgressTimestamp));
m_progressSignal.connect(boost::bind(SetProgress, boost::ref(m_lastProgress), _2));
m_progressSignal.connect(boost::bind(boost::ref(emitProgress), _1, _2));
SE_LOG_DEBUG(NULL, "session %s created", getPath());
}
void Session::passwordRequest(const std::string &descr, const ConfigPasswordKey &key)
{
PushLogger<Logger> guard(m_me);
m_passwordRequest = m_server.passwordRequest(descr, key, m_me);
}
void Session::dbusResultCb(const std::string &operation, bool success, const SyncReport &report, const std::string &error) throw()
{
PushLogger<Logger> guard(m_me);
try {
SE_LOG_DEBUG(NULL, "%s helper call completed, %s",
operation.c_str(),
!error.empty() ? error.c_str() :
success ? "<<successfully>>" :
"<<unsuccessfully>>");
if (error.empty()) {
doneCb(success, report);
} else {
// Translate back into local exception, will be handled by
// catch clause and (eventually) failureCb().
Exception::tryRethrowDBus(error);
// generic fallback
throw GDBusCXX::dbus_error("org.syncevolution.gdbuscxx.Exception",
error);
}
} catch (...) {
failureCb();
}
}
void Session::failureCb() throw()
{
PushLogger<Logger> guard(m_me);
try {
if (m_status == SESSION_DONE) {
// ignore errors that happen after session already closed,
// only log them
std::string explanation;
Exception::handle(explanation, HANDLE_EXCEPTION_NO_ERROR);
m_server.logOutput(getPath(),
Logger::ERROR,
explanation,
"");
} else {
// finish session with failure
uint32_t error;
try {
throw;
} catch (...) {
// only record problem
std::string explanation;
error = Exception::handle(explanation, HANDLE_EXCEPTION_NO_ERROR);
m_server.logOutput(getPath(),
Logger::ERROR,
explanation,
"");
}
// set error, but don't overwrite older one
if (!m_error) {
SE_LOG_DEBUG(NULL, "session failed: remember %d error", error);
m_error = error;
}
// will fire status signal, including the error
doneCb(false);
}
} catch (...) {
// fatal problem, log it and terminate
Exception::handle(HANDLE_EXCEPTION_FATAL);
}
}
void Session::doneCb(bool success, const SyncReport &report) throw()
{
PushLogger<Logger> guard(m_me);
try {
if (m_status == SESSION_DONE) {
return;
}
m_status = SESSION_DONE;
m_syncStatus = SYNC_DONE;
if (!success && !m_error) {
// some kind of local, internal problem
m_error = STATUS_FATAL + sysync::LOCAL_STATUS_CODE;
}
fireStatus(true);
boost::shared_ptr<Connection> connection = m_connection.lock();
if (connection) {
connection->shutdown();
}
// tell everyone who is interested that our config changed (includes D-Bus signal)
if (m_setConfig) {
m_server.m_configChangedSignal(m_configName);
}
SE_LOG_DEBUG(NULL, "session %s done, config %s, %s, result %d",
getPath(),
m_configName.c_str(),
m_setConfig ? "modified" : "not modified",
m_error);
m_doneSignal((SyncMLStatus)m_error, report);
// now also kill helper
m_helper.reset();
if (m_forkExecParent) {
// Abort (just in case, helper should already be waiting
// for SIGURG).
m_forkExecParent->stop(SIGTERM);
// Quit.
m_forkExecParent->stop(SIGURG);
}
m_server.removeSyncSession(this);
m_server.dequeue(this);
} catch (...) {
// fatal problem, log it and terminate (?!)
Exception::handle();
}
}
Session::~Session()
{
SE_LOG_DEBUG(NULL, "session %s deconstructing", getPath());
// If we are not done yet, then something went wrong.
doneCb(false);
}
/** child has quit before connecting, invoke result.failed() with suitable exception pending */
static void raiseChildTermError(int status, const SimpleResult &result)
{
try {
SE_THROW(StringPrintf("helper died unexpectedly with return code %d before connecting", status));
} catch (...) {
result.failed();
}
}
void Session::runOperationAsync(SessionCommon::RunOperation op,
const SuccessCb_t &helperReady,
const StringMap &env)
{
PushLogger<Logger> guard(m_me);
m_server.addSyncSession(this);
m_runOperation = op;
m_status = SESSION_RUNNING;
m_syncStatus = SYNC_RUNNING;
fireStatus(true);
useHelperAsync(SimpleResult(helperReady,
boost::bind(&Session::failureCb, this)),
env);
}
void Session::useHelperAsync(const SimpleResult &result, const StringMap &env)
{
PushLogger<Logger> guard(m_me);
try {
if (m_helper) {
// exists already, invoke callback directly
result.done();
}
// Construct m_forkExecParent if it doesn't exist yet or not
// currently starting. The only situation where the latter
// might happen is when the helper is still starting when
// a new request comes in. In that case we reuse the same
// helper process for both operations.
if (!m_forkExecParent ||
m_forkExecParent->getState() != ForkExecParent::STARTING) {
std::vector<std::string> args;
args.push_back("--dbus-verbosity");
args.push_back(StringPrintf("%d", m_server.getDBusLogLevel()));
m_forkExecParent = SyncEvo::ForkExecParent::create("syncevo-dbus-helper", args);
#ifdef USE_DLT
if (getenv("SYNCEVOLUTION_USE_DLT")) {
m_forkExecParent->addEnvVar("SYNCEVOLUTION_USE_DLT", StringPrintf("%d", LoggerDLT::getCurrentDLTLogLevel()));
}
#endif
BOOST_FOREACH (const StringPair &entry, env) {
SE_LOG_DEBUG(NULL, "running helper with env variable %s=%s",
entry.first.c_str(), entry.second.c_str());
m_forkExecParent->addEnvVar(entry.first, entry.second);
}
// We own m_forkExecParent, so the "this" pointer for
// onConnect will live longer than the signal in
// m_forkExecParent -> no need for resource
// tracking. onConnect sets up m_helper. The other two
// only log the event.
m_forkExecParent->m_onConnect.connect(bind(&Session::onConnect, this, _1));
m_forkExecParent->m_onQuit.connect(boost::bind(&Session::onQuit, this, _1));
m_forkExecParent->m_onFailure.connect(boost::bind(&Session::onFailure, this, _1, _2));
if (!getenv("SYNCEVOLUTION_DEBUG")) {
// Any output from the helper is unexpected and will be
// logged as error. The helper initializes stderr and
// stdout redirection once it runs, so anything that
// reaches us must have been problems during early process
// startup or final shutdown.
m_forkExecParent->m_onOutput.connect(bind(&Session::onOutput, this, _1, _2));
}
}
// Now also connect result with the right events. Will be
// called after setting up m_helper (first come, first
// serve). We copy the "result" instance with boost::bind, and
// the creator of it must have made sure that we can invoke it
// at any time without crashing.
//
// If the helper quits before connecting, the startup
// failed. Need to remove that connection when successful.
boost::signals2::connection c = m_forkExecParent->m_onQuit.connect(boost::bind(&raiseChildTermError,
_1,
result));
m_forkExecParent->m_onConnect.connect(boost::bind(&Session::useHelper2,
this,
result,
c));
if (m_forkExecParent->getState() == ForkExecParent::IDLE) {
m_forkExecParent->start();
}
} catch (...) {
// The assumption here is that any exception is related only
// to the requested operation, and that the server itself is still
// healthy.
result.failed();
}
}
void Session::messagev(const MessageOptions &options,
const char *format,
va_list args)
{
// log with session path and empty process name,
// just like the syncevo-dbus-helper does
m_server.message2DBus(options,
format, args,
getPath(), "");
}
static void Logging2ServerAndStdout(Server &server,
const GDBusCXX::DBusObject_t &path,
const Logger::MessageOptions &options,
const char *format,
...)
{
va_list args;
va_start(args, format);
server.message2DBus(options, format, args, path, options.m_processName ? *options.m_processName : "");
va_end(args);
}
static void Logging2Server(Server &server,
const GDBusCXX::DBusObject_t &path,
const std::string &strLevel,
const std::string &explanation,
const std::string &procname)
{
static bool dbg = getenv("SYNCEVOLUTION_DEBUG");
if (dbg) {
// Print to D-Bus directly. The helper handles its own
// printing to the console.
server.logOutput(path,
Logger::strToLevel(strLevel.c_str()),
explanation,
procname);
} else {
// Print to D-Bus and console, because the helper
// relies on us to do that. Its own stdout/stderr
// was redirected into our pipe and any output
// there is considered an error.
Logger::MessageOptions options(Logger::strToLevel(strLevel.c_str()));
options.m_processName = &procname;
options.m_flags = Logger::MessageOptions::ALREADY_LOGGED;
Logging2ServerAndStdout(server, path, options, "%s", explanation.c_str());
}
}
void Session::useHelper2(const SimpleResult &result, const boost::signals2::connection &c)
{
PushLogger<Logger> guard(m_me);
try {
// helper is running, don't call result.failed() when it quits
// sometime in the future
c.disconnect();
// Verify that helper is really ready. Might not be the
// case when something internally failed in onConnect.
if (m_helper) {
// Resend all output from helper via the server's own
// LogOutput signal, with the session's object path as
// first parameter.
//
// Any code in syncevo-dbus-server which might produce
// output related to the session runs while a Session::LoggingGuard
// captures output by pushing Session as logger onto the
// logging stack. The Session::messagev implementation then
// also calls m_server.logOutput, as if the syncevo-dbus-helper
// had produced that output.
//
// The downside is that unrelated output (like
// book-keeping messages about other clients) will also be
// captured.
m_helper->m_logOutput.activate(boost::bind(Logging2Server,
boost::ref(m_server),
getPath(),
_1,
_2,
_3));
result.done();
} else {
SE_THROW("internal error, helper not ready");
}
} catch (...) {
// Same assumption as above: let's hope the server is still
// sane.
result.failed();
}
}
void Session::onConnect(const GDBusCXX::DBusConnectionPtr &conn) throw ()
{
PushLogger<Logger> guard(m_me);
try {
std::string instance = m_forkExecParent->getInstance();
SE_LOG_DEBUG(NULL, "helper %s has connected", instance.c_str());
m_helper.reset(new SessionProxy(conn, instance));
// Activate signal watch on helper signals.
m_helper->m_syncProgress.activate(boost::bind(&Session::syncProgress, this, _1, _2, _3, _4));
m_helper->m_sourceProgress.activate(boost::bind(&Session::sourceProgress, this, _1, _2, _3, _4, _5, _6));
m_helper->m_sourceSynced.activate(boost::bind(boost::ref(m_sourceSynced), _1, _2));
m_sourceSynced.connect(boost::bind(StoreSyncSourceReport, boost::ref(m_syncSourceReports), _1, _2));
m_helper->m_waiting.activate(boost::bind(&Session::setWaiting, this, _1));
m_helper->m_syncSuccessStart.activate(boost::bind(boost::ref(Session::m_syncSuccessStartSignal)));
m_helper->m_configChanged.activate(boost::bind(boost::ref(m_server.m_configChangedSignal), ""));
m_helper->m_passwordRequest.activate(boost::bind(&Session::passwordRequest, this, _1, _2));
} catch (...) {
Exception::handle();
}
}
void Session::onQuit(int status) throw ()
{
PushLogger<Logger> guard(m_me);
try {
SE_LOG_DEBUG(NULL, "helper quit with return code %d, was %s",
status,
m_wasAborted ? "aborted" : "not aborted");
if (m_status == SESSION_DONE) {
// don't care anymore whether the helper goes down, not an error
SE_LOG_DEBUG(NULL, "session already completed, ignore helper");
} else if (m_wasAborted &&
((WIFEXITED(status) && WEXITSTATUS(status) == 0) ||
(WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM))) {
SE_LOG_DEBUG(NULL, "helper terminated via SIGTERM, as expected");
if (!m_error) {
m_error = sysync::LOCERR_USERABORT;
SE_LOG_DEBUG(NULL, "helper was asked to quit -> error %d = LOCERR_USERABORT",
m_error);
}
} else {
// Premature exit from helper?! Not necessarily, it could
// be that we get the "helper has quit" signal from
// ForkExecParent before processing the helper's D-Bus
// method reply. So instead of recording an error here,
// wait for that reply. If the helper died without sending
// it, then D-Bus will generate a "connection lost" error
// for our pending method call.
//
// Except that libdbus does not deliver that error
// reliably. As a workaround, schedule closing the
// session as an idle callback, after that potential
// future method return call was handled. The assumption
// is that it is pending - it must be, because with the
// helper gone, IO with it must be ready. Just to be sure
// a small delay is used.
}
m_server.addTimeout(boost::bind(&Session::doneCb,
m_me,
false,
SyncReport()),
1 /* seconds */);
} catch (...) {
Exception::handle();
}
}
void Session::onFailure(SyncMLStatus status, const std::string &explanation) throw ()
{
PushLogger<Logger> guard(m_me);
try {
SE_LOG_DEBUG(NULL, "helper failed, status code %d = %s, %s",
status,
Status2String(status).c_str(),
explanation.c_str());
} catch (...) {
Exception::handle();
}
}
void Session::onOutput(const char *buffer, size_t length)
{
PushLogger<Logger> guard(m_me);
// treat null-bytes inside the buffer like line breaks
size_t off = 0;
do {
SE_LOG_ERROR("session-helper", "%s", buffer + off);
off += strlen(buffer + off) + 1;
} while (off < length);
}
void Session::activateSession()
{
PushLogger<Logger> guard(m_me);
if (m_status != SESSION_IDLE) {
SE_THROW("internal error, session changing from non-idle to active");
}
m_status = SESSION_ACTIVE;
if (m_syncStatus == SYNC_QUEUEING) {
m_syncStatus = SYNC_IDLE;
fireStatus(true);
}
boost::shared_ptr<Connection> c = m_connection.lock();
if (c) {
c->ready();
}
m_sessionActiveSignal();
}
void Session::passwordResponse(bool timedOut, bool aborted, const std::string &password)
{
PushLogger<Logger> guard(m_me);
if (m_helper) {
// Ignore communicaton failures with helper here,
// we'll notice that elsewhere
m_helper->m_passwordResponse.start(timedOut, aborted, password,
boost::function<void (const std::string &)>());
}
}
void Session::syncProgress(sysync::TProgressEventEnum type,
int32_t extra1, int32_t extra2, int32_t extra3)
{
PushLogger<Logger> guard(m_me);
switch(type) {
case sysync::PEV_CUSTOM_START:
m_cmdlineOp = (RunOperation)extra1;
break;
case sysync::PEV_SESSIONSTART:
m_progData.setStep(ProgressData::PRO_SYNC_INIT);
fireProgress(true);
break;
case sysync::PEV_SESSIONEND:
// Ignore the error here. It was seen
// (TestSessionAPIsDummy.testAutoSyncNetworkFailure) that the
// engine reports 20017 = user abort when the real error is a
// transport error encountered outside of the
// engine. Recording the error as seen by the engine leads to
// an incorrect final session result. Instead wait for the
// result of the sync method invocation.
//
// if((uint32_t)extra1 != m_error) {
// SE_LOG_DEBUG(NULL, "session sync progress: failed with code %d", extra1);
// m_error = extra1;
// fireStatus(true);
// }
m_progData.setStep(ProgressData::PRO_SYNC_INVALID);
fireProgress(true);
break;
case sysync::PEV_SENDSTART:
m_progData.sendStart();
break;
case sysync::PEV_SENDEND:
case sysync::PEV_RECVSTART:
case sysync::PEV_RECVEND:
m_progData.receiveEnd();
fireProgress();
break;
case sysync::PEV_DISPLAY100:
case sysync::PEV_SUSPENDCHECK:
case sysync::PEV_DELETING:
break;
case sysync::PEV_SUSPENDING:
m_syncStatus = SYNC_SUSPEND;
fireStatus(true);
break;
default:
;
}
}
void Session::sourceProgress(sysync::TProgressEventEnum type,
const std::string &sourceName,
SyncMode sourceSyncMode,
int32_t extra1, int32_t extra2, int32_t extra3)
{
PushLogger<Logger> guard(m_me);
// a command line operation can be many things, helper must have told us
SessionCommon::RunOperation op = m_runOperation == SessionCommon::OP_CMDLINE ?
m_cmdlineOp :
m_runOperation;
switch(op) {
case SessionCommon::OP_SYNC: {
// Helper will create new source entries by sending a
// sysync::PEV_PREPARING with SYNC_NONE. Must fire progress
// and status events for such new sources.
SourceProgresses_t::iterator pit = m_sourceProgress.find(sourceName);
bool sourceProgressCreated = pit == m_sourceProgress.end();
SourceProgress &progress = sourceProgressCreated ? m_sourceProgress[sourceName] : pit->second;
SourceStatuses_t::iterator sit = m_sourceStatus.find(sourceName);
bool sourceStatusCreated = sit == m_sourceStatus.end();
SourceStatus &status = sourceStatusCreated ? m_sourceStatus[sourceName] : sit->second;
switch(type) {
case sysync::PEV_SYNCSTART:
if (sourceSyncMode != SYNC_NONE) {
m_progData.setStep(ProgressData::PRO_SYNC_UNINIT);
fireProgress();
}
break;
case sysync::PEV_SYNCEND:
if (sourceSyncMode != SYNC_NONE) {
status.set(PrettyPrintSyncMode(sourceSyncMode), "done", extra1);
fireStatus(true);
}
break;
case sysync::PEV_PREPARING:
if (sourceSyncMode != SYNC_NONE) {
progress.m_phase = "preparing";
progress.m_prepareCount = extra1;
progress.m_prepareTotal = extra2;
m_progData.itemPrepare();
fireProgress(true);
} else {
// Check whether the sources where created.
if (sourceProgressCreated) {
fireProgress();
}
if (sourceStatusCreated) {
fireStatus();
}
}
break;
case sysync::PEV_ITEMSENT:
if (sourceSyncMode != SYNC_NONE) {
progress.m_phase = "sending";
progress.m_sendCount = extra1;
progress.m_sendTotal = extra2;
fireProgress(true);
}
break;
case sysync::PEV_ITEMRECEIVED:
if (sourceSyncMode != SYNC_NONE) {
progress.m_phase = "receiving";
progress.m_receiveCount = extra1;
progress.m_receiveTotal = extra2;
m_progData.itemReceive(sourceName, extra1, extra2);
fireProgress();
}
break;
case sysync::PEV_ITEMPROCESSED:
progress.m_added = extra1;
progress.m_updated = extra2;
progress.m_deleted = extra3;
// Do not fireProgress() here! We are going to get a
// PEV_ITEMRECEIVED directly afterwards (see
// dbus-sync.cpp).
break;
case sysync::PEV_ALERTED:
if (sourceSyncMode != SYNC_NONE) {
// Reset item counts, must be set (a)new.
// Relevant in multi-cycle syncing.
progress.m_receiveCount = -1;
progress.m_receiveTotal = -1;
progress.m_sendCount = -1;
progress.m_sendTotal = -1;
status.set(PrettyPrintSyncMode(sourceSyncMode), "running", 0);
fireStatus(true);
m_progData.setStep(ProgressData::PRO_SYNC_DATA);
m_progData.addSyncMode(sourceSyncMode);
fireProgress();
}
break;
default:
;
}
break;
}
case SessionCommon::OP_RESTORE: {
switch(type) {
case sysync::PEV_ALERTED:
// count the total number of sources to be restored
m_restoreSrcTotal++;
break;
case sysync::PEV_SYNCSTART: {
if (sourceSyncMode != SYNC_NONE) {
SourceStatus &status = m_sourceStatus[sourceName];
// set statuses as 'restore-from-backup'
status.set(PrettyPrintSyncMode(sourceSyncMode), "running", 0);
fireStatus(true);
}
break;
}
case sysync::PEV_SYNCEND: {
if (sourceSyncMode != SYNC_NONE) {
m_restoreSrcEnd++;
SourceStatus &status = m_sourceStatus[sourceName];
status.set(PrettyPrintSyncMode(sourceSyncMode), "done", 0);
m_progData.setProgress(100 * m_restoreSrcEnd / m_restoreSrcTotal);
fireStatus(true);
fireProgress(true);
}
break;
}
default:
break;
}
break;
}
default:
break;
}
}
bool Session::setFilters(SyncConfig &config)
{
PushLogger<Logger> guard(m_me);
/** apply temporary configs to config */
config.setConfigFilter(true, "", m_syncFilter);
// set all sources in the filter to config
BOOST_FOREACH(const SourceFilters_t::value_type &value, m_sourceFilters) {
config.setConfigFilter(false, value.first, value.second);
}
return m_tempConfig;
}
void Session::setWaiting(bool isWaiting)
{
PushLogger<Logger> guard(m_me);
// if stepInfo doesn't change, then ignore it to avoid duplicate status info
if(m_stepIsWaiting != isWaiting) {
m_stepIsWaiting = isWaiting;
fireStatus(true);
}
}
void Session::restore(const string &dir, bool before, const std::vector<std::string> &sources)
{
PushLogger<Logger> guard(m_me);
if (m_runOperation == SessionCommon::OP_RESTORE) {
string msg = StringPrintf("restore started, cannot restore again");
SE_THROW_EXCEPTION(InvalidCall, msg);
} else if (m_runOperation != SessionCommon::OP_NULL) {
// actually this never happen currently, for during the real restore process,
// it never poll the sources in default main context
string msg = StringPrintf("%s started, cannot restore", runOpToString(m_runOperation).c_str());
SE_THROW_EXCEPTION(InvalidCall, msg);
}
if (m_status != SESSION_ACTIVE) {
SE_THROW_EXCEPTION(InvalidCall, "session is not active, call not allowed at this time");
}
runOperationAsync(SessionCommon::OP_RESTORE,
boost::bind(&Session::restore2, this, dir, before, sources));
}
void Session::restore2(const string &dir, bool before, const std::vector<std::string> &sources)
{
PushLogger<Logger> guard(m_me);
if (!m_forkExecParent || !m_helper) {
SE_THROW("syncing cannot continue, helper died");
}
// helper is ready, tell it what to do
m_helper->m_restore.start(m_configName, dir, before, sources,
boost::bind(&Session::dbusResultCb, m_me, "restore()", _1, SyncReport(), _2));
}
void Session::execute(const vector<string> &args, const map<string, string> &vars)
{
PushLogger<Logger> guard(m_me);
if (m_runOperation == SessionCommon::OP_CMDLINE) {
SE_THROW_EXCEPTION(InvalidCall, "cmdline started, cannot start again");
} else if (m_runOperation != SessionCommon::OP_NULL) {
string msg = StringPrintf("%s started, cannot start cmdline", runOpToString(m_runOperation).c_str());
SE_THROW_EXCEPTION(InvalidCall, msg);
}
if (m_status != SESSION_ACTIVE) {
SE_THROW_EXCEPTION(InvalidCall, "session is not active, call not allowed at this time");
}
runOperationAsync(SessionCommon::OP_CMDLINE,
boost::bind(&Session::execute2,
this,
args, vars));
}
void Session::execute2(const vector<string> &args, const map<string, string> &vars)
{
PushLogger<Logger> guard(m_me);
if (!m_forkExecParent || !m_helper) {
SE_THROW("syncing cannot continue, helper died");
}
// helper is ready, tell it what to do
m_helper->m_execute.start(args, vars,
boost::bind(&Session::dbusResultCb, m_me, "execute()", _1, SyncReport(), _2));
}
/*Implementation of Session.CheckPresence */
void Session::checkPresence (string &status)
{
PushLogger<Logger> guard(m_me);
vector<string> transport;
m_server.checkPresence(m_configName, status, transport);
}
void Session::sendViaConnection(const DBusArray<uint8_t> buffer,
const std::string &type,
const std::string &url)
{
PushLogger<Logger> guard(m_me);
try {
boost::shared_ptr<Connection> connection = m_connection.lock();
if (!connection) {
SE_THROW_EXCEPTION(TransportException,
"D-Bus peer has disconnected");
}
connection->send(buffer, type, url);
} catch (...) {
std::string explanation;
Exception::handle(explanation);
connectionState(explanation);
}
}
void Session::shutdownConnection()
{
PushLogger<Logger> guard(m_me);
try {
boost::shared_ptr<Connection> connection = m_connection.lock();
if (!connection) {
SE_THROW_EXCEPTION(TransportException,
"D-Bus peer has disconnected");
}
connection->sendFinalMsg();
} catch (...) {
std::string explanation;
Exception::handle(explanation);
connectionState(explanation);
}
}
void Session::storeMessage(const DBusArray<uint8_t> &message,
const std::string &type)
{
PushLogger<Logger> guard(m_me);
// ignore errors
if (m_helper) {
m_helper->m_storeMessage.start(message, type,
boost::function<void (const std::string &)>());
}
}
void Session::connectionState(const std::string &error)
{
PushLogger<Logger> guard(m_me);
// ignore errors
if (m_helper) {
m_helper->m_connectionState.start(error,
boost::function<void (const std::string &)>());
}
}
SE_END_CXX