From b6b75de59ba284b0497e450d2bbe76523d1ee523 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Tue, 7 May 2013 16:39:50 +0200 Subject: PIM: new return value for SyncPeer(), new SyncProgress signal (FDO #63417) The SyncPeer() result is derived from the sync statistics. To have them available, the "sync done" signal must include the SyncReport. Start and end of a sync could already be detected; "modified" signals while a sync runs depends on a new signal inside the SyncContext when switching from one cycle to the next and at the end of the last one. --- src/dbus/server/client.cpp | 2 +- src/dbus/server/dbus-sync.cpp | 3 ++ src/dbus/server/pim/README | 23 ++++++++ src/dbus/server/pim/manager.cpp | 47 ++++++++++++++--- src/dbus/server/pim/manager.h | 17 +++++- src/dbus/server/pim/pim-manager-api.txt | 26 +++++++-- src/dbus/server/pim/testpim.py | 93 +++++++++++++++++++++++++++++---- src/dbus/server/session-common.h | 77 +++++++++++++++++++++++++++ src/dbus/server/session-helper.cpp | 11 ++-- src/dbus/server/session-helper.h | 7 ++- src/dbus/server/session.cpp | 27 ++++++---- src/dbus/server/session.h | 20 ++++--- src/syncevo/SyncContext.cpp | 3 ++ src/syncevo/SyncContext.h | 6 +++ 14 files changed, 315 insertions(+), 47 deletions(-) diff --git a/src/dbus/server/client.cpp b/src/dbus/server/client.cpp index 3c100f20..37d15994 100644 --- a/src/dbus/server/client.cpp +++ b/src/dbus/server/client.cpp @@ -50,7 +50,7 @@ void Client::detach(Resource *resource) // give clients a chance to query the session m_server.delaySessionDestruction(session); // allow other sessions to start - session->done(); + session->done(false); } } // this will trigger removal of the resource if diff --git a/src/dbus/server/dbus-sync.cpp b/src/dbus/server/dbus-sync.cpp index 87ebdc19..5303234d 100644 --- a/src/dbus/server/dbus-sync.cpp +++ b/src/dbus/server/dbus-sync.cpp @@ -90,6 +90,9 @@ DBusSync::DBusSync(const SessionCommon::SyncParams ¶ms, SYNC_NONE, 0, 0, 0); } + + // Forward the SourceSyncedSignal via D-Bus. + m_sourceSyncedSignal.connect(boost::bind(m_helper.emitSourceSynced, _1, _2)); } DBusSync::~DBusSync() diff --git a/src/dbus/server/pim/README b/src/dbus/server/pim/README index 97d53a39..e351e0a8 100644 --- a/src/dbus/server/pim/README +++ b/src/dbus/server/pim/README @@ -247,6 +247,29 @@ Not supported via the API at the moment: - selecting a specific phone address book - selecting which vCard properties get cached +Syncing +======= + +SetSync() in SyncEvolution will return a dict with all of the +following entries set: + "modified": boolean - data was modified + "added" : integer - number of new contacts + "updated" : integer - number of updated contacts + "removed" : integer - number of deleted contacts + +In other words, the caller can reliably detect when nothing changed, +but when contacts were modified or added, it needs to read them to +determine which kind of properties were modified or added. + +The SyncProgress is triggered by SyncEvolution with three different +keys (in this order, with "modified" occuring zero or more times): + "started" "modified"* "done" + +"started" and "done" send an empty data dictionary. "modified" sends +the same dictionary as the one returned by SyncPeer(), if contact data +was modified. So by definition, "modified" will be True in the +dictionary, but is included anyway for the sake of consistency. + Contact Data ============ diff --git a/src/dbus/server/pim/manager.cpp b/src/dbus/server/pim/manager.cpp index 1b8a949a..2ed3fbe2 100644 --- a/src/dbus/server/pim/manager.cpp +++ b/src/dbus/server/pim/manager.cpp @@ -80,7 +80,8 @@ Manager::Manager(const boost::shared_ptr &server) : MANAGER_IFACE), m_mainThread(g_thread_self()), m_server(server), - m_locale(LocaleFactory::createFactory()) + m_locale(LocaleFactory::createFactory()), + emitSyncProgress(*this, "SyncProgress") { } @@ -143,6 +144,7 @@ void Manager::init() add(this, &Manager::addContact, "AddContact"); add(this, &Manager::modifyContact, "ModifyContact"); add(this, &Manager::removeContact, "RemoveContact"); + add(emitSyncProgress); // Ready, make it visible via D-Bus. activate(); @@ -1298,7 +1300,7 @@ void Manager::doRemovePeer(const boost::shared_ptr &session, result->done(); } -void Manager::syncPeer(const boost::shared_ptr &result, +void Manager::syncPeer(const boost::shared_ptr > &result, const std::string &uid) { checkPeerUID(uid); @@ -1311,12 +1313,30 @@ void Manager::syncPeer(const boost::shared_ptr &result, boost::bind(&Manager::doSyncPeer, this, _1, result, uid)); } -static void doneSyncPeer(const boost::shared_ptr &result, - SyncMLStatus status) +static Manager::SyncResult SyncReport2Result(const SyncReport &report) +{ + Manager::SyncResult result; + int added = 0, updated = 0, removed = 0; + if (!report.empty()) { + const SyncSourceReport &source = report.begin()->second; + added = source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ADDED, SyncSourceReport::ITEM_TOTAL); + updated = source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_UPDATED, SyncSourceReport::ITEM_TOTAL); + removed = source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_REMOVED, SyncSourceReport::ITEM_TOTAL); + } + result["modified"] = added || updated || removed; + result["added"] = added; + result["updated"] = updated; + result["removed"] = removed; + return result; +} + +static void doneSyncPeer(const boost::shared_ptr > &result, + SyncMLStatus status, + const SyncReport &report) { if (status == STATUS_OK || status == STATUS_HTTP_OK) { - result->done(); + result->done(SyncReport2Result(report)); } else if (status == (SyncMLStatus)sysync::LOCERR_USERABORT) { result->failed(GDBusCXX::dbus_error(MANAGER_ERROR_ABORTED, "running sync aborted, probably by StopSync()")); } else { @@ -1325,15 +1345,28 @@ static void doneSyncPeer(const boost::shared_ptr &result, } void Manager::doSyncPeer(const boost::shared_ptr &session, - const boost::shared_ptr &result, + const boost::shared_ptr > &result, const std::string &uid) { + // Keep client informed about progress. + emitSyncProgress(uid, "started", SyncResult()); + session->m_doneSignal.connect(boost::bind(boost::ref(emitSyncProgress), uid, "done", SyncResult())); + session->m_sourceSynced.connect(boost::bind(&Manager::report2SyncProgress, m_self, uid, _1, _2)); // After sync(), the session is tracked as the active sync session // by the server. It was removed from our own m_pending list by // doSession(). session->sync("ephemeral", SessionCommon::SourceModes_t()); // Relay result to caller when done. - session->m_doneSignal.connect(boost::bind(doneSyncPeer, result, _1)); + session->m_doneSignal.connect(boost::bind(doneSyncPeer, result, _1, _2)); +} + +void Manager::report2SyncProgress(const std::string &uid, + const std::string &sourceName, + const SyncSourceReport &source) +{ + SyncReport report; + report.addSyncSourceReport("foo", source); + emitSyncProgress(uid, "modified", SyncReport2Result(report)); } void Manager::stopSync(const boost::shared_ptr &result, diff --git a/src/dbus/server/pim/manager.h b/src/dbus/server/pim/manager.h index 884407e6..ef838061 100644 --- a/src/dbus/server/pim/manager.h +++ b/src/dbus/server/pim/manager.h @@ -28,6 +28,7 @@ #include "folks.h" #include "locale-factory.h" #include "../server.h" +#include "../session.h" #include #include @@ -135,14 +136,26 @@ class Manager : public GDBusCXX::DBusObjectHelper const std::string &uid); public: + typedef std::map > SyncResult; /** Manager.SyncPeer() */ - void syncPeer(const boost::shared_ptr &result, + void syncPeer(const boost::shared_ptr > &result, const std::string &uid); + + /** Manager.SyncProgress */ + GDBusCXX::EmitSignal3 emitSyncProgress; + + private: void doSyncPeer(const boost::shared_ptr &session, - const boost::shared_ptr &result, + const boost::shared_ptr > &result, const std::string &uid); + void report2SyncProgress(const std::string &uid, + const std::string &sourceName, + const SyncSourceReport &source); + public: /** Manager.StopSync() */ void stopSync(const boost::shared_ptr &result, diff --git a/src/dbus/server/pim/pim-manager-api.txt b/src/dbus/server/pim/pim-manager-api.txt index 7b28643b..3283c7c4 100644 --- a/src/dbus/server/pim/pim-manager-api.txt +++ b/src/dbus/server/pim/pim-manager-api.txt @@ -238,13 +238,21 @@ Methods: part of the active address books, it will be removed automatically. - void SyncPeer(string uid) + dict SyncPeer(string uid) Retrieve contacts from the peer and ensure that the local cache is identical to the address book of the peer. The call - returns once the operation is complete. Only if there was no - error can the caller assume that the cache is up-to-date. - Otherwise it is in an undefined state. + returns once the operation is complete. + + Only if there was no error can the caller assume that the cache + is up-to-date. In this case, a string to variant dictionary + is returned which provided additional information about the sync. + The content of the dictionary is implementation dependent. + + If the call fails, no dictionary is returned and the local + cache may or may not be up-to-date. It may or may not have been + updated. The caller needs to check the local cache to find out + what it contains. void StopSync(string uid) @@ -307,6 +315,16 @@ Methods: Remove the contact and all of its associated data (like the photo, if the photo file is owned by the contact storage). +Signals: + + SyncProgress(string uid, string event, dict data) + + Provides information about a running sync for the peer with + the given "uid". The "event" string describes what happened + and the "data" dictionary provides further information about + it with a mapping from event specific string keys to variants + as value. + Service: org._01.pim.contacts Interface: org._01.pim.contacts.ViewControl diff --git a/src/dbus/server/pim/testpim.py b/src/dbus/server/pim/testpim.py index 6f384b75..9e5efc58 100755 --- a/src/dbus/server/pim/testpim.py +++ b/src/dbus/server/pim/testpim.py @@ -649,6 +649,24 @@ END:VCARD(\r|\n)*''', expected = sources.copy() peers = {} + syncProgress = [] + signal = bus.add_signal_receiver(lambda uid, event, data: (logging.printf('received SyncProgress: %s, %s, %s', uid, event, data), syncProgress.append((uid, event, data)), logging.printf('progress %s' % syncProgress)), + 'SyncProgress', + 'org._01.pim.contacts.Manager', + None, #'org._01.pim.contacts', + '/org/01/pim/contacts', + byte_arrays=True, + utf8_strings=True) + def checkSync(expected, result): + self.assertEqual(expected, result) + while not (uid, 'done', {}) in syncProgress: + self.loopIteration('added signal') + self.assertEqual([(uid, 'started', {}), + (uid, 'modified', expected), + (uid, 'done', {})], + syncProgress) + + # Must be the Bluetooth MAC address (like A0:4E:04:1E:AD:30) # of a phone which is paired, currently connected, and # supports both PBAP and SyncML. SyncML is needed for putting @@ -707,7 +725,9 @@ END:VCARD(\r|\n)*''', # Remember current list of files and modification time stamp. files = listsyncevo() - # Remove all data locally. + # Remove all data locally. There may or may not have been data + # locally, because the database of the peer might have existed + # from previous tests. self.manager.SyncPeer(uid, timeout=self.timeout) # TODO: check that syncPhone() really used PBAP - but how? @@ -817,8 +837,14 @@ END:VCARD output.write(john) output.close() self.syncPhone(phone, uid) - self.manager.SyncPeer(uid, - timeout=self.timeout) + syncProgress = [] + result = self.manager.SyncPeer(uid, + timeout=self.timeout) + checkSync({'modified': True, + 'added': 1, + 'updated': 0, + 'removed': 0}, + result) # Also exclude modified database files. self.assertEqual(files, listsyncevo(exclude=exclude)) @@ -834,16 +860,28 @@ END:VCARD peers[uid], timeout=self.timeout) files = listsyncevo(exclude=exclude) - self.manager.SyncPeer(uid, - timeout=self.timeout) + syncProgress = [] + result = self.manager.SyncPeer(uid, + timeout=self.timeout) + checkSync({'modified': False, + 'added': 0, + 'updated': 0, + 'removed': 0}, + result) exclude.append(logdir + '(/$)') self.assertEqual(files, listsyncevo(exclude=exclude)) self.assertEqual(2, len(os.listdir(logdir))) # At most one! - self.manager.SyncPeer(uid, - timeout=self.timeout) + syncProgress = [] + result = self.manager.SyncPeer(uid, + timeout=self.timeout) + checkSync({'modified': False, + 'added': 0, + 'updated': 0, + 'removed': 0}, + result) exclude.append(logdir + '(/$)') self.assertEqual(files, listsyncevo(exclude=exclude)) self.assertEqual(2, len(os.listdir(logdir))) @@ -854,12 +892,49 @@ END:VCARD peers[uid], timeout=self.timeout) files = listsyncevo(exclude=exclude) - self.manager.SyncPeer(uid, - timeout=self.timeout) + syncProgress = [] + result = self.manager.SyncPeer(uid, + timeout=self.timeout) + checkSync({'modified': False, + 'added': 0, + 'updated': 0, + 'removed': 0}, + result) exclude.append(logdir + '(/$)') self.assertEqual(files, listsyncevo(exclude=exclude)) self.assertEqual(4, len(os.listdir(logdir))) + # Update contact. + john = '''BEGIN:VCARD +VERSION:3.0 +FN:John Doe +N:Doe;John +END:VCARD''' + output = open(item, "w") + output.write(john) + output.close() + self.syncPhone(phone, uid) + syncProgress = [] + result = self.manager.SyncPeer(uid, + timeout=self.timeout) + checkSync({'modified': True, + 'added': 0, + 'updated': 1, + 'removed': 0}, + result) + + # Remove contact. + os.unlink(item) + self.syncPhone(phone, uid) + syncProgress = [] + result = self.manager.SyncPeer(uid, + timeout=self.timeout) + checkSync({'modified': True, + 'added': 0, + 'updated': 0, + 'removed': 1}, + result) + # Test invalid maxsession values. with self.assertRaisesRegexp(dbus.DBusException, "negative 'maxsessions' not allowed: -1"): diff --git a/src/dbus/server/session-common.h b/src/dbus/server/session-common.h index ce8bb1c4..83fe246c 100644 --- a/src/dbus/server/session-common.h +++ b/src/dbus/server/session-common.h @@ -195,6 +195,83 @@ namespace GDBusCXX { base::append(builder, array); } }; + + template <> struct dbus_traits : + public dbus_traits< std::string > + { + typedef dbus_traits< std::string > base; + + typedef SyncReport host_type; + typedef const SyncReport &arg_type; + +#ifdef GDBUS_CXX_GIO + static void get(GDBusCXX::ExtractArgs &context, + GDBusCXX::reader_type &iter, host_type &report) + { + std::string dump; + base::get(context, iter, dump); + report = SyncReport(dump); + } +#else + static void get(GDBusCXX::connection_type *conn, GDBusCXX::message_type *msg, + GDBusCXX::reader_type &iter, host_type &report) + { + std::string dump; + base::get(conn, msg, iter, dump); + report = SyncReport(dump); + } +#endif + + static void append(GDBusCXX::builder_type &builder, arg_type report) + { + base::append(builder, report.toString()); + } + }; + + template <> struct dbus_traits : + public dbus_traits< std::string > + { + typedef dbus_traits< std::string > base; + + typedef SyncSourceReport host_type; + typedef const SyncSourceReport &arg_type; + +#ifdef GDBUS_CXX_GIO + static void get(GDBusCXX::ExtractArgs &context, + GDBusCXX::reader_type &iter, host_type &source) + { + std::string dump; + base::get(context, iter, dump); + SyncReport report = SyncReport(dump); + const SyncSourceReport *foo = report.findSyncSourceReport("foo"); + if (!foo) { + SE_THROW("incomplete SyncReport"); + } + source = *foo; + } +#else + static void get(GDBusCXX::connection_type *conn, GDBusCXX::message_type *msg, + GDBusCXX::reader_type &iter, host_type &source) + { + std::string dump; + base::get(conn, msg, iter, dump); + SyncReport report = SyncReport(dump); + const SyncSourceReport *foo = report.findSyncSourceReport("foo"); + if (!foo) { + SE_THROW("incomplete SyncReport"); + } + source = *foo; + } +#endif + + static void append(GDBusCXX::builder_type &builder, arg_type source) + { + SyncReport report; + report.addSyncSourceReport("foo", source); + base::append(builder, report.toString()); + } + }; + } #endif // SESSION_COMMON_H diff --git a/src/dbus/server/session-helper.cpp b/src/dbus/server/session-helper.cpp index 4507576f..b84d85ca 100644 --- a/src/dbus/server/session-helper.cpp +++ b/src/dbus/server/session-helper.cpp @@ -125,6 +125,7 @@ SessionHelper::SessionHelper(GMainLoop *loop, emitLogOutput(*this, "LogOutput"), emitSyncProgress(*this, "SyncProgress"), emitSourceProgress(*this, "SourceProgress"), + emitSourceSynced(*this, "SourceSynced"), emitWaiting(*this, "Waiting"), emitSyncSuccessStart(*this, "SyncSuccessStart"), emitConfigChanged(*this, "ConfigChanged"), @@ -141,6 +142,7 @@ SessionHelper::SessionHelper(GMainLoop *loop, add(emitLogOutput); add(emitSyncProgress); add(emitSourceProgress); + add(emitSourceSynced); add(emitWaiting); add(emitSyncSuccessStart); add(emitConfigChanged); @@ -184,18 +186,19 @@ bool SessionHelper::connected() } void SessionHelper::sync(const SessionCommon::SyncParams ¶ms, - const boost::shared_ptr< GDBusCXX::Result1 > &result) + const boost::shared_ptr< GDBusCXX::Result2 > &result) { m_operation = boost::bind(&SessionHelper::doSync, this, params, result); g_main_loop_quit(m_loop); } bool SessionHelper::doSync(const SessionCommon::SyncParams ¶ms, - const boost::shared_ptr< GDBusCXX::Result1 > &result) + const boost::shared_ptr< GDBusCXX::Result2 > &result) { try { m_sync.reset(new DBusSync(params, *this)); - SyncMLStatus status = m_sync->sync(); + SyncReport report; + SyncMLStatus status = m_sync->sync(&report); if (status) { // Clear the abort signal, to allow the process to send // out the D-Bus response. Our parent will signal us again @@ -205,7 +208,7 @@ bool SessionHelper::doSync(const SessionCommon::SyncParams ¶ms, "sync failed", status); } - result->done(true); + result->done(true, report); } catch (...) { dbusErrorCallback(result); } diff --git a/src/dbus/server/session-helper.h b/src/dbus/server/session-helper.h index e06b28fd..40d7d4af 100644 --- a/src/dbus/server/session-helper.h +++ b/src/dbus/server/session-helper.h @@ -57,14 +57,14 @@ class SessionHelper : public GDBusCXX::DBusObjectHelper, /** called by main event loop: initiate a sync operation */ void sync(const SessionCommon::SyncParams ¶ms, - const boost::shared_ptr< GDBusCXX::Result1 > &result); + const boost::shared_ptr< GDBusCXX::Result2 > &result); /** * called by run(): do the sync operation * @return true if the helper is meant to terminate */ bool doSync(const SessionCommon::SyncParams ¶ms, - const boost::shared_ptr< GDBusCXX::Result1 > &result); + const boost::shared_ptr< GDBusCXX::Result2 > &result); void restore(const std::string &configName, const string &dir, bool before, const std::vector &sources, @@ -110,6 +110,9 @@ class SessionHelper : public GDBusCXX::DBusObjectHelper, std::string, SyncMode, int32_t, int32_t, int32_t, true> emitSourceProgress; + /** SyncContext::m_sourceSyncedSignal */ + GDBusCXX::EmitSignal2 emitSourceSynced; + /** SyncContext::reportStepCmd -> true/false for "waiting on IO" */ GDBusCXX::EmitSignal1 emitWaiting; diff --git a/src/dbus/server/session.cpp b/src/dbus/server/session.cpp index 8b2394c7..4662d7d4 100644 --- a/src/dbus/server/session.cpp +++ b/src/dbus/server/session.cpp @@ -74,6 +74,7 @@ public: m_logOutput(*this, "LogOutput", false), m_syncProgress(*this, "SyncProgress", false), m_sourceProgress(*this, "SourceProgress", false), + m_sourceSynced(*this, "SourceSynced", false), m_waiting(*this, "Waiting", false), m_syncSuccessStart(*this, "SyncSuccessStart", false), m_configChanged(*this, "ConfigChanged", false), @@ -87,7 +88,7 @@ public: /* GDBusCXX::DBusClientCall1 > m_getReports; */ /* GDBusCXX::DBusClientCall0 m_checkSource; */ /* GDBusCXX::DBusClientCall1 m_getDatabases; */ - GDBusCXX::DBusClientCall1 m_sync; + GDBusCXX::DBusClientCall2 m_sync; GDBusCXX::DBusClientCall1 m_restore; GDBusCXX::DBusClientCall1 m_execute; /* GDBusCXX::DBusClientCall0 m_serverShutdown; */ @@ -103,6 +104,7 @@ public: GDBusCXX::SignalWatch6 m_sourceProgress; + GDBusCXX::SignalWatch2 m_sourceSynced; GDBusCXX::SignalWatch1 m_waiting; GDBusCXX::SignalWatch0 m_syncSuccessStart; GDBusCXX::SignalWatch0 m_configChanged; @@ -410,7 +412,7 @@ void Session::sync2(const std::string &mode, const SessionCommon::SourceModes_t // the error is recorded before ending the session. Premature // exits by the helper are handled by D-Bus, which then will abort // the pending method call. - m_helper->m_sync.start(params, boost::bind(&Session::dbusResultCb, m_me, "sync()", _1, _2)); + m_helper->m_sync.start(params, boost::bind(&Session::dbusResultCb, m_me, "sync()", _1, _2, _3)); } void Session::abort() @@ -599,7 +601,7 @@ void Session::passwordRequest(const std::string &descr, const ConfigPasswordKey m_passwordRequest = m_server.passwordRequest(descr, key, m_me); } -void Session::dbusResultCb(const std::string &operation, bool success, const std::string &error) throw() +void Session::dbusResultCb(const std::string &operation, bool success, const SyncReport &report, const std::string &error) throw() { PushLogger guard(m_me); try { @@ -609,7 +611,7 @@ void Session::dbusResultCb(const std::string &operation, bool success, const std success ? "<>" : "<>"); if (error.empty()) { - doneCb(success); + doneCb(success, report); } else { // Translate back into local exception, will be handled by // catch clause and (eventually) failureCb(). @@ -656,7 +658,7 @@ void Session::failureCb() throw() m_error = error; } // will fire status signal, including the error - doneCb(); + doneCb(false); } } catch (...) { // fatal problem, log it and terminate @@ -664,7 +666,7 @@ void Session::failureCb() throw() } } -void Session::doneCb(bool success) throw() +void Session::doneCb(bool success, const SyncReport &report) throw() { PushLogger guard(m_me); try { @@ -695,7 +697,7 @@ void Session::doneCb(bool success) throw() m_configName.c_str(), m_setConfig ? "modified" : "not modified", m_error); - m_doneSignal((SyncMLStatus)m_error); + m_doneSignal((SyncMLStatus)m_error, report); // now also kill helper m_helper.reset(); @@ -718,7 +720,8 @@ void Session::doneCb(bool success) throw() Session::~Session() { SE_LOG_DEBUG(NULL, "session %s deconstructing", getPath()); - doneCb(); + // If we are not done yet, then something went wrong. + doneCb(false); } /** child has quit before connecting, invoke result.failed() with suitable exception pending */ @@ -911,6 +914,7 @@ void Session::onConnect(const GDBusCXX::DBusConnectionPtr &conn) throw () // Activate signal watch on helper signals. m_helper->m_syncProgress.activate(boost::bind(&Session::syncProgress, this, _1, _2, _3, _4)); m_helper->m_sourceProgress.activate(boost::bind(&Session::sourceProgress, this, _1, _2, _3, _4, _5, _6)); + m_helper->m_sourceSynced.activate(boost::bind(boost::ref(m_sourceSynced), _1, _2)); m_helper->m_waiting.activate(boost::bind(&Session::setWaiting, this, _1)); m_helper->m_syncSuccessStart.activate(boost::bind(boost::ref(Session::m_syncSuccessStartSignal))); m_helper->m_configChanged.activate(boost::bind(boost::ref(m_server.m_configChangedSignal), "")); @@ -958,7 +962,8 @@ void Session::onQuit(int status) throw () } m_server.addTimeout(boost::bind(&Session::doneCb, m_me, - false), + false, + SyncReport()), 0.1 /* seconds */); } catch (...) { Exception::handle(); @@ -1245,7 +1250,7 @@ void Session::restore2(const string &dir, bool before, const std::vectorm_restore.start(m_configName, dir, before, sources, - boost::bind(&Session::dbusResultCb, m_me, "restore()", _1, _2)); + boost::bind(&Session::dbusResultCb, m_me, "restore()", _1, SyncReport(), _2)); } void Session::execute(const vector &args, const map &vars) @@ -1276,7 +1281,7 @@ void Session::execute2(const vector &args, const map &va // helper is ready, tell it what to do m_helper->m_execute.start(args, vars, - boost::bind(&Session::dbusResultCb, m_me, "execute()", _1, _2)); + boost::bind(&Session::dbusResultCb, m_me, "execute()", _1, SyncReport(), _2)); } /*Implementation of Session.CheckPresence */ diff --git a/src/dbus/server/session.h b/src/dbus/server/session.h index df23f196..e1df0254 100644 --- a/src/dbus/server/session.h +++ b/src/dbus/server/session.h @@ -81,6 +81,9 @@ class Session : public GDBusCXX::DBusObjectHelper, SYNC_ILLEGAL }; + typedef std::map SourceStatuses_t; + typedef std::map SourceProgresses_t; + private: Server &m_server; std::vector m_flags; @@ -203,11 +206,9 @@ class Session : public GDBusCXX::DBusObjectHelper, /** progress data, holding progress calculation related info */ ProgressData m_progData; - typedef std::map SourceStatuses_t; SourceStatuses_t m_sourceStatus; uint32_t m_error; - typedef std::map SourceProgresses_t; SourceProgresses_t m_sourceProgress; // syncProgress() and sourceProgress() turn raw data from helper @@ -291,6 +292,7 @@ class Session : public GDBusCXX::DBusObjectHelper, /** like fireStatus() for progress information */ void fireProgress(bool flush = false); +public: /** Session.StatusChanged */ GDBusCXX::EmitSignal3 emitProgress; -public: /** * Sessions must always be held in a shared pointer * because some operations depend on that. This @@ -323,7 +324,7 @@ public: * explicitly mark an idle session as completed, even if it doesn't * get deleted yet (exceptions not expected by caller) */ - void done() throw () { doneCb(); } + void done(bool success) throw () { doneCb(success); } private: Session(Server &server, @@ -444,9 +445,13 @@ public: SyncSuccessStartSignal_t m_syncSuccessStartSignal; /** sync completed (may have failed) */ - typedef boost::signals2::signal DoneSignal_t; + typedef boost::signals2::signal DoneSignal_t; DoneSignal_t m_doneSignal; + /** a source was synced, emitted multiple times during a multi-cycle sync */ + typedef boost::signals2::signal SourceSyncedSignal_t; + SourceSyncedSignal_t m_sourceSynced; + /** * Called by server when the session is ready to run. * Only the session itself can deactivate itself. @@ -466,7 +471,7 @@ 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(); + void dbusResultCb(const std::string &operation, bool success, const SyncReport &report, const std::string &error) throw(); /** * to be called inside a catch() clause: returns error for any @@ -481,8 +486,9 @@ private: * * @param success if false, then ensure that m_error is set * before finalizing the session + * @param report valid only in case of success */ - void doneCb(bool success = true) throw(); + void doneCb(bool success, const SyncReport &report = SyncReport()) throw(); }; SE_END_CXX diff --git a/src/syncevo/SyncContext.cpp b/src/syncevo/SyncContext.cpp index a0344fa3..54964837 100644 --- a/src/syncevo/SyncContext.cpp +++ b/src/syncevo/SyncContext.cpp @@ -1768,6 +1768,8 @@ void SyncContext::displaySourceProgress(sysync::TProgressEventEnum type, source.recordFirstSync(extra1 == 2); source.recordResumeSync(extra2 == 1); } else if (SyncMode(mode) != SYNC_NONE) { + // Broadcast statistics before moving into next cycle. + m_sourceSyncedSignal(source.getName(), source); // may happen when the source is used in multiple // SyncML sessions; only remember the initial sync // mode in that case and count all following syncs @@ -3234,6 +3236,7 @@ SyncMLStatus SyncContext::sync(SyncReport *report) // but some items failed, we report a "partial failure" // status. BOOST_FOREACH(SyncSource *source, sourceList) { + m_sourceSyncedSignal(source->getName(), *source); if (source->getStatus() == STATUS_OK && (source->getItemStat(SyncSource::ITEM_LOCAL, SyncSource::ITEM_ANY, diff --git a/src/syncevo/SyncContext.h b/src/syncevo/SyncContext.h index 4a4e1bfc..8269c24f 100644 --- a/src/syncevo/SyncContext.h +++ b/src/syncevo/SyncContext.h @@ -158,6 +158,12 @@ class SyncContext : public SyncConfig { typedef boost::signals2::signal InitMainSignal; static InitMainSignal &GetInitMainSignal(); + /** + * A signal invoked each time a source has gone through a sync cycle. + */ + typedef boost::signals2::signal SourceSyncedSignal; + SourceSyncedSignal m_sourceSyncedSignal; + /** * true if binary was compiled as stable release * (see gen-autotools.sh) -- cgit v1.2.3