D-Bus server: fork/exec for sync, command line and restore operations

This commit moves the blocking syncing, database restore and command
line execution into a separate, short-lived process executing the
syncevo-dbus-helper. The advantage is that the main
syncevo-dbus-server remains responsive under all circumstances (fully
asynchronous now) and suffers less from memory leaks and/or crashes
during a sync.

The core idea behind the new architecture is that Session remains the
D-Bus facing side of a session. It continues to run inside
syncevo-dbus-server and uses the syncevo-dbus-helper transparently via
a custom D-Bus interface between the two processes. State changes of
the helper are mirrored in the server.

Later the helper might also be used multiple times in a Session. For
example, anything related to loading backends should be moved into the
helper (currently the "is config usable" check still runs in the
syncevo-dbus-server and needs to load/initialize backends). The
startup code of the helper already handles that (see boolean result of
operation callback), but it is not used yet in practice.

At the moment, only the helper provides a D-Bus API. It sends out
signals when it needs information from the server. The server watches
those and replies when ready. The helper monitors the connection to
the parent and detects that it won't get an answer if that connection
goes down.

The problem of "helper died unexpectedly" is also handled, by not
returning a D-Bus method reply until the requested operation is
completed (different from the way how the public D-Bus API is
defined!).

The Connection class continues to use such a Session, as before. It's
now fully asynchronous and exchanges messages with the helper via the
Session class.

Inside syncevo-dbus-server, boost::signals2 and the dbus-callbacks
infrastructure for asynchronous methods execution are used heavily
now. The glib event loop is entered exactly once and only left to shut
down.

Inside syncevo-dbus-helper, the event loop is entered only as
needed. Password requests sent from syncevo-local-sync to
syncevo-dbus-helper are handled asynchronously inside the event loop
driven by the local transport.

syncevo-dbus-helper and syncevo-local-sync are conceptually very
similar. Should investigate whether a single executable can serve both
functions.

The AutoSyncManager was completely rewritten. The data structure is a
lot simpler now (basically just a cache of transient information about
a sync config and the relevant config properties that define auto
syncing). The main work happens inside the schedule() call, which
verifies whether a session can run and, if not possible for some
reasons, ensures that it gets invoked again when that blocker is
gone (timeout over, server idle, etc.). The new code also uses
signals/slots instead of explicit coupling between the different
classes.

All code still lives inside the src/dbus/server directory. This
simplifies checking differences in partly modified files like
dbus-sync.cpp. A future commit will move the helper files.

The syslog logger code is referenced by the server, but never used.
This functionality needs further thought:
- Make usage depend on command line option? Beware that test-dbus.py
  looks for the "ready to run" output and thus startup breaks when
  all output goes to syslog instead of stdout.
- Redirect glib messages into syslog (done by LogRedirect, disabled when
  using LoggerSyslog)?

The syncevo-dbus-server now sends the final "Session.StatusChanged
done" signal immediately. The old implementation accidentally delayed
sending that for 100 seconds. The revised test-dbus.py checks for
more "session done" quit events to cover this fix.

Only user-visible messages should have the INFO level in any of the
helpers. Messages about starting and stopping processes are related to
implementation details and thus should only have DEBUG level.

The user also doesn't care about where the operation eventually
runs. All messages related to it should be in INFO/DEBUG/ERROR
messages without a process name. Therefore now syncevo-dbus-server
logs with a process name (also makes some explicit argv[0] logging
redundant; requires changes in test-dbus.py) and syncevo-dbus-helper
doesn't.

syncevo-local-sync is different from syncevo-dbus-helper: it produces
user-relevant output (the other half of the local sync). It's output
is carefully chosen so that the process name is something the user
understands (target context) and output can be clearly related to one
side or the other (for example, context names are included in the sync
table).

Output handling is based on the same idea as output handling in the
syncevo-dbus-server:
- Session registers itself as the top-most logger and sends
  SyncEvolution logging via D-Bus to the parent, which re-sends
  it with the right D-Bus object path as output of the session.
- Output redirection catches all other output and feeds it back
  to the Session log handler, from where it goes via D-Bus
  to the parent.

The advantage of this approach is that level information is made
available directly to the parent and that message boundaries are
preserved properly.

stderr and stdout are redirected into the parent and logged there as
error. Normally the child should not print anything. While it runs,
LogRedirect inside it will capture output and log it
internally. Anything reaching the parent thus must be from early
process startup or shutdown.

Almost all communication from syncevo-dbus-helper to
syncevo-dbus-server is purely information for the syncevo-dbus-server;
syncevo-dbus-helper doesn't care whether the signal can be
delivered. The only exception is the information request, which must
succeed.

Instead of catching exceptions everywhere, the optional signals are
declared as such in the EmitSignal template parameterization and
no longer throw exceptions when something goes wrong. They also don't
log anything, because that could lead to quite a lof of output.
This commit is contained in:
Patrick Ohly 2012-03-26 17:19:25 +02:00
parent 82655be7ca
commit a009f28520
30 changed files with 3168 additions and 1589 deletions

View File

@ -20,6 +20,7 @@
#include "auto-sync-manager.h"
#include "session.h"
#include "server.h"
#include "dbus-callbacks.h"
#include <glib.h>
#include <glib/gi18n.h>
@ -29,284 +30,405 @@
SE_BEGIN_CXX
AutoSyncManager::AutoSyncManager(Server &server) :
m_server(server), m_syncSuccessStart(false)
m_server(server),
m_autoTermLocked(false)
{
}
boost::shared_ptr<AutoSyncManager> AutoSyncManager::create(Server &server)
static void updatePresence(Timespec *t, bool present)
{
*t = present ?
Timespec::monotonic() :
Timespec();
}
boost::shared_ptr<AutoSyncManager> AutoSyncManager::createAutoSyncManager(Server &server)
{
boost::shared_ptr<AutoSyncManager> result(new AutoSyncManager(server));
result->m_me = result;
result->init();
// update cached information about a config each time it changes
server.m_configChangedSignal.connect(Server::ConfigChangedSignal_t::slot_type(&AutoSyncManager::initConfig, result.get(), _1).track(result));
// monitor running sessions
server.m_newSyncSessionSignal.connect(Server::NewSyncSessionSignal_t::slot_type(&AutoSyncManager::sessionStarted, result.get(), _1).track(result));
// Keep track of the time when a transport became online. As with
// time of last sync, we are pessimistic here and assume that the
// transport just now became available.
PresenceStatus &p = server.getPresenceStatus();
Timespec now = Timespec::monotonic();
if (p.getBtPresence()) {
result->m_btStartTime = now;
}
p.m_btPresenceSignal.connect(PresenceStatus::PresenceSignal_t::slot_type(updatePresence,
&result->m_btStartTime,
_1).track(result));
if (p.getHttpPresence()) {
result->m_httpStartTime = now;
}
p.m_httpPresenceSignal.connect(PresenceStatus::PresenceSignal_t::slot_type(updatePresence,
&result->m_httpStartTime,
_1).track(result));
return result;
}
void AutoSyncManager::init()
{
m_notificationManager = NotificationManagerFactory::createManager();
m_notificationManager->init();
m_peerMap.clear();
SyncConfig::ConfigList list = SyncConfig::getConfigs();
BOOST_FOREACH(const SyncConfig::ConfigList::value_type &server, list) {
initConfig(server.first);
}
m_notificationManager = NotificationManagerFactory::createManager();
m_notificationManager->init();
}
void AutoSyncManager::initConfig(const std::string &configName)
{
SE_LOG_DEBUG(NULL, NULL, "auto sync: updating info about config %s", configName.c_str());
if (configName.empty()) {
// Anything might have changed. Check all configs we know
// about (might have been removed) and all existing configs
// (might have been modified).
std::set<std::string> configs;
BOOST_FOREACH (const PeerMap::value_type &entry, m_peerMap) {
const std::string &configName = entry.first;
configs.insert(configName);
}
BOOST_FOREACH (const StringPair &entry, SyncConfig::getConfigs()) {
const std::string &configName = entry.first;
configs.insert(configName);
}
BOOST_FOREACH (const std::string &configName, configs) {
if (!configName.empty()) {
initConfig(configName);
}
}
// TODO: only call schedule() once in this case, instead
// once per recursive initConfig().
}
// TODO: once we depend on shared settings, remember to check
// all other configs which share the same set of settings.
// Not currently the case.
// Create anew or update, directly in map. Never remove
// old entries, because we want to keep the m_lastSyncTime
// in cases where configs get removed and recreated.
boost::shared_ptr<AutoSyncTask> &task = m_peerMap[configName];
if (!task) {
task.reset(new AutoSyncTask(configName));
// We should check past sessions here. Instead we assume
// the "worst" case, which is that the session ran zero
// seconds ago. This has the additional benefit that we
// don't run automatic sync sessions directly after
// starting up (the system or syncevo-dbus-server).
task->m_lastSyncTime = Timespec::monotonic();
}
SyncConfig config (configName);
if(!config.exists()) {
if (config.exists()) {
std::vector<std::string> urls = config.getSyncURL();
std::string autoSync = config.getAutoSync();
//enable http and bt?
bool http = false, bt = false;
bool any = false;
if (autoSync.empty() ||
boost::iequals(autoSync, "0") ||
boost::iequals(autoSync, "f")) {
http = false;
bt = false;
any = false;
} else if (boost::iequals(autoSync, "1") ||
boost::iequals(autoSync, "t")) {
http = true;
bt = true;
any = true;
} else {
BOOST_FOREACH(std::string op,
boost::tokenizer< boost::char_separator<char> >(autoSync,
boost::char_separator<char>(","))) {
if(boost::iequals(op, "http")) {
http = true;
} else if(boost::iequals(op, "obex-bt")) {
bt = true;
}
}
}
task->m_peerName = config.getPeerName();
if (task->m_peerName.empty()) {
task->m_peerName = configName;
}
task->m_interval = config.getAutoSyncInterval();
task->m_delay = config.getAutoSyncDelay();
task->m_remoteDeviceId = config.getRemoteDevID();
// Assume that whatever change was made might have resolved
// the past problem -> allow auto syncing again.
task->m_permanentFailure = false;
SE_LOG_DEBUG(NULL, NULL,
"auto sync: %s: auto sync '%s', %s, %s, %d seconds repeat interval, %d seconds online delay",
configName.c_str(),
autoSync.c_str(),
bt ? "Bluetooth" : "no Bluetooth",
http ? "HTTP" : "no HTTP",
task->m_interval, task->m_delay);
task->m_urls.clear();
BOOST_FOREACH(std::string url, urls) {
AutoSyncTask::Transport transport = AutoSyncTask::NEEDS_OTHER; // fallback for unknown sync URL
if (boost::istarts_with(url, "http")) {
transport = AutoSyncTask::NEEDS_HTTP;
} else if (boost::istarts_with(url, "local")) {
// TODO: instead of assuming that local sync needs HTTP, really look into the target config
// and determine what the peerType is
transport = AutoSyncTask::NEEDS_HTTP;
} else if (boost::istarts_with(url, "obex-bt")) {
transport = AutoSyncTask::NEEDS_BT;
}
if((transport == AutoSyncTask::NEEDS_HTTP && http) ||
(transport == AutoSyncTask::NEEDS_BT && bt) ||
(transport == AutoSyncTask::NEEDS_OTHER && any)) {
task->m_urls.push_back(std::make_pair(transport, url));
SE_LOG_DEBUG(NULL, NULL,
"auto sync: adding config %s url %s",
configName.c_str(),
url.c_str());
}
}
} else {
// Just clear urls, which disables auto syncing.
task->m_urls.clear();
}
bool lock = preventTerm();
if (m_autoTermLocked && !lock) {
SE_LOG_DEBUG(NULL, NULL, "auto sync: allow auto shutdown");
m_server.autoTermUnref();
m_autoTermLocked = false;
} else if (!m_autoTermLocked && lock) {
SE_LOG_DEBUG(NULL, NULL, "auto sync: prevent auto shutdown");
m_server.autoTermRef();
m_autoTermLocked = true;
}
// reschedule
schedule("initConfig() for " + configName);
}
void AutoSyncManager::schedule(const std::string &reason)
{
SE_LOG_DEBUG(NULL, NULL, "auto sync: reschedule, %s", reason.c_str());
// idle callback will be (re)set if needed
m_idleConnection.disconnect();
if (!preventTerm()) {
SE_LOG_DEBUG(NULL, NULL, "auto sync: nothing to do");
return;
}
std::vector<std::string> urls = config.getSyncURL();
std::string autoSync = config.getAutoSync();
//enable http and bt?
bool http = false, bt = false;
bool any = false;
if(autoSync.empty() || boost::iequals(autoSync, "0")
|| boost::iequals(autoSync, "f")) {
http = false;
bt = false;
any = false;
} else if(boost::iequals(autoSync, "1") || boost::iequals(autoSync, "t")) {
http = true;
bt = true;
any = true;
} else {
BOOST_FOREACH(std::string op,
boost::tokenizer< boost::char_separator<char> >(autoSync, boost::char_separator<char>(","))) {
if(boost::iequals(op, "http")) {
http = true;
} else if(boost::iequals(op, "obex-bt")) {
bt = true;
}
}
if (!m_server.isIdle()) {
// Only schedule automatic syncs when nothing else is
// going on or pending.
SE_LOG_DEBUG(NULL, NULL, "auto sync: server not idle");
connectIdle();
return;
}
unsigned int interval = config.getAutoSyncInterval();
unsigned int duration = config.getAutoSyncDelay();
// Now look for a suitable task that is ready to run.
Timespec now = Timespec::monotonic();
BOOST_FOREACH (const PeerMap::value_type &entry, m_peerMap) {
const std::string &configName = entry.first;
const boost::shared_ptr<AutoSyncTask> &task = entry.second;
SE_LOG_DEBUG(NULL, NULL, "auto sync: %s: auto sync '%s', %s, %s, %d seconds repeat interval, %d seconds online duration",
configName.c_str(),
autoSync.c_str(),
bt ? "Bluetooth" : "no Bluetooth",
http ? "HTTP" : "no HTTP",
interval, duration);
BOOST_FOREACH(std::string url, urls) {
AutoSyncTask::Transport transport = AutoSyncTask::NEEDS_OTHER; // fallback for unknown sync URL
if (boost::istarts_with(url, "http")) {
transport = AutoSyncTask::NEEDS_HTTP;
} else if (boost::istarts_with(url, "local")) {
// TODO: instead of assuming that local sync needs HTTP, really look into the target config
// and determine what the peerType is
transport = AutoSyncTask::NEEDS_HTTP;
} else if (boost::istarts_with(url, "obex-bt")) {
transport = AutoSyncTask::NEEDS_BT;
if (task->m_interval <= 0 || // not enabled
task->m_permanentFailure) { // don't try again
continue;
}
if((transport == AutoSyncTask::NEEDS_HTTP && http) ||
(transport == AutoSyncTask::NEEDS_BT && bt) ||
(transport == AutoSyncTask::NEEDS_OTHER && any)) {
AutoSyncTask syncTask(configName, duration, transport, url);
PeerMap::iterator it = m_peerMap.find(interval);
if(it != m_peerMap.end()) {
SE_LOG_DEBUG(NULL, NULL,
"auto sync: adding config %s url %s to existing interval %ld",
configName.c_str(),
url.c_str(),
(long)interval);
it->second->push_back(syncTask);
if (task->m_lastSyncTime + task->m_interval > now) {
// Ran too recently, check again in the future. Always
// reset timer, because both m_lastSyncTime and m_interval
// may have changed.
int seconds = (task->m_lastSyncTime + task->m_interval - now).seconds() + 1;
SE_LOG_DEBUG(NULL, NULL, "auto sync: %s: interval expires in %ds",
configName.c_str(),
seconds);
task->m_intervalTimeout.runOnce(seconds,
boost::bind(&AutoSyncManager::schedule,
this,
configName + " interval timer"));
continue;
}
std::string readyURL;
BOOST_FOREACH (const AutoSyncTask::URLInfo_t::value_type &urlinfo, task->m_urls) {
// check m_delay against presence of transport
Timespec *starttime = NULL;
PresenceStatus::PresenceSignal_t *signal = NULL;
Timeout *timeout = NULL;
switch (urlinfo.first) {
case AutoSyncTask::NEEDS_HTTP:
starttime = &m_httpStartTime;
signal = &m_server.getPresenceStatus().m_httpPresenceSignal;
timeout = &task->m_httpTimeout;
break;
case AutoSyncTask::NEEDS_BT:
starttime = &m_btStartTime;
signal = &m_server.getPresenceStatus().m_btPresenceSignal;
timeout = &task->m_btTimeout;
break;
case AutoSyncTask::NEEDS_OTHER:
break;
}
if (!starttime || // some other transport, assumed to be online, use it
(*starttime && // present
(task->m_delay <= 0 || *starttime + task->m_delay > now))) { // present long enough
readyURL = urlinfo.second;
break;
}
if (!*starttime) {
// check again when it becomes present
signal->connect(PresenceStatus::PresenceSignal_t::slot_type(&AutoSyncManager::schedule,
this,
"presence change").track(m_me));
} else {
boost::shared_ptr<AutoSyncTaskList> list(new AutoSyncTaskList(*this, interval));
list->push_back(syncTask);
list->createTimeoutSource();
if (m_peerMap.empty()) {
// Adding first auto sync task. Ensure that we don't shut down.
SE_LOG_DEBUG(NULL, NULL, "auto sync: adding first config %s url %s, prevent auto-termination",
configName.c_str(),
url.c_str());
m_server.autoTermRef();
} else {
SE_LOG_DEBUG(NULL, NULL, "auto sync: adding config %s url %s, %ld already added earlier",
configName.c_str(),
url.c_str(),
(long)m_peerMap.size());
}
m_peerMap.insert(std::make_pair(interval, list));
// check again after waiting the requested amount of time
int seconds = (*starttime + task->m_delay - now).seconds() + 1;
SE_LOG_DEBUG(NULL, NULL, "auto sync: %s: presence delay expires in %ds",
configName.c_str(),
seconds);
timeout->runOnce(seconds,
boost::bind(&AutoSyncManager::schedule,
this,
configName + " transport timer"));
}
}
if (!readyURL.empty()) {
// Found a task, run it. The session is not attached to any client,
// but we keep a pointer to it, so it won't go away.
// m_task = task;
// Just in case... also done in syncDone() when we detect
// that session is completed.
m_server.delaySessionDestruction(m_session);
task->m_syncSuccessStart = false;
m_session = Session::createSession(m_server,
task->m_remoteDeviceId,
configName,
m_server.getNextSession());
// Temporarily set sync URL to the one which we picked above
// once the session is active (setConfig() not allowed earlier).
ReadOperations::Config_t config;
config[""]["syncURL"] = readyURL;
m_session->m_sessionActiveSignal.connect(boost::bind(&Session::setConfig,
m_session.get(),
true, true,
config));
// Run sync as soon as it is active.
m_session->m_sessionActiveSignal.connect(boost::bind(&Session::sync,
m_session.get(),
"",
SessionCommon::SourceModes_t()));
// Now run it.
m_session->activate();
m_server.enqueue(m_session);
// Reschedule when server is idle again.
connectIdle();
return;
}
}
SE_LOG_DEBUG(NULL, NULL, "auto sync: nothing to do");
}
void AutoSyncManager::remove(const std::string &configName)
void AutoSyncManager::connectIdle()
{
//wipe out tasks in the m_peerMap
PeerMap::iterator it = m_peerMap.begin();
while(it != m_peerMap.end()) {
boost::shared_ptr<AutoSyncTaskList> &list = it->second;
AutoSyncTaskList::iterator taskIt = list->begin();
while(taskIt != list->end()) {
if(boost::iequals(taskIt->m_peer, configName)) {
taskIt = list->erase(taskIt);
} else {
++taskIt;
}
}
//if list is empty, remove the list from map
if(list->empty()) {
PeerMap::iterator erased = it++;
m_peerMap.erase(erased);
if (m_peerMap.empty()) {
// removed last entry, remove lock on auto termination
SE_LOG_DEBUG(NULL, NULL, "auto sync: last auto sync config %s gone, allow auto-termination",
configName.c_str());
m_server.autoTermUnref();
} else {
SE_LOG_DEBUG(NULL, NULL, "auto sync: sync config %s gone, still %ld configure for auto-sync",
configName.c_str(),
(long)m_peerMap.size());
}
} else {
++it;
}
}
//wipe out scheduled tasks in the working queue based on configName
list<AutoSyncTask>::iterator qit = m_workQueue.begin();
while(qit != m_workQueue.end()) {
if(boost::iequals(qit->m_peer, configName)) {
qit = m_workQueue.erase(qit);
} else {
++qit;
}
}
m_idleConnection =
m_server.m_idleSignal.connect(Server::IdleSignal_t::slot_type(&AutoSyncManager::schedule,
this,
"server is idle").track(m_me));
}
void AutoSyncManager::update(const std::string &configName)
void AutoSyncManager::sessionStarted(const boost::shared_ptr<Session> &session)
{
SE_LOG_DEBUG(NULL, NULL, "auto sync: refreshing %s", configName.c_str());
// remove task from m_peerMap and tasks in the working queue for this config
remove(configName);
// re-load the config and re-init peer map
initConfig(configName);
//don't clear if the task is running
if(m_session && !hasActiveSession()
&& boost::iequals(m_session->getConfigName(), configName)) {
SE_LOG_DEBUG(NULL, NULL, "auto sync: removing queued session for %s during update",
// Do we have a task for this config?
std::string configName = session->getConfigName();
PeerMap::iterator it = m_peerMap.find(configName);
if (it == m_peerMap.end()) {
SE_LOG_DEBUG(NULL, NULL, "auto sync: ignore running sync %s without config",
configName.c_str());
m_server.dequeue(m_session.get());
m_session.reset();
m_activeTask.reset();
startTask();
return;
}
boost::shared_ptr<AutoSyncManager> me = m_me.lock();
if (!me) {
SE_LOG_DEBUG(NULL, NULL, "auto sync: already destructing, ignore new sync %s",
configName.c_str());
return;
}
const boost::shared_ptr<AutoSyncTask> &task = it->second;
task->m_lastSyncTime = Timespec::monotonic();
// track permanent failure
session->m_doneSignal.connect(Session::DoneSignal_t::slot_type(&AutoSyncManager::anySyncDone, this, task.get(), _1).track(task).track(me));
if (m_session == session) {
// Only for our own auto sync session: notify user once session starts successful.
//
// In the (unlikely) case that the AutoSyncTask gets deleted, the
// slot won't get involved, thus skipping user notifications.
// Also protects against manager destructing before session.
session->m_syncSuccessStartSignal.connect(Session::SyncSuccessStartSignal_t::slot_type(&AutoSyncManager::autoSyncSuccessStart, this, task.get()).track(task).track(me));
// Notify user once session ends, with or without failure.
// Same instance tracking as for sync success start.
session->m_doneSignal.connect(Session::DoneSignal_t::slot_type(&AutoSyncManager::autoSyncDone, this, task.get(), _1).track(task).track(me));
}
}
void AutoSyncManager::scheduleAll()
bool AutoSyncManager::preventTerm()
{
BOOST_FOREACH(PeerMap::value_type &elem, m_peerMap) {
elem.second->scheduleTaskList();
}
}
bool AutoSyncManager::addTask(const AutoSyncTask &syncTask)
{
if(taskLikelyToRun(syncTask)) {
m_workQueue.push_back(syncTask);
return true;
}
return false;
}
bool AutoSyncManager::findTask(const AutoSyncTask &syncTask)
{
if(m_activeTask && *m_activeTask == syncTask) {
return true;
}
BOOST_FOREACH(const AutoSyncTask &task, m_workQueue) {
if(task == syncTask) {
BOOST_FOREACH (const PeerMap::value_type &entry, m_peerMap) {
const boost::shared_ptr<AutoSyncTask> &task = entry.second;
if (task->m_interval > 0 &&
!task->m_permanentFailure &&
!task->m_urls.empty()) {
// that task might run
return true;
}
}
return false;
}
bool AutoSyncManager::taskLikelyToRun(const AutoSyncTask &syncTask)
void AutoSyncManager::autoSyncSuccessStart(AutoSyncTask *task)
{
PresenceStatus &status = m_server.getPresenceStatus();
if (syncTask.m_transport == AutoSyncTask::NEEDS_HTTP && status.getHttpPresence()) {
// don't add duplicate tasks
if(!findTask(syncTask)) {
Timer& timer = status.getHttpTimer();
// if the time peer have been around is longer than 'autoSyncDelay',
// then return true
if (timer.timeout(syncTask.m_delay * 1000 /* seconds to milliseconds */)) {
return true;
}
}
} else if ((syncTask.m_transport == AutoSyncTask::NEEDS_BT && status.getBtPresence()) ||
syncTask.m_transport == AutoSyncTask::NEEDS_OTHER) {
// don't add duplicate tasks
if(!findTask(syncTask)) {
return true;
}
}
return false;
}
void AutoSyncManager::startTask()
{
// get the front task and run a sync
// if there has been a session for the front task, do nothing
if(hasTask() && !m_session) {
m_activeTask.reset(new AutoSyncTask(m_workQueue.front()));
m_workQueue.pop_front();
std::string newSession = m_server.getNextSession();
m_session = Session::createSession(m_server,
"",
m_activeTask->m_peer,
newSession);
m_session->setPriority(Session::PRI_AUTOSYNC);
m_session->addListener(this);
m_session->activate();
m_server.enqueue(m_session);
}
}
bool AutoSyncManager::hasActiveSession()
{
return m_session && m_session->getActive();
}
void AutoSyncManager::prepare()
{
if(m_session && m_session->getActive()) {
// now a config may contain many urls, so replace it with our own temporarily
// otherwise it only picks the first one
ReadOperations::Config_t config;
StringMap stringMap;
stringMap["syncURL"] = m_activeTask->m_url;
config[""] = stringMap;
m_session->setConfig(true, true, config);
std::string mode;
Session::SourceModes_t sourceModes;
m_session->sync("", Session::SourceModes_t());
}
}
void AutoSyncManager::syncSuccessStart()
{
m_syncSuccessStart = true;
SE_LOG_INFO(NULL, NULL,"Automatic sync for '%s' has been successfully started.\n", m_activeTask->m_peer.c_str());
task->m_syncSuccessStart = true;
SE_LOG_INFO(NULL, NULL,"Automatic sync for '%s' has been successfully started.\n",
task->m_peerName.c_str());
if (m_server.notificationsEnabled()) {
std::string summary = StringPrintf(_("%s is syncing"), m_activeTask->m_peer.c_str());
std::string body = StringPrintf(_("We have just started to sync your computer with the %s sync service."), m_activeTask->m_peer.c_str());
//TODO: set config information for 'sync-ui'
std::string summary = StringPrintf(_("%s is syncing"), task->m_peerName.c_str());
std::string body = StringPrintf(_("We have just started to sync your computer with the %s sync service."),
task->m_peerName.c_str());
// TODO: set config information for 'sync-ui'
m_notificationManager->publish(summary, body);
}
}
@ -327,19 +449,20 @@ static bool ErrorIsTemporary(SyncMLStatus status)
}
}
void AutoSyncManager::syncDone(SyncMLStatus status)
void AutoSyncManager::autoSyncDone(AutoSyncTask *task, SyncMLStatus status)
{
SE_LOG_INFO(NULL, NULL,"Automatic sync for '%s' has been done.\n", m_activeTask->m_peer.c_str());
SE_LOG_INFO(NULL, NULL,"Automatic sync for '%s' has been done.\n", task->m_peerName.c_str());
if (m_server.notificationsEnabled()) {
// send a notification to notification server
std::string summary, body;
if(m_syncSuccessStart && status == STATUS_OK) {
if (task->m_syncSuccessStart && status == STATUS_OK) {
// if sync is successfully started and done
summary = StringPrintf(_("%s sync complete"), m_activeTask->m_peer.c_str());
body = StringPrintf(_("We have just finished syncing your computer with the %s sync service."), m_activeTask->m_peer.c_str());
summary = StringPrintf(_("%s sync complete"), task->m_peerName.c_str());
body = StringPrintf(_("We have just finished syncing your computer with the %s sync service."),
task->m_peerName.c_str());
//TODO: set config information for 'sync-ui'
m_notificationManager->publish(summary, body);
} else if (m_syncSuccessStart || !ErrorIsTemporary(status)) {
} else if (task->m_syncSuccessStart || !ErrorIsTemporary(status)) {
// if sync is successfully started and has errors, or not started successful with a permanent error
// that needs attention
summary = StringPrintf(_("Sync problem."));
@ -351,34 +474,21 @@ void AutoSyncManager::syncDone(SyncMLStatus status)
// keep session around to give clients a chance to query it
m_server.delaySessionDestruction(m_session);
m_session->done();
m_session.reset();
m_activeTask.reset();
m_syncSuccessStart = false;
}
void AutoSyncManager::AutoSyncTaskList::createTimeoutSource()
void AutoSyncManager::anySyncDone(AutoSyncTask *task, SyncMLStatus status)
{
//if interval is 0, only run auto sync when changes are detected.
if(m_interval) {
m_source = g_timeout_add_seconds(m_interval, taskListTimeoutCb, static_cast<gpointer>(this));
}
}
gboolean AutoSyncManager::AutoSyncTaskList::taskListTimeoutCb(gpointer data)
{
AutoSyncTaskList *list = static_cast<AutoSyncTaskList*>(data);
list->scheduleTaskList();
return TRUE;
}
void AutoSyncManager::AutoSyncTaskList::scheduleTaskList()
{
BOOST_FOREACH(AutoSyncTask &syncTask, *this) {
m_manager.addTask(syncTask);
}
g_main_loop_quit(m_manager.m_server.getLoop());
// set "permanently failed" flag according to most recent result
task->m_permanentFailure = !ErrorIsTemporary(status);
SE_LOG_DEBUG(NULL, NULL, "auto sync: sync session %s done, result %d %s",
task->m_configName.c_str(),
status,
task->m_permanentFailure ?
"is a permanent failure" :
status == STATUS_OK ?
"is success" :
"is temporary failure");
}
SE_END_CXX

View File

@ -21,155 +21,157 @@
#define AUTO_SYNC_MANAGER_H
#include <boost/algorithm/string/predicate.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/signals2.hpp>
#include <syncevo/SyncML.h>
#include <syncevo/SmartPtr.h>
#include <syncevo/util.h>
#include "notification-manager-factory.h"
#include "session-listener.h"
#include "timeout.h"
SE_BEGIN_CXX
class Server;
class Session;
/**
* Manager to manage automatic sync.
*
* Once a configuration is enabled with automatic sync, possibly http
* or obex-bt or both, one or more tasks for different URLs are added
* in the task map, grouped by their intervals. A task have to be
* checked whether there is an existing same task in the working
* queue. Once actived, it is put in the working queue.
* or obex-bt or both, the manager tracks whether they are ready to
* run. For that it watches which transports are available (and how
* long), which syncs run, etc.
*
* At any time, there is at most one session for the first task. Once
* it is active by Server, we prepare it and make it ready to
* run. After completion, a new session is created again for the next
* task. And so on.
* Automatic syncs only run when the server is idle. Then a new
* Session is created and thus runs immediately. Because multiple
* parallel sessions are not currently supported by SyncEvolution,
* scheduling the next session waits until the server is idle again.
*
* The Server is in charge of dispatching requests from dbus
* clients and automatic sync tasks.
*
* See Server::run().
*
* Here there are 3 scenarios which have been considered to do
* automatic sync right now:
* 1) For a config enables autosync, an interval has passed.
* 2) Once users log in or resume and an interval has passed. Not
* implemented yet.
* 3) Evolution data server notify any changes. Not implemented yet.
* Currently only time-based automatic syncs are supported.
* Syncs triggered by local or remote changes will be added
* later.
*/
class AutoSyncManager : public SessionListener
class AutoSyncManager
{
Server &m_server;
boost::weak_ptr<AutoSyncManager> m_me;
public:
/** true if we currently hold a ref for AutoTerm */
bool m_autoTermLocked;
/** currently running auto sync session */
boost::shared_ptr<Session> m_session;
/** connects m_server.m_idleSignal with schedule() */
boost::signals2::connection m_idleConnection;
/** time when Bluetooth and HTTP transports became available, zero if not available */
Timespec m_btStartTime, m_httpStartTime;
/** initialize m_idleConnection */
void connectIdle();
public:
/**
* A single task for automatic sync.
* Each task maintain one task for only one sync URL, which never combines
* more than one sync URLs. The difference from 'syncURL' property here is
* that different URLs may have different transports with different statuses.
* Another reason is that SyncContext only use the first URL if it has many sync
* URLs when running. So we split, schedule and process them one by one.
* Each task contains one peer name, peer duration and peer url.
* It is created in initialization and may be updated due to config change.
* It is scheduled by AutoSyncManager to be put in the working queue.
*
* Caches information about the corresponding configuration.
* Some of that information is directly from the config, other
* is collected from sessions (time of last sync).
*
* Each task maintains information for all sync URLs.
*/
class AutoSyncTask
{
public:
/** the peer name of a config */
std::string m_peer;
/** the time that the peer must at least have been around (seconds) */
public:
/**
* unique, normalized config name, set when task is created the first time;
* by definition it cannot be changed later
*/
const std::string m_configName;
/**
* user-configurable peer name, with config name as fallback
*/
std::string m_peerName;
/** copy of config's remoteDeviceId sync property */
std::string m_remoteDeviceId;
/** last auto sync attempt succeeded (needed for notification logic) */
bool m_syncSuccessStart;
/** last auto sync attempt showed permanent failure (don't retry) */
bool m_permanentFailure;
/** autoSyncDelay = the time that the peer must at least have been around (seconds) */
unsigned int m_delay;
/** each task matches with exactly one transport supported for a peer */
/**
* autoSyncInterval = the minimum time in seconds between syncs.
*
* Documentation is vague about whether this is measured as the time from
* start to start or between end to start. Traditionally, the implementation
* was between starts (= fixed rate). This assumed that syncs are short compared
* to the interval. In the extreme case (seen in testing), a sync takes longer
* than the interval and thus the next sync is started immediately - probably
* not what is expected. Keeping the behavior for now.
*/
unsigned int m_interval;
/**
* currently the start time of the last sync, measured with
* the monotonicly increasing OS time
*/
Timespec m_lastSyncTime;
/** maps syncURL to a specific transport mechanism */
enum Transport {
NEEDS_HTTP,
NEEDS_BT,
NEEDS_OTHER
} m_transport;
/** individual sync URL for which this task was created, matches m_transport */
std::string m_url;
};
AutoSyncTask(const std::string &peer, unsigned int delay, Transport transport, const std::string &url)
: m_peer(peer), m_delay(delay), m_transport(transport), m_url(url)
/** list of sync URLs for which autosyncing over their transport mechanism
, in same order as in syncURL */
typedef std::list< std::pair<Transport, std::string> > URLInfo_t;
URLInfo_t m_urls;
AutoSyncTask(const std::string &configName) :
m_configName(configName),
m_syncSuccessStart(false),
m_permanentFailure(false),
m_delay(0),
m_interval(0)
{
}
/** compare whether two tasks are the same. May refine it later with more information */
bool operator==(const AutoSyncTask &right) const
{
return boost::iequals(m_peer, right.m_peer) &&
m_url == right.m_url;
}
/* /\** compare whether two tasks are the same, based on unique config name *\/ */
/* bool operator==(const AutoSyncTask &right) const */
/* { */
/* return m_peer == right.m_peer; */
/* } */
Timeout m_intervalTimeout;
Timeout m_btTimeout;
Timeout m_httpTimeout;
};
/* /\** remove tasks from m_peerMap and m_workQueue created from the config *\/ */
/* void remove(const std::string &configName); */
/**
* AutoSyncTaskList is used to manage sync tasks which are grouped by the
* interval. Each list has one timeout gsource.
* A map with information about *all* configs ever seen while auto
* sync manager was active, including configs without auto sync
* enabled (to track when and if they ran) and deleted configs
* (because they might get recreated).
*/
class AutoSyncTaskList : public std::list<AutoSyncTask>
{
AutoSyncManager &m_manager;
/** the interval used to create timeout source (seconds) */
unsigned int m_interval;
/** timeout gsource */
GLibEvent m_source;
/** callback of timeout source */
static gboolean taskListTimeoutCb(gpointer data);
public:
AutoSyncTaskList(AutoSyncManager &manager, unsigned int interval)
: m_manager(manager), m_interval(interval), m_source(0)
{}
~AutoSyncTaskList() {
if(m_source) {
g_source_remove(m_source);
}
}
/** create timeout source once all tasks are added */
void createTimeoutSource();
/** check task list and put task into working queue */
void scheduleTaskList();
};
/** init a config and set up auto sync task for it */
void initConfig(const std::string &configName);
/** remove tasks from m_peerMap and m_workQueue created from the config */
void remove(const std::string &configName);
/** a map to contain all auto sync tasks. All initialized tasks are stored here.
* Tasks here are grouped by auto sync interval */
typedef std::map<unsigned int, boost::shared_ptr<AutoSyncTaskList> > PeerMap;
typedef std::map<std::string, boost::shared_ptr<AutoSyncTask> > PeerMap;
PeerMap m_peerMap;
/**
* a working queue that including tasks which are pending for doing sync.
* Tasks here are picked from m_peerMap and scheduled to do auto sync */
std::list<AutoSyncTask> m_workQueue;
/**
* the current active task, which may own a session
*/
boost::shared_ptr<AutoSyncTask> m_activeTask;
/**
* the only session created for active task and is put in the session queue.
* at most one session at any time no matter how many tasks we actually have
*/
boost::shared_ptr<Session> m_session;
/** the current sync of session is successfully started */
bool m_syncSuccessStart;
/** used to send notifications */
boost::shared_ptr<NotificationManagerBase> m_notificationManager;
@ -180,72 +182,42 @@ class AutoSyncManager : public SessionListener
*/
void init();
/** operations on tasks queue */
void clearAllTasks() { m_workQueue.clear(); }
/** check m_peerMap and put all tasks in it to working queue */
void scheduleAll();
/**
* check m_peerMap: runs syncs that are ready, sets/updates timers for the rest
*
* @param reason a short explanation why the method gets called (for debugging)
*/
void schedule(const std::string &reason);
/**
* add an auto sync task in the working queue
* Do check before adding a task in the working queue
* Return true if the task is added in the list.
* Watch further progress (if auto sync session),
* record start time (in all cases).
*/
bool addTask(const AutoSyncTask &syncTask);
void sessionStarted(const boost::shared_ptr<Session> &session);
/** find an auto sync task in the working queue or is running */
bool findTask(const AutoSyncTask &syncTask);
/** Show "sync started" notification. */
void autoSyncSuccessStart(AutoSyncTask *task);
/**
* check whether a task is suitable to put in the working queue
* Manager has the information needed to make the decision
*/
bool taskLikelyToRun(const AutoSyncTask &syncTask);
/** Show completion notification. */
void autoSyncDone(AutoSyncTask *task, SyncMLStatus status);
/** Record result. */
void anySyncDone(AutoSyncTask *task, SyncMLStatus status);
AutoSyncManager(Server &server);
public:
static boost::shared_ptr<AutoSyncManager> create(Server &server);
static boost::shared_ptr<AutoSyncManager> createAutoSyncManager(Server &server);
/**
* prevent dbus server automatic termination when it has
* any auto sync task enabled in the configs.
* If returning true, prevent automatic termination.
*/
bool preventTerm() { return !m_peerMap.empty(); }
bool preventTerm();
/**
* called when a config is changed. This causes re-loading the config
*/
void update(const std::string &configName);
/* Is there anything ready to run? */
bool hasTask() { return !m_workQueue.empty(); }
/* Is there anything with automatic syncing waiting for its time to run? */
bool hasAutoConfigs() { return !m_peerMap.empty(); }
/**
* pick the front task from the working queue and create a session for it.
* The session won't be used to do sync until it is active so 'prepare' is
* for calling 'sync' to make the session ready to run
* If there has been a session for the front task, do nothing
*/
void startTask();
/** check whether the active session is owned by Automatic Sync Manger */
bool hasActiveSession();
/** set config and run sync to make the session ready to run */
void prepare();
/**
* Acts as a session listener to track sync statuses if the session is
* belonged to auto sync manager to do auto sync.
* Two methods to listen to session sync changes.
*/
virtual void syncSuccessStart();
virtual void syncDone(SyncMLStatus status);
/** init a config and set up auto sync task for it */
void initConfig(const std::string &configName);
};
SE_END_CXX

View File

@ -21,7 +21,6 @@
#include "client.h"
#include "session.h"
#include "server.h"
#include "resource.h"
SE_BEGIN_CXX
@ -44,6 +43,8 @@ void Client::detach(Resource *resource)
++it) {
if (it->get() == resource) {
if (it->unique()) {
// client was the last owner, and thus the session must be idle (otherwise
// it would also be referenced as active session)
boost::shared_ptr<Session> session = boost::dynamic_pointer_cast<Session>(*it);
if (session) {
// give clients a chance to query the session

View File

@ -30,7 +30,6 @@ SE_BEGIN_CXX
class Server;
class Resource;
class Session;
/**
* Tracks a single client and all sessions and connections that it is

View File

@ -23,6 +23,8 @@
#include <syncevo/Cmdline.h>
#include "dbus-sync.h"
#include "exceptions.h"
#include "session-helper.h"
SE_BEGIN_CXX
@ -34,44 +36,34 @@ SE_BEGIN_CXX
class CmdlineWrapper : public Cmdline
{
virtual SyncContext* createSyncClient() {
return new DBusSync(m_server, m_session);
SessionCommon::SyncParams params;
params.m_config = getConfigName();
return new DBusSync(params, m_helper);
}
Session &m_session;
SessionHelper &m_helper;
/** environment variables passed from client */
map<string, string> m_envVars;
public:
CmdlineWrapper(Session &session,
CmdlineWrapper(SessionHelper &helper,
const vector<string> &args,
const map<string, string> &vars) :
Cmdline(args),
m_session(session),
m_helper(helper),
m_envVars(vars)
{}
bool run(LogRedirect &redirect)
bool run()
{
bool success = true;
//temporarily set environment variables and restore them after running
list<boost::shared_ptr<ScopedEnvChange> > changes;
BOOST_FOREACH(const StringPair &var, m_envVars) {
changes.push_back(boost::shared_ptr<ScopedEnvChange>(new ScopedEnvChange(var.first, var.second)));
}
// exceptions must be handled (= printed) before returning,
// so that our client gets the output
try {
success = Cmdline::run();
} catch (...) {
redirect.flush();
throw;
}
// always forward all currently pending redirected output
// before closing the session
redirect.flush();
bool success = Cmdline::run();
return success;
}
};

View File

@ -31,16 +31,19 @@ SE_BEGIN_CXX
void Connection::failed(const std::string &reason)
{
SE_LOG_DEBUG(NULL, NULL, "connection failed: %s", reason.c_str());
if (m_failure.empty()) {
m_failure = reason;
if (m_session) {
m_session->setStubConnectionError(reason);
}
}
if (m_state != FAILED) {
abort();
}
m_state = FAILED;
// notify client
abort();
// ensure that state is failed
m_state = SessionCommon::FAILED;
// tell helper (again)
m_statusSignal(reason);
}
std::string Connection::buildDescription(const StringMap &peer)
@ -78,14 +81,6 @@ std::string Connection::buildDescription(const StringMap &peer)
return buffer;
}
void Connection::wakeupSession()
{
if (m_loop) {
g_main_loop_quit(m_loop);
m_loop = NULL;
}
}
void Connection::process(const Caller_t &caller,
const GDBusCXX::DBusArray<uint8_t> &message,
const std::string &message_type)
@ -110,7 +105,7 @@ void Connection::process(const Caller_t &caller,
// any kind of error from now on terminates the connection
try {
switch (m_state) {
case SETUP: {
case SessionCommon::SETUP: {
std::string config;
std::string peerDeviceID;
bool serverMode = false;
@ -277,15 +272,14 @@ void Connection::process(const Caller_t &caller,
info.toString());
}
// abort previous session of this client
m_server.killSessions(info.m_deviceID);
// identified peer, still need to abort previous sessions below
peerDeviceID = info.m_deviceID;
} else {
throw runtime_error(StringPrintf("message type '%s' not supported for starting a sync", message_type.c_str()));
}
// run session as client or server
m_state = PROCESSING;
m_state = SessionCommon::PROCESSING;
m_session = Session::createSession(m_server,
peerDeviceID,
config,
@ -302,27 +296,47 @@ void Connection::process(const Caller_t &caller,
// or overwritten with the error given to us in
// Connection::close()
m_session->setStubConnectionError("closed prematurely");
// Now abort all earlier sessions, if necessary. The new
// session will be enqueued below and thus won't get
// killed. It also won't run unless all other sessions
// before it terminate, therefore we don't need to check
// for success.
if (!peerDeviceID.empty()) {
// TODO: On failure we should kill the connection (beware,
// it might go away before killing completes and/or
// fails - need to use shared pointer tracking).
//
// boost::shared_ptr<Connection> c = m_me.lock();
// if (!c) {
// SE_THROW("internal error: Connection::process() cannot lock its own instance");
// }
m_server.killSessionsAsync(peerDeviceID,
SimpleResult(SuccessCb_t(),
ErrorCb_t()));
}
m_server.enqueue(m_session);
break;
}
case PROCESSING:
case SessionCommon::PROCESSING:
throw std::runtime_error("protocol error: already processing a message");
break;
case WAITING:
case SessionCommon::WAITING:
m_incomingMsg = SharedBuffer(reinterpret_cast<const char *>(message.second),
message.first);
m_incomingMsgType = message_type;
m_state = PROCESSING;
// get out of DBusTransportAgent::wait()
wakeupSession();
m_messageSignal(DBusArray<uint8_t>(m_incomingMsg.size(),
reinterpret_cast<uint8_t *>(m_incomingMsg.get())),
m_incomingMsgType);
m_state = SessionCommon::PROCESSING;
m_timeout.deactivate();
break;
case FINAL:
wakeupSession();
case SessionCommon::FINAL:
throw std::runtime_error("protocol error: final reply sent, no further message processing possible");
case DONE:
case SessionCommon::DONE:
throw std::runtime_error("protocol error: connection closed, no further message processing possible");
break;
case FAILED:
case SessionCommon::FAILED:
throw std::runtime_error(m_failure);
break;
default:
@ -338,6 +352,38 @@ void Connection::process(const Caller_t &caller,
}
}
void Connection::send(const DBusArray<uint8_t> buffer,
const std::string &type,
const std::string &url)
{
if (m_state != SessionCommon::PROCESSING) {
SE_THROW_EXCEPTION(TransportException,
"cannot send to our D-Bus peer");
}
// Change state in advance. If we fail while replying, then all
// further resends will fail with the error above.
m_state = SessionCommon::WAITING;
activateTimeout();
m_incomingMsg = SharedBuffer();
// TODO: turn D-Bus exceptions into transport exceptions
StringMap meta;
meta["URL"] = url;
reply(buffer, type, meta, false, m_sessionID);
}
void Connection::sendFinalMsg()
{
if (m_state != SessionCommon::FAILED) {
// send final, empty message and wait for close
m_state = SessionCommon::FINAL;
reply(GDBusCXX::DBusArray<uint8_t>(0, 0),
"", StringMap(),
true, m_sessionID);
}
}
void Connection::close(const Caller_t &caller,
bool normal,
const std::string &error)
@ -355,16 +401,18 @@ void Connection::close(const Caller_t &caller,
}
if (!normal ||
m_state != FINAL) {
m_state != SessionCommon::FINAL) {
std::string err = error.empty() ?
"connection closed unexpectedly" :
error;
m_statusSignal(err);
if (m_session) {
m_session->setStubConnectionError(err);
}
failed(err);
} else {
m_state = DONE;
m_state = SessionCommon::DONE;
m_statusSignal("");
if (m_session) {
m_session->setStubConnectionError("");
}
@ -380,10 +428,10 @@ void Connection::abort()
if (!m_abortSent) {
sendAbort();
m_abortSent = true;
m_state = FAILED;
}
}
void Connection::shutdown()
{
// trigger removal of this connection by removing all
@ -403,9 +451,9 @@ Connection::Connection(Server &server,
m_server(server),
m_peer(peer),
m_mustAuthenticate(must_authenticate),
m_state(SETUP),
m_state(SessionCommon::SETUP),
m_sessionID(sessionID),
m_loop(NULL),
m_timeoutSeconds(-1),
sendAbort(*this, "Abort"),
m_abortSent(false),
reply(*this, "Reply"),
@ -418,20 +466,31 @@ Connection::Connection(Server &server,
m_server.autoTermRef();
}
boost::shared_ptr<Connection> Connection::createConnection(Server &server,
const DBusConnectionPtr &conn,
const std::string &sessionID,
const StringMap &peer,
bool must_authenticate)
{
boost::shared_ptr<Connection> c(new Connection(server, conn, sessionID, peer, must_authenticate));
c->m_me = c;
return c;
}
Connection::~Connection()
{
SE_LOG_DEBUG(NULL, NULL, "done with connection to '%s'%s%s%s",
m_description.c_str(),
m_state == DONE ? ", normal shutdown" : " unexpectedly",
m_state == SessionCommon::DONE ? ", normal shutdown" : " unexpectedly",
m_failure.empty() ? "" : ": ",
m_failure.c_str());
try {
if (m_state != DONE) {
if (m_state != SessionCommon::DONE) {
abort();
m_state = SessionCommon::FAILED;
}
// DBusTransportAgent waiting? Wake it up.
wakeupSession();
m_session.use_count();
m_statusSignal(m_failure);
m_session.reset();
} catch (...) {
// log errors, but do not propagate them because we are
@ -458,6 +517,12 @@ void Connection::ready()
}
m_session->setConfig (false, false, from);
}
// As we cannot resend messages via D-Bus even if running as
// client (API not designed for it), let's use the hard server
// timeout from RetryDuration here.
m_timeoutSeconds = config.getRetryDuration();
const SyncContext context (configName);
std::list<std::string> sources = context.getSyncSources();
@ -505,4 +570,27 @@ void Connection::ready()
m_session->sync(m_syncMode, m_sourceModes);
}
void Connection::activateTimeout()
{
if (m_timeoutSeconds >= 0) {
m_timeout.runOnce(m_timeoutSeconds,
boost::bind(&Connection::timeoutCb,
this));
} else {
m_timeout.deactivate();
}
}
void Connection::timeoutCb()
{
failed(StringPrintf("timed out after %ds", m_timeoutSeconds));
// Don't delete ourselves while some code of the Connection still
// runs. Instead let server do that as part of its event loop.
boost::shared_ptr<Connection> c = m_me.lock();
if (c) {
m_server.delayDeletion(c);
}
}
SE_END_CXX

View File

@ -21,10 +21,19 @@
#define CONNECTION_H
#include "session.h"
#include "session-common.h"
#include "resource.h"
#include <boost/signals2.hpp>
#include <gdbus-cxx-bridge.h>
#include <syncevo/SynthesisEngine.h>
SE_BEGIN_CXX
class Server;
class Session;
/**
* Represents and implements the Connection interface.
@ -40,38 +49,33 @@ class Server;
*/
class Connection : public GDBusCXX::DBusObjectHelper, public Resource
{
private:
Server &m_server;
boost::weak_ptr<Connection> m_me;
StringMap m_peer;
bool m_mustAuthenticate;
enum {
SETUP, /**< ready for first message */
PROCESSING, /**< received message, waiting for engine's reply */
WAITING, /**< waiting for next follow-up message */
FINAL, /**< engine has sent final reply, wait for ACK by peer */
DONE, /**< peer has closed normally after the final reply */
FAILED /**< in a failed state, no further operation possible */
} m_state;
SessionCommon::ConnectionState m_state;
std::string m_failure;
/** first parameter for Session::sync() */
std::string m_syncMode;
/** second parameter for Session::sync() */
Session::SourceModes_t m_sourceModes;
SessionCommon::SourceModes_t m_sourceModes;
const std::string m_sessionID;
boost::shared_ptr<Session> m_session;
/**
* main loop that our DBusTransportAgent is currently waiting in,
* NULL if not waiting
* Defines the timeout in seconds. -1 and thus "no timeout" by default.
*
* The timeout is acticated each time the connection goes into
* WAITING mode. Once it triggers, the connection is put into
* the FAILED and queued for delayed deletion in the server.
*/
GMainLoop *m_loop;
/**
* get our peer session out of the DBusTransportAgent,
* if it is currently waiting for us (indicated via m_loop)
*/
void wakeupSession();
int m_timeoutSeconds;
Timeout m_timeout;
void activateTimeout();
void timeoutCb();
/**
* buffer for received data, waiting here for engine to ask
@ -117,6 +121,7 @@ class Connection : public GDBusCXX::DBusObjectHelper, public Resource
/** Connection.Abort */
GDBusCXX::EmitSignal0 sendAbort;
bool m_abortSent;
/** Connection.Reply */
GDBusCXX::EmitSignal5<const GDBusCXX::DBusArray<uint8_t> &,
const std::string &,
@ -124,27 +129,47 @@ class Connection : public GDBusCXX::DBusObjectHelper, public Resource
bool,
const std::string &> reply;
friend class DBusTransportAgent;
public:
const std::string m_description;
Connection(Server &server,
const GDBusCXX::DBusConnectionPtr &conn,
const std::string &session_num,
const StringMap &peer,
bool must_authenticate);
public:
const std::string m_description;
static boost::shared_ptr<Connection> createConnection(Server &server,
const GDBusCXX::DBusConnectionPtr &conn,
const std::string &session_num,
const StringMap &peer,
bool must_authenticate);
~Connection();
/** session requested by us is ready to run a sync */
void ready();
/** send outgoing message via connection */
void send(const GDBusCXX::DBusArray<uint8_t> buffer,
const std::string &type,
const std::string &url);
/** send last, empty message and enter FINAL state */
void sendFinalMsg();
/** connection is no longer needed, ensure that it gets deleted */
void shutdown();
/** peer is not trusted, must authenticate as part of SyncML */
bool mustAuthenticate() const { return m_mustAuthenticate; }
/** new incoming message ready */
typedef boost::signals2::signal<void (const GDBusCXX::DBusArray<uint8_t> &, const std::string &)> MessageSignal_t;
MessageSignal_t m_messageSignal;
/** connection went down (empty string) or failed (error message) */
typedef boost::signals2::signal<void (const std::string &)> StatusSignal_t;
StatusSignal_t m_statusSignal;
};
SE_END_CXX

View File

@ -18,37 +18,112 @@
*/
#include "dbus-sync.h"
#include "session.h"
#include "session-helper.h"
#include "dbus-transport-agent.h"
#include "server.h"
#include <syncevo/SyncSource.h>
#include <syncevo/SuspendFlags.h>
#include <syncevo/ForkExec.h>
SE_BEGIN_CXX
DBusSync::DBusSync(const std::string &config,
Session &session) :
SyncContext(config, true),
m_session(session)
DBusSync::DBusSync(const SessionCommon::SyncParams &params,
SessionHelper &helper) :
SyncContext(params.m_config, true),
m_helper(helper),
m_params(params),
m_waiting(false)
{
setUserInterface(this);
setServerAlerted(params.m_serverAlerted);
if (params.m_serverMode) {
initServer(params.m_sessionID,
SharedBuffer(params.m_initialMessage.c_str(), params.m_initialMessage.size()),
params.m_initialMessageType);
}
if (params.m_remoteInitiated) {
setRemoteInitiated(true);
}
// Watch status of parent and our own process and cancel
// any pending password request if parent or we go down.
boost::shared_ptr<ForkExecChild> forkexec = m_helper.getForkExecChild();
if (forkexec) {
m_parentWatch = forkexec->m_onQuit.connect(boost::bind(&DBusSync::passwordResponse, this, true, false, ""));
}
m_suspendFlagsWatch = SuspendFlags::getSuspendFlags().m_stateChanged.connect(boost::bind(&DBusSync::suspendFlagsChanged, this, _1));
// Apply temporary config filters. The parameters of this function
// override the source filters, if set.
setConfigFilter(true, "", params.m_syncFilter);
FilterConfigNode::ConfigFilter filter;
filter = params.m_sourceFilter;
if (!params.m_mode.empty()) {
filter["sync"] = params.m_mode;
}
setConfigFilter(false, "", filter);
BOOST_FOREACH(const std::string &source,
getSyncSources()) {
SessionCommon::SourceFilters_t::const_iterator fit = params.m_sourceFilters.find(source);
filter = fit == params.m_sourceFilters.end() ?
FilterConfigNode::ConfigFilter() :
fit->second;
SessionCommon::SourceModes_t::const_iterator it = params.m_sourceModes.find(source);
if (it != params.m_sourceModes.end()) {
filter["sync"] = it->second;
}
setConfigFilter(false, source, filter);
}
// Create source status and progress entries for each source in
// the parent. See Session::sourceProgress().
BOOST_FOREACH(const std::string source,
getSyncSources()) {
m_helper.emitSourceProgress(sysync::PEV_PREPARING,
source,
SYNC_NONE,
0, 0, 0);
}
}
DBusSync::~DBusSync()
{
m_parentWatch.disconnect();
m_suspendFlagsWatch.disconnect();
}
boost::shared_ptr<TransportAgent> DBusSync::createTransportAgent()
{
if (m_session.useStubConnection()) {
// use the D-Bus Connection to send and receive messages
boost::shared_ptr<TransportAgent> agent(new DBusTransportAgent(m_session.getServer().getLoop(),
m_session,
m_session.getStubConnection()));
// We don't know whether we'll run as client or server.
// But we as we cannot resend messages via D-Bus even if running as
// client (API not designed for it), let's use the hard timeout
// from RetryDuration here.
int timeout = getRetryDuration();
agent->setTimeout(timeout);
if (m_params.m_serverAlerted || m_params.m_serverMode) {
// Use the D-Bus Connection to send and receive messages.
boost::shared_ptr<DBusTransportAgent> agent(new DBusTransportAgent(m_helper));
// Hook up agent with D-Bus in the helper. The agent may go
// away at any time, so use instance tracking.
m_helper.m_messageSignal.connect(SessionHelper::MessageSignal_t::slot_type(&DBusTransportAgent::storeMessage,
agent.get(),
_1,
_2).track(agent));
m_helper.m_connectionStateSignal.connect(SessionHelper::ConnectionStateSignal_t::slot_type(&DBusTransportAgent::storeState,
agent.get(),
_1).track(agent));
if (m_params.m_serverAlerted) {
// A SAN message was sent to us, need to reply.
agent->serverAlerted();
} else if (m_params.m_serverMode) {
// Let transport return initial message to engine.
agent->storeMessage(GDBusCXX::DBusArray<uint8_t>(m_params.m_initialMessage.size(),
reinterpret_cast<const uint8_t *>(m_params.m_initialMessage.c_str())),
m_params.m_initialMessageType);
}
return agent;
} else {
// no connection, use HTTP via libsoup/GMainLoop
GMainLoop *loop = m_session.getServer().getLoop();
GMainLoop *loop = m_helper.getLoop();
boost::shared_ptr<TransportAgent> agent = SyncContext::createTransportAgent(loop);
return agent;
}
@ -58,7 +133,7 @@ void DBusSync::displaySyncProgress(sysync::TProgressEventEnum type,
int32_t extra1, int32_t extra2, int32_t extra3)
{
SyncContext::displaySyncProgress(type, extra1, extra2, extra3);
m_session.syncProgress(type, extra1, extra2, extra3);
m_helper.emitSyncProgress(type, extra1, extra2, extra3);
}
void DBusSync::displaySourceProgress(sysync::TProgressEventEnum type,
@ -66,55 +141,159 @@ void DBusSync::displaySourceProgress(sysync::TProgressEventEnum type,
int32_t extra1, int32_t extra2, int32_t extra3)
{
SyncContext::displaySourceProgress(type, source, extra1, extra2, extra3);
m_session.sourceProgress(type, source, extra1, extra2, extra3);
m_helper.emitSourceProgress(type, source.getName(), source.getFinalSyncMode(),
extra1, extra2, extra3);
}
void DBusSync::reportStepCmd(sysync::uInt16 stepCmd)
{
switch(stepCmd) {
case sysync::STEPCMD_SENDDATA:
case sysync::STEPCMD_RESENDDATA:
case sysync::STEPCMD_NEEDDATA:
//sending or waiting data
m_session.setStepInfo(true);
break;
default:
// otherwise, processing
m_session.setStepInfo(false);
break;
case sysync::STEPCMD_SENDDATA:
case sysync::STEPCMD_RESENDDATA:
case sysync::STEPCMD_NEEDDATA:
// sending or waiting
if (!m_waiting) {
m_helper.emitWaiting(true);
m_waiting = true;
}
break;
default:
// otherwise, processing
if (m_waiting) {
m_helper.emitWaiting(false);
m_waiting = false;
}
break;
}
}
void DBusSync::syncSuccessStart()
{
m_session.syncSuccessStart();
}
int DBusSync::sleep(int intervals)
{
time_t start = time(NULL);
while (true) {
g_main_context_iteration(NULL, false);
time_t now = time(NULL);
if (checkForSuspend() || checkForAbort()) {
return (intervals - now + start);
}
if (intervals - now + start <= 0) {
return intervals - now +start;
}
}
m_helper.emitSyncSuccessStart();
}
string DBusSync::askPassword(const string &passwordName,
const string &descr,
const ConfigPasswordKey &key)
{
string password = DBusUserInterface::askPassword(passwordName, descr, key);
std::string password;
std::string error;
if(password.empty()) {
password = m_session.askPassword(passwordName, descr, key);
askPasswordAsync(passwordName, descr, key,
boost::bind(static_cast<std::string & (std::string::*)(const std::string &)>(&std::string::assign),
&password, _1),
boost::bind(static_cast<SyncMLStatus (*)(std::string &, HandleExceptionFlags)>(&Exception::handle),
boost::ref(error), HANDLE_EXCEPTION_NO_ERROR));
// We know that askPasswordAsync() is done when it cleared the
// callback functors.
while (m_passwordSuccess) {
g_main_context_iteration(NULL, true);
}
if (!error.empty()) {
Exception::tryRethrow(error);
SE_THROW(StringPrintf("password request failed: %s", error.c_str()));
}
return password;
}
void DBusSync::askPasswordAsync(const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key,
const boost::function<void (const std::string &)> &success,
const boost::function<void ()> &failureException)
{
// cannot handle more than one password request at a time
m_passwordSuccess.clear();
m_passwordFailure.clear();
m_passwordDescr = descr;
string password;
if (GetLoadPasswordSignal()(passwordName, descr, key, password)) {
// handled
success(password);
return;
}
try {
SE_LOG_DEBUG(NULL, NULL, "asking parent for password");
m_passwordSuccess = success;
m_passwordFailure = failureException;
m_helper.emitPasswordRequest(descr, key);
if (!m_helper.connected()) {
SE_LOG_DEBUG(NULL, NULL, "password request failed, lost connection");
SE_THROW_EXCEPTION_STATUS(StatusException,
StringPrintf("Could not get the '%s' password from user, no connection to UI.",
descr.c_str()),
STATUS_PASSWORD_TIMEOUT);
}
if (SuspendFlags::getSuspendFlags().getState() != SuspendFlags::NORMAL) {
SE_LOG_DEBUG(NULL, NULL, "password request failed, was asked to terminate");
SE_THROW_EXCEPTION_STATUS(StatusException,
StringPrintf("Could not get the '%s' password from user, was asked to shut down.",
descr.c_str()),
STATUS_PASSWORD_TIMEOUT);
}
} catch (...) {
m_passwordSuccess.clear();
m_passwordFailure.clear();
failureException();
}
}
void DBusSync::passwordResponse(bool timedOut, bool aborted, const std::string &password)
{
boost::function<void (const std::string &)> success;
boost::function<void ()> failureException;
std::swap(success, m_passwordSuccess);
std::swap(failureException, m_passwordFailure);
if (success && failureException) {
SE_LOG_DEBUG(NULL, NULL, "password result: %s",
timedOut ? "timeout or parent gone" :
aborted ? "user abort" :
password.empty() ? "empty password" :
"valid password");
try {
if (timedOut) {
SE_THROW_EXCEPTION_STATUS(StatusException,
StringPrintf("Could not get the '%s' password from user.",
m_passwordDescr.c_str()),
STATUS_PASSWORD_TIMEOUT);
} else if (aborted) {
SE_THROW_EXCEPTION_STATUS(StatusException,
StringPrintf("User did not provide the '%s' password.",
m_passwordDescr.c_str()),
SyncMLStatus(sysync::LOCERR_USERABORT));
} else {
success(password);
}
} catch (...) {
failureException();
}
}
}
void DBusSync::suspendFlagsChanged(SuspendFlags &flags)
{
if (flags.getState() != SuspendFlags::NORMAL) {
passwordResponse(true, false, "");
}
}
bool DBusSync::savePassword(const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key)
{
if (GetSavePasswordSignal()(passwordName, password, key)) {
return true;
}
// not saved
return false;
}
void DBusSync::readStdin(std::string &content)
{
// might get called, must be avoided by user
SE_THROW("reading from stdin not supported when running with daemon, use --daemon=no");
}
SE_END_CXX

View File

@ -20,26 +20,49 @@
#ifndef DBUS_SYNC_H
#define DBUS_SYNC_H
#include "dbus-user-interface.h"
#include "session-common.h"
#include <syncevo/SyncContext.h>
#include <syncevo/UserInterface.h>
#include <syncevo/SuspendFlags.h>
#include <boost/signals2.hpp>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
class Session;
class SessionHelper;
namespace SessionCommon {
struct SyncParams;
}
/**
* A running sync engine which keeps answering on D-Bus whenever
* possible and updates the Session while the sync runs.
* Maps sync events to D-Bus signals in SessionHelper.
* Does password requests by sending out a request for
* them via SessionHelper and waiting until a reply (positive
* or negative) is received.
*/
class DBusSync : public SyncContext, private DBusUserInterface
class DBusSync : public SyncContext, private UserInterface
{
Session &m_session;
SessionHelper &m_helper;
SessionCommon::SyncParams m_params;
bool m_waiting;
boost::function<void (const std::string &)> m_passwordSuccess;
boost::function<void ()> m_passwordFailure;
std::string m_passwordDescr;
boost::signals2::connection m_parentWatch;
boost::signals2::connection m_suspendFlagsWatch;
void suspendFlagsChanged(SuspendFlags &flags);
public:
DBusSync(const std::string &config,
Session &session);
~DBusSync() {}
DBusSync(const SessionCommon::SyncParams &params,
SessionHelper &helper);
~DBusSync();
/** to be called by SessionHelper when it gets a response via D-Bus */
void passwordResponse(bool timedOut, bool aborted, const std::string &password);
protected:
virtual boost::shared_ptr<TransportAgent> createTransportAgent();
@ -48,21 +71,17 @@ protected:
virtual void displaySourceProgress(sysync::TProgressEventEnum type,
SyncSource &source,
int32_t extra1, int32_t extra2, int32_t extra3);
virtual void reportStepCmd(sysync::uInt16 stepCmd);
/** called when a sync is successfully started */
virtual void syncSuccessStart();
virtual int sleep(int intervals);
/**
* Implement askPassword to retrieve password in gnome-keyring.
* If not found, then ask it from dbus clients.
*/
string askPassword(const string &passwordName,
const string &descr,
const ConfigPasswordKey &key);
virtual void askPasswordAsync(const std::string &passwordName, const std::string &descr, const ConfigPasswordKey &key,
const boost::function<void (const std::string &)> &success,
const boost::function<void ()> &failureException);
virtual bool savePassword(const std::string &passwordName, const std::string &password, const ConfigPasswordKey &key);
virtual void readStdin(std::string &content);
};
SE_END_CXX

View File

@ -18,161 +18,154 @@
*/
#include "dbus-transport-agent.h"
#include "connection.h"
#include "session-helper.h"
SE_BEGIN_CXX
DBusTransportAgent::DBusTransportAgent(GMainLoop *loop,
Session &session,
boost::weak_ptr<Connection> connection) :
m_loop(loop),
m_session(session),
m_connection(connection),
m_timeoutSeconds(0),
m_eventTriggered(false),
m_waiting(false)
DBusTransportAgent::DBusTransportAgent(SessionHelper &helper) :
m_helper(helper),
m_state(SessionCommon::SETUP)
{
}
DBusTransportAgent::~DBusTransportAgent()
void DBusTransportAgent::serverAlerted()
{
boost::shared_ptr<Connection> connection = m_connection.lock();
if (connection) {
connection->shutdown();
SE_LOG_DEBUG(NULL, NULL, "D-Bus transport: server alerted (old state: %s, %s)",
SessionCommon::ConnectionStateToString(m_state).c_str(),
m_error.c_str());
if (m_state == SessionCommon::SETUP) {
m_state = SessionCommon::PROCESSING;
} else {
SE_THROW_EXCEPTION(TransportException,
"setting 'server alerted' only allowed during setup");
}
}
void DBusTransportAgent::storeMessage(const GDBusCXX::DBusArray<uint8_t> &buffer,
const std::string &type)
{
SE_LOG_DEBUG(NULL, NULL, "D-Bus transport: store incoming message, %ld bytes, %s (old state: %s, %s)",
(long)buffer.first,
type.c_str(),
SessionCommon::ConnectionStateToString(m_state).c_str(),
m_error.c_str());
if (m_state == SessionCommon::SETUP ||
m_state == SessionCommon::WAITING) {
m_incomingMsg = SharedBuffer(reinterpret_cast<const char *>(buffer.second), buffer.first);
m_incomingMsgType = type;
m_state = SessionCommon::PROCESSING;
} else if (m_state == SessionCommon::PROCESSING &&
m_incomingMsgType == type &&
m_incomingMsg.size() == buffer.first &&
!memcmp(m_incomingMsg.get(), buffer.second, buffer.first)) {
// Exactly the same message, accept resend without error, and
// without doing anything.
} else {
SE_THROW_EXCEPTION(TransportException,
"unexpected message");
}
}
void DBusTransportAgent::storeState(const std::string &error)
{
SE_LOG_DEBUG(NULL, NULL, "D-Bus transport: got error '%s', current error is '%s', state %s",
error.c_str(), m_error.c_str(), SessionCommon::ConnectionStateToString(m_state).c_str());
if (!error.empty()) {
// specific error encountered
m_state = SessionCommon::FAILED;
if (m_error.empty()) {
m_error = error;
}
} else if (m_state == SessionCommon::FINAL) {
// expected loss of connection
m_state = SessionCommon::DONE;
} else {
// unexpected loss of connection
m_state = SessionCommon::FAILED;
}
}
void DBusTransportAgent::send(const char *data, size_t len)
{
boost::shared_ptr<Connection> connection = m_connection.lock();
if (!connection) {
SE_THROW_EXCEPTION(TransportException,
"D-Bus peer has disconnected");
}
if (connection->m_state != Connection::PROCESSING) {
SE_LOG_DEBUG(NULL, NULL, "D-Bus transport: outgoing message %ld bytes, %s, %s",
(long)len, m_type.c_str(), m_url.c_str());
if (m_state != SessionCommon::PROCESSING) {
SE_THROW_EXCEPTION(TransportException,
"cannot send to our D-Bus peer");
}
// Change state in advance. If we fail while replying, then all
// further resends will fail with the error above.
connection->m_state = Connection::WAITING;
connection->m_incomingMsg = SharedBuffer();
if (m_timeoutSeconds) {
m_eventSource = g_timeout_add_seconds(m_timeoutSeconds, timeoutCallback, static_cast<gpointer>(this));
}
m_eventTriggered = false;
// TODO: turn D-Bus exceptions into transport exceptions
StringMap meta;
meta["URL"] = m_url;
connection->reply(GDBusCXX::makeDBusArray(len, reinterpret_cast<const uint8_t *>(data)),
m_type, meta, false, connection->m_sessionID);
m_state = SessionCommon::WAITING;
m_incomingMsg = SharedBuffer();
m_helper.emitMessage(GDBusCXX::DBusArray<uint8_t>(len, reinterpret_cast<const uint8_t *>(data)),
m_type,
m_url);
}
void DBusTransportAgent::shutdown()
{
boost::shared_ptr<Connection> connection = m_connection.lock();
if (!connection) {
SE_THROW_EXCEPTION(TransportException,
"D-Bus peer has disconnected");
}
if (connection->m_state != Connection::FAILED) {
// send final, empty message and wait for close
connection->m_state = Connection::FINAL;
connection->reply(GDBusCXX::DBusArray<uint8_t>(0, 0),
"", StringMap(),
true, connection->m_sessionID);
SE_LOG_DEBUG(NULL, NULL, "D-Bus transport: shut down (old state: %s, %s)",
SessionCommon::ConnectionStateToString(m_state).c_str(),
m_error.c_str());
if (m_state != SessionCommon::FAILED) {
m_state = SessionCommon::FINAL;
m_helper.emitShutdown();
}
}
gboolean DBusTransportAgent::timeoutCallback(gpointer transport)
void DBusTransportAgent::doWait()
{
DBusTransportAgent *me = static_cast<DBusTransportAgent *>(transport);
me->m_eventTriggered = true;
if (me->m_waiting) {
g_main_loop_quit(me->m_loop);
}
return false;
SE_LOG_DEBUG(NULL, NULL, "D-Bus transport: wait - old state: %s, %s",
SessionCommon::ConnectionStateToString(m_state).c_str(),
m_error.c_str());
// Block for one iteration. Both D-Bus calls and signals (thanks
// to the SuspendFlags guard in the running sync session) will
// wake us up.
g_main_context_iteration(NULL, true);
SE_LOG_DEBUG(NULL, NULL, "D-Bus transport: wait - new state: %s, %s",
SessionCommon::ConnectionStateToString(m_state).c_str(),
m_error.c_str());
}
void DBusTransportAgent::doWait(boost::shared_ptr<Connection> &connection)
{
// let Connection wake us up when it has a reply or
// when it closes down
connection->m_loop = m_loop;
// release our reference so that the Connection instance can
// be destructed when requested by the D-Bus peer
connection.reset();
// now wait
m_waiting = true;
g_main_loop_run(m_loop);
m_waiting = false;
}
DBusTransportAgent::Status DBusTransportAgent::wait(bool noReply)
{
boost::shared_ptr<Connection> connection = m_connection.lock();
if (!connection) {
SE_THROW_EXCEPTION(TransportException,
"D-Bus peer has disconnected");
}
switch (connection->m_state) {
case Connection::PROCESSING:
m_incomingMsg = connection->m_incomingMsg;
m_incomingMsgType = connection->m_incomingMsgType;
switch (m_state) {
case SessionCommon::PROCESSING:
return GOT_REPLY;
break;
case Connection::FINAL:
if (m_eventTriggered) {
return TIME_OUT;
}
doWait(connection);
case SessionCommon::FINAL:
doWait();
// if the connection is still available, then keep waiting
connection = m_connection.lock();
if (connection) {
if (m_state == SessionCommon::FINAL) {
return ACTIVE;
} else if (m_session.getStubConnectionError().empty()) {
} else if (m_error.empty()) {
return INACTIVE;
} else {
SE_THROW_EXCEPTION(TransportException, m_session.getStubConnectionError());
SE_THROW_EXCEPTION(TransportException, m_error);
return FAILED;
}
break;
case Connection::WAITING:
case SessionCommon::WAITING:
if (noReply) {
// message is sent as far as we know, so return
return INACTIVE;
}
if (m_eventTriggered) {
return TIME_OUT;
}
doWait(connection);
doWait();
// tell caller to check again
return ACTIVE;
break;
case Connection::DONE:
case SessionCommon::DONE:
if (!noReply) {
SE_THROW_EXCEPTION(TransportException,
"internal error: transport has shut down, can no longer receive reply");
}
return CLOSED;
default:
SE_THROW_EXCEPTION(TransportException,
"internal error: send() on connection which is not ready");
"send() on connection which is not ready");
break;
}

View File

@ -25,52 +25,57 @@
#include <syncevo/SynthesisEngine.h>
#include <boost/weak_ptr.hpp>
#include <gdbus-cxx-bridge.h>
#include "session-common.h"
SE_BEGIN_CXX
class Session;
class Connection;
class SessionHelper;
/**
* A proxy for a Connection instance. The Connection instance can go
* away (weak pointer, must be locked and and checked each time it is
* needed). The agent must remain available as long as the engine
* needs and basically becomes unusuable once the connection dies.
* A proxy for a Connection instance in the syncevo-dbus-server. The
* Connection instance can go away (weak pointer, must be locked and
* and checked each time it is needed). The agent must remain
* available as long as the engine needs and basically becomes
* unusuable once the connection dies. That information is relayed
* to it via the D-Bus API.
*
* Reconnecting is not currently supported.
*/
class DBusTransportAgent : public TransportAgent
{
GMainLoop *m_loop;
Session &m_session;
boost::weak_ptr<Connection> m_connection;
SessionHelper &m_helper;
/** information about outgoing message, provided by user of this instance */
std::string m_url;
std::string m_type;
/*
* When the timeout occurs, we always abort the current
* transmission. If it is invoked while we are not in the wait()
* of this transport, then we remember that in m_eventTriggered
* and return from wait() right away. The main loop is only
* quit when the transport is waiting in it. This is a precaution
* to not interfere with other parts of the code.
*/
int m_timeoutSeconds;
GLibEvent m_eventSource;
bool m_eventTriggered;
bool m_waiting;
/** latest message sent to us */
SharedBuffer m_incomingMsg;
std::string m_incomingMsgType;
void doWait(boost::shared_ptr<Connection> &connection);
static gboolean timeoutCallback(gpointer transport);
/** explanation for problem, sent to us by syncevo-dbus-server */
std::string m_error;
/**
* Current state. Changed by us as messages are sent and received
* and by syncevo-dbus-server:
* - connectionState with error -> failed
* - connectionState without error -> closed
*/
SessionCommon::ConnectionState m_state;
void doWait();
public:
DBusTransportAgent(GMainLoop *loop,
Session &session,
boost::weak_ptr<Connection> connection);
~DBusTransportAgent();
DBusTransportAgent(SessionHelper &helper);
void serverAlerted();
void storeMessage(const GDBusCXX::DBusArray<uint8_t> &buffer,
const std::string &type);
void storeState(const std::string &error);
virtual void setURL(const std::string &url) { m_url = url; }
virtual void setContentType(const std::string &type) { m_type = type; }
@ -78,11 +83,7 @@ class DBusTransportAgent : public TransportAgent
virtual void cancel() {}
virtual void shutdown();
virtual Status wait(bool noReply = false);
virtual void setTimeout(int seconds)
{
m_timeoutSeconds = seconds;
m_eventSource = 0;
}
virtual void setTimeout(int seconds) {}
virtual void getReply(const char *&data, size_t &len, std::string &contentType);
};

View File

@ -18,7 +18,6 @@
*/
#include "info-req.h"
#include "session.h"
#include "server.h"
using namespace GDBusCXX;
@ -28,14 +27,19 @@ SE_BEGIN_CXX
InfoReq::InfoReq(Server &server,
const string &type,
const InfoMap &parameters,
const Session *session,
const string &sessionPath,
uint32_t timeout) :
m_server(server), m_session(session), m_infoState(IN_REQ),
m_status(ST_RUN), m_type(type), m_param(parameters),
m_timeout(timeout), m_timer(m_timeout * 1000)
m_server(server),
m_sessionPath(sessionPath),
m_id(server.getNextInfoReq()),
m_timeoutSeconds(timeout),
m_infoState(IN_REQ),
m_status(ST_RUN),
m_type(type),
m_param(parameters)
{
m_id = m_server.getNextInfoReq();
m_server.emitInfoReq(*this);
m_timeout.runOnce(m_timeoutSeconds, boost::bind(boost::ref(m_timeoutSignal)));
m_param.clear();
}
@ -43,58 +47,6 @@ InfoReq::~InfoReq()
{
m_handler = "";
done();
m_server.removeInfoReq(*this);
}
InfoReq::Status InfoReq::check()
{
if(m_status == ST_RUN) {
// give an opportunity to poll the sources on the main context
g_main_context_iteration(g_main_loop_get_context(m_server.getLoop()), false);
checkTimeout();
}
return m_status;
}
bool InfoReq::getResponse(InfoMap &response)
{
if (m_status == ST_OK) {
response = m_response;
return true;
}
return false;
}
InfoReq::Status InfoReq::wait(InfoMap &response, uint32_t interval)
{
// give a chance to check whether it has been timeout
check();
if(m_status == ST_RUN) {
guint checkSource = g_timeout_add_seconds(interval,
(GSourceFunc) checkCallback,
static_cast<gpointer>(this));
while(m_status == ST_RUN) {
g_main_context_iteration(g_main_loop_get_context(m_server.getLoop()), true);
}
// if the source is not removed
if(m_status != ST_TIMEOUT && m_status != ST_CANCEL) {
g_source_remove(checkSource);
}
}
if (m_status == ST_OK) {
response = m_response;
}
return m_status;
}
void InfoReq::cancel()
{
if(m_status == ST_RUN) {
m_handler = "";
done();
m_status = ST_CANCEL;
}
}
string InfoReq::statusToString(Status status)
@ -127,31 +79,6 @@ string InfoReq::infoStateToString(InfoState state)
}
}
gboolean InfoReq::checkCallback(gpointer data)
{
// TODO: check abort and suspend(MB#8730)
// if InfoRequest("request") is sent and waiting for InfoResponse("working"),
// add a timeout mechanism
InfoReq *req = static_cast<InfoReq*>(data);
if (req->checkTimeout()) {
return FALSE;
}
return TRUE;
}
bool InfoReq::checkTimeout()
{
// if waiting for client response, check time out
if(m_status == ST_RUN) {
if (m_timer.timeout()) {
m_status = ST_TIMEOUT;
return true;
}
}
return false;
}
void InfoReq::setResponse(const Caller_t &caller, const string &state, const InfoMap &response)
{
if(m_status != ST_RUN) {
@ -161,27 +88,28 @@ void InfoReq::setResponse(const Caller_t &caller, const string &state, const Inf
m_infoState = IN_WAIT;
m_server.emitInfoReq(*this);
//reset the timer, used to check timeout
m_timer.reset();
} else if(m_infoState == IN_WAIT && state == "response") {
m_timeout.runOnce(m_timeoutSeconds, boost::bind(boost::ref(m_timeoutSignal)));
} else if ((m_infoState == IN_WAIT || m_infoState == IN_REQ) && state == "response") {
m_response = response;
m_handler = caller;
done();
m_status = ST_OK;
m_responseSignal(m_response);
done();
}
}
string InfoReq::getSessionPath() const
{
return m_session ? m_session->getPath() : "";
return m_sessionPath;
}
void InfoReq::done()
{
if (m_infoState != IN_DONE) {
m_infoState = IN_DONE;
m_server.emitInfoReq(*this);
}
m_server.removeInfoReq(getId());
}
SE_END_CXX

View File

@ -22,13 +22,15 @@
#include <string>
#include "timer.h"
#include "gdbus-cxx-bridge.h"
#include "timeout.h"
#include <boost/signals2.hpp>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
class Server;
class Session;
/**
* A wrapper for handling info request and response.
@ -52,39 +54,39 @@ public:
InfoReq(Server &server,
const std::string &type,
const InfoMap &parameters,
const Session *session,
const std::string &sessionPath,
uint32_t timeout = 120);
~InfoReq();
/**
* check whether the request is ready. Also give an opportunity
* to poll the sources and then check the response is ready
* @return the state of the request
*/
Status check();
std::string getId() const { return m_id; }
std::string getSessionPath() const;
std::string getInfoStateStr() const { return infoStateToString(m_infoState); }
std::string getHandler() const { return m_handler; }
std::string getType() const { return m_type; }
const InfoMap& getParam() const { return m_param; }
/**
* wait the response until timeout, abort or suspend. It may be blocked.
* The response is returned though the parameter 'response' when the Status is
* 'ST_OK'. Otherwise, corresponding statuses are returned.
* @param response the received response if gotten
* @param interval the interval to check abort, suspend and timeout, in seconds
* @return the current status
* Connect to this signal to be notified that a final response has
* been received.
*/
Status wait(InfoMap &response, uint32_t interval = 3);
typedef boost::signals2::signal<void (const InfoMap &)> ResponseSignal_t;
ResponseSignal_t m_responseSignal;
/**
* get response when it is ready. If false, nothing will be set in response
* Connect to this signal to be notified when it is considered timed out.
* The timeout counting restarts each time any client sends any kind of
* response.
*/
bool getResponse(InfoMap &response);
/** cancel the request. If request is done, cancel won't do anything */
void cancel();
typedef boost::signals2::signal<void ()> TimeoutSignal_t;
TimeoutSignal_t m_timeoutSignal;
/** get current status in string format */
std::string getStatusStr() const { return statusToString(m_status); }
/** set response from dbus clients */
void setResponse(const GDBusCXX::Caller_t &caller, const std::string &state, const InfoMap &response);
private:
static std::string statusToString(Status status);
@ -96,35 +98,18 @@ private:
static std::string infoStateToString(InfoState state);
/** callback for the timemout source */
static gboolean checkCallback(gpointer data);
/** check whether the request is timeout */
bool checkTimeout();
friend class Server;
/** set response from dbus clients */
void setResponse(const GDBusCXX::Caller_t &caller, const std::string &state, const InfoMap &response);
/** send 'done' state if needed */
void done();
std::string getId() const { return m_id; }
std::string getSessionPath() const;
std::string getInfoStateStr() const { return infoStateToString(m_infoState); }
std::string getHandler() const { return m_handler; }
std::string getType() const { return m_type; }
const InfoMap& getParam() const { return m_param; }
Server &m_server;
/** caller's session, might be NULL */
const Session *m_session;
const std::string m_sessionPath;
/** unique id of this info request */
std::string m_id;
/** times out this amount of seconds after last interaction with client */
uint32_t m_timeoutSeconds;
Timeout m_timeout;
/** info req state defined in dbus api */
InfoState m_infoState;
@ -143,11 +128,7 @@ void setResponse(const GDBusCXX::Caller_t &caller, const std::string &state, con
/** response returned from dbus clients */
InfoMap m_response;
/** default timeout is 120 seconds */
uint32_t m_timeout;
/** a timer */
Timer m_timer;
void done();
};
SE_END_CXX

View File

@ -25,9 +25,12 @@
#include "server.h"
#include "restart.h"
#include "session-common.h"
#include <syncevo/SyncContext.h>
#include <syncevo/SuspendFlags.h>
#include <syncevo/LogRedirect.h>
#include <syncevo/LogSyslog.h>
using namespace SyncEvo;
using namespace GDBusCXX;
@ -35,7 +38,8 @@ using namespace GDBusCXX;
namespace {
GMainLoop *loop = NULL;
bool shutdownRequested = false;
}
const char * const execName = "syncevo-dbus-server";
const char * const debugEnv = "SYNCEVOLUTION_DEBUG";
void niam(int sig)
{
@ -44,7 +48,7 @@ void niam(int sig)
g_main_loop_quit (loop);
}
static bool parseDuration(int &duration, const char* value)
bool parseDuration(int &duration, const char* value)
{
if(value == NULL) {
return false;
@ -58,6 +62,8 @@ static bool parseDuration(int &duration, const char* value)
}
}
} // anonymous namespace
int main(int argc, char **argv, char **envp)
{
// remember environment for restart
@ -84,41 +90,59 @@ int main(int argc, char **argv, char **envp)
opt++;
}
try {
SyncContext::initMain("syncevo-dbus-server");
SyncContext::initMain(execName);
loop = g_main_loop_new (NULL, FALSE);
setvbuf(stderr, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
LogRedirect redirect(true);
const char *debugVar(getenv(debugEnv));
const bool debugEnabled(debugVar && *debugVar);
// TODO: redirect output *and* log it via syslog?!
boost::shared_ptr<LoggerBase> logger((true || debugEnabled) ?
static_cast<LoggerBase *>(new LogRedirect(true)) :
static_cast<LoggerBase *>(new LoggerSyslog(execName)));
// make daemon less chatty - long term this should be a command line option
LoggerBase::instance().setLevel(getenv("SYNCEVOLUTION_DEBUG") ?
LoggerBase::instance().setLevel(debugEnabled ?
LoggerBase::DEBUG :
LoggerBase::INFO);
// syncevo-dbus-server should hardly ever produce output that
// is relevant for end users, so include the somewhat cryptic
// process name for developers in this process, and not in
// syncevo-dbus-helper.
Logger::setProcessName("syncevo-dbus-server");
SE_LOG_DEBUG(NULL, NULL, "syncevo-dbus-server: catch SIGINT/SIGTERM in our own shutdown function");
signal(SIGTERM, niam);
signal(SIGINT, niam);
boost::shared_ptr<SuspendFlags::Guard> guard = SuspendFlags::getSuspendFlags().activate();
DBusErrorCXX err;
DBusConnectionPtr conn = dbus_get_bus_connection("SESSION",
"org.syncevolution",
SessionCommon::SERVICE_NAME,
true,
&err);
if (!conn) {
err.throwFailure("dbus_get_bus_connection()", " failed - server already running?");
}
// make this object the main owner of the connection
DBusObject obj(conn, "foo", "bar", true);
boost::scoped_ptr<DBusObject> obj(new DBusObject(conn, "foo", "bar", true));
SyncEvo::Server server(loop, shutdownRequested, restart, conn, duration);
server.activate();
boost::scoped_ptr<SyncEvo::Server> server(new SyncEvo::Server(loop, shutdownRequested, restart, conn, duration));
server->activate();
SE_LOG_INFO(NULL, NULL, "%s: ready to run", argv[0]);
server.run(redirect);
SE_LOG_INFO(NULL, NULL, "%s: terminating", argv[0]);
SE_LOG_INFO(NULL, NULL, "ready to run");
server->run();
SE_LOG_DEBUG(NULL, NULL, "cleaning up");
server.reset();
conn.reset();
obj.reset();
guard.reset();
SE_LOG_INFO(NULL, NULL, "terminating");
return 0;
} catch ( const std::exception &ex ) {
SE_LOG_ERROR(NULL, NULL, "%s", ex.what());

View File

@ -102,13 +102,6 @@ void PresenceStatus::updatePresenceStatus (bool newStatus, PresenceStatus::Trans
void PresenceStatus::updatePresenceStatus (bool httpPresence, bool btPresence) {
bool httpChanged = (m_httpPresence != httpPresence);
bool btChanged = (m_btPresence != btPresence);
if(httpChanged) {
m_httpTimer.reset();
}
if(btChanged) {
m_btTimer.reset();
}
if (m_initiated && !httpChanged && !btChanged) {
//nothing changed
return;
@ -123,6 +116,13 @@ void PresenceStatus::updatePresenceStatus (bool httpPresence, bool btPresence) {
// switch to new status
m_httpPresence = httpPresence;
m_btPresence = btPresence;
if (httpChanged) {
m_httpPresenceSignal(httpPresence);
}
if (btChanged) {
m_btPresenceSignal(btPresence);
}
//iterate all configured peers and fire singals
BOOST_FOREACH (StatusPair &peer, m_peers) {

View File

@ -20,21 +20,20 @@
#ifndef PRESENCE_STATUS_H
#define PRESENCE_STATUS_H
#include "timer.h"
#include "read-operations.h"
#include <boost/signals.hpp>
SE_BEGIN_CXX
class Server;
class PresenceStatus {
bool m_httpPresence;
bool m_btPresence;
bool m_initiated;
Server &m_server;
/** two timers to record when the statuses of network and bt are changed */
Timer m_httpTimer;
Timer m_btTimer;
enum PeerStatus {
/* The transport is not available (local problem) */
NOTRANSPORT,
@ -46,9 +45,9 @@ class PresenceStatus {
INVALID
};
typedef map<string, vector<pair <string, PeerStatus> > > StatusMap;
typedef pair<const string, vector<pair <string, PeerStatus> > > StatusPair;
typedef pair <string, PeerStatus> PeerStatusPair;
typedef std::map<std::string, std::vector<std::pair <std::string, PeerStatus> > > StatusMap;
typedef std::pair<const std::string, std::vector<std::pair <std::string, PeerStatus> > > StatusPair;
typedef std::pair<std::string, PeerStatus> PeerStatusPair;
StatusMap m_peers;
static std::string status2string (PeerStatus status) {
@ -71,8 +70,7 @@ class PresenceStatus {
public:
PresenceStatus (Server &server)
:m_httpPresence (false), m_btPresence (false), m_initiated (false), m_server (server),
m_httpTimer(), m_btTimer()
:m_httpPresence (false), m_btPresence (false), m_initiated (false), m_server (server)
{
}
@ -85,7 +83,7 @@ class PresenceStatus {
void init();
/* Implement Server::checkPresence*/
void checkPresence (const string &peer, string& status, std::vector<std::string> &transport);
void checkPresence (const std::string &peer, std::string& status, std::vector<std::string> &transport);
void updateConfigPeers (const std::string &peer, const ReadOperations::Config_t &config);
@ -93,8 +91,11 @@ class PresenceStatus {
bool getHttpPresence() { return m_httpPresence; }
bool getBtPresence() { return m_btPresence; }
Timer& getHttpTimer() { return m_httpTimer; }
Timer& getBtTimer() { return m_btTimer; }
/** emitted on changes of the current value */
typedef boost::signals2::signal<void (bool)> PresenceSignal_t;
PresenceSignal_t m_httpPresenceSignal;
PresenceSignal_t m_btPresenceSignal;
private:
void updatePresenceStatus (bool httpPresence, bool btPresence);

View File

@ -18,6 +18,7 @@
*/
#include "read-operations.h"
#include "dbus-user-interface.h"
#include "server.h"
#include "dbus-sync.h"

View File

@ -27,7 +27,6 @@
SE_BEGIN_CXX
class DBusSync;
class Server;
/**

View File

@ -12,8 +12,7 @@ src_dbus_server_server_cpp_files = \
src/dbus/server/client.cpp \
src/dbus/server/connection.cpp \
src/dbus/server/connman-client.cpp \
src/dbus/server/dbus-sync.cpp \
src/dbus/server/dbus-transport-agent.cpp \
src/dbus/server/dbus-callbacks.cpp \
src/dbus/server/dbus-user-interface.cpp \
src/dbus/server/exceptions.cpp \
src/dbus/server/info-req.cpp \
@ -33,7 +32,7 @@ src_dbus_server_server_h_files = \
src/dbus/server/cmdline-wrapper.h \
src/dbus/server/resource.h \
src/dbus/server/restart.h \
src/dbus/server/session-listener.h \
src/dbus/server/session-common.h \
src/dbus/server/source-progress.h \
src/dbus/server/source-status.h \
src/dbus/server/timeout.h \
@ -50,6 +49,29 @@ src_dbus_server_libsyncevodbusserver_la_LIBADD = $(LIBNOTIFY_LIBS) $(MLITE_LIBS)
src_dbus_server_libsyncevodbusserver_la_CPPFLAGS = -DHAVE_CONFIG_H -DSYNCEVOLUTION_LOCALEDIR=\"${SYNCEVOLUTION_LOCALEDIR}\" -I$(top_srcdir)/src -I$(top_srcdir)/test -I$(top_srcdir) -I$(gdbus_dir) $(BACKEND_CPPFLAGS)
src_dbus_server_libsyncevodbusserver_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(SYNTHESIS_CFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) $(LIBNOTIFY_CFLAGS) $(MLITE_CFLAGS) $(SYNCEVO_WFLAGS)
# Session helper: syncevo-dbus-helper
noinst_LTLIBRARIES += src/dbus/server/libsyncevodbushelper.la
src_dbus_server_dbus_helper_cpp_files = \
src/dbus/server/dbus-callbacks.cpp \
src/dbus/server/dbus-sync.cpp \
src/dbus/server/dbus-transport-agent.cpp \
src/dbus/server/session-helper.cpp
src_dbus_server_dbus_helper_h_files = \
$(src_dbus_server_dbus_helper_cpp_files:.cpp=.h) \
src/dbus/server/cmdline-wrapper.h
src_dbus_server_libsyncevodbushelper_la_SOURCES = \
$(src_dbus_server_dbus_helper_h_files) \
$(src_dbus_server_dbus_helper_cpp_files) \
src/dbus/server/session-common.h \
src/dbus/server/sync-helper.cpp
src_dbus_server_libsyncevodbushelper_la_LIBADD = $(DBUS_LIBS)
src_dbus_server_libsyncevodbushelper_la_CPPFLAGS = -DHAVE_CONFIG_H -DSYNCEVOLUTION_LOCALEDIR=\"${SYNCEVOLUTION_LOCALEDIR}\" -I$(top_srcdir)/src -I$(top_srcdir)/test -I$(top_srcdir) -I$(gdbus_dir) $(BACKEND_CPPFLAGS)
src_dbus_server_libsyncevodbushelper_la_CXXFLAGS = $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(SYNTHESIS_CFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) $(SYNCEVO_WFLAGS)
# Deal with .service, .desktop and startup script files.
CLEANFILES += \
$(src_dbus_server_service_files) \

View File

@ -20,7 +20,8 @@
#include <fstream>
#include <syncevo/LogRedirect.h>
#include <boost/bind.hpp>
#include <syncevo/GLibSupport.h>
#include "server.h"
@ -31,11 +32,19 @@
#include "timeout.h"
#include "restart.h"
#include "client.h"
#include "auto-sync-manager.h"
#include <boost/pointer_cast.hpp>
using namespace GDBusCXX;
SE_BEGIN_CXX
static void logIdle(bool idle)
{
SE_LOG_DEBUG(NULL, NULL, "server is %s", idle ? "idle" : "not idle");
}
void Server::clientGone(Client *c)
{
for (Clients_t::iterator it = m_clients.begin();
@ -95,9 +104,7 @@ StringMap Server::getVersions()
void Server::attachClient(const Caller_t &caller,
const boost::shared_ptr<Watch> &watch)
{
boost::shared_ptr<Client> client = addClient(getConnection(),
caller,
watch);
boost::shared_ptr<Client> client = addClient(caller, watch);
autoTermRef();
client->increaseAttachCount();
}
@ -153,18 +160,17 @@ void Server::connect(const Caller_t &caller,
}
std::string new_session = getNextSession();
boost::shared_ptr<Connection> c(new Connection(*this,
getConnection(),
new_session,
peer,
must_authenticate));
boost::shared_ptr<Connection> c(Connection::createConnection(*this,
getConnection(),
new_session,
peer,
must_authenticate));
SE_LOG_DEBUG(NULL, NULL, "connecting D-Bus client %s with connection %s '%s'",
caller.c_str(),
c->getPath(),
c->m_description.c_str());
boost::shared_ptr<Client> client = addClient(getConnection(),
caller,
boost::shared_ptr<Client> client = addClient(caller,
watch);
client->attach(c);
c->activate();
@ -183,8 +189,7 @@ void Server::startSessionWithFlags(const Caller_t &caller,
SE_THROW("server shutting down");
}
boost::shared_ptr<Client> client = addClient(getConnection(),
caller,
boost::shared_ptr<Client> client = addClient(caller,
watch);
std::string new_session = getNextSession();
boost::shared_ptr<Session> session = Session::createSession(*this,
@ -225,12 +230,11 @@ Server::Server(GMainLoop *loop,
const DBusConnectionPtr &conn,
int duration) :
DBusObjectHelper(conn,
"/org/syncevolution/Server",
"org.syncevolution.Server",
SessionCommon::SERVER_PATH,
SessionCommon::SERVER_IFACE,
boost::bind(&Server::autoTermCallback, this)),
m_loop(loop),
m_shutdownRequested(shutdownRequested),
m_doShutdown(false),
m_restart(restart),
m_lastSession(time(NULL)),
m_activeSession(NULL),
@ -290,13 +294,14 @@ Server::Server(GMainLoop *loop,
getPresenceStatus().updatePresenceStatus(true, PresenceStatus::HTTP_TRANSPORT);
}
// create auto sync manager, now that server is ready
m_autoSync = AutoSyncManager::create(*this);
// log entering and leaving idle state
m_idleSignal.connect(boost::bind(logIdle, _1));
// connect auto sync manager and Server ConfigChanged signal to source
// for that information
namedConfigChanged.connect(NamedConfigChanged_t::slot_type(&AutoSyncManager::update, m_autoSync.get(), _1).track(m_autoSync));
namedConfigChanged.connect(boost::bind(&Server::configChanged, this));
// connect ConfigChanged signal to source for that information
m_configChangedSignal.connect(boost::bind(boost::ref(configChanged)));
// create auto sync manager, now that server is ready
m_autoSync = AutoSyncManager::createAutoSyncManager(*this);
}
Server::~Server()
@ -305,13 +310,15 @@ Server::~Server()
m_syncSession.reset();
m_workQueue.clear();
m_clients.clear();
m_autoSync.reset();
m_infoReqMap.clear();
LoggerBase::popLogger();
}
bool Server::shutdown()
{
Timespec now = Timespec::monotonic();
bool autosync = m_autoSync->preventTerm();
bool autosync = m_autoSync && m_autoSync->preventTerm();
SE_LOG_DEBUG(NULL, NULL, "shut down or restart server at %lu.%09lu because of file modifications, auto sync %s",
now.tv_sec, now.tv_nsec, autosync ? "on" : "off");
if (autosync) {
@ -321,7 +328,6 @@ bool Server::shutdown()
m_restart->restart();
} else {
// leave server now
m_doShutdown = true;
g_main_loop_quit(m_loop);
SE_LOG_INFO(NULL, NULL, "server shutting down because files loaded into memory were modified on disk");
}
@ -343,7 +349,7 @@ void Server::fileModified()
m_shutdownRequested = true;
}
void Server::run(LogRedirect &redirect)
void Server::run()
{
// This has the intended side effect that it loads everything into
// memory which might be dynamically loadable, like backend
@ -378,61 +384,11 @@ void Server::run(LogRedirect &redirect)
}
}
while (!m_doShutdown) {
if (!m_activeSession ||
!m_activeSession->readyToRun()) {
// hook up SIGINT/SIGTERM with our event loop temporarily
SuspendFlags &suspendflags = SuspendFlags::getSuspendFlags();
boost::shared_ptr<SuspendFlags::Guard> guard = suspendflags.activate();
boost::signals2::scoped_connection c(suspendflags.m_stateChanged.connect(boost::bind(&Server::checkQueue,
this)));
if (suspendflags.getState() == SuspendFlags::ABORT) {
SE_LOG_DEBUG(NULL, NULL, "server idle, shutdown requested via signal => quit");
m_doShutdown = true;
} else if (m_shutdownRequested && !m_lastFileMod) {
SE_LOG_DEBUG(NULL, NULL, "server idle, shutdown requested, but neither via signal nor file mod => quit");
m_doShutdown = true;
} else {
g_main_loop_run(m_loop);
}
}
if (m_activeSession &&
m_activeSession->readyToRun()) {
// this session must be owned by someone, otherwise
// it would not be set as active session
boost::shared_ptr<Session> session = m_activeSessionRef.lock();
if (!session) {
throw runtime_error("internal error: session no longer available");
}
try {
// ensure that the session doesn't go away
m_syncSession.swap(session);
m_activeSession->run(redirect);
} catch (const std::exception &ex) {
SE_LOG_ERROR(NULL, NULL, "%s", ex.what());
} catch (...) {
SE_LOG_ERROR(NULL, NULL, "unknown error");
}
session.swap(m_syncSession);
dequeue(session.get());
}
if (!m_shutdownRequested && m_autoSync && m_autoSync->hasTask()) {
// if there is at least one pending task and no session is created for auto sync,
// pick one task and create a session
m_autoSync->startTask();
}
// Make sure check whether m_activeSession is owned by autosync
// Otherwise activeSession is owned by AutoSyncManager but it never
// be ready to run. Because methods of Session, like 'sync', are able to be
// called when it is active.
if (!m_shutdownRequested && m_autoSync->hasActiveSession())
{
// if the autosync is the active session, then invoke 'sync'
// to make it ready to run
m_autoSync->prepare();
}
if (!m_shutdownRequested) {
g_main_loop_run(m_loop);
}
SE_LOG_DEBUG(NULL, NULL, "%s", "Exiting Server::run");
}
@ -451,8 +407,7 @@ boost::shared_ptr<Client> Server::findClient(const Caller_t &ID)
return boost::shared_ptr<Client>();
}
boost::shared_ptr<Client> Server::addClient(const DBusConnectionPtr &conn,
const Caller_t &ID,
boost::shared_ptr<Client> Server::addClient(const Caller_t &ID,
const boost::shared_ptr<Watch> &watch)
{
boost::shared_ptr<Client> client(findClient(ID));
@ -478,23 +433,29 @@ void Server::detach(Resource *resource)
void Server::enqueue(const boost::shared_ptr<Session> &session)
{
bool idle = isIdle();
WorkQueue_t::iterator it = m_workQueue.end();
while (it != m_workQueue.begin()) {
--it;
if (it->lock()->getPriority() <= session->getPriority()) {
// skip over dead sessions, they will get cleaned up elsewhere
boost::shared_ptr<Session> session = it->lock();
if (session && session->getPriority() <= session->getPriority()) {
++it;
break;
}
}
m_workQueue.insert(it, session);
checkQueue();
if (idle) {
m_idleSignal(false);
}
}
int Server::killSessions(const std::string &peerDeviceID)
void Server::killSessionsAsync(const std::string &peerDeviceID,
const SimpleResult &onResult)
{
int count = 0;
WorkQueue_t::iterator it = m_workQueue.begin();
while (it != m_workQueue.end()) {
boost::shared_ptr<Session> session = it->lock();
@ -508,33 +469,29 @@ int Server::killSessions(const std::string &peerDeviceID)
c->shutdown();
}
it = m_workQueue.erase(it);
count++;
} else {
++it;
}
}
if (m_activeSession &&
m_activeSession->getPeerDeviceID() == peerDeviceID) {
// Check active session. We need to wait for it to shut down cleanly.
boost::shared_ptr<Session> active = m_activeSessionRef.lock();
if (active &&
active->getPeerDeviceID() == peerDeviceID) {
SE_LOG_DEBUG(NULL, NULL, "aborting active session %s because it matches deviceID %s",
m_activeSession->getSessionID().c_str(),
active->getSessionID().c_str(),
peerDeviceID.c_str());
try {
// abort, even if not necessary right now
m_activeSession->abort();
} catch (...) {
// TODO: catch only that exception which indicates
// incorrect use of the function
}
dequeue(m_activeSession);
count++;
// hand over work to session
active->abortAsync(onResult);
} else {
onResult.done();
}
return count;
}
void Server::dequeue(Session *session)
{
bool idle = isIdle();
if (m_syncSession.get() == session) {
// This is the running sync session.
// It's not in the work queue and we have to
@ -548,61 +505,71 @@ void Server::dequeue(Session *session)
if (it->lock().get() == session) {
// remove from queue
m_workQueue.erase(it);
// session was idle, so nothing else to do
return;
break;
}
}
if (m_activeSession == session) {
// The session is releasing the lock, so someone else might
// run now.
session->setActive(false);
sessionChanged(session->getPath(), false);
m_activeSession = NULL;
m_activeSessionRef.reset();
checkQueue();
return;
}
if (!idle && isIdle()) {
m_idleSignal(true);
}
}
void Server::addSyncSession(Session *session)
{
// Only one session can run a sync, and only the active session
// can make itself the sync session.
if (m_syncSession) {
if (m_syncSession.get() != session) {
SE_THROW("already have a sync session");
} else {
return;
}
}
m_syncSession = m_activeSessionRef.lock();
m_newSyncSessionSignal(m_syncSession);
if (!m_syncSession) {
SE_THROW("session should not start a sync, all clients already detached");
}
if (m_syncSession.get() != session) {
m_syncSession.reset();
SE_THROW("inactive session asked to become sync session");
}
}
void Server::removeSyncSession(Session *session)
{
if (session == m_syncSession.get()) {
// Normally the owner calls this, but if it is already gone,
// then do it again and thus effectively start counting from
// now.
delaySessionDestruction(m_syncSession);
m_syncSession.reset();
} else {
SE_LOG_DEBUG(NULL, NULL, "ignoring removeSyncSession() for session %s, it is not the sync session",
session->getSessionID().c_str());
}
}
void Server::checkQueue()
{
if (m_shutdownRequested) {
// don't schedule new sessions, instead check whether we can shut down now
if (!m_activeSession && !m_doShutdown) {
if (SuspendFlags::getSuspendFlags().getState() == SuspendFlags::ABORT) {
// don't delay any further, shut down
SE_LOG_DEBUG(NULL, NULL, "session done, shutdown requested via signal => quit");
m_doShutdown = true;
g_main_loop_quit(m_loop);
} else if (!m_lastFileMod) {
// shutdown requested without file modifications, don't need
// quiesence period
SE_LOG_DEBUG(NULL, NULL, "session done, shutdown requested neither via signal nor file mod => quit");
m_doShutdown = true;
g_main_loop_quit(m_loop);
} else if (!m_shutdownTimer) {
Timespec now = Timespec::monotonic();
if (m_lastFileMod + SHUTDOWN_QUIESENCE_SECONDS <= now) {
SE_LOG_DEBUG(NULL, NULL, "session done, shutdown requested, quiesence period over => quit");
m_doShutdown = true;
g_main_loop_quit(m_loop);
} else {
// activate timer not set in fileModified() because there was a running session
int seconds = (m_lastFileMod + SHUTDOWN_QUIESENCE_SECONDS - now).seconds();
SE_LOG_DEBUG(NULL, NULL, "session done, shutdown requested, %d seconds left in quiesence period => wait",
seconds);
m_shutdownTimer.activate(seconds,
boost::bind(&Server::shutdown, this));
}
}
}
if (m_activeSession) {
// still busy
return;
}
if (m_activeSession) {
// still busy
if (m_shutdownRequested) {
// Don't schedule new sessions. Instead return to Server::run().
SE_LOG_DEBUG(NULL, NULL, "shutting down in checkQueue(), idle and shutdown was requested");
g_main_loop_quit(m_loop);
return;
}
@ -613,12 +580,8 @@ void Server::checkQueue()
// activate the session
m_activeSession = session.get();
m_activeSessionRef = session;
session->setActive(true);
session->activateSession();
sessionChanged(session->getPath(), true);
//if the active session is changed, give a chance to quit the main loop
//and make it ready to run if it is owned by AutoSyncManager.
//Otherwise, server might be blocked.
g_main_loop_quit(m_loop);
return;
}
}
@ -634,6 +597,10 @@ bool Server::sessionExpired(const boost::shared_ptr<Session> &session)
void Server::delaySessionDestruction(const boost::shared_ptr<Session> &session)
{
if (!session) {
return;
}
SE_LOG_DEBUG(NULL, NULL, "delaying destruction of session %s by one minute",
session->getSessionID().c_str());
addTimeout(boost::bind(&Server::sessionExpired,
@ -641,6 +608,75 @@ void Server::delaySessionDestruction(const boost::shared_ptr<Session> &session)
60 /* 1 minute */);
}
inline void insertPair(std::map<string, string> &params,
const string &key,
const string &value)
{
if(!value.empty()) {
params.insert(pair<string, string>(key, value));
}
}
boost::shared_ptr<InfoReq> Server::passwordRequest(const string &descr,
const ConfigPasswordKey &key,
const boost::weak_ptr<Session> &s)
{
boost::shared_ptr<Session> session = s.lock();
if (!session) {
// already gone, ignore request
return boost::shared_ptr<InfoReq>();
}
std::map<string, string> params;
insertPair(params, "description", descr);
insertPair(params, "user", key.user);
insertPair(params, "SyncML server", key.server);
insertPair(params, "domain", key.domain);
insertPair(params, "object", key.object);
insertPair(params, "protocol", key.protocol);
insertPair(params, "authtype", key.authtype);
insertPair(params, "port", key.port ? StringPrintf("%u",key.port) : "");
boost::shared_ptr<InfoReq> req = createInfoReq("password", params, *session);
// Return password or failure to Session and thus the session helper.
req->m_responseSignal.connect(boost::bind(&Server::passwordResponse,
this,
_1,
s));
// Tell session about timeout.
req->m_timeoutSignal.connect(InfoReq::TimeoutSignal_t::slot_type(&Session::passwordResponse,
session.get(),
true,
false,
"").track(s));
// Request becomes obsolete when session is done.
session->m_doneSignal.connect(boost::bind(&Server::removeInfoReq,
this,
req->getId()));
return req;
}
void Server::passwordResponse(const InfoReq::InfoMap &response,
const boost::weak_ptr<Session> &s)
{
boost::shared_ptr<Session> session = s.lock();
if (!session) {
// already gone, ignore request
return;
}
InfoReq::InfoMap::const_iterator it = response.find("password");
if (it == response.end()) {
// no password provided, user wants to abort
session->passwordResponse(false, true, "");
} else {
// password provided, might be empty
session->passwordResponse(false, false, it->second);
}
}
bool Server::callTimeout(const boost::shared_ptr<Timeout> &timeout, const boost::function<bool ()> &callback)
{
if (!callback()) {
@ -652,7 +688,7 @@ bool Server::callTimeout(const boost::shared_ptr<Timeout> &timeout, const boost:
}
void Server::addTimeout(const boost::function<bool ()> &callback,
int seconds)
int seconds)
{
boost::shared_ptr<Timeout> timeout(new Timeout);
m_timeouts.push_back(timeout);
@ -666,25 +702,33 @@ void Server::addTimeout(const boost::function<bool ()> &callback,
}
void Server::infoResponse(const Caller_t &caller,
const std::string &id,
const std::string &state,
const std::map<string, string> &response)
const std::string &id,
const std::string &state,
const std::map<string, string> &response)
{
InfoReqMap::iterator it = m_infoReqMap.find(id);
// if not found, ignore
if (it != m_infoReqMap.end()) {
boost::shared_ptr<InfoReq> infoReq = it->second.lock();
infoReq->setResponse(caller, state, response);
const boost::shared_ptr<InfoReq> infoReq = it->second.lock();
if (infoReq) {
infoReq->setResponse(caller, state, response);
}
}
}
boost::shared_ptr<InfoReq> Server::createInfoReq(const string &type,
const std::map<string, string> &parameters,
const Session *session)
const Session &session)
{
boost::shared_ptr<InfoReq> infoReq(new InfoReq(*this, type, parameters, session));
boost::weak_ptr<InfoReq> item(infoReq) ;
m_infoReqMap.insert(pair<string, boost::weak_ptr<InfoReq> >(infoReq->getId(), item));
boost::shared_ptr<InfoReq> infoReq(new InfoReq(*this, type, parameters, session.getPath()));
m_infoReqMap.insert(std::make_pair(infoReq->getId(), infoReq));
// will be removed automatically
infoReq->m_responseSignal.connect(boost::bind(&Server::removeInfoReq,
this,
infoReq->getId()));
infoReq->m_timeoutSignal.connect(boost::bind(&Server::removeInfoReq,
this,
infoReq->getId()));
return infoReq;
}
@ -703,18 +747,16 @@ void Server::emitInfoReq(const InfoReq &req)
req.getParam());
}
void Server::removeInfoReq(const InfoReq &req)
void Server::removeInfoReq(const std::string &id)
{
// remove InfoRequest from hash map
InfoReqMap::iterator it = m_infoReqMap.find(req.getId());
if (it != m_infoReqMap.end()) {
m_infoReqMap.erase(it);
}
m_infoReqMap.erase(id);
}
void Server::getDeviceList(SyncConfig::DeviceList &devices)
{
//wait bluez or other device managers
// TODO: make this asynchronous?!
while(!m_bluezManager->isDone()) {
g_main_loop_run(m_loop);
}
@ -818,11 +860,7 @@ void Server::messagev(Level level,
// for general server output, the object path field is dbus server
// the object path can't be empty for object paths prevent using empty string.
string strLevel = Logger::levelToStr(level);
if(m_activeSession) {
logOutput(m_activeSession->getPath(), strLevel, log);
} else {
logOutput(getPath(), strLevel, log);
}
logOutput(getPath(), strLevel, log);
}
SE_END_CXX

View File

@ -20,48 +20,44 @@
#ifndef SYNCEVO_DBUS_SERVER_H
#define SYNCEVO_DBUS_SERVER_H
#include <boost/weak_ptr.hpp>
#include <set>
#include "auto-sync-manager.h"
#include "exceptions.h"
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/signals2.hpp>
#include "exceptions.h"
#include "auto-term.h"
#include "read-operations.h"
#include "connman-client.h"
#include "network-manager-client.h"
#include "presence-status.h"
#include "timeout.h"
#include "dbus-callbacks.h"
#include <syncevo/declarations.h>
SE_BEGIN_CXX
class Resource;
class Session;
class Connection;
class DBusTransportAgent;
class Server;
class InfoReq;
class BluezManager;
class Restart;
class Client;
class Resource;
class LogRedirect;
class GLibNotify;
class AutoSyncManager;
/**
* Implements the main org.syncevolution.Server interface.
*
* All objects created by it get a reference to the creating
* Server instance so that they can call some of its
* methods. Because that instance holds references to all
* of these objects and deletes them before destructing itself,
* that reference is guaranteed to remain valid.
* The Server class is responsible for listening to clients and
* spinning of sync sessions as requested by clients.
*/
class Server : public GDBusCXX::DBusObjectHelper,
public LoggerBase
public LoggerBase
{
GMainLoop *m_loop;
bool &m_shutdownRequested;
bool m_doShutdown;
Timespec m_lastFileMod;
boost::shared_ptr<SyncEvo::Restart> &m_restart;
@ -97,6 +93,9 @@ class Server : public GDBusCXX::DBusObjectHelper,
* only one session may make such modifications at a time. A
* plain pointer which is reset by the session's deconstructor.
*
* The server doesn't hold a shared pointer to the session so
* that it can be deleted when the last client detaches from it.
*
* A weak pointer alone did not work because it does not provide access
* to the underlying pointer after the last corresponding shared
* pointer is gone (which triggers the deconstructing of the session).
@ -112,6 +111,10 @@ class Server : public GDBusCXX::DBusObjectHelper,
* The running sync session. Having a separate reference to it
* ensures that the object won't go away prematurely, even if all
* clients disconnect.
*
* The session itself needs to request this special treatment with
* addSyncSession() and remove itself with removeSyncSession() when
* done.
*/
boost::shared_ptr<Session> m_syncSession;
@ -155,6 +158,10 @@ class Server : public GDBusCXX::DBusObjectHelper,
*/
void clientGone(Client *c);
public:
// D-Bus API, also usable directly
/** Server.GetCapabilities() */
vector<string> getCapabilities();
@ -261,6 +268,7 @@ class Server : public GDBusCXX::DBusObjectHelper,
ops.getDatabases(sourceName, databases);
}
/** Server.GetConfigs() */
void getConfigs(bool getTemplates,
std::vector<std::string> &configNames)
{
@ -282,17 +290,6 @@ class Server : public GDBusCXX::DBusObjectHelper,
const std::string &state,
const std::map<string, string> &response);
friend class InfoReq;
/** emit InfoRequest */
void emitInfoReq(const InfoReq &);
/** get the next id of InfoRequest */
std::string getNextInfoReq();
/** remove InfoReq from hash map */
void removeInfoReq(const InfoReq &req);
/** Server.SessionChanged */
GDBusCXX::EmitSignal2<const GDBusCXX::DBusObject_t &,
bool> sessionChanged;
@ -322,19 +319,22 @@ class Server : public GDBusCXX::DBusObjectHelper,
const std::string &,
const std::map<string, string> &> infoRequest;
/**
* More specific "config changed signal", called with
* normalized config name as parameter.
*/
typedef boost::signals2::signal<void (const std::string &configName)> NamedConfigChanged_t;
NamedConfigChanged_t namedConfigChanged;
/** Server.LogOutput */
GDBusCXX::EmitSignal3<const GDBusCXX::DBusObject_t &,
string,
const std::string &> logOutput;
friend class Session;
private:
friend class InfoReq;
/** emit InfoRequest */
void emitInfoReq(const InfoReq &);
/** get the next id of InfoRequest */
std::string getNextInfoReq();
/** remove InfoReq from hash map */
void removeInfoReq(const std::string &infoReqId);
PresenceStatus m_presence;
ConnmanClient m_connman;
@ -378,7 +378,28 @@ public:
GMainLoop *getLoop() { return m_loop; }
/** process D-Bus calls until the server is ready to quit */
void run(LogRedirect &redirect);
void run();
/** true iff no work is pending */
bool isIdle() const { return !m_activeSession && m_workQueue.empty(); }
/** isIdle() might have changed its value, current value included */
typedef boost::signals2::signal<void (bool isIdle)> IdleSignal_t;
IdleSignal_t m_idleSignal;
/**
* More specific "config changed signal", called with normalized
* config name as parameter. Config name is empty if all configs
* were affected.
*/
typedef boost::signals2::signal<void (const std::string &configName)> ConfigChangedSignal_t;
ConfigChangedSignal_t m_configChangedSignal;
/**
* Called when a session starts its real work (= calls addSyncSession()).
*/
typedef boost::signals2::signal<void (const boost::shared_ptr<Session> &)> NewSyncSessionSignal_t;
NewSyncSessionSignal_t m_newSyncSessionSignal;
/**
* look up client by its ID
@ -388,8 +409,7 @@ public:
/**
* find client by its ID or create one anew
*/
boost::shared_ptr<Client> addClient(const GDBusCXX::DBusConnectionPtr &conn,
const GDBusCXX::Caller_t &ID,
boost::shared_ptr<Client> addClient(const GDBusCXX::Caller_t &ID,
const boost::shared_ptr<GDBusCXX::Watch> &watch);
/** detach this resource from all clients which own it */
@ -407,8 +427,13 @@ public:
* Remove all sessions with this device ID from the
* queue. If the active session also has this ID,
* the session will be aborted and/or deactivated.
*
* Has to be asynchronous because it might involve ensuring that
* there is no running helper for this device ID, which requires
* communicating with the helper.
*/
int killSessions(const std::string &peerDeviceID);
void killSessionsAsync(const std::string &peerDeviceID,
const SimpleResult &result);
/**
* Remove a session from the work queue. If it is running a sync,
@ -418,6 +443,21 @@ public:
*/
void dequeue(Session *session);
/**
* Remember that the session is running a sync (or some other
* important operation) and keeps a pointer to it, to prevent
* deleting it. Currently can only called by the active sync
* session. Will fail if all clients have detached already.
*
* If successful, it triggers m_newSyncSessionSignal.
*/
void addSyncSession(Session *session);
/**
* Session is done, ready to be deleted again.
*/
void removeSyncSession(Session *session);
/**
* Checks whether the server is ready to run another session
* and if so, activates the first one in the queue.
@ -439,6 +479,46 @@ public:
*/
void delaySessionDestruction(const boost::shared_ptr<Session> &session);
/**
* Works for any kind of object: keep shared pointer until the
* event loop is idle, then unref it inside. Useful for instances
* which need to delete themselves.
*/
template <class T> static void delayDeletion(const boost::shared_ptr<T> &t) {
g_idle_add(delayDeletionCb<T>, new boost::shared_ptr<T>(t));
}
template <class T> static gboolean delayDeletionCb(gpointer userData) throw () {
boost::shared_ptr<T> *t = static_cast<boost::shared_ptr<T> *>(userData);
try {
t->reset();
delete t;
} catch (...) {
// Something unexpected went wrong, can only shut down.
Exception::handle(HANDLE_EXCEPTION_FATAL);
}
return false;
}
/**
* Handle the password request from a specific session. Ask our
* clients, relay answer to session if it is still around at the
* time when we get the response.
*
* Server does not keep a strong reference to info request,
* caller must do that or the request will automatically be
* deleted.
*/
boost::shared_ptr<InfoReq> passwordRequest(const std::string &descr,
const ConfigPasswordKey &key,
const boost::weak_ptr<Session> &session);
/** got response for earlier request, need to extract password and tell session */
void passwordResponse(const StringMap &response,
const boost::weak_ptr<Session> &session);
/**
* Invokes the given callback once in the given amount of seconds.
* Keeps a copy of the callback. If the Server is destructed
@ -448,9 +528,15 @@ public:
void addTimeout(const boost::function<bool ()> &callback,
int seconds);
/**
* InfoReq will be added to map automatically and removed again
* when it completes or times out. Caller is responsible for
* calling removeInfoReq() when the request becomes obsolete
* sooner than that.
*/
boost::shared_ptr<InfoReq> createInfoReq(const string &type,
const std::map<string, string> &parameters,
const Session *session);
const Session &session);
void autoTermRef(int counts = 1) { m_autoTerm.ref(counts); }
void autoTermUnref(int counts = 1) { m_autoTerm.unref(counts); }

View File

@ -0,0 +1,153 @@
/*
* 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
*/
#ifndef SESSION_COMMON_H
#define SESSION_COMMON_H
#include "source-status.h"
#include "source-progress.h"
#include <syncevo/util.h>
#include <syncevo/DBusTraits.h>
#include <syncevo/FilterConfigNode.h>
SE_BEGIN_CXX
/**
* This namespace holds constants and defines for Sessions and its
* consumers.
*/
namespace SessionCommon
{
const char * const SERVICE_NAME = "org.syncevolution";
const char * const CONNECTION_PATH = "/org/syncevolution/Connection";
const char * const CONNECTION_IFACE = "org.syncevolution.Connection";
const char * const SESSION_PATH = "/org/syncevolution/Session";
const char * const SESSION_IFACE = "org.syncevolution.Session";
const char * const SERVER_PATH = "/org/syncevolution/Server";
const char * const SERVER_IFACE = "org.syncevolution.Server";
const char * const HELPER_PATH = "/dbushelper";
const char * const HELPER_IFACE = "org.syncevolution.Helper";
const char * const HELPER_DESTINATION = "direct.peer"; // doesn't matter, routing is off
/**
* The operation running inside the session.
*/
enum RunOperation {
OP_SYNC, /**< running a sync */
OP_RESTORE, /**< restoring data */
OP_CMDLINE, /**< executing command line */
OP_NULL /**< idle, accepting commands via D-Bus */
};
inline std::string runOpToString(RunOperation op) {
static const char * const strings[] = {
"sync",
"restore",
"cmdline"
};
return op >= OP_SYNC && op <= OP_CMDLINE ?
strings[op] :
"";
}
/**
* Used by both Connection class (inside server) and
* DBusTransportAgent (inside helper).
*/
enum ConnectionState {
SETUP, /**< ready for first message */
PROCESSING, /**< received message, waiting for engine's reply */
WAITING, /**< waiting for next follow-up message */
FINAL, /**< engine has sent final reply, wait for ACK by peer */
DONE, /**< peer has closed normally after the final reply */
FAILED /**< in a failed state, no further operation possible */
};
/** maps to names for debugging */
inline std::string ConnectionStateToString(ConnectionState state)
{
static const char * const strings[] = {
"SETUP",
"PROCESSING",
"WAITING",
"FINAL",
"DONE",
"FAILED"
};
return state >= SETUP && state <= FAILED ?
strings[state] :
"???";
}
typedef StringMap SourceModes_t;
typedef std::map<std::string, SyncEvo::FilterConfigNode::ConfigFilter> SourceFilters_t;
/**
* all the information that syncevo-dbus-server needs to
* send to syncevo-dbus-helper before the latter can
* run a sync
*/
struct SyncParams
{
SyncParams() :
m_serverMode(false),
m_serverAlerted(false),
m_remoteInitiated(false)
{}
std::string m_config;
std::string m_mode;
SourceModes_t m_sourceModes;
bool m_serverMode;
bool m_serverAlerted;
bool m_remoteInitiated;
std::string m_sessionID;
std::string m_initialMessage;
std::string m_initialMessageType;
SyncEvo::FilterConfigNode::ConfigFilter m_syncFilter;
SyncEvo::FilterConfigNode::ConfigFilter m_sourceFilter;
SourceFilters_t m_sourceFilters;
};
}
SE_END_CXX
namespace GDBusCXX {
using namespace SyncEvo::SessionCommon;
using namespace SyncEvo;
template<> struct dbus_traits<SyncParams> :
public dbus_struct_traits<SyncParams,
dbus_member<SyncParams, std::string, &SyncParams::m_config,
dbus_member<SyncParams, std::string, &SyncParams::m_mode,
dbus_member<SyncParams, SourceModes_t, &SyncParams::m_sourceModes,
dbus_member<SyncParams, bool, &SyncParams::m_serverMode,
dbus_member<SyncParams, bool, &SyncParams::m_serverAlerted,
dbus_member<SyncParams, bool, &SyncParams::m_remoteInitiated,
dbus_member<SyncParams, std::string, &SyncParams::m_sessionID,
dbus_member<SyncParams, std::string, &SyncParams::m_initialMessage,
dbus_member<SyncParams, std::string, &SyncParams::m_initialMessageType,
dbus_member<SyncParams, FilterConfigNode::ConfigFilter, &SyncParams::m_syncFilter,
dbus_member<SyncParams, FilterConfigNode::ConfigFilter, &SyncParams::m_sourceFilter,
dbus_member_single<SyncParams, SourceFilters_t, &SyncParams::m_sourceFilters
> > > > > > > > > > > > >
{};
}
#endif // SESSION_COMMON_H

View File

@ -0,0 +1,265 @@
/*
* Copyright (C) 2012 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 <syncevo/LogRedirect.h>
#include "session-helper.h"
#include "dbus-callbacks.h"
#include "cmdline-wrapper.h"
#include <syncevo/SuspendFlags.h>
#include <syncevo/ForkExec.h>
#include <boost/foreach.hpp>
SE_BEGIN_CXX
SessionHelper::SessionHelper(GMainLoop *loop,
const GDBusCXX::DBusConnectionPtr &conn,
const boost::shared_ptr<ForkExecChild> &forkexec,
LogRedirect *parentLogger) :
GDBusCXX::DBusObjectHelper(conn,
SessionCommon::HELPER_PATH,
SessionCommon::HELPER_IFACE,
GDBusCXX::DBusObjectHelper::Callback_t(), // we don't care about a callback per message
true), // direct connection, close it when done
m_loop(loop),
m_conn(conn),
m_forkexec(forkexec),
m_parentLogger(parentLogger),
emitLogOutput(*this, "LogOutput"),
emitSyncProgress(*this, "SyncProgress"),
emitSourceProgress(*this, "SourceProgress"),
emitWaiting(*this, "Waiting"),
emitSyncSuccessStart(*this, "SyncSuccessStart"),
emitConfigChanged(*this, "ConfigChanged"),
emitPasswordRequest(*this, "PasswordRequest"),
emitMessage(*this, "Message"),
emitShutdown(*this, "Shutdown")
{
add(this, &SessionHelper::sync, "Sync");
add(this, &SessionHelper::restore, "Restore");
add(this, &SessionHelper::execute, "Execute");
add(this, &SessionHelper::passwordResponse, "PasswordResponse");
add(this, &SessionHelper::storeMessage, "StoreMessage");
add(this, &SessionHelper::connectionState, "ConnectionState");
add(emitLogOutput);
add(emitSyncProgress);
add(emitSourceProgress);
add(emitWaiting);
add(emitSyncSuccessStart);
add(emitConfigChanged);
add(emitPasswordRequest);
add(emitMessage);
add(emitShutdown);
activate();
LoggerBase::pushLogger(this);
}
SessionHelper::~SessionHelper()
{
LoggerBase::popLogger();
}
static void dumpString(const std::string &output)
{
fputs(output.c_str(), stdout);
}
void SessionHelper::messagev(Level level,
const char *prefix,
const char *file,
int line,
const char *function,
const char *format,
va_list args)
{
static bool dbg = getenv("SYNCEVOLUTION_DEBUG");
if (dbg) {
// let parent LogRedirect or utility function handle the output *in addition* to
// logging via D-Bus
va_list argsCopy;
va_copy(argsCopy, args);
if (m_parentLogger) {
m_parentLogger->messagev(level, prefix, file, line, function, format, argsCopy);
} else {
formatLines(level, DEBUG, getProcessName(),
prefix, format, argsCopy,
boost::bind(dumpString, _1));
}
va_end(argsCopy);
} else {
// Only flush parent logger, to capture output sent to
// stdout/stderr by some library and send it via D-Bus
// (recursively!) before printing out own, new output.
m_parentLogger->flush();
}
// send to parent
string log = StringPrintfV(format, args);
string strLevel = Logger::levelToStr(level);
emitLogOutput(strLevel, log);
}
void SessionHelper::run()
{
SuspendFlags &s = SuspendFlags::getSuspendFlags();
while (true) {
if (s.getState() != SuspendFlags::NORMAL) {
SE_LOG_DEBUG(NULL, NULL, "terminating because of suspend or abort signal");
break;
}
if (m_operation &&
m_operation()) {
SE_LOG_DEBUG(NULL, NULL, "terminating as requested by operation");
break;
}
g_main_loop_run(m_loop);
}
}
bool SessionHelper::connected()
{
return m_forkexec && m_forkexec->getState() == ForkExecChild::CONNECTED;
}
void SessionHelper::sync(const SessionCommon::SyncParams &params,
const boost::shared_ptr< GDBusCXX::Result1<bool> > &result)
{
m_operation = boost::bind(&SessionHelper::doSync, this, params, result);
g_main_loop_quit(m_loop);
}
bool SessionHelper::doSync(const SessionCommon::SyncParams &params,
const boost::shared_ptr< GDBusCXX::Result1<bool> > &result)
{
try {
m_sync.reset(new DBusSync(params, *this));
SyncMLStatus status = m_sync->sync();
if (status) {
SE_THROW_EXCEPTION_STATUS(StatusException,
"sync failed",
status);
}
result->done(true);
} catch (...) {
dbusErrorCallback(result);
}
m_sync.reset();
// quit helper
return true;
}
void SessionHelper::restore(const std::string &configName,
const string &dir, bool before, const std::vector<std::string> &sources,
const boost::shared_ptr< GDBusCXX::Result1<bool> > &result)
{
m_operation = boost::bind(&SessionHelper::doRestore, this, configName, dir, before, sources, result);
g_main_loop_quit(m_loop);
}
bool SessionHelper::doRestore(const std::string &configName,
const string &dir, bool before, const std::vector<std::string> &sources,
const boost::shared_ptr< GDBusCXX::Result1<bool> > &result)
{
try {
SessionCommon::SyncParams params;
params.m_config = configName;
DBusSync sync(params, *this);
if (!sources.empty()) {
BOOST_FOREACH(const std::string &source, sources) {
FilterConfigNode::ConfigFilter filter;
filter["sync"] = "two-way";
sync.setConfigFilter(false, source, filter);
}
// disable other sources
FilterConfigNode::ConfigFilter disabled;
disabled["sync"] = "disabled";
sync.setConfigFilter(false, "", disabled);
}
sync.restore(dir,
before ?
SyncContext::DATABASE_BEFORE_SYNC :
SyncContext::DATABASE_AFTER_SYNC);
result->done(true);
} catch (...) {
dbusErrorCallback(result);
}
// quit helper
return true;
}
void SessionHelper::execute(const vector<string> &args, const map<string, string> &vars,
const boost::shared_ptr< GDBusCXX::Result1<bool> > &result)
{
m_operation = boost::bind(&SessionHelper::doExecute, this, args, vars, result);
g_main_loop_quit(m_loop);
}
bool SessionHelper::doExecute(const vector<string> &args, const map<string, string> &vars,
const boost::shared_ptr< GDBusCXX::Result1<bool> > &result)
{
try {
CmdlineWrapper cmdline(*this, args, vars);
if (!cmdline.parse()) {
SE_THROW_EXCEPTION(DBusSyncException, "arguments parsing error");
}
bool success = false;
// a command line operation can be many things, tell parent
SessionCommon::RunOperation op;
op = cmdline.isSync() ? SessionCommon::OP_SYNC :
cmdline.isRestore() ? SessionCommon::OP_RESTORE :
SessionCommon::OP_CMDLINE;
emitSyncProgress(sysync::PEV_CUSTOM_START, op, 0, 0);
try {
success = cmdline.run();
} catch (...) {
if (cmdline.configWasModified()) {
emitConfigChanged();
}
throw;
}
if (cmdline.configWasModified()) {
emitConfigChanged();
}
result->done(success);
} catch (...) {
dbusErrorCallback(result);
}
// quit helper
return true;
}
void SessionHelper::passwordResponse(bool timedOut, bool aborted, const std::string &password)
{
if (m_sync) {
m_sync->passwordResponse(timedOut, aborted, password);
} else {
SE_LOG_DEBUG(NULL, NULL, "discarding obsolete password response");
}
}
SE_END_CXX

View File

@ -0,0 +1,160 @@
/*
* Copyright (C) 2012 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
*/
#ifndef SESSION_HELPER_H
#define SESSION_HELPER_H
#include "session-common.h"
#include "dbus-sync.h"
#include <gdbus-cxx-bridge.h>
#include <boost/function.hpp>
#include <glib.h>
#include <boost/shared_ptr.hpp>
#include <syncevo/declarations.h>
SE_BEGIN_CXX
class LogRedirect;
class ForkExecChild;
/**
* Waits for requests via the internal D-Bus connection in run(), sent
* by the Session class in syncevo-dbus-server. Then for each request
* it remembers what to do in m_operation and returns from the event
* loop and executes the requested operation, pretty much like the
* traditional syncevo-dbus-server did.
*/
class SessionHelper : public GDBusCXX::DBusObjectHelper,
private LoggerBase,
private boost::noncopyable
{
GMainLoop *m_loop;
GDBusCXX::DBusConnectionPtr m_conn;
boost::shared_ptr<ForkExecChild> m_forkexec;
LogRedirect *m_parentLogger;
boost::function<bool ()> m_operation;
/** valid during doSync() */
boost::scoped_ptr<DBusSync> m_sync;
/** called by main event loop: initiate a sync operation */
void sync(const SessionCommon::SyncParams &params,
const boost::shared_ptr< GDBusCXX::Result1<bool> > &result);
/**
* called by run(): do the sync operation
* @return true if the helper is meant to terminate
*/
bool doSync(const SessionCommon::SyncParams &params,
const boost::shared_ptr< GDBusCXX::Result1<bool> > &result);
void restore(const std::string &configName,
const string &dir, bool before, const std::vector<std::string> &sources,
const boost::shared_ptr< GDBusCXX::Result1<bool> > &result);
bool doRestore(const std::string &configName,
const string &dir, bool before, const std::vector<std::string> &sources,
const boost::shared_ptr< GDBusCXX::Result1<bool> > &result);
void execute(const vector<string> &args, const map<string, string> &vars,
const boost::shared_ptr< GDBusCXX::Result1<bool> > &result);
bool doExecute(const vector<string> &args, const map<string, string> &vars,
const boost::shared_ptr< GDBusCXX::Result1<bool> > &result);
/** SessionHelper.PasswordResponse */
void passwordResponse(bool timedOut, bool aborted, const std::string &password);
// Logger implementation -> output via D-Bus emitLogOutput
virtual void messagev(Level level,
const char *prefix,
const char *file,
int line,
const char *function,
const char *format,
va_list args);
virtual bool isProcessSafe() const { return false; }
public:
SessionHelper(GMainLoop *loop,
const GDBusCXX::DBusConnectionPtr &conn,
const boost::shared_ptr<ForkExecChild> &forkexec,
LogRedirect *parentLogger);
~SessionHelper();
void run();
GMainLoop *getLoop() const { return m_loop; }
/** Still have connection to parent. Shortcut which asks the ForkExecChild class. */
bool connected();
boost::shared_ptr<ForkExecChild> getForkExecChild() { return m_forkexec; }
/** Server.LogOutput for the session D-Bus object */
GDBusCXX::EmitSignal2<std::string,
std::string, true> emitLogOutput;
/** SyncContext::displaySyncProgress */
GDBusCXX::EmitSignal4<sysync::TProgressEventEnum,
int32_t, int32_t, int32_t, true> emitSyncProgress;
/** SyncContext::displaySourceProgress */
GDBusCXX::EmitSignal6<sysync::TProgressEventEnum,
std::string, SyncMode,
int32_t, int32_t, int32_t, true> emitSourceProgress;
/** SyncContext::reportStepCmd -> true/false for "waiting on IO" */
GDBusCXX::EmitSignal1<bool, true> emitWaiting;
/** SyncContext::syncSuccessStart */
GDBusCXX::EmitSignal0Template<true> emitSyncSuccessStart;
/** Cmdline::configWasModified() */
GDBusCXX::EmitSignal0Template<true> emitConfigChanged;
/** SyncContext::askPassword */
GDBusCXX::EmitSignal2<std::string, ConfigPasswordKey> emitPasswordRequest;
/** send message to parent's connection (buffer, type, url) */
GDBusCXX::EmitSignal3<GDBusCXX::DBusArray<uint8_t>, std::string, std::string> emitMessage;
/** tell parent's connection to shut down */
GDBusCXX::EmitSignal0Template<false> emitShutdown;
/** store the next message received by the session's connection */
void storeMessage(const GDBusCXX::DBusArray<uint8_t> &message,
const std::string &type) {
m_messageSignal(message, type);
}
typedef boost::signals2::signal<void (const GDBusCXX::DBusArray<uint8_t> &,
const std::string &)> MessageSignal_t;
MessageSignal_t m_messageSignal;
/** store the latest connection state information */
void connectionState(const std::string &error)
{
m_connectionStateSignal(error);
}
typedef boost::signals2::signal<void (const std::string &)> ConnectionStateSignal_t;
ConnectionStateSignal_t m_connectionStateSignal;
};
SE_END_CXX
#endif // SESSION_HELPER_H

View File

@ -1,51 +0,0 @@
/*
* 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
*/
#ifndef SESSION_LISTENER_H
#define SESSION_LISTENER_H
#include <syncevo/declarations.h>
SE_BEGIN_CXX
/**
* a listener to listen changes of session
* currently only used to track changes of running a sync in a session
*/
class SessionListener
{
public:
/**
* method is called when a sync is successfully started.
* Here 'successfully started' means the synthesis engine starts
* to access the sources.
*/
virtual void syncSuccessStart() {}
/**
* method is called when a sync is done. Also
* sync status are passed.
*/
virtual void syncDone(SyncMLStatus status) {}
virtual ~SessionListener() {}
};
SE_END_CXX
#endif // SESSION_LISTENER_H

File diff suppressed because it is too large Load Diff

View File

@ -24,8 +24,11 @@
#include <boost/weak_ptr.hpp>
#include <boost/utility.hpp>
#include <gdbus-cxx-bridge.h>
#include <syncevo/SuspendFlags.h>
#include "session-common.h"
#include "read-operations.h"
#include "progress-data.h"
#include "source-progress.h"
@ -33,20 +36,30 @@
#include "timer.h"
#include "timeout.h"
#include "resource.h"
#include "dbus-callbacks.h"
SE_BEGIN_CXX
class Server;
class Connection;
class CmdlineWrapper;
class DBusSync;
class SessionListener;
class LogRedirect;
class Server;
class ForkExecParent;
class SessionProxy;
class InfoReq;
/**
* Represents and implements the Session interface. Use
* boost::shared_ptr to track it and ensure that there are references
* to it as long as the connection is needed.
*
* The actual implementation is split into two parts:
* - state as exposed via D-Bus is handled entirely in this class
* - syncing and command line execution run inside
* the forked syncevo-dbus-helper
*
* This allows creating and tracking a Session locally in
* syncevo-dbus-server and minimizes asynchronous calls into the
* helper. The helper is started on demand (which might be never,
* for simple sessions).
*/
class Session : public GDBusCXX::DBusObjectHelper,
public Resource,
@ -58,6 +71,51 @@ class Session : public GDBusCXX::DBusObjectHelper,
const std::string m_sessionID;
std::string m_peerDeviceID;
/** Starts the helper, on demand (see useHelperAsync()). */
boost::shared_ptr<ForkExecParent> m_forkExecParent;
/** The D-Bus proxy for the helper. */
boost::shared_ptr<SessionProxy> m_helper;
/**
* Ensures that helper is running and that its D-Bus API is
* available via m_helper, then invokes the success
* callback. Startup errors are reported back via the error
* callback. It is the responsibility of that error callback to
* turn the session into the right failure state, usually via
* Session::failed(). Likewise, any unexpected failures or helper
* shutdowns need to be monitored by the caller of
* useHelperAsync(). useHelperAsync() merely logs these events.
*
* useHelperAsync() and its helper function, useHelper2(), are the
* ones called directly from the main event loop. They ensure that
* any exceptions thrown inside them, including exceptions thrown
* by the result.done(), are logged and turned into
* result.failed() calls.
*
* In practice, the helper is started at most once per session, to
* run the operation (see runOperation()). When it terminates, the
* session is either considered "done" or "failed", depending on
* whether the operation has completed already.
*/
void useHelperAsync(const SimpleResult &result);
/**
* Finish the work started by useHelperAsync once helper has
* connected. The operation might still fail at this point.
*/
void useHelper2(const SimpleResult &result, const boost::signals2::connection &c);
/** set up m_helper */
void onConnect(const GDBusCXX::DBusConnectionPtr &conn) throw ();
/** unset m_helper but not m_forkExecParent (still processing signals) */
void onQuit(int result) throw ();
/** set after abort() and suspend(), to turn "child died' into the LOCERR_USERABORT status code */
void expectChildTerm(int result) throw ();
/** log failure */
void onFailure(SyncMLStatus status, const std::string &explanation) throw ();
/** log error output from helper */
void onOutput(const char *buffer, size_t length);
bool m_serverMode;
bool m_serverAlerted;
SharedBuffer m_initialMessage;
@ -70,8 +128,7 @@ class Session : public GDBusCXX::DBusObjectHelper,
/** temporary config changes */
FilterConfigNode::ConfigFilter m_syncFilter;
FilterConfigNode::ConfigFilter m_sourceFilter;
typedef std::map<std::string, FilterConfigNode::ConfigFilter> SourceFilters_t;
SourceFilters_t m_sourceFilters;
SessionCommon::SourceFilters_t m_sourceFilters;
/** whether dbus clients set temporary configs */
bool m_tempConfig;
@ -82,63 +139,53 @@ class Session : public GDBusCXX::DBusObjectHelper,
*/
bool m_setConfig;
/**
* True while clients are allowed to make calls other than Detach(),
* which is always allowed. Some calls are not allowed while this
* session runs a sync, which is indicated by a non-NULL m_sync
* pointer.
*/
bool m_active;
/** Session life cycle */
enum SessionStatus {
SESSION_IDLE, /**< not active yet, only Detach() allowed */
SESSION_ACTIVE, /**< active, config changes and Sync()/Execute() allowed */
SESSION_RUNNING, /**< one-time operation (Sync() or Execute()) in progress */
SESSION_DONE /**< operation completed, only Detach() still allowed */
};
SessionStatus m_status;
/**
* True once done() was called.
* set when operation was aborted, enables special handling of "child quit" in onQuit().
*/
bool m_done;
bool m_wasAborted;
/**
* Indicates whether this session was initiated by the peer or locally.
*/
bool m_remoteInitiated;
/**
* The SyncEvolution instance which currently prepares or runs a sync.
*/
boost::shared_ptr<DBusSync> m_sync;
/**
* the sync status for session
*/
enum SyncStatus {
SYNC_QUEUEING, ///< waiting to become ready for use
SYNC_IDLE, ///< ready, session is initiated but sync not started
SYNC_RUNNING, ///< sync is running
SYNC_ABORT, ///< sync is aborting
SYNC_SUSPEND, ///< sync is suspending
SYNC_DONE, ///< sync is done
SYNC_QUEUEING, ///< waiting to become ready for use
SYNC_IDLE, ///< ready, session is initiated but sync not started
SYNC_RUNNING, ///< sync is running
SYNC_ABORT, ///< sync is aborting
SYNC_SUSPEND, ///< sync is suspending
SYNC_DONE, ///< sync is done
SYNC_ILLEGAL
};
/**
* current sync status; suspend and abort must be mirrored in global SuspendFlags
*/
class SyncStatusOwner : boost::noncopyable {
public:
SyncStatusOwner() : m_status(SYNC_QUEUEING), m_active(false) {}
SyncStatusOwner(SyncStatus status) : m_status(SYNC_QUEUEING), m_active(false)
{
setStatus(status);
}
operator SyncStatus () { return m_status; }
SyncStatusOwner &operator = (SyncStatus status) { setStatus(status); return *this; }
void setStatus(SyncStatus status);
private:
SyncStatus m_status;
bool m_active;
boost::shared_ptr<SuspendFlags::StateBlocker> m_blocker;
} m_syncStatus;
/** maps to names as used in D-Bus API */
inline std::string static syncStatusToString(SyncStatus state)
{
static const char * const strings[SYNC_ILLEGAL] = {
"queueing",
"idle",
"running",
"aborting",
"suspending",
"done"
};
return state >= SYNC_QUEUEING && state < SYNC_ILLEGAL ?
strings[state] :
"";
}
/** step info: whether engine is waiting for something */
bool m_stepIsWaiting;
@ -161,37 +208,46 @@ class Session : public GDBusCXX::DBusObjectHelper,
typedef std::map<std::string, SourceProgress> SourceProgresses_t;
SourceProgresses_t m_sourceProgress;
// syncProgress() and sourceProgress() turn raw data from helper
// into usable information on D-Bus server side
void syncProgress(sysync::TProgressEventEnum type,
int32_t extra1, int32_t extra2, int32_t extra3);
void sourceProgress(sysync::TProgressEventEnum type,
const std::string &sourceName,
SyncMode sourceSyncMode,
int32_t extra1, int32_t extra2, int32_t extra3);
/** timer for fire status/progress usages */
Timer m_statusTimer;
Timer m_progressTimer;
/** restore used */
string m_restoreDir;
bool m_restoreBefore;
/** the total number of sources to be restored */
int m_restoreSrcTotal;
/** the number of sources that have been restored */
int m_restoreSrcEnd;
/**
* status of the session
* Wrapper around useHelperAsync() which sets up the session
* to execute a specific operation (sync, command line, ...).
*/
enum RunOperation {
OP_SYNC, /**< running a sync */
OP_RESTORE, /**< restoring data */
OP_CMDLINE, /**< executing command line */
OP_NULL /**< idle, accepting commands via D-Bus */
};
void runOperationAsync(SessionCommon::RunOperation op,
const SuccessCb_t &helperReady);
static string runOpToString(RunOperation op);
/**
* A Session can be used for exactly one of the operations. This
* is the one. This gets set by the D-Bus method implementation
* which triggers the operation. All other D-Bus method
* implementations need to check it before allowing an operation
* or method call which would conflict or be illegal.
*/
SessionCommon::RunOperation m_runOperation;
RunOperation m_runOperation;
/** listener to listen to changes of sync */
SessionListener *m_listener;
/** Cmdline to execute command line args */
boost::shared_ptr<CmdlineWrapper> m_cmdline;
/**
* If m_runOperation == OP_CMDLINE, then we need further information
* from the helper about the actual operation. We get that information
* via a sync progress signal with event == PEV_CUSTOM_START.
*/
SessionCommon::RunOperation m_cmdlineOp;
/** Session.Attach() */
void attach(const GDBusCXX::Caller_t &caller);
@ -208,18 +264,20 @@ class Session : public GDBusCXX::DBusObjectHelper,
SourceProgresses_t &sources);
/** Session.Restore() */
void restore(const string &dir, bool before,const std::vector<std::string> &sources);
void restore(const string &dir, bool before, const std::vector<std::string> &sources);
void restore2(const string &dir, bool before, const std::vector<std::string> &sources);
/** Session.checkPresence() */
void checkPresence (string &status);
/** Session.Execute() */
void execute(const vector<string> &args, const map<string, string> &vars);
void execute2(const vector<string> &args, const map<string, string> &vars);
/**
* Must be called each time that properties changing the
* overall status are changed. Ensures that the corresponding
* D-Bus signal is sent.
* overall status are changed (m_syncStatus, m_error, m_sourceStatus).
* Ensures that the corresponding D-Bus signal is sent.
*
* Doesn't always send the signal immediately, because often it is
* likely that more status changes will follow shortly. To ensure
@ -239,8 +297,6 @@ class Session : public GDBusCXX::DBusObjectHelper,
GDBusCXX::EmitSignal2<int32_t,
const SourceProgresses_t &> emitProgress;
static string syncStatusToString(SyncStatus state);
public:
/**
* Sessions must always be held in a shared pointer
@ -261,8 +317,11 @@ public:
*/
~Session();
/** explicitly mark the session as completed, even if it doesn't get deleted yet */
void done();
/**
* explicitly mark an idle session as completed, even if it doesn't
* get deleted yet (exceptions not expected by caller)
*/
void done() throw () { doneCb(); }
private:
Session(Server &server,
@ -271,6 +330,15 @@ private:
const std::string &session,
const std::vector<std::string> &flags = std::vector<std::string>());
boost::weak_ptr<Session> m_me;
boost::shared_ptr<InfoReq> m_passwordRequest;
void passwordRequest(const std::string &descr, const ConfigPasswordKey &key);
void sendViaConnection(const GDBusCXX::DBusArray<uint8_t> buffer,
const std::string &type,
const std::string &url);
void shutdownConnection();
void storeMessage(const GDBusCXX::DBusArray<uint8_t> &message,
const std::string &type);
void connectionState(const std::string &error);
public:
enum {
@ -309,40 +377,11 @@ public:
void setStubConnectionError(const std::string error) { m_connectionError = error; }
std::string getStubConnectionError() { return m_connectionError; }
Server &getServer() { return m_server; }
std::string getConfigName() { return m_configName; }
std::string getSessionID() const { return m_sessionID; }
std::string getPeerDeviceID() const { return m_peerDeviceID; }
/**
* TRUE if the session is ready to take over control
*/
bool readyToRun() { return (m_syncStatus != SYNC_DONE) && (m_runOperation != OP_NULL); }
/**
* transfer control to the session for the duration of the sync,
* returns when the sync is done (successfully or unsuccessfully)
*/
void run(LogRedirect &redirect);
/**
* called when the session is ready to run (true) or
* lost the right to make changes (false)
*/
void setActive(bool active);
bool getActive() { return m_active; }
void syncProgress(sysync::TProgressEventEnum type,
int32_t extra1, int32_t extra2, int32_t extra3);
void sourceProgress(sysync::TProgressEventEnum type,
SyncSource &source,
int32_t extra1, int32_t extra2, int32_t extra3);
string askPassword(const string &passwordName,
const string &descr,
const ConfigPasswordKey &key);
/** Session.GetFlags() */
std::vector<std::string> getFlags() { return m_flags; }
@ -358,33 +397,79 @@ public:
bool update, bool temporary,
const ReadOperations::Config_t &config);
typedef StringMap SourceModes_t;
/** Session.Sync() */
void sync(const std::string &mode, const SourceModes_t &source_modes);
void sync(const std::string &mode, const SessionCommon::SourceModes_t &sourceModes);
/**
* finish the work started by sync once helper is ready (invoked
* by useHelperAsync() and thus may throw exceptions)
*/
void sync2(const std::string &mode, const SessionCommon::SourceModes_t &sourceModes);
/** Session.Abort() */
void abort();
/** abort active session, trigger result once done */
void abortAsync(const SimpleResult &result);
/** Session.Suspend() */
void suspend();
/**
/**
* step info for engine: whether the engine is blocked by something
* If yes, 'waiting' will be appended as specifiers in the status string.
* see GetStatus documentation.
*/
void setStepInfo(bool isWaiting);
void setWaiting(bool isWaiting);
/** session was just activated */
typedef boost::signals2::signal<void ()> SessionActiveSignal_t;
SessionActiveSignal_t m_sessionActiveSignal;
/** sync is successfully started */
void syncSuccessStart();
typedef boost::signals2::signal<void ()> SyncSuccessStartSignal_t;
SyncSuccessStartSignal_t m_syncSuccessStartSignal;
/** sync completed (may have failed) */
typedef boost::signals2::signal<void (SyncMLStatus)> DoneSignal_t;
DoneSignal_t m_doneSignal;
/**
* add a listener of the session. Old set listener is returned
* Called by server when the session is ready to run.
* Only the session itself can deactivate itself.
*/
SessionListener* addListener(SessionListener *listener);
void activateSession();
/**
* Called by server when it has a password response for the
* session. The session ensures that it only has one pending
* request at a time, so these parameters are enough to identify
* the request.
*/
void passwordResponse(bool timedOut, bool aborted, const std::string &password);
void setRemoteInitiated (bool remote) { m_remoteInitiated = remote;}
private:
/** set m_syncFilter and m_sourceFilters to config */
virtual bool setFilters(SyncConfig &config);
void dbusResultCb(const std::string &operation, bool success, const std::string &error) throw();
/**
* to be called inside a catch() clause: returns error for any
* pending D-Bus method and then calls doneCb()
*/
void failureCb() throw();
/**
* explicitly mark the session as completed, even if it doesn't
* get deleted yet (invoked directly or indirectly from event
* loop and thus must not throw exceptions)
*
* @param success if false, then ensure that m_error is set
* before finalizing the session
*/
void doneCb(bool success = true) throw();
};
SE_END_CXX

View File

@ -0,0 +1,173 @@
/*
* Copyright (C) 2012 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
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "session-helper.h"
#include <syncevo/Logging.h>
#include <syncevo/ForkExec.h>
#include <syncevo/SuspendFlags.h>
#include <syncevo/SyncContext.h>
#include <syncevo/LogRedirect.h>
using namespace SyncEvo;
using namespace GDBusCXX;
namespace {
GMainLoop *loop = NULL;
// that one is actually never called. probably a bug in ForkExec - it should
// call m_onFailure instead of throwing an exception
void onFailure(const std::string &error, bool &failed) throw ()
{
SE_LOG_DEBUG(NULL, NULL, "failure, quitting now: %s", error.c_str());
failed = true;
}
void onConnect(const DBusConnectionPtr &conn,
LogRedirect *parentLogger,
const boost::shared_ptr<ForkExecChild> &forkexec,
boost::shared_ptr<SessionHelper> &helper)
{
helper.reset(new SessionHelper(loop, conn, forkexec, parentLogger));
}
void onAbort()
{
g_main_loop_quit(loop);
}
} // anonymous namespace
/**
* This program is a helper of syncevo-dbus-server which provides the
* Connection and Session DBus interfaces and runs individual sync
* sessions. It is only intended to be started by syncevo-dbus-server,
*/
int main(int argc, char **argv, char **envp)
{
// delay the client for debugging purposes
const char *delay = getenv("SYNCEVOLUTION_LOCAL_CHILD_DELAY");
if (delay) {
sleep(atoi(delay));
}
SyncContext::initMain("syncevo-dbus-helper");
loop = g_main_loop_new(NULL, FALSE);
// Suspend and abort are signaled via SIGINT/SIGTERM
// respectively. SuspendFlags handle that for us.
SuspendFlags &s = SuspendFlags::getSuspendFlags();
boost::shared_ptr<SuspendFlags::Guard> guard = s.activate();
bool debug = getenv("SYNCEVOLUTION_DEBUG");
// Redirect both stdout and stderr. The only code
// writing to it should be third-party libraries
// which are unaware of the SyncEvolution logging system.
// Redirecting is useful to get such output into our
// sync logfile, once we have one.
boost::scoped_ptr<LogRedirect> redirect;
if (!debug) {
redirect.reset(new LogRedirect(true));
}
setvbuf(stderr, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
try {
if (debug) {
LoggerBase::instance().setLevel(Logger::DEBUG);
Logger::setProcessName(StringPrintf("syncevo-dbus-helper-%ld", (long)getpid()));
}
// syncevo-dbus-helper produces the output which is of most
// interest to users, and therefore it is allowed to print
// [INFO/ERROR/DEBUG] without including a process name in
// the brackets, like the other processes do.
// Logger::setProcessName("syncevo-dbus-helper");
boost::shared_ptr<ForkExecChild> forkexec = ForkExecChild::create();
boost::shared_ptr<SessionHelper> helper;
bool failed = false;
forkexec->m_onConnect.connect(boost::bind(onConnect, _1, redirect.get(),
boost::cref(forkexec),
boost::ref(helper)));
forkexec->m_onFailure.connect(boost::bind(onFailure, _2, boost::ref(failed)));
forkexec->connect();
// Run until we are connected, failed or get interrupted.
boost::signals2::connection c =
s.m_stateChanged.connect(boost::bind(&onAbort));
SE_LOG_DEBUG(NULL, NULL, "helper (pid %d) finished setup, waiting for parent connection", getpid());
while (true) {
if (s.getState() != SuspendFlags::NORMAL) {
// not an error, someone wanted us to stop
SE_LOG_DEBUG(NULL, NULL, "aborted via signal while starting, terminating");
// tell caller that we aborted by terminating via the SIGTERM signal
return 0;
}
if (failed) {
SE_THROW("parent connection failed");
}
if (helper) {
// done
break;
}
// wait
g_main_loop_run(loop);
}
// Now we no longer care whether the parent connection fails.
// TODO: What if the parent fails to call us and instead closes his
// side of the connection? Will we notice and abort?
c.disconnect();
SE_LOG_DEBUG(NULL, NULL, "connected to parent, run helper");
helper->run();
SE_LOG_DEBUG(NULL, NULL, "helper operation done");
helper.reset();
SE_LOG_DEBUG(NULL, NULL, "helper destroyed");
// Wait for confirmation from parent that we are allowed to
// quit. This is necessary because we might have pending IO
// for the parent, like D-Bus method replies.
while (true) {
if (s.getState() == SuspendFlags::ABORT) {
// not an error, someone wanted us to stop
SE_LOG_DEBUG(NULL, NULL, "aborted via signal after completing operation, terminating");
return 0;
}
if (forkexec->getState() != ForkExecChild::CONNECTED) {
// no point running any longer, parent is gone
SE_LOG_DEBUG(NULL, NULL, "parent has quit, terminating");
return 1;
}
g_main_context_iteration(NULL, true);
}
} catch ( const std::exception &ex ) {
SE_LOG_ERROR(NULL, NULL, "%s", ex.what());
} catch (...) {
SE_LOG_ERROR(NULL, NULL, "unknown error");
}
return 1;
}

View File

@ -182,6 +182,7 @@ src_syncevo_local_sync_DEPENDENCIES = $(builddir)/$(gdbus_build_dir)/libgdbussyn
# Do the linking here, as with all SyncEvolution executables.
# Sources are compiled in dbus/server.
if COND_DBUS
# DBus Server
libexec_PROGRAMS += src/syncevo-dbus-server
src_syncevo_dbus_server_SOURCES = \
@ -192,10 +193,25 @@ endif
src_syncevo_dbus_server_LDADD = $(builddir)/src/dbus/server/libsyncevodbusserver.la $(gdbus_build_dir)/libgdbussyncevo.la $(CORE_LDADD) $(LIBNOTIFY_LIBS) $(MLITE_LIBS) $(DBUS_LIBS)
src_syncevo_dbus_server_CPPFLAGS = -DHAVE_CONFIG_H -I$(gdbus_dir) $(src_cppflags) -DSYNCEVOLUTION_LOCALEDIR=\"${SYNCEVOLUTION_LOCALEDIR}\"
src_syncevo_dbus_server_CXXFLAGS = $(PCRECPP_CFLAGS) $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) $(LIBSOUP_CFLAGS) $(LIBNOTIFY_CFLAGS) $(MLITE_CFLAGS) $(SYNCEVO_WFLAGS)
src_syncevo_dbus_server_LDFLAGS = $(PCRECPP_LIBS) $(CORE_LD_FLAGS) $(LIBSOUP_LIBS)
src_syncevo_dbus_server_CXXFLAGS = $(PCRECPP_CFLAGS) $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) $(LIBNOTIFY_CFLAGS) $(MLITE_CFLAGS) $(SYNCEVO_WFLAGS)
src_syncevo_dbus_server_LDFLAGS = $(PCRECPP_LIBS) $(CORE_LD_FLAGS)
src_syncevo_dbus_server_DEPENDENCIES = $(builddir)/src/dbus/server/libsyncevodbusserver.la $(gdbus_build_dir)/libgdbussyncevo.la $(EXTRA_LTLIBRARIES) $(CORE_DEP) $(SYNTHESIS_DEP)
# syncevo-dbus-server's helper binary
libexec_PROGRAMS += src/syncevo-dbus-helper
if ENABLE_UNIT_TESTS
nodist_src_syncevo_dbus_helper_SOURCES = test/test.cpp
endif
src_syncevo_dbus_helper_SOURCES = \
$(CORE_SOURCES)
src_syncevo_dbus_helper_LDADD = $(builddir)/src/dbus/server/libsyncevodbushelper.la $(gdbus_build_dir)/libgdbussyncevo.la $(CORE_LDADD) $(DBUS_LIBS)
src_syncevo_dbus_helper_CPPFLAGS = -DHAVE_CONFIG_H -I$(gdbus_dir) $(src_cppflags) -DSYNCEVOLUTION_LOCALEDIR=\"${SYNCEVOLUTION_LOCALEDIR}\"
src_syncevo_dbus_helper_CXXFLAGS = $(PCRECPP_CFLAGS) $(SYNCEVOLUTION_CXXFLAGS) $(CORE_CXXFLAGS) $(GLIB_CFLAGS) $(DBUS_CFLAGS) $(SYNCEVO_WFLAGS)
src_syncevo_dbus_helper_LDFLAGS = $(PCRECPP_LIBS) $(CORE_LD_FLAGS)
src_syncevo_dbus_helper_DEPENDENCIES = $(builddir)/src/dbus/server/libsyncevodbushelper.la $(gdbus_build_dir)/libgdbussyncevo.la $(EXTRA_LTLIBRARIES) $(CORE_DEP) $(SYNTHESIS_DEP)
endif # COND_DBUS
# With --disable-shared autotools links against libfunambol.a which does not
# pull any of the test suites into the test binary, so they would not be

View File

@ -485,7 +485,7 @@ class DBusUtil(Timeout):
while self.isServerRunning():
newsize = os.path.getsize(syncevolog)
if newsize != size:
if "syncevo-dbus-server: ready to run" in open(syncevolog).read():
if "] ready to run\n" in open(syncevolog).read():
break
size = newsize
time.sleep(1)
@ -1293,6 +1293,8 @@ class TestNamedConfig(unittest.TestCase, DBusUtil):
class TestDBusServerPresence(unittest.TestCase, DBusUtil):
"""Tests Presence signal and checkPresence API"""
# TODO: check auto sync + presence combination
# Our fake ConnMan implementation must be present on the
# bus also outside of tests, because syncevo-dbus-server
# will try to call it before setUp(). The implementation's
@ -2658,8 +2660,10 @@ class TestSessionAPIsReal(unittest.TestCase, DBusUtil):
if percentage > 20:
if self.operation == "abort":
self.session.Abort()
self.operation = "aborted"
if self.operation == "suspend":
self.session.Suspend()
self.operation = "suspended"
@timeout(300)
def testSync(self):
@ -2775,6 +2779,12 @@ class TestConnection(unittest.TestCase, DBusUtil):
self.config[""]["remoteDeviceId"] = deviceId
self.session.SetConfig(False, False, self.config, utf8_strings=True)
self.session.Detach()
# SyncEvolution <= 1.2.2 delayed the "Session.StatusChanged"
# "done" signal. The correct behavior is to send that
# important change right away.
loop.run()
self.assertEqual(DBusUtil.quit_events, ["session done"])
DBusUtil.quit_events = []
def run(self, result):
self.runTest(result, own_xdg=True)
@ -2842,7 +2852,9 @@ class TestConnection(unittest.TestCase, DBusUtil):
self.assertEqual(len(sessions), 1)
# transport failure, only addressbook active and later aborted
self.assertEqual(sessions[0]["status"], "20043")
self.assertEqual(sessions[0]["error"], "D-Bus peer has disconnected")
# Exact error string doesn't matter much, it is mostly internal.
if sessions[0]["error"] != "D-Bus peer has disconnected": # old error
self.assertEqual(sessions[0]["error"], "transport problem: send() on connection which is not ready")
self.assertEqual(sessions[0]["source-addressbook-status"], "20017")
# The other three sources are disabled and should not be listed in the
# report. Used to be listed with status 0 in the past, which would also
@ -2972,14 +2984,19 @@ class TestConnection(unittest.TestCase, DBusUtil):
connection2.Process(message1_clientB, 'application/vnd.syncml+xml')
conpath3, connection3 = self.getConnection()
connection3.Process(message1_clientB, 'application/vnd.syncml+xml')
# Queueing session for connection2 done now.
loop.run()
self.assertEqual(DBusUtil.quit_events, [ "connection " + conpath2 + " aborted" ])
loop.run()
self.assertEqual(DBusUtil.quit_events, ["connection " + conpath2 + " aborted",
"session done"])
DBusUtil.quit_events = []
# now quit for good
connection3.Close(False, 'good bye client B')
loop.run()
self.assertEqual(DBusUtil.quit_events, [ "connection " + conpath3 + " aborted" ])
loop.run()
self.assertEqual(DBusUtil.quit_events, ["connection " + conpath3 + " aborted",
"session done"])
DBusUtil.quit_events = []
connection.Close(False, 'good bye client A')
loop.run()