auto syncing: fix D-Bus API violations (part of BMC #20966)

Auto-sync sessions did not properly activate their D-Bus support and
thus couldn't be accessed via the Session D-Bus API. Must have
affected showing progress of such sessions in the GTK sync-ui.

They also weren't kept around for one minute, like the sessions
started by a client. Therefore UIs which need to retrieve information
about a completed session failed for a second reason.

Fixed by adding the necessary "activate()" and use "addTimeout()"
trick for session expiry also in the AutoSyncManager. The later was
moved into DBusServer for that.

These issues were found with the new
TestSessionAPIsDummy.testAutoSyncFailure D-Bus test.
This commit is contained in:
Patrick Ohly 2011-07-12 12:46:47 +02:00
parent 77bf1f3b4e
commit f47d31580d
2 changed files with 110 additions and 24 deletions

View File

@ -1430,6 +1430,9 @@ class DBusServer : public DBusObjectHelper,
*/
bool callTimeout(const boost::shared_ptr<Timeout> &timeout, const boost::function<bool ()> &callback);
/** called 1 minute after last client detached from a session */
static bool sessionExpired(const boost::shared_ptr<Session> &session);
public:
DBusServer(GMainLoop *loop, const DBusConnectionPtr &conn, int duration);
~DBusServer();
@ -1484,6 +1487,21 @@ public:
*/
void checkQueue();
/**
* Special behavior for sessions: keep them around for another
* minute after the are no longer needed. Must be called by the
* creator of the session right before it would normally cause the
* destruction of the session.
*
* This allows another client to attach and/or get information
* about the session.
*
* This is implemented as a timeout which holds a reference to the
* session. Once the timeout fires, it is called and then removed,
* which removes the reference.
*/
void delaySessionDestruction(const boost::shared_ptr<Session> &session);
/**
* Invokes the given callback once in the given amount of seconds.
* Keeps a copy of the callback. If the DBusServer is destructed
@ -1597,9 +1615,6 @@ class Client
/** current client setting for notifications (see HAS_NOTIFY) */
bool m_notificationsEnabled;
/** called 1 minute after last client detached from a session */
static bool sessionExpired(const boost::shared_ptr<Session> &session);
public:
const Caller_t m_ID;
@ -2759,14 +2774,6 @@ Client::~Client()
}
}
bool Client::sessionExpired(const boost::shared_ptr<Session> &session)
{
SE_LOG_DEBUG(NULL, NULL, "session %s expired",
session->getSessionID().c_str());
// don't call me again
return false;
}
void Client::detach(Resource *resource)
{
for (Resources_t::iterator it = m_resources.begin();
@ -2776,19 +2783,8 @@ void Client::detach(Resource *resource)
if (it->unique()) {
boost::shared_ptr<Session> session = boost::dynamic_pointer_cast<Session>(*it);
if (session) {
// Special behavior for sessions: keep them
// around for another minute after the last
// client detaches. This allows another client
// to attach and/or get information about the
// session.
// This is implemented as a timeout which holds
// a reference to the session. Once the timeout
// fires, it is called and then removed, which
// removes the reference.
m_server.addTimeout(boost::bind(&Client::sessionExpired,
session),
60 /* 1 minute */);
// give clients a chance to query the session
m_server.delaySessionDestruction(session);
// allow other sessions to start
session->done();
}
@ -3856,6 +3852,8 @@ Session::Session(DBusServer &server,
add(this, &Session::execute, "Execute");
add(emitStatus);
add(emitProgress);
SE_LOG_DEBUG(NULL, NULL, "session %s created", getPath());
}
void Session::done()
@ -3863,6 +3861,7 @@ void Session::done()
if (m_done) {
return;
}
SE_LOG_DEBUG(NULL, NULL, "session %s done", getPath());
/* update auto sync manager when a config is changed */
if (m_setConfig) {
@ -3883,6 +3882,7 @@ void Session::done()
Session::~Session()
{
SE_LOG_DEBUG(NULL, NULL, "session %s deconstructing", getPath());
done();
}
@ -5954,6 +5954,23 @@ void DBusServer::checkQueue()
}
}
bool DBusServer::sessionExpired(const boost::shared_ptr<Session> &session)
{
SE_LOG_DEBUG(NULL, NULL, "session %s expired",
session->getSessionID().c_str());
// don't call me again
return false;
}
void DBusServer::delaySessionDestruction(const boost::shared_ptr<Session> &session)
{
SE_LOG_DEBUG(NULL, NULL, "delaying destruction of session %s by one minute",
session->getSessionID().c_str());
addTimeout(boost::bind(&DBusServer::sessionExpired,
session),
60 /* 1 minute */);
}
bool DBusServer::callTimeout(const boost::shared_ptr<Timeout> &timeout, const boost::function<bool ()> &callback)
{
if (!callback()) {
@ -6678,6 +6695,7 @@ void AutoSyncManager::startTask()
newSession);
m_session->setPriority(Session::PRI_AUTOSYNC);
m_session->addListener(this);
m_session->activate();
m_server.enqueue(m_session);
}
}
@ -6736,6 +6754,11 @@ void AutoSyncManager::syncDone(SyncMLStatus status)
m_notificationManager->publish(summary, body);
}
}
// 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;

View File

@ -1866,6 +1866,69 @@ class TestSessionAPIsDummy(unittest.TestCase, DBusUtil):
Timeout.removeTimeout(timeout_handler)
self.failUnlessEqual(self.lastState, "done")
@timeout(60)
def testAutoSyncFailure(self):
"""TestSessionAPIsDummy.testAutoSyncFailure - test that auto-sync is triggered, fails here"""
self.setupConfig()
# enable auto-sync
config = self.config
config[""]["autoSync"] = "1"
config[""]["autoSyncDelay"] = "0"
config[""]["autoSyncInterval"] = "10s"
config[""]["password"] = "foobar"
self.session.SetConfig(True, False, config, utf8_strings=True)
def session_ready(object, ready):
if self.running and object != self.sessionpath:
self.auto_sync_session_path = object
DBusUtil.quit_events.append("session " + object + (ready and " ready" or " done"))
loop.quit()
signal = bus.add_signal_receiver(session_ready,
'SessionChanged',
'org.syncevolution.Server',
'org.syncevolution',
None,
byte_arrays=True,
utf8_strings=True)
# shut down current session, will allow auto-sync
self.session.Detach()
# wait for start and end of auto-sync session
loop.run()
loop.run()
end = time.time()
self.failUnlessEqual(DBusUtil.quit_events, ["session " + self.auto_sync_session_path + " ready",
"session " + self.auto_sync_session_path + " done"])
DBusUtil.quit_events = []
# session must be around for a while after terminating, to allow
# reading information about it by clients who didn't start it
# and thus wouldn't know what the session was about otherwise
session = dbus.Interface(bus.get_object('org.syncevolution',
self.auto_sync_session_path),
'org.syncevolution.Session')
reports = session.GetReports(0, 100, utf8_strings=True)
self.failUnlessEqual(len(reports), 1)
self.failUnlessEqual(reports[0]["status"], "20043")
name = session.GetConfigName()
self.failUnlessEqual(name, "dummy-test")
flags = session.GetFlags()
self.failUnlessEqual(flags, [])
first_auto = self.auto_sync_session_path
# check that interval between auto-sync sessions is right
loop.run()
start = time.time()
loop.run()
self.failUnlessEqual(DBusUtil.quit_events, ["session " + self.auto_sync_session_path + " ready",
"session " + self.auto_sync_session_path + " done"])
self.failIfEqual(first_auto, self.auto_sync_session_path)
delta = start - end
self.failUnless(delta < 13)
self.failUnless(delta > 7)
class TestSessionAPIsReal(unittest.TestCase, DBusUtil):
""" This class is used to test those unit tests of session APIs, depending on doing sync.
Thus we need a real server configuration to confirm sync could be run successfully.