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:
parent
77bf1f3b4e
commit
f47d31580d
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue