[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Freeze Exception for syncevolution 1.2.99.2-1



Hi,

I would like to get a freeze exception for syncevolution 1.2.99.2-1.
This fixes one important bug, enables hardening flags and bumps the
upstream version to a new release candidate for version 1.3. The
upstream changes are documented in [1].

Attached is the debdiff between the version in wheezy and the intended
1.2.99.2-1 code.  The package is also available via gitweb[2].

Please CC me in replies.

Regards,
Tino

[1] https://syncevolution.org/blogs/pohly/2012/syncevolution-12992-released

[2] http://anonscm.debian.org/gitweb/?p=collab-maint/syncevolution.git
diffstat for syncevolution-1.2.99.1 syncevolution-1.2.99.2

 NEWS                                                  |  149 ++++++++++++++
 configure.ac                                          |    2 
 debian/changelog                                      |    9 
 debian/control                                        |    2 
 debian/rules                                          |    5 
 debian/syncevolution-libs.install                     |    1 
 src/backends/akonadi/configure-sub.in                 |    2 
 src/backends/webdav/NeonCXX.cpp                       |   31 ++-
 src/backends/webdav/NeonCXX.h                         |   10 -
 src/backends/webdav/WebDAVSource.cpp                  |  180 +++++++++++++++---
 src/backends/webdav/WebDAVSource.h                    |   17 +
 src/dbus/interfaces/syncevo-connection-full.xml       |    2 
 src/dbus/interfaces/syncevo-server-full.xml           |   23 +-
 src/dbus/interfaces/syncevo-session-full.xml          |   27 +-
 src/dbus/server/server.cpp                            |    2 
 src/dbus/server/server.h                              |    5 
 src/dbus/server/session-helper.cpp                    |    2 
 src/dbus/server/session-helper.h                      |    4 
 src/dbus/server/session.cpp                           |   78 ++++++-
 src/dbus/server/session.h                             |   25 ++
 src/gdbusxx/gdbus-cxx-bridge.h                        |    4 
 src/syncevo/Cmdline.cpp                               |   65 ++++--
 src/syncevo/Cmdline.h                                 |    5 
 src/syncevo/ForkExec.cpp                              |   29 ++
 src/syncevo/ForkExec.h                                |    2 
 src/syncevo/LocalTransportAgent.cpp                   |  119 ++++++++++-
 src/syncevo/LocalTransportAgent.h                     |    3 
 src/syncevo/Logging.h                                 |   27 ++
 src/syncevo/SyncContext.cpp                           |    9 
 src/syncevo/SyncSource.cpp                            |    4 
 src/syncevo/configs/remoterules/client/03funambol.xml |    7 
 src/syncevo/configs/scripting/11calendar.xml          |   17 +
 src/syncevolution.cpp                                 |   16 -
 test/ClientTest.cpp                                   |  154 ++++++++++-----
 test/ClientTest.h                                     |    4 
 test/client-test-main.cpp                             |    7 
 test/runtests.py                                      |   10 -
 test/test-dbus.py                                     |   49 ++--
 38 files changed, 894 insertions(+), 213 deletions(-)

diff -Nru syncevolution-1.2.99.1/configure.ac syncevolution-1.2.99.2/configure.ac
--- syncevolution-1.2.99.1/configure.ac	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/configure.ac	2012-07-02 17:58:20.000000000 +0200
@@ -8,7 +8,7 @@
 #
 # Starting with the 1.1 release cycle, the rpm-style
 # .99 pseudo-version number is used to mark a pre-release.
-AC_INIT([syncevolution], [m4_esyscmd([build/gen-git-version.sh 1.2.99.1])])
+AC_INIT([syncevolution], [m4_esyscmd([build/gen-git-version.sh 1.2.99.2])])
 # STABLE_VERSION=1.0.1+
 AC_SUBST(STABLE_VERSION)
 
diff -Nru syncevolution-1.2.99.1/debian/changelog syncevolution-1.2.99.2/debian/changelog
--- syncevolution-1.2.99.1/debian/changelog	2012-06-29 12:42:39.000000000 +0200
+++ syncevolution-1.2.99.2/debian/changelog	2012-07-08 13:53:30.000000000 +0200
@@ -1,3 +1,12 @@
+syncevolution (1.2.99.2-1) unstable; urgency=low
+
+  * New upstream release candidate
+  * Build with hardening flags enabled
+  * Install /usr/lib/syncevolution/syncevo-local-sync.
+    Thanks to Gregor Herrmann for the patch. (Closes: #679657)
+
+ -- Tino Keitel <tino+debian@tikei.de>  Sun, 08 Jul 2012 13:53:22 +0200
+
 syncevolution (1.2.99.1-1) unstable; urgency=low
 
   * New upstream release candidate (Closes: #675288)
diff -Nru syncevolution-1.2.99.1/debian/control syncevolution-1.2.99.2/debian/control
--- syncevolution-1.2.99.1/debian/control	2012-06-29 12:42:39.000000000 +0200
+++ syncevolution-1.2.99.2/debian/control	2012-07-08 13:53:30.000000000 +0200
@@ -10,7 +10,7 @@
  libgconf2-dev, libgnome-keyring-dev, xsltproc, 
  libopenobex1-dev [linux-any], libnotify-dev,
  python-docutils, libical-dev, libneon27-gnutls-dev, libpcre3-dev,
- libcppunit-dev
+ libcppunit-dev, dpkg-dev (>= 1.16.1~)
 Standards-Version: 3.9.2
 Homepage: http://www.syncevolution.org
 Vcs-Git: git://git.debian.org/git/collab-maint/syncevolution
diff -Nru syncevolution-1.2.99.1/debian/rules syncevolution-1.2.99.2/debian/rules
--- syncevolution-1.2.99.1/debian/rules	2012-06-29 12:42:39.000000000 +0200
+++ syncevolution-1.2.99.2/debian/rules	2012-07-08 13:53:30.000000000 +0200
@@ -1,11 +1,14 @@
 #!/usr/bin/make -f
 # -*- makefile -*-
-UPSTREAMTAG=upstream/1.2.99.1
+UPSTREAMTAG=upstream/1.2.99.2
 
 SOURCEPKG=$(shell dpkg-parsechangelog | sed  -n 's/^Source: \(.*\)/\1/p')
 UPSTREAM=$(shell dpkg-parsechangelog |  sed -n 's/^Version: \(.*\)-[^-]*/\1/p')
 ORIG=${SOURCEPKG}_${UPSTREAM}.orig.tar.gz
 
+DPKG_EXPORT_BUILDFLAGS = 1
+include /usr/share/dpkg/buildflags.mk
+
 export CONFIG_SHELL=/bin/bash
 
 %: 
diff -Nru syncevolution-1.2.99.1/debian/syncevolution-libs.install syncevolution-1.2.99.2/debian/syncevolution-libs.install
--- syncevolution-1.2.99.1/debian/syncevolution-libs.install	2012-06-29 12:42:39.000000000 +0200
+++ syncevolution-1.2.99.2/debian/syncevolution-libs.install	2012-07-08 13:53:30.000000000 +0200
@@ -1 +1,2 @@
 usr/lib/syncevolution/backends/*.so
+usr/lib/syncevolution/syncevo-local-sync
diff -Nru syncevolution-1.2.99.1/NEWS syncevolution-1.2.99.2/NEWS
--- syncevolution-1.2.99.1/NEWS	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/NEWS	2012-07-02 17:58:20.000000000 +0200
@@ -1,3 +1,152 @@
+SyncEvolution 1.2.99.1 -> 1.2.99.2, 30.06.2012
+==============================================
+
+Next step towards SyncEvolution 1.3. It adds a workaround for
+Funambol's OneMedia and fixes an old bug which became more severe in
+1.2.99.1. Also has some usability improvements for
+CalDAV/CardDAV. Hopefully it will not take long to stabilize the code,
+so test it now while it is still hot :-)
+
+
+Details:
+
+* Funambol: ignore UID
+
+  Funambol's OneMedia sends UID, but not RECURRENCE-ID. That becomes a
+  problem when multiple events of the same event series are added to a
+  backend which follows the iCalendar 2.0 standard (CalDAV, EDS, KDE),
+  because these events all look like the master event, and there can be
+  only one of those.
+
+  SyncEvolution now strips the UID from all events coming from any
+  Funambol server (regardless of the version). If a future Funambol
+  server release adds support for both UID and RECURRENCE-ID, then
+  SyncEvolution will have to be updated to take advantage of the
+  improved server. Because the RECURRENCE-ID is also getting
+  stripped (despite not being set at the moment), SyncEvolution should
+  continue to work as it does now even if the server changes.
+
+  It would have been nice to limit this workaround to affected Funambol
+  server versions, but an inquiry on the Funambol mailing list didn't
+  get a reply, therefore SyncEvolution is playing it safe and assumes
+  that all future Funambol releases will have the same problem.
+
+* WebDAV: fixed data corruption issue when uploading item with long UID
+
+  In some cases data with a very long UID wasn't handled correctly,
+  causing the out-going data to be malformed and probably causing a
+  rejection by the server. The root cause is incorrect string handling.
+  In releases before 1.2.99.1, only the --import operation of contacts
+  into CardDAV were affected. In 1.2.99.1, the same code also got used
+  for calendar items and then could also affect syncing.
+
+* engine: add DTSTAMP+LAST-MODIFIED before writing calendar items
+
+  When writing calendar items into a backend storage as iCalendar 2.0 or
+  vCalendar 1.0, they should have DTSTAMP and LAST-MODIFIED values. DTSTAMP
+  is expected by some CalDAV servers (like Apple). LAST-MODIFIED is usually
+  added by the storage, but not always.
+
+  In the text/plain -> syncevolution -> text/calendar -> Radicale -> EDS
+  -> syncevolution chain the LAST-MODIFIED was not added by Radicale, which caused
+  problems for change tracking in an EDS-based SyncEvolution.
+
+* Google Calendar: updated URL redirect handling
+
+  Google Calendar sometimes returns redirect requests to human-readable
+  web sites (an "unavailable" page, a login form). This is of course
+  bogus when the client is an automated CalDAV client.
+
+  The "unavailable.html" case was already handled. Made it a bit more
+  flexible to also catch possible variations of it (additional
+  parameters, https instead of http).
+
+  Added the https://accounts.google.com/ServiceLogin case. Not sure
+  whether retrying will help in that case, but there's not much else
+  that SyncEvolution can do.
+
+* CalDAV + VJOURNAL: handle UID conflicts
+
+  When asked to insert a VJOURNAL which already existed (= same UID),
+  CalDAV servers respond with a 412 "Precondition failed" error. This
+  needs to be detected and translated into an "item needs to be merged"
+  result so that the engine can load the existing item, merge the data,
+  and then write back.
+
+* WebDAV: --status for WebDAV source aborted
+
+  The command line --status operation did not complete when applied to a
+  CalDAV/CardDAV source. Instead it aborted because the operation took a
+  code path where the backend was not fully initialized.
+
+* CalDAV/CardDAV sync: improved target side output
+
+  Added a "target side of local sync ready" INFO message to introduce
+  the output which has the target context in the [INFO] tag. The sync report
+  from the target side now has the target context embedded in brackets
+  after the "Changes applied during synchronization" header, to avoid
+  ambiguities.
+
+  Sometimes the backend has to resend requests because of temporary
+  issues. If the problem turned out to be permanent, there was a long
+  period of time, retryDuration=5 minutes to be precice, in which no
+  visible progress happened.
+
+  Now SyncEvolution's WebDAV backend will print a message like this
+  before going to sleep until it is time to retry:
+
+  [INFO @googlecalendar] operation temporarily (?) failed, going to retry in 5.0s before giving up in 1
+
+  The uncertainty comes from several factors. In this example, the 401
+  might indicate a permanent problem (wrong credentials), or it could be
+  Google reporting a temporary authorization problem which is (probably)
+  meant to slow down the client while it asks the user to re-enter the
+  password. SyncEvolution only asks for passwords once, so it tries
+  again with the same password if it was successful with it in the
+  past. Otherwise it gives up immediately.
+
+  Another dubious example are name server lookup errors. They can be
+  ermanent (wrong host name) or temporary (name server
+  down). SyncEvolution errs on the side of retrying, to avoid
+  interrupting an operation which still has a chance to continue.
+
+  Output from the target side of a local sync was passed through stderr
+  redirection as chunks of text to the frontends. This had several
+  drawbacks:
+  - forwarding only happened when the local sync parent was processing
+    the output redirection, which (due to limitations of the implementation)
+    only happens when it needs to print something itself
+  - debug messages were not forwarded
+  - message boundaries might have been lost
+
+  In particular the new INFO messages are relevant while the sync runs
+  and need to be shown immediately.
+
+* command line: fixed password + property lookup during --print-databases
+
+  --print-databases for an existing configuration did not look up
+  passwords stored in a keyring, causing the operation to fail for
+  backends like CalDAV/CardDAV where credentials are required.
+
+  Overriding source properties in that case also only worked when using
+  the unqualified property name ("databasePassword=foo") but not when
+  using the source name as prefix ("calendar/databasePassword=foo").
+
+* Developers:
+
+  Implementing the improved local sync output required extending the
+  D-Bus API. The Server.LogOutput signal now has an additional
+  "process name" parameter. Normally it is empty. For messages
+  originating from the target side, it carries that extra target
+  context string.
+
+  This D-Bus API change is backward compatible. Older clients can still
+  subscribe to and decode the LogOutput messages, they'll simply ignore
+  the extra parameter. Newer clients expecting that extra parameter
+  won't work with an older D-Bus daemon: they'll fail to decode the
+  D-Bus message.
+
+
 SyncEvolution 1.2.2 -> 1.2.99.1, 22.06.2012
 ===========================================
 
diff -Nru syncevolution-1.2.99.1/src/backends/akonadi/configure-sub.in syncevolution-1.2.99.2/src/backends/akonadi/configure-sub.in
--- syncevolution-1.2.99.1/src/backends/akonadi/configure-sub.in	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/backends/akonadi/configure-sub.in	2012-07-02 17:58:20.000000000 +0200
@@ -14,7 +14,7 @@
    fi
 fi
 if ! test "$KDEPIM_LIBS"; then
-   KDEPIM_LIBS="-L`kde4-config --prefix`/lib`kde4-config --libsuffix` -lakonadi-kde -lQtDBus -lQtCore -lkdeui -lkdecore"
+   KDEPIM_LIBS="-L`kde4-config --install lib` -lakonadi-kde `pkg-config --libs QtDBus` -lQtCore -lkdeui -lkdecore"
 fi
 AC_LANG_PUSH(C++)
 old_CPPFLAGS="$CPPFLAGS"
diff -Nru syncevolution-1.2.99.1/src/backends/webdav/NeonCXX.cpp syncevolution-1.2.99.2/src/backends/webdav/NeonCXX.cpp
--- syncevolution-1.2.99.1/src/backends/webdav/NeonCXX.cpp	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/backends/webdav/NeonCXX.cpp	2012-07-02 17:58:20.000000000 +0200
@@ -514,7 +514,8 @@
     }
 }
 
-bool Session::checkError(int error, int code, const ne_status *status, const string &location)
+bool Session::checkError(int error, int code, const ne_status *status, const string &location,
+                         const std::set<int> *expectedCodes)
 {
     flush();
     SuspendFlags &s = SuspendFlags::getSuspendFlags();
@@ -544,8 +545,10 @@
     if ((error == NE_ERROR || error == NE_OK) &&
         (code >= 300 && code <= 399)) {
         // special case Google: detect redirect to temporary error page
-        // and retry
-        if (location == "http://www.google.com/googlecalendar/unavailable.html";) {
+        // and retry; same for redirect to login page
+        if (boost::starts_with(location, "http://www.google.com/googlecalendar/unavailable.html";) ||
+            boost::starts_with(location, "https://www.google.com/googlecalendar/unavailable.html";) ||
+            boost::starts_with(location, "https://accounts.google.com/ServiceLogin";)) {
             retry = true;
         } else {
             SE_THROW_EXCEPTION_2(RedirectException,
@@ -561,6 +564,12 @@
     switch (error) {
     case NE_OK:
         // request itself completed, but might still have resulted in bad status
+        if (expectedCodes &&
+            expectedCodes->find(code) != expectedCodes->end()) {
+            // return to caller immediately as if we had succeeded,
+            // without throwing an exception and without retrying
+            return true;
+        }
         if (code &&
             (code < 200 || code >= 300)) {
             if (status) {
@@ -666,6 +675,13 @@
                                      operation.c_str(),
                                      duration,
                                      m_attempt);
+                        // Inform the user, because this will take a
+                        // while and we don't want to give the
+                        // impression of being stuck.
+                        SE_LOG_INFO(NULL, NULL, "operation temporarily (?) failed, going to retry in %.1lfs before giving up in %.1lfs: %s",
+                                    duration,
+                                    (m_deadline - now).duration(),
+                                    descr.c_str());
                         Sleep(duration);
                     } else {
                         SE_LOG_DEBUG(NULL, NULL, "retry %s immediately (due already), attempt #%d",
@@ -861,7 +877,7 @@
 }
 #endif
 
-bool Request::run()
+bool Request::run(const std::set<int> *expectedCodes)
 {
     int error;
 
@@ -874,7 +890,7 @@
         error = ne_xml_dispatch_request(m_req, m_parser->get());
     }
 
-    return checkError(error);
+    return checkError(error, expectedCodes);
 }
 
 int Request::addResultData(void *userdata, const char *buf, size_t len)
@@ -884,9 +900,10 @@
     return 0;
 }
 
-bool Request::checkError(int error)
+bool Request::checkError(int error, const std::set<int> *expectedCodes)
 {
-    return m_session.checkError(error, getStatus()->code, getStatus(), getResponseHeader("Location"));
+    return m_session.checkError(error, getStatus()->code, getStatus(), getResponseHeader("Location"),
+                                expectedCodes);
 }
 
 }
diff -Nru syncevolution-1.2.99.1/src/backends/webdav/NeonCXX.h syncevolution-1.2.99.2/src/backends/webdav/NeonCXX.h
--- syncevolution-1.2.99.1/src/backends/webdav/NeonCXX.h	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/backends/webdav/NeonCXX.h	2012-07-02 17:58:20.000000000 +0200
@@ -309,12 +309,16 @@
      * @param code       HTTP status code
      * @param status     optional ne_status pointer, non-NULL for all requests
      * @param location   optional "Location" header value
+     * @param expectedCodes   set of codes which are normal and must not result
+     *                        in retrying or an exception (returns true, as if the
+     *                        operation succeeded)
      *
      * @return true for success, false if retry needed (only if deadline not empty);
      *         errors reported via exceptions
      */ 
     bool checkError(int error, int code = 0, const ne_status *status = NULL,
-                    const string &location = "");
+                    const string &location = "",
+                    const std::set<int> *expectedCodes = NULL);
 
     ne_session *getSession() const { return m_session; }
 
@@ -528,7 +532,7 @@
      *
      * @return result of Session::checkError()
      */
-    bool run();
+    bool run(const std::set<int> *expectedCodes = NULL);
 
     std::string getResponseHeader(const std::string &name) {
         const char *value = ne_get_response_header(m_req, name.c_str());
@@ -552,7 +556,7 @@
     static int addResultData(void *userdata, const char *buf, size_t len);
 
     /** throw error if error code *or* current status indicates failure */
-    bool checkError(int error);
+    bool checkError(int error, const std::set<int> *expectedCodes = NULL);
 };
 
 /** thrown for 301 HTTP status */
diff -Nru syncevolution-1.2.99.1/src/backends/webdav/WebDAVSource.cpp syncevolution-1.2.99.2/src/backends/webdav/WebDAVSource.cpp
--- syncevolution-1.2.99.1/src/backends/webdav/WebDAVSource.cpp	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/backends/webdav/WebDAVSource.cpp	2012-07-02 17:58:20.000000000 +0200
@@ -13,6 +13,8 @@
 
 #include <syncevo/LogRedirect.h>
 
+#include <boost/assign.hpp>
+
 #include <stdio.h>
 #include <errno.h>
 
@@ -274,21 +276,17 @@
     }
 
     // first check if the item already contains the right UID
-    std::string uid = extractUID(item);
+    size_t start, end;
+    std::string uid = extractUID(item, &start, &end);
     if (uid == olduid) {
         return &item;
     }
 
     // insert or overwrite
     buffer = item;
-    size_t start = buffer.find(UID);
-    if (start != buffer.npos) {
-        start += UID.size();
-        size_t end = buffer.find("\n", start);
-        if (end != buffer.npos) {
-            // overwrite
-            buffer.replace(start, end, olduid);
-        }
+    if (start != std::string::npos) {
+        // overwrite
+        buffer.replace(start, end - start, olduid);
     } else {
         // insert
         start = buffer.find("\nEND:" + getContent());
@@ -302,19 +300,53 @@
 
 
 
-std::string WebDAVSource::extractUID(const std::string &item)
+std::string WebDAVSource::extractUID(const std::string &item, size_t *startp, size_t *endp)
 {
     std::string luid;
+    if (startp) {
+        *startp = std::string::npos;
+    }
+    if (endp) {
+        *endp = std::string::npos;
+    }
     // find UID, use that plus ".vcf" as resource name (expected by Yahoo Contacts)
     size_t start = item.find(UID);
     if (start != item.npos) {
         start += UID.size();
         size_t end = item.find("\n", start);
         if (end != item.npos) {
+            if (startp) {
+                *startp = start;
+            }
             luid = item.substr(start, end - start);
             if (boost::ends_with(luid, "\r")) {
                 luid.resize(luid.size() - 1);
             }
+            // keep checking for more lines because of folding
+            while (end + 1 < item.size() &&
+                   item[end + 1] == ' ') {
+                start = end + 1;
+                end = item.find("\n", start);
+                if (end == item.npos) {
+                    // incomplete, abort
+                    luid = "";
+                    if (startp) {
+                        *startp = std::string::npos;
+                    }
+                    break;
+                }
+                luid += item.substr(start, end - start);
+                if (boost::ends_with(luid, "\r")) {
+                    luid.resize(luid.size() - 1);
+                }
+            }
+            // success, return all information
+            if (endp) {
+                // don't include \r or \n
+                *endp = item[end - 1] == '\r' ?
+                    end - 1 :
+                    end;
+            }
         }
     }
     return luid;
@@ -1227,6 +1259,8 @@
         return "";
     }
 
+    contactServer();
+
     Timespec deadline = createDeadline();
     Neon::Session::PropfindPropCallback_t callback =
         boost::bind(&WebDAVSource::openPropCallback,
@@ -1298,7 +1332,7 @@
             Neon::XMLParser parser;
             parser.initReportParser(boost::bind(&WebDAVSource::checkItem, this,
                                                 boost::ref(revisions),
-                                                _1, _2, boost::ref(data)));
+                                                _1, _2, &data));
             parser.pushHandler(boost::bind(Neon::XMLParser::accept, "urn:ietf:params:xml:ns:caldav", "calendar-data", _2, _3),
                                boost::bind(Neon::XMLParser::append, boost::ref(data), _2, _3));
             Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser);
@@ -1311,6 +1345,83 @@
     }
 }
 
+std::string WebDAVSource::findByUID(const std::string &uid,
+                                    const Timespec &deadline)
+{
+    RevisionMap_t revisions;
+    std::string query;
+    if (getContent() == "VCARD") {
+        // use CardDAV
+        query =
+            "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
+            "<C:addressbook-query xmlns:D=\"DAV:\"\n"
+            "xmlns:C=\"urn:ietf:params:xml:ns:carddav:addressbook\">\n"
+            "<D:prop>\n"
+            "<D:getetag/>\n"
+            "</D:prop>\n"
+            "<C:filter>\n"
+            "<C:comp-filter name=\"" + getContent() + "\">\n"
+            "<C:prop-filter name=\"UID\">\n"
+            "<C:text-match>" + uid + "</C:text-match>\n"
+            "</C:prop-filter>\n"
+            "</C:comp-filter>\n"
+            "</C:filter>\n"
+            "</C:addressbook-query>\n";
+    } else {
+        // use CalDAV
+        query =
+            "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
+            "<C:calendar-query xmlns:D=\"DAV:\"\n"
+            "xmlns:C=\"urn:ietf:params:xml:ns:caldav\">\n"
+            "<D:prop>\n"
+            "<D:getetag/>\n"
+            "</D:prop>\n"
+            "<C:filter>\n"
+            "<C:comp-filter name=\"VCALENDAR\">\n"
+            "<C:comp-filter name=\"" + getContent() + "\">\n"
+            "<C:prop-filter name=\"UID\">\n"
+            // Collation from RFC 4791, not supported yet by all servers.
+            // Apple Calendar Server did not like CDATA.
+            // TODO: escape special characters.
+            "<C:text-match" /* collation=\"i;octet\" */ ">" /* <[CDATA[ */ + uid + /* ]]> */ "</C:text-match>\n"
+            "</C:prop-filter>\n"
+            "</C:comp-filter>\n"
+            "</C:comp-filter>\n"
+            "</C:filter>\n"
+            "</C:calendar-query>\n";
+    }
+    getSession()->startOperation("REPORT 'UID lookup'", deadline);
+    while (true) {
+        Neon::XMLParser parser;
+        parser.initReportParser(boost::bind(&WebDAVSource::checkItem, this,
+                                            boost::ref(revisions),
+                                            _1, _2, (std::string *)0));
+        Neon::Request report(*getSession(), "REPORT", getCalendar().m_path, query, parser);
+        report.addHeader("Depth", "1");
+        report.addHeader("Content-Type", "application/xml; charset=\"utf-8\"");
+        if (report.run()) {
+            break;
+        }
+    }
+
+    switch (revisions.size()) {
+    case 0:
+        SE_THROW_EXCEPTION_STATUS(TransportStatusException,
+                                  "object not found",
+                                  SyncMLStatus(404));
+        break;
+    case 1:
+        return revisions.begin()->first;
+        break;
+    default:
+        // should not happen
+        SE_THROW(StringPrintf("UID %s not unique?!", uid.c_str()));
+    }
+
+    // not reached
+    return "";
+}
+
 void WebDAVSource::listAllItemsCallback(const Neon::URI &uri,
                                         const ne_prop_result_set *results,
                                         RevisionMap_t &revisions,
@@ -1353,7 +1464,7 @@
 int WebDAVSource::checkItem(RevisionMap_t &revisions,
                             const std::string &href,
                             const std::string &etag,
-                            std::string &data)
+                            std::string *data)
 {
     // Ignore responses with no data: this is not perfect (should better
     // try to figure out why there is no data), but better than
@@ -1361,20 +1472,23 @@
     //
     // One situation is the response for the collection itself,
     // which comes with a 404 status and no data with Google Calendar.
-    if (data.empty()) {
+    if (data && data->empty()) {
         return 0;
     }
 
     // No need to parse, user content cannot start at start of line in
     // iCalendar 2.0.
-    if (data.find("\nBEGIN:" + getContent()) != data.npos) {
+    if (!data ||
+        data->find("\nBEGIN:" + getContent()) != data->npos) {
         std::string davLUID = path2luid(Neon::URI::parse(href).m_path);
         std::string rev = ETag2Rev(etag);
         revisions[davLUID] = rev;
     }
 
     // reset data for next item
-    data.clear();
+    if (data) {
+        data->clear();
+    }
     return 0;
 }
 
@@ -1469,7 +1583,8 @@
             req.addHeader("If-None-Match", "*");
         }
         req.addHeader("Content-Type", contentType().c_str());
-        if (!req.run()) {
+        static const std::set<int> expected = boost::assign::list_of(412);
+        if (!req.run(&expected)) {
             goto retry;
         }
         SE_LOG_DEBUG(NULL, NULL, "add item status: %s",
@@ -1482,6 +1597,16 @@
         case 201:
             // created
             break;
+        case 412: {
+            // "Precondition Failed": our only precondition is the one about
+            // If-None-Match, which means that there must be an existing item
+            // with the same UID. Go find it, so that we can report back the
+            // right luid.
+            std::string uid = extractUID(item);
+            std::string luid = findByUID(uid, deadline);
+            return InsertItemResult(luid, "", ITEM_NEEDS_MERGE);
+            break;
+        }
         default:
             SE_THROW_EXCEPTION_STATUS(TransportStatusException,
                                       std::string("unexpected status for insert: ") +
@@ -1665,19 +1790,9 @@
         // TODO: match exactly the expected revision, aka ETag,
         // or implement locking.
         // req.addHeader("If-Match", etag);
-        try {
-            if (req->run()) {
-                break;
-            }
-        } catch (const TransportStatusException &ex) {
-            if (ex.syncMLStatus() == 412) {
-                // Radicale reports 412 'Precondition Failed'. Hmm, okay.
-                // Let's map it to the expected 404.
-                SE_THROW_EXCEPTION_STATUS(TransportStatusException,
-	                                  "object not found (was 412 'Precondition Failed')",
-	                                  SyncMLStatus(404));
-            }
-            throw;
+        static const std::set<int> expected = boost::assign::list_of(412);
+        if (req->run(&expected)) {
+            break;
         }
     }
     SE_LOG_DEBUG(NULL, NULL, "remove item status: %s",
@@ -1689,6 +1804,13 @@
     case 200:
         // reported by Radicale, also okay
         break;
+    case 412:
+        // Radicale reports 412 'Precondition Failed'. Hmm, okay.
+        // Let's map it to the expected 404.
+        SE_THROW_EXCEPTION_STATUS(TransportStatusException,
+                                  "object not found (was 412 'Precondition Failed')",
+                                  SyncMLStatus(404));
+        break;
     default:
         SE_THROW_EXCEPTION_STATUS(TransportStatusException,
                                   std::string("unexpected status for removal: ") +
diff -Nru syncevolution-1.2.99.1/src/backends/webdav/WebDAVSource.h syncevolution-1.2.99.2/src/backends/webdav/WebDAVSource.h
--- syncevolution-1.2.99.1/src/backends/webdav/WebDAVSource.h	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/backends/webdav/WebDAVSource.h	2012-07-02 17:58:20.000000000 +0200
@@ -193,10 +193,23 @@
     virtual const std::string *setResourceName(const std::string &item, std::string &buffer, const std::string &luid);
 
     /**
+     * Find one item by its UID property value and return the corresponding
+     * resource name relative to the current collection (aka luid).
+     */
+    std::string findByUID(const std::string &uid, const Timespec &deadline);
+
+    /**
      * Get UID property value from vCard 3.0 or iCalendar 2.0 text
      * items.
+     * @retval startp   offset of first character of UID value (i.e., directly after colon),
+     *                  npos if no UID was found
+     * @retval endp     offset of first line break character (\r or \n) after UID value,
+     *                  npos if no UID was found
+     * @return UID value without line breaks and folding characters removed
      */
-    static std::string extractUID(const std::string &item);
+    static std::string extractUID(const std::string &item,
+                                  size_t *startp = NULL,
+                                  size_t *endp = NULL);
 
     /**
      * .vcf for VCARD and .ics for everything else.
@@ -235,7 +248,7 @@
     int checkItem(RevisionMap_t &revisions,
                   const std::string &href,
                   const std::string &etag,
-                  std::string &data);
+                  std::string *data);
 
     void backupData(const boost::function<Operations::BackupData_t> &op,
                     const Operations::ConstBackupInfo &oldBackup,
diff -Nru syncevolution-1.2.99.1/src/dbus/interfaces/syncevo-connection-full.xml syncevolution-1.2.99.2/src/dbus/interfaces/syncevo-connection-full.xml
--- syncevolution-1.2.99.1/src/dbus/interfaces/syncevo-connection-full.xml	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/dbus/interfaces/syncevo-connection-full.xml	2012-07-02 17:58:20.000000000 +0200
@@ -102,7 +102,7 @@
             "URL" - the URL for an HTTP POST.
           </doc:summary>
         </doc:doc>
-        <annotation name="com.trolltech.QtDBus.QtTypeName.In2" value="QStringMap"/>
+        <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QStringMap"/>
       </arg>      
 
       <arg type="b" name="final">
diff -Nru syncevolution-1.2.99.1/src/dbus/interfaces/syncevo-server-full.xml syncevolution-1.2.99.2/src/dbus/interfaces/syncevo-server-full.xml
--- syncevolution-1.2.99.1/src/dbus/interfaces/syncevo-server-full.xml	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/dbus/interfaces/syncevo-server-full.xml	2012-07-02 17:58:20.000000000 +0200
@@ -129,7 +129,7 @@
             "system" - some plain text information about system libraries,
             "backends" - available backend libraries
         </doc:summary></doc:doc>
-        <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QStringMap"/>
+        <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QStringMap"/>
       </arg>
     </method>  
 
@@ -234,7 +234,7 @@
         and thus can be used to find all templates refering to the same device.
         </doc:para>
       </doc:description></doc:doc>
-      <arg type="b" name="template" direction="in">
+      <arg type="b" name="templateName" direction="in">
         <doc:doc><doc:summary>
           if TRUE, will return template names, otherwise will return 
           configured servers
@@ -331,7 +331,7 @@
       <arg type="s" name="server" direction="in">
         <doc:doc><doc:summary>server name</doc:summary></doc:doc>
       </arg>
-      <arg type="b" name="template" direction="in">
+      <arg type="b" name="templateName" direction="in">
         <doc:doc><doc:summary>
           if TRUE, will return a matching template configuration, otherwise
           will return a matching server configuration
@@ -342,7 +342,7 @@
           server (or template) configuration
         </doc:summary></doc:doc>
       </arg>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QStringMultiMap"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QStringMultiMap"/>
     </method>
 
     <method name="CheckPresence">
@@ -411,7 +411,7 @@
                 its pair-value won't be included in the dictionary.
         </doc:description></doc:doc>
       </arg>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QArrayOfStringMap"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QArrayOfStringMap"/>
     </method>
 
     <method name="GetDatabases">
@@ -444,7 +444,7 @@
           </doc:description>
         </doc:doc>
       </arg>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QArrayOfDatabases"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QArrayOfDatabases"/>
     </method>
 
     <method name="CheckSource">
@@ -585,7 +585,7 @@
             version of the transport entity.
           </doc:summary>
         </doc:doc>
-        <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QStringMap"/>
+        <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QStringMap"/>
       </arg>
       <arg type="b" name="must_authenticate" direction="in">
         <doc:doc>
@@ -824,7 +824,7 @@
           </doc:summary>
         </doc:doc>
       </arg>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.In5" value="QStringMap"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.In5" value="QStringMap"/>
     </signal>
 
     <method name="InfoResponse">
@@ -853,7 +853,7 @@
           </doc:summary>
         </doc:doc>
       </arg>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.In2" value="QStringMap"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QStringMap"/>
     </method>
 
     <signal name="LogOutput">
@@ -872,6 +872,11 @@
       <arg type="s" name="output">
         <doc:doc><doc:summary>the output string to be broadcast</doc:summary></doc:doc>
       </arg>
+      <arg type="s" name="procname">
+        <doc:doc><doc:summary>A short tag identifying which process
+        produced the output. Should be shown to the user. Empty for
+        main process in a sync.</doc:summary></doc:doc>
+      </arg>
     </signal>
 
   </interface>
diff -Nru syncevolution-1.2.99.1/src/dbus/interfaces/syncevo-session-full.xml syncevolution-1.2.99.2/src/dbus/interfaces/syncevo-session-full.xml
--- syncevolution-1.2.99.1/src/dbus/interfaces/syncevo-session-full.xml	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/dbus/interfaces/syncevo-session-full.xml	2012-07-02 17:58:20.000000000 +0200
@@ -49,14 +49,14 @@
 
     <method name="GetConfig">
       <doc:doc><doc:description>Get the configuration identified by the name given to StartSession()</doc:description></doc:doc>
-      <arg type="b" name="template" direction="in">
+      <arg type="b" name="templateName" direction="in">
         <doc:doc><doc:summary>if TRUE, will return a matching template configuration, otherwise will return the stored configuration</doc:summary></doc:doc>
       </arg>
       <arg type="a{sa{ss}}" name="configuration" direction="out">
         <doc:doc><doc:summary>server configuration</doc:summary></doc:doc>
         <doc:doc><doc:description>See Server.GetConfig() for dictionary description.</doc:description></doc:doc>
       </arg>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QStringMultiMap"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QStringMultiMap"/>
     </method>
 
     <method name="SetConfig">
@@ -104,7 +104,7 @@
         <doc:doc><doc:summary>server configuration</doc:summary></doc:doc>
         <doc:doc><doc:description>See Server.GetConfig() for dictionary description.</doc:description></doc:doc>
       </arg>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.In2" value="QStringMultiMap"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QStringMultiMap"/>
     </method>
 
     <method name="GetNamedConfig">
@@ -115,9 +115,8 @@
       <arg type="s" name="name" direction="in">
         <doc:doc><doc:summary>configuration name</doc:summary></doc:doc>
       </arg>
-      <arg type="b" name="template" direction="in"/>
       <arg type="a{sa{ss}}" name="configuration" direction="out"/>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QStringMultiMap"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QStringMultiMap"/>
     </method>
 
     <method name="SetNamedConfig">
@@ -130,7 +129,7 @@
         <doc:doc><doc:summary>temporary changes of the configuration are currently only supported for the configuration chosen when creating the session</doc:summary></doc:doc>
       </arg>
       <arg type="a{sa{ss}}" name="configuration" direction="in"/>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.In2" value="QStringMultiMap"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.In3" value="QStringMultiMap"/>
     </method>
 
     <method name="GetReports">
@@ -145,7 +144,7 @@
         <doc:doc><doc:summary>synchronization reports</doc:summary></doc:doc>
         <doc:doc><doc:description>See Server.GetReports() for array description.</doc:description></doc:doc>
       </arg>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QArrayOfStringMap"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QArrayOfStringMap"/>
     </method>
 
     
@@ -177,7 +176,7 @@
           </doc:description>
         </doc:doc>
       </arg>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QArrayOfDatabases"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QArrayOfDatabases"/>
     </method>
 
     <method name="CheckSource">
@@ -231,7 +230,7 @@
         <doc:doc><doc:summary>synchronization source modes</doc:summary></doc:doc>
         <doc:doc><doc:description>Source modes to override the 'mode' variable for specific sources. The dictionary key is source name, value is synchronization mode. Valid synchronization modes are all synchronization modes used in syncevolution server configuration files and the empty string.</doc:description></doc:doc>
       </arg>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="QStringMap"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QStringMap"/>
     </method>
 
     <method name="Abort">
@@ -318,7 +317,7 @@
         <doc:doc><doc:summary>Synchronization source status dictionary</doc:summary></doc:doc>
         <doc:doc><doc:description>Dictionary key is source name. The value structs contain synchronization mode, source status and error code. Valid values for status are the same as for status parameter above. "done" represents a synced source when the whole sync is not done yet.</doc:description></doc:doc>
       </arg>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.Out2" value="QSyncStatusMap"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out2" value="QSyncStatusMap"/>
     </method>
 
     <method name="GetProgress">
@@ -330,7 +329,7 @@
         <doc:doc><doc:summary>Synchronization source progress dictionary</doc:summary></doc:doc>
         <doc:doc><doc:description>Dictionary key is source name. The value structs contain phase (can be one of "", "preparing", "sending", "receiving"), prepare count, prepare total, send count, send total, receive count and receive total. -1 is used for unknown. Normally only the 'counts' increase but there are cases where the total will increase as well.</doc:description></doc:doc>
       </arg>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.Out1" value="QSyncProgressMap"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QSyncProgressMap"/>
     </method>
 
     <method name="Execute">
@@ -345,7 +344,7 @@
       </arg>
       <arg type="a{ss}" name="vars" direction="in">
         <doc:doc><doc:summary>Environment variables in clients</doc:summary></doc:doc>
-        <annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="QStringMap"/>
+        <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QStringMap"/>
       </arg>
     </method>
 
@@ -354,14 +353,14 @@
       <arg type="s" name="status"/>
       <arg type="u" name="error"/>
       <arg type="a{s(ssu)}" name="sources"/>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.In2" value="QSyncStatusMap"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QSyncStatusMap"/>
     </signal> 
 
     <signal name="ProgressChanged">
       <doc:doc><doc:description>Synchronization progress change. See GetProgress() for argument descriptions.</doc:description></doc:doc>
       <arg type="i" name="progress"/>
       <arg type="a{s(siiiiii)}" name="sources"/>
-      <annotation name="com.trolltech.QtDBus.QtTypeName.In1" value="QSyncProgressMap"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QSyncProgressMap"/>
     </signal>
 
   </interface>
diff -Nru syncevolution-1.2.99.1/src/dbus/server/server.cpp syncevolution-1.2.99.2/src/dbus/server/server.cpp
--- syncevolution-1.2.99.1/src/dbus/server/server.cpp	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/dbus/server/server.cpp	2012-07-02 17:58:20.000000000 +0200
@@ -874,7 +874,7 @@
     // 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);
-    logOutput(getPath(), strLevel, log);
+    logOutput(getPath(), strLevel, log, getProcessName());
 }
 
 SE_END_CXX
diff -Nru syncevolution-1.2.99.1/src/dbus/server/server.h syncevolution-1.2.99.2/src/dbus/server/server.h
--- syncevolution-1.2.99.1/src/dbus/server/server.h	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/dbus/server/server.h	2012-07-02 17:58:20.000000000 +0200
@@ -328,8 +328,9 @@
                           const std::map<string, string> &> infoRequest;
 
     /** Server.LogOutput */
-    GDBusCXX::EmitSignal3<const GDBusCXX::DBusObject_t &,
-                          string,
+    GDBusCXX::EmitSignal4<const GDBusCXX::DBusObject_t &,
+                          const std::string &,
+                          const std::string &,
                           const std::string &> logOutput;
 
  private:
diff -Nru syncevolution-1.2.99.1/src/dbus/server/session.cpp syncevolution-1.2.99.2/src/dbus/server/session.cpp
--- syncevolution-1.2.99.1/src/dbus/server/session.cpp	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/dbus/server/session.cpp	2012-07-02 17:58:20.000000000 +0200
@@ -95,7 +95,7 @@
     /* GDBusCXX::DBusClientCall0                                    m_setActive; */
     /* GDBusCXX::SignalWatch3<std::string, uint32_t, */
     /*                        SessionCommon::SourceStatuses_t>      m_statusChanged; */
-    GDBusCXX::SignalWatch2<std::string, std::string> m_logOutput;
+    GDBusCXX::SignalWatch3<std::string, std::string, std::string> m_logOutput;
     GDBusCXX::SignalWatch4<sysync::TProgressEventEnum,
                            int32_t, int32_t, int32_t> m_syncProgress;
     GDBusCXX::SignalWatch6<sysync::TProgressEventEnum,
@@ -202,6 +202,7 @@
                              bool update, bool temporary,
                              const ReadOperations::Config_t &config)
 {
+    Session::LoggingGuard guard(this);
     if (m_runOperation != SessionCommon::OP_NULL) {
         string msg = StringPrintf("%s started, cannot change configuration at this time", runOpToString(m_runOperation).c_str());
         SE_THROW_EXCEPTION(InvalidCall, msg);
@@ -320,6 +321,7 @@
 
 void Session::initServer(SharedBuffer data, const std::string &messageType)
 {
+    Session::LoggingGuard guard(this);
     m_serverMode = true;
     m_initialMessage = data;
     m_initialMessageType = messageType;
@@ -327,6 +329,7 @@
 
 void Session::sync(const std::string &mode, const SessionCommon::SourceModes_t &sourceModes)
 {
+    Session::LoggingGuard guard(this);
     if (m_runOperation == SessionCommon::OP_SYNC) {
         string msg = StringPrintf("%s started, cannot start again", runOpToString(m_runOperation).c_str());
         SE_THROW_EXCEPTION(InvalidCall, msg);
@@ -347,6 +350,7 @@
 
 void Session::sync2(const std::string &mode, const SessionCommon::SourceModes_t &sourceModes)
 {
+    Session::LoggingGuard guard(this);
     if (!m_forkExecParent || !m_helper) {
         SE_THROW("syncing cannot continue, helper died");
     }
@@ -409,6 +413,7 @@
 
 void Session::abort()
 {
+    Session::LoggingGuard guard(this);
     if (m_runOperation != SessionCommon::OP_SYNC && m_runOperation != SessionCommon::OP_CMDLINE) {
         SE_THROW_EXCEPTION(InvalidCall, "sync not started, cannot abort at this time");
     }
@@ -429,6 +434,7 @@
 
 void Session::suspend()
 {
+    Session::LoggingGuard guard(this);
     if (m_runOperation != SessionCommon::OP_SYNC && m_runOperation != SessionCommon::OP_CMDLINE) {
         SE_THROW_EXCEPTION(InvalidCall, "sync not started, cannot suspend at this time");
     }
@@ -445,6 +451,7 @@
 
 void Session::abortAsync(const SimpleResult &result)
 {
+    Session::LoggingGuard guard(this);
     if (!m_forkExecParent) {
         result.done();
     } else {
@@ -461,6 +468,7 @@
                         uint32_t &error,
                         SourceStatuses_t &sources)
 {
+    Session::LoggingGuard guard(this);
     status = syncStatusToString(m_syncStatus);
     if (m_stepIsWaiting) {
         status += ";waiting";
@@ -473,12 +481,14 @@
 void Session::getProgress(int32_t &progress,
                           SourceProgresses_t &sources)
 {
+    Session::LoggingGuard guard(this);
     progress = m_progress;
     sources = m_sourceProgress;
 }
 
 void Session::fireStatus(bool flush)
 {
+    Session::LoggingGuard guard(this);
     std::string status;
     uint32_t error;
     SourceStatuses_t sources;
@@ -495,6 +505,7 @@
 
 void Session::fireProgress(bool flush)
 {
+    Session::LoggingGuard guard(this);
     int32_t progress;
     SourceProgresses_t sources;
 
@@ -584,11 +595,13 @@
 
 void Session::passwordRequest(const std::string &descr, const ConfigPasswordKey &key)
 {
+    Session::LoggingGuard guard(this);
     m_passwordRequest = m_server.passwordRequest(descr, key, m_me);
 }
 
 void Session::dbusResultCb(const std::string &operation, bool success, const std::string &error) throw()
 {
+    Session::LoggingGuard guard(this);
     try {
         SE_LOG_DEBUG(NULL, NULL, "%s helper call completed, %s",
                      operation.c_str(),
@@ -612,6 +625,7 @@
 
 void Session::failureCb() throw()
 {
+    Session::LoggingGuard guard(this);
     try {
         if (m_status == SESSION_DONE) {
             // ignore errors that happen after session already closed,
@@ -620,7 +634,8 @@
             Exception::handle(explanation, HANDLE_EXCEPTION_NO_ERROR);
             m_server.logOutput(getPath(),
                                Logger::levelToStr(Logger::ERROR),
-                               explanation);
+                               explanation,
+                               "");
         } else {
             // finish session with failure
             uint32_t error;
@@ -632,7 +647,8 @@
                 error = Exception::handle(explanation, HANDLE_EXCEPTION_NO_ERROR);
                 m_server.logOutput(getPath(),
                                    Logger::levelToStr(Logger::ERROR),
-                                   explanation);
+                                   explanation,
+                                   "");
             }
             // set error, but don't overwrite older one
             if (!m_error) {
@@ -650,6 +666,7 @@
 
 void Session::doneCb(bool success) throw()
 {
+    Session::LoggingGuard guard(this);
     try {
         if (m_status == SESSION_DONE) {
             return;
@@ -712,6 +729,7 @@
 void Session::runOperationAsync(SessionCommon::RunOperation op,
                                 const SuccessCb_t &helperReady)
 {
+    Session::LoggingGuard guard(this);
     m_server.addSyncSession(this);
     m_runOperation = op;
     m_status = SESSION_RUNNING;
@@ -724,6 +742,7 @@
 
 void Session::useHelperAsync(const SimpleResult &result)
 {
+    Session::LoggingGuard guard(this);
     try {
         if (m_helper) {
             // exists already, invoke callback directly
@@ -784,8 +803,24 @@
     }
 }
 
+void Session::messagev(Level level,
+                       const char *prefix,
+                       const char *file,
+                       int line,
+                       const char *function,
+                       const char *format,
+                       va_list args)
+{
+    // log with session path and empty process name,
+    // just like the syncevo-dbus-helper does
+    string strLevel = Logger::levelToStr(level);
+    string log = StringPrintfV(format, args);
+    m_server.logOutput(getPath(), strLevel, log, "");
+}
+
 void Session::useHelper2(const SimpleResult &result, const boost::signals2::connection &c)
 {
+    Session::LoggingGuard guard(this);
     try {
         // helper is running, don't call result.failed() when it quits
         // sometime in the future
@@ -798,15 +833,21 @@
             // LogOutput signal, with the session's object path as
             // first parameter.
             //
-            // TODO: is there any output in syncevo-dbus-server which
-            // should be treated as output of the session? It would have
-            // to be sent via the LogOutput signal with the session's
-            // object path, instead of the server's. The log level check
-            // also might have to be done differently.
+            // Any code in syncevo-dbus-server which might produce
+            // output related to the session runs while a Session::LoggingGuard
+            // captures output by pushing Session as logger onto the
+            // logging stack. The Session::messagev implementation then
+            // also calls m_server.logOutput, as if the syncevo-dbus-helper
+            // had produced that output.
+            //
+            // The downside is that unrelated output (like
+            // book-keeping messages about other clients) will also be
+            // captured.
             m_helper->m_logOutput.activate(boost::bind(boost::ref(m_server.logOutput),
                                                        getPath(),
                                                        _1,
-                                                       _2));
+                                                       _2,
+                                                       _3));
 
             result.done();
         } else {
@@ -821,6 +862,7 @@
 
 void Session::onConnect(const GDBusCXX::DBusConnectionPtr &conn) throw ()
 {
+    Session::LoggingGuard guard(this);
     try {
         SE_LOG_DEBUG(NULL, NULL, "helper has connected");
         m_helper.reset(new SessionProxy(conn));
@@ -839,6 +881,7 @@
 
 void Session::onQuit(int status) throw ()
 {
+    Session::LoggingGuard guard(this);
     try {
         SE_LOG_DEBUG(NULL, NULL, "helper quit with return code %d, was %s",
                      status,
@@ -872,6 +915,7 @@
 
 void Session::onFailure(SyncMLStatus status, const std::string &explanation) throw ()
 {
+    Session::LoggingGuard guard(this);
     try {
         SE_LOG_DEBUG(NULL, NULL, "helper failed, status code %d = %s, %s",
                      status,
@@ -884,6 +928,7 @@
 
 void Session::onOutput(const char *buffer, size_t length)
 {
+    Session::LoggingGuard guard(this);
     // treat null-bytes inside the buffer like line breaks
     size_t off = 0;
     do {
@@ -894,6 +939,7 @@
 
 void Session::activateSession()
 {
+    Session::LoggingGuard guard(this);
     if (m_status != SESSION_IDLE) {
         SE_THROW("internal error, session changing from non-idle to active");
     }
@@ -914,6 +960,7 @@
 
 void Session::passwordResponse(bool timedOut, bool aborted, const std::string &password)
 {
+    Session::LoggingGuard guard(this);
     if (m_helper) {
         // Ignore communicaton failures with helper here,
         // we'll notice that elsewhere
@@ -926,6 +973,7 @@
 void Session::syncProgress(sysync::TProgressEventEnum type,
                            int32_t extra1, int32_t extra2, int32_t extra3)
 {
+    Session::LoggingGuard guard(this);
     switch(type) {
     case sysync::PEV_CUSTOM_START:
         m_cmdlineOp = (RunOperation)extra1;
@@ -978,6 +1026,7 @@
                              SyncMode sourceSyncMode,
                              int32_t extra1, int32_t extra2, int32_t extra3)
 {
+    Session::LoggingGuard guard(this);
     // a command line operation can be many things, helper must have told us
     SessionCommon::RunOperation op = m_runOperation == SessionCommon::OP_CMDLINE ?
         m_cmdlineOp :
@@ -1095,6 +1144,7 @@
 
 bool Session::setFilters(SyncConfig &config)
 {
+    Session::LoggingGuard guard(this);
     /** apply temporary configs to config */
     config.setConfigFilter(true, "", m_syncFilter);
     // set all sources in the filter to config
@@ -1106,6 +1156,7 @@
 
 void Session::setWaiting(bool isWaiting)
 {
+    Session::LoggingGuard guard(this);
     // if stepInfo doesn't change, then ignore it to avoid duplicate status info
     if(m_stepIsWaiting != isWaiting) {
         m_stepIsWaiting = isWaiting;
@@ -1115,6 +1166,7 @@
 
 void Session::restore(const string &dir, bool before, const std::vector<std::string> &sources)
 {
+    Session::LoggingGuard guard(this);
     if (m_runOperation == SessionCommon::OP_RESTORE) {
         string msg = StringPrintf("restore started, cannot restore again");
         SE_THROW_EXCEPTION(InvalidCall, msg);
@@ -1134,6 +1186,7 @@
 
 void Session::restore2(const string &dir, bool before, const std::vector<std::string> &sources)
 {
+    Session::LoggingGuard guard(this);
     if (!m_forkExecParent || !m_helper) {
         SE_THROW("syncing cannot continue, helper died");
     }
@@ -1145,6 +1198,7 @@
 
 void Session::execute(const vector<string> &args, const map<string, string> &vars)
 {
+    Session::LoggingGuard guard(this);
     if (m_runOperation == SessionCommon::OP_CMDLINE) {
         SE_THROW_EXCEPTION(InvalidCall, "cmdline started, cannot start again");
     } else if (m_runOperation != SessionCommon::OP_NULL) {
@@ -1163,6 +1217,7 @@
 
 void Session::execute2(const vector<string> &args, const map<string, string> &vars)
 {
+    Session::LoggingGuard guard(this);
     if (!m_forkExecParent || !m_helper) {
         SE_THROW("syncing cannot continue, helper died");
     }
@@ -1175,6 +1230,7 @@
 /*Implementation of Session.CheckPresence */
 void Session::checkPresence (string &status)
 {
+    Session::LoggingGuard guard(this);
     vector<string> transport;
     m_server.checkPresence(m_configName, status, transport);
 }
@@ -1183,6 +1239,7 @@
                                 const std::string &type,
                                 const std::string &url)
 {
+    Session::LoggingGuard guard(this);
     try {
         boost::shared_ptr<Connection> connection = m_connection.lock();
 
@@ -1201,6 +1258,7 @@
 
 void Session::shutdownConnection()
 {
+    Session::LoggingGuard guard(this);
     try {
         boost::shared_ptr<Connection> connection = m_connection.lock();
 
@@ -1220,6 +1278,7 @@
 void Session::storeMessage(const DBusArray<uint8_t> &message,
                            const std::string &type)
 {
+    Session::LoggingGuard guard(this);
     // ignore errors
     if (m_helper) {
         m_helper->m_storeMessage.start(message, type,
@@ -1229,6 +1288,7 @@
 
 void Session::connectionState(const std::string &error)
 {
+    Session::LoggingGuard guard(this);
     // ignore errors
     if (m_helper) {
         m_helper->m_connectionState.start(error,
diff -Nru syncevolution-1.2.99.1/src/dbus/server/session.h syncevolution-1.2.99.2/src/dbus/server/session.h
--- syncevolution-1.2.99.1/src/dbus/server/session.h	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/dbus/server/session.h	2012-07-02 17:58:20.000000000 +0200
@@ -62,6 +62,7 @@
  * for simple sessions).
  */
 class Session : public GDBusCXX::DBusObjectHelper,
+                public LoggerBase,
                 public Resource,
                 private ReadOperations,
                 private boost::noncopyable
@@ -340,6 +341,30 @@
                       const std::string &type);
     void connectionState(const std::string &error);
 
+    /**
+     * Sends all messages via D-Bus, as if they came from the session
+     * helper. To be activated only temporarily while executing code
+     * in the server which is related to the session.
+     */
+    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; }
+
+    class LoggingGuard {
+    public:
+        LoggingGuard(Session *session) {
+            LoggerBase::pushLogger(session);
+        }
+        ~LoggingGuard() {
+            LoggerBase::popLogger();
+        }
+    };
+
 public:
     enum {
         PRI_CMDLINE = -10,
diff -Nru syncevolution-1.2.99.1/src/dbus/server/session-helper.cpp syncevolution-1.2.99.2/src/dbus/server/session-helper.cpp
--- syncevolution-1.2.99.1/src/dbus/server/session-helper.cpp	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/dbus/server/session-helper.cpp	2012-07-02 17:58:20.000000000 +0200
@@ -115,7 +115,7 @@
     // send to parent
     string log = StringPrintfV(format, args);
     string strLevel = Logger::levelToStr(level);
-    emitLogOutput(strLevel, log);
+    emitLogOutput(strLevel, log, getProcessName());
 }
 
 void SessionHelper::run()
diff -Nru syncevolution-1.2.99.1/src/dbus/server/session-helper.h syncevolution-1.2.99.2/src/dbus/server/session-helper.h
--- syncevolution-1.2.99.1/src/dbus/server/session-helper.h	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/dbus/server/session-helper.h	2012-07-02 17:58:20.000000000 +0200
@@ -107,8 +107,8 @@
     boost::shared_ptr<ForkExecChild> getForkExecChild() { return m_forkexec; }
 
     /** Server.LogOutput for the session D-Bus object */
-    GDBusCXX::EmitSignal2<std::string,
-        std::string, true> emitLogOutput;
+    GDBusCXX::EmitSignal3<std::string,
+        std::string, std::string, true> emitLogOutput;
 
     /** SyncContext::displaySyncProgress */
     GDBusCXX::EmitSignal4<sysync::TProgressEventEnum,
diff -Nru syncevolution-1.2.99.1/src/gdbusxx/gdbus-cxx-bridge.h syncevolution-1.2.99.2/src/gdbusxx/gdbus-cxx-bridge.h
--- syncevolution-1.2.99.1/src/gdbusxx/gdbus-cxx-bridge.h	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/gdbusxx/gdbus-cxx-bridge.h	2012-07-02 17:58:20.000000000 +0200
@@ -1211,8 +1211,8 @@
 struct VariantTypeDouble  { static const GVariantType* getVariantType() { return G_VARIANT_TYPE_DOUBLE;  } };
 
 #define GDBUS_CXX_QUOTE(x) #x
-#define GDBUS_CXX_LINE GDBUS_CXX_QUOTE(__LINE__)
-#define GDBUS_CXX_SOURCE_INFO __FILE__ ":" GDBUS_CXX_LINE
+#define GDBUS_CXX_LINE(l) GDBUS_CXX_QUOTE(l)
+#define GDBUS_CXX_SOURCE_INFO __FILE__ ":" GDBUS_CXX_LINE(__LINE__)
 
 template<class host, class VariantTraits> struct basic_marshal : public dbus_traits_base
 {
diff -Nru syncevolution-1.2.99.1/src/syncevo/Cmdline.cpp syncevolution-1.2.99.2/src/syncevo/Cmdline.cpp
--- syncevolution-1.2.99.1/src/syncevo/Cmdline.cpp	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/syncevo/Cmdline.cpp	2012-07-02 17:58:20.000000000 +0200
@@ -646,6 +646,30 @@
     }
 };
 
+void Cmdline::checkSyncPasswords(SyncContext &context)
+{
+    ConfigPropertyRegistry& registry = SyncConfig::getRegistry();
+    BOOST_FOREACH(const ConfigProperty *prop, registry) {
+        prop->checkPassword(context.getUserInterfaceNonNull(),
+                            context.getConfigName(),
+                            *context.getProperties());
+    }
+}
+
+void Cmdline::checkSourcePasswords(SyncContext &context,
+                                   const std::string &sourceName,
+                                   SyncSourceNodes &nodes)
+{
+    ConfigPropertyRegistry &registry = SyncSourceConfig::getRegistry();
+    BOOST_FOREACH(const ConfigProperty *prop, registry) {
+        prop->checkPassword(context.getUserInterfaceNonNull(),
+                            context.getConfigName(),
+                            *context.getProperties(),
+                            sourceName,
+                            nodes.getProperties());
+    }
+}
+
 bool Cmdline::run() {
     // --dry-run is only supported by some operations.
     // Be very strict about it and make sure it is off in all
@@ -686,8 +710,9 @@
         boost::shared_ptr<SyncSourceNodes> nodes;
         std::string header;
         boost::shared_ptr<SyncContext> context;
-        FilterConfigNode::ConfigFilter sourceFilter = m_props.createSourceFilter(m_server, "");
-        FilterConfigNode::ConfigFilter::const_iterator backend = sourceFilter.find("backend");
+        FilterConfigNode::ConfigFilter sourceFilter;
+        std::string sourceName;
+        FilterConfigNode::ConfigFilter::const_iterator backend;
 
         if (!m_server.empty()) {
             // list for specific backend chosen via config
@@ -695,18 +720,23 @@
                 SE_THROW(StringPrintf("must specify exactly one source after the config name '%s'",
                                       m_server.c_str()));
             }
-            context.reset(new SyncContext(m_server));
+            sourceName = *m_sources.begin();
+            sourceFilter = m_props.createSourceFilter(m_server, sourceName);
+            backend = sourceFilter.find("backend");
+            context.reset(createSyncClient());
             if (!context->exists()) {
                 SE_THROW(StringPrintf("config '%s' does not exist", m_server.c_str()));
             }
-            nodes.reset(new SyncSourceNodes(context->getSyncSourceNodesNoTracking(*m_sources.begin())));
-            header = StringPrintf("%s/%s", m_server.c_str(), m_sources.begin()->c_str());
+            nodes.reset(new SyncSourceNodes(context->getSyncSourceNodesNoTracking(sourceName)));
+            header = StringPrintf("%s/%s", m_server.c_str(), sourceName.c_str());
             if (!nodes->dataConfigExists()) {
                 SE_THROW(StringPrintf("%s does not exist",
                                       header.c_str()));
             }
         } else {
-            context.reset(new SyncContext);
+            sourceFilter = m_props.createSourceFilter(m_server, "");
+            backend = sourceFilter.find("backend");
+            context.reset(createSyncClient());
             boost::shared_ptr<FilterConfigNode> sharedNode(new VolatileConfigNode());
             boost::shared_ptr<FilterConfigNode> configNode(new VolatileConfigNode());
             boost::shared_ptr<FilterConfigNode> hiddenNode(new VolatileConfigNode());
@@ -726,6 +756,11 @@
             // list for specific backend
             auto_ptr<SyncSource> source(SyncSource::createSource(params, false, NULL));
             if (source.get() != NULL) {
+                if (!m_server.empty() && nodes) {
+                    // ensure that we have passwords for this config
+                    checkSyncPasswords(*context);
+                    checkSourcePasswords(*context, sourceName, *nodes);
+                }
                 listSources(*source, header);
                 SE_LOG_SHOW(NULL, NULL, "\n");
             } else {
@@ -1278,22 +1313,8 @@
         sysync::TSyError err;
 #define CHECK_ERROR(_op) if (err) { SE_THROW_EXCEPTION_STATUS(StatusException, string(source->getName()) + ": " + (_op), SyncMLStatus(err)); }
 
-        // acquire passwords before doing anything (interactive password
-        // access not supported for the command line)
-        {
-            ConfigPropertyRegistry& registry = SyncConfig::getRegistry();
-            BOOST_FOREACH(const ConfigProperty *prop, registry) {
-                prop->checkPassword(context->getUserInterfaceNonNull(), m_server, *context->getProperties());
-            }
-        }
-        {
-            ConfigPropertyRegistry &registry = SyncSourceConfig::getRegistry();
-            BOOST_FOREACH(const ConfigProperty *prop, registry) {
-                prop->checkPassword(context->getUserInterfaceNonNull(), m_server, *context->getProperties(),
-                                    source->getName(), sourceNodes.getProperties());
-            }
-        }
-
+        checkSyncPasswords(*context);
+        checkSourcePasswords(*context, source->getName(), sourceNodes);
         source->open();
         const SyncSource::Operations &ops = source->getOperations();
         if (m_printItems) {
diff -Nru syncevolution-1.2.99.1/src/syncevo/Cmdline.h syncevolution-1.2.99.2/src/syncevo/Cmdline.h
--- syncevolution-1.2.99.1/src/syncevo/Cmdline.h	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/syncevo/Cmdline.h	2012-07-02 17:58:20.000000000 +0200
@@ -327,6 +327,11 @@
      * @return encoded luid of inserted item
      */
     CmdlineLUID insertItem(SyncSourceRaw *source, const std::string &luid, const std::string &data);
+
+    static void checkSyncPasswords(SyncContext &context);
+    static void checkSourcePasswords(SyncContext &context,
+                                     const std::string &sourceName,
+                                     SyncSourceNodes &nodes);
 };
 
 
diff -Nru syncevolution-1.2.99.1/src/syncevo/configs/remoterules/client/03funambol.xml syncevolution-1.2.99.2/src/syncevo/configs/remoterules/client/03funambol.xml
--- syncevolution-1.2.99.1/src/syncevo/configs/remoterules/client/03funambol.xml	1970-01-01 01:00:00.000000000 +0100
+++ syncevolution-1.2.99.2/src/syncevo/configs/remoterules/client/03funambol.xml	2012-07-02 17:58:20.000000000 +0200
@@ -0,0 +1,7 @@
+      <remoterule name="Funambol">
+          <manufacturer>Funambol</manufacturer>
+          <model>DS Server*</model>
+          <rulescript><![CDATA[
+             stripUID = TRUE;
+          ]]></rulescript>
+      </remoterule>
diff -Nru syncevolution-1.2.99.1/src/syncevo/configs/scripting/11calendar.xml syncevolution-1.2.99.2/src/syncevo/configs/scripting/11calendar.xml
--- syncevolution-1.2.99.1/src/syncevo/configs/scripting/11calendar.xml	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/syncevo/configs/scripting/11calendar.xml	2012-07-02 17:58:20.000000000 +0200
@@ -4,6 +4,13 @@
       STRING MATCHES[];
       STRING CAT,CN,EM;
       INTEGER i;
+      // Remove UID/RECURRENCE-ID from servers which do not support
+      // the semantic properly (for example, Funambol supports
+      // UID but drops RECURRENCE-ID, causing conflicts).
+      if (SESSIONVAR("stripUID")) {
+          UID = UNASSIGNED;
+          ORIGSTART = UNASSIGNED;
+      }
       // make sure we have all trailing and leading spaces eliminated
       DESCRIPTION=NORMALIZED(DESCRIPTION);
       SUMMARY=NORMALIZED(SUMMARY);
@@ -255,3 +262,13 @@
         DESCRIPTION = SUBSTR(DESCRIPTION, lensummary + 1, lendescr - lensummary - 1);
     }
     ]]></macro>
+
+    <macro name="CALENDAR_BEFOREWRITE_SCRIPT"><![CDATA[
+      // set UTC time of generation for iCalendar 2.0 DTSTAMP
+      DGENERATED = NOW();
+      // ensure that there is a DMODIFIED = LAST-MODIFIED property,
+      // not all storages add it
+      if (!DMODIFIED) {
+         DMODIFIED = DGENERATED;
+      }
+    ]]></macro>
diff -Nru syncevolution-1.2.99.1/src/syncevo/ForkExec.cpp syncevolution-1.2.99.2/src/syncevo/ForkExec.cpp
--- syncevolution-1.2.99.1/src/syncevo/ForkExec.cpp	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/syncevo/ForkExec.cpp	2012-07-02 17:58:20.000000000 +0200
@@ -18,6 +18,7 @@
  */
 
 #include "ForkExec.h"
+#include <syncevo/LogRedirect.h>
 
 #if defined(HAVE_GLIB)
 
@@ -118,9 +119,28 @@
  * Child setup function, called insided forked process before exec().
  * only async-signal-safe functions allowed according to http://developer.gnome.org/glib/2.30/glib-Spawning-Processes.html#GSpawnChildSetupFunc
  */
-static void setStdoutToStderr(gpointer /* user_data */) throw()
+void ForkExecParent::forked(gpointer data) throw()
 {
-    dup2(STDERR_FILENO, STDOUT_FILENO);
+    ForkExecParent *me = static_cast<ForkExecParent *>(data);
+
+    // When debugging, undo the LogRedirect output redirection that
+    // we inherited from the parent process. That ensures that
+    // any output is printed directly, instead of going through
+    // the parent's output processing in LogRedirect.
+    if (getenv("SYNCEVOLUTION_DEBUG")) {
+        int index = LoggerBase::numLoggers();
+        LogRedirect *redirect = NULL;
+        while (--index >= 0 &&
+               !(redirect = dynamic_cast<LogRedirect *>(LoggerBase::loggerAt(index)))) {
+        }
+        if (redirect) {
+            redirect->reset();
+        }
+    }
+
+    if (me->m_mergedStdoutStderr) {
+        dup2(STDERR_FILENO, STDOUT_FILENO);
+    }
 }
 
 void ForkExecParent::start()
@@ -193,9 +213,8 @@
                                   static_cast<gchar **>(m_argv.get()),
                                   static_cast<gchar **>(m_env.get()),
                                   flags,
-                                  // child setup function: redirect stdout to stderr
-                                  m_mergedStdoutStderr ? setStdoutToStderr : NULL,
-                                  NULL, // child setup user data
+                                  // child setup function: redirect stdout to stderr, undo LogRedirect
+                                  forked, this,
                                   &m_childPid,
                                   NULL, // set stdin to /dev/null
                                   (m_mergedStdoutStderr || m_onStdout.empty()) ? NULL : &out,
diff -Nru syncevolution-1.2.99.1/src/syncevo/ForkExec.h syncevolution-1.2.99.2/src/syncevo/ForkExec.h
--- syncevolution-1.2.99.1/src/syncevo/ForkExec.h	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/syncevo/ForkExec.h	2012-07-02 17:58:20.000000000 +0200
@@ -231,6 +231,8 @@
                                 gpointer data) throw ();
 
     void checkCompletion() throw ();
+
+    static void forked(gpointer me) throw();
 };
 
 /**
diff -Nru syncevolution-1.2.99.1/src/syncevo/LocalTransportAgent.cpp syncevolution-1.2.99.2/src/syncevo/LocalTransportAgent.cpp
--- syncevolution-1.2.99.1/src/syncevo/LocalTransportAgent.cpp	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/syncevo/LocalTransportAgent.cpp	2012-07-02 17:58:20.000000000 +0200
@@ -26,6 +26,9 @@
 #include <syncevo/GLibSupport.h>
 #include <syncevo/DBusTraits.h>
 #include <syncevo/SuspendFlags.h>
+#include <syncevo/LogRedirect.h>
+
+#include <synthesis/syerror.h>
 
 #include <stddef.h>
 #include <sys/socket.h>
@@ -119,17 +122,19 @@
 /**
  * Uses the D-Bus API provided by LocalTransportAgentChild.
  */
-class LocalTransportChild : private GDBusCXX::DBusRemoteObject
+class LocalTransportChild : public GDBusCXX::DBusRemoteObject
 {
  public:
     static const char *path() { return "/"; }
     static const char *interface() { return "org.syncevolution.localtransport.child"; }
     static const char *destination() { return "local.destination"; }
+    static const char *logOutputName() { return "LogOutput"; }
     static const char *startSyncName() { return "StartSync"; }
     static const char *sendMsgName() { return "SendMsg"; }
 
     LocalTransportChild(const GDBusCXX::DBusConnectionPtr &conn) :
         GDBusCXX::DBusRemoteObject(conn, path(), interface(), destination()),
+        m_logOutput(*this, logOutputName(), false),
         m_startSync(*this, startSyncName()),
         m_sendMsg(*this, sendMsgName())
     {}
@@ -143,12 +148,21 @@
     /** use this to send a message back from child to parent */
     typedef boost::shared_ptr< GDBusCXX::Result2< std::string, GDBusCXX::DBusArray<uint8_t> > > ReplyPtr;
 
+    /** log output with level and message; process name will be added by parent */
+    GDBusCXX::SignalWatch2<string, string> m_logOutput;
+
     /** LocalTransportAgentChild::startSync() */
     GDBusCXX::DBusClientCall2<std::string, GDBusCXX::DBusArray<uint8_t> > m_startSync;
     /** LocalTransportAgentChild::sendMsg() */
     GDBusCXX::DBusClientCall2<std::string, GDBusCXX::DBusArray<uint8_t> > m_sendMsg;
+
 };
 
+void LocalTransportAgent::logChildOutput(const std::string &level, const std::string &message)
+{
+    ProcNameGuard guard(m_clientContext);
+    SE_LOG(Logger::strToLevel(level.c_str()), NULL, NULL, "%s", message.c_str());
+}
 
 void LocalTransportAgent::onChildConnect(const GDBusCXX::DBusConnectionPtr &conn)
 {
@@ -162,6 +176,7 @@
     m_parent->add(this, &LocalTransportAgent::storeSyncReport, LocalTransportParent::storeSyncReportName());
     m_parent->activate();
     m_child.reset(new LocalTransportChild(conn));
+    m_child->m_logOutput.activate(boost::bind(&LocalTransportAgent::logChildOutput, this, _1, _2));
 
     // now tell child what to do
     LocalTransportChild::ActiveSources_t sources;
@@ -357,12 +372,20 @@
                     if (m_clientReport.getStatus() != STATUS_OK &&
                         m_clientReport.getStatus() != STATUS_HTTP_OK) {
                         // Report that status, with an error message which contains the explanation
-                        // added to the client's error.
-                        std::string explanation = "failure in local sync child";
-                        pcrecpp::RE re(StringPrintf(".* \\((?:local|remote), status %d\\): (.*)",
-                                                    m_clientReport.getStatus()));
+                        // added to the client's error. We are a bit fuzzy about matching the status:
+                        // 10xxx matches xxx and vice versa.
+                        int status = m_clientReport.getStatus();
+                        if (status >= sysync::LOCAL_STATUS_CODE && status <= sysync::LOCAL_STATUS_CODE_END) {
+                            status -= sysync::LOCAL_STATUS_CODE;
+                        }
+                        std::string explanation = StringPrintf("failure on target side %s of local sync",
+                                                               m_clientContext.c_str());
+                        static const pcrecpp::RE re("\\((?:local|remote), status (\\d+)\\): (.*)");
+                        int clientStatus;
                         std::string clientExplanation;
-                        if (re.FullMatch(m_clientReport.getError(), &clientExplanation)) {
+                        if (re.PartialMatch(m_clientReport.getError(), &clientStatus, &clientExplanation) &&
+                            (status == clientStatus ||
+                             status == clientStatus - sysync::LOCAL_STATUS_CODE)) {
                             explanation += ": ";
                             explanation += clientExplanation;
                         }
@@ -485,7 +508,30 @@
     raise(sigterm);
 }
 
-class LocalTransportAgentChild : public TransportAgent
+/**
+ * Provides the "LogOutput" signal.
+ * LocalTransportAgentChild adds the method implementations
+ * before activating it.
+ */
+class LocalTransportChildImpl : public GDBusCXX::DBusObjectHelper
+{
+public:
+    LocalTransportChildImpl(const GDBusCXX::DBusConnectionPtr &conn) :
+        GDBusCXX::DBusObjectHelper(conn,
+                                   LocalTransportChild::path(),
+                                   LocalTransportChild::interface(),
+                                   GDBusCXX::DBusObjectHelper::Callback_t(),
+                                   true),
+        m_logOutput(*this, LocalTransportChild::logOutputName())
+    {
+        add(m_logOutput);
+    };
+
+    GDBusCXX::EmitSignal2<std::string,
+                          std::string> m_logOutput;
+};
+
+class LocalTransportAgentChild : public TransportAgent, private LoggerBase
 {
     /** final return code of our main(): non-zero indicates that we need to shut down */
     int m_ret;
@@ -495,6 +541,9 @@
      */
     SyncReport m_clientReport;
 
+    /** used to capture libneon output */
+    boost::scoped_ptr<LogRedirect> m_parentLogger;
+
     /**
      * provides connection to parent, created in constructor
      */
@@ -508,7 +557,7 @@
     /**
      * our D-Bus interface, created in onConnect()
      */
-    boost::scoped_ptr<GDBusCXX::DBusObjectHelper> m_child;
+    boost::scoped_ptr<LocalTransportChildImpl> m_child;
 
     /**
      * sync context, created in Sync() D-Bus call
@@ -577,11 +626,7 @@
         SE_LOG_DEBUG(NULL, NULL, "child connected to parent");
 
         // provide our own API
-        m_child.reset(new GDBusCXX::DBusObjectHelper(conn,
-                                                     LocalTransportChild::path(),
-                                                     LocalTransportChild::interface(),
-                                                     GDBusCXX::DBusObjectHelper::Callback_t(),
-                                                     true));
+        m_child.reset(new LocalTransportChildImpl(conn));
         m_child->add(this, &LocalTransportAgentChild::startSync, LocalTransportChild::startSyncName());
         m_child->add(this, &LocalTransportAgentChild::sendMsg, LocalTransportChild::sendMsgName());
         m_child->activate();
@@ -740,18 +785,52 @@
         }
     }
 
+    /**
+     * Write message into our own log and send to parent.
+     */
+    virtual void messagev(Level level,
+                          const char *prefix,
+                          const char *file,
+                          int line,
+                          const char *function,
+                          const char *format,
+                          va_list args)
+    {
+        if (m_parentLogger) {
+            m_parentLogger->process();
+        }
+        if (m_child) {
+            // prefix is used to set session path
+            // 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);
+            string log = StringPrintfV(format, args);
+            m_child->m_logOutput(strLevel, log);
+        }
+    }
+
+    virtual bool isProcessSafe() const { return false; }
+
 public:
     LocalTransportAgentChild() :
         m_ret(0),
+        m_parentLogger(new LogRedirect(false)),
         m_forkexec(SyncEvo::ForkExecChild::create()),
         m_reportSent(false),
         m_status(INACTIVE)
     {
+        LoggerBase::pushLogger(this);
+
         m_forkexec->m_onConnect.connect(boost::bind(&LocalTransportAgentChild::onConnect, this, _1));
         m_forkexec->m_onFailure.connect(boost::bind(&LocalTransportAgentChild::onFailure, this, _1, _2));
         m_forkexec->connect();
     }
 
+    ~LocalTransportAgentChild()
+    {
+        LoggerBase::popLogger();
+    }
+
     void run()
     {
         SuspendFlags &s = SuspendFlags::getSuspendFlags();
@@ -788,7 +867,7 @@
             sigaction(SIGTERM, &new_action, NULL);
 
             SE_LOG_DEBUG(NULL, NULL, "LocalTransportChild: ignore SIGINT, die in SIGTERM");
-
+            SE_LOG_INFO(NULL, NULL, "target side of local sync ready");
             m_client->sync(&m_clientReport);
         } catch (...) {
             string explanation;
@@ -963,6 +1042,18 @@
 
     SyncContext::initMain("syncevo-local-sync");
 
+    // Out stderr is either connected to the original stderr (when
+    // SYNCEVOLUTION_DEBUG is set) or the local sync's parent
+    // LogRedirect. However, that stderr is not normally used.
+    // Instead we install our own LogRedirect (to get output like the
+    // one from libneon into the child log) in
+    // LocalTransportAgentChild and send all logging output
+    // to the local sync parent via D-Bus, to be forwarded to the
+    // user as part of the normal message stream of the
+    // sync session.
+    setvbuf(stderr, NULL, _IONBF, 0);
+    setvbuf(stdout, NULL, _IONBF, 0);
+
     // SIGPIPE must be ignored, some system libs (glib GIO?) trigger
     // it. SIGINT/TERM will be handled via SuspendFlags once the sync
     // runs.
diff -Nru syncevolution-1.2.99.1/src/syncevo/LocalTransportAgent.h syncevolution-1.2.99.2/src/syncevo/LocalTransportAgent.h
--- syncevolution-1.2.99.1/src/syncevo/LocalTransportAgent.h	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/syncevo/LocalTransportAgent.h	2012-07-02 17:58:20.000000000 +0200
@@ -120,12 +120,13 @@
      * provides access to the forked process' D-Bus API
      * - start sync (returns child's first message)
      * - send server reply (returns child's next message or empty when done)
+     * - emits output via signal
      *
      * Only non-NULL when child is running and connected.
      */
     boost::shared_ptr<LocalTransportChild> m_child;
 
-
+    void logChildOutput(const std::string &level, const std::string &message);
     void onChildConnect(const GDBusCXX::DBusConnectionPtr &conn);
     void onFailure(const std::string &error);
     void onChildQuit(int status);
diff -Nru syncevolution-1.2.99.1/src/syncevo/Logging.h syncevolution-1.2.99.2/src/syncevo/Logging.h
--- syncevolution-1.2.99.1/src/syncevo/Logging.h	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/syncevo/Logging.h	2012-07-02 17:58:20.000000000 +0200
@@ -191,6 +191,33 @@
 };
 
 /**
+ * Changes the process name temporarily.
+ */
+class ProcNameGuard {
+    std::string m_oldProcName;
+    bool m_modified;
+
+ public:
+    ProcNameGuard(const std::string &procname) :
+        m_oldProcName(Logger::getProcessName())
+    {
+        if (m_oldProcName != procname) {
+            Logger::setProcessName(procname);
+            m_modified = true;
+        } else {
+            m_modified = false;
+        }
+    }
+
+    ~ProcNameGuard()
+    {
+        if (m_modified) {
+            Logger::setProcessName(m_oldProcName);
+        }
+    }
+};
+
+/**
  * Global logging, implemented as a singleton with one instance per
  * process.
  *
diff -Nru syncevolution-1.2.99.1/src/syncevo/SyncContext.cpp syncevolution-1.2.99.2/src/syncevo/SyncContext.cpp
--- syncevolution-1.2.99.1/src/syncevo/SyncContext.cpp	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/syncevo/SyncContext.cpp	2012-07-02 17:58:20.000000000 +0200
@@ -1352,7 +1352,11 @@
 
                 // pretty-print report
                 if (m_logLevel > LOGGING_QUIET) {
-                    SE_LOG_SHOW(NULL, NULL, "\nChanges applied during synchronization:");
+                    std::string procname = Logger::getProcessName();
+                    SE_LOG_SHOW(NULL, NULL, "\nChanges applied during synchronization%s%s%s:",
+                                procname.empty() ? "" : " (",
+                                procname.c_str(),
+                                procname.empty() ? "" : ")");
                 }
                 if (m_logLevel > LOGGING_QUIET && report) {
                     ostringstream out;
@@ -2353,7 +2357,8 @@
         "      delayedabort = FALSE;\n"
         "      INTEGER alarmTimeToUTC;\n"
         "      alarmTimeToUTC = FALSE;\n"
-        "      // for VCALENDAR_COMPARE_SCRIPT: don't use UID by default\n"
+        "      INTEGER stripUID;\n"
+        "      stripUID = FALSE;\n"
         "    ]]></sessioninitscript>\n";
 
     ostringstream clientorserver;
diff -Nru syncevolution-1.2.99.1/src/syncevo/SyncSource.cpp syncevolution-1.2.99.2/src/syncevo/SyncSource.cpp
--- syncevolution-1.2.99.1/src/syncevo/SyncSource.cpp	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/syncevo/SyncSource.cpp	2012-07-02 17:58:20.000000000 +0200
@@ -661,7 +661,8 @@
          * to info.m_afterReadScript and info.m_beforeWriteScript.
          */
         info.m_afterReadScript = "$VCALENDAR10_AFTERREAD_SCRIPT;\n";
-        info.m_beforeWriteScript = "$VCALENDAR10_BEFOREWRITE_SCRIPT;\n";
+        info.m_beforeWriteScript = "$VCALENDAR10_BEFOREWRITE_SCRIPT;\n"
+            "$CALENDAR_BEFOREWRITE_SCRIPT;\n";
     } else if (type == "text/calendar" ||
                boost::starts_with(type, "text/calendar+")) {
         info.m_native = "iCalendar20";
@@ -670,6 +671,7 @@
         info.m_datatypes =
             "        <use datatype='vCalendar10' mode='rw'/>\n"
             "        <use datatype='iCalendar20' mode='rw' preferred='yes'/>\n";
+        info.m_beforeWriteScript = "$CALENDAR_BEFOREWRITE_SCRIPT;\n";
     } else if (type == "text/plain") {
         info.m_fieldlist = "Note";
         info.m_profile = "\"Note\", 2";
diff -Nru syncevolution-1.2.99.1/src/syncevolution.cpp syncevolution-1.2.99.2/src/syncevolution.cpp
--- syncevolution-1.2.99.1/src/syncevolution.cpp	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/src/syncevolution.cpp	2012-07-02 17:58:20.000000000 +0200
@@ -171,7 +171,7 @@
     void sessionChangedCb(const DBusObject_t &object, bool active);
 
     /** callback of 'Server.LogOutput' */
-    void logOutputCb(const DBusObject_t &object, const string &level, const string &log);
+    void logOutputCb(const DBusObject_t &object, const string &level, const string &log, const string &procname);
 
     /** callback of 'Server.InfoRequest' */
     void infoReqCb(const string &,
@@ -235,7 +235,7 @@
     // listen to dbus server signal 'SessionChanged'
     SignalWatch2<DBusObject_t, bool> m_sessionChanged;
     // listen to dbus server signal 'LogOutput'
-    SignalWatch3<DBusObject_t, string, string> m_logOutput;
+    SignalWatch4<DBusObject_t, string, string, string> m_logOutput;
     // listen to dbus server signal 'InfoRequest'
     SignalWatch6<string, 
                  DBusObject_t,
@@ -286,7 +286,7 @@
     void setRunSync(bool runSync) { m_runSync = runSync; }
 
     /** pass through logoutput and print them if m_output is true */
-    void logOutput(Logger::Level level, const string &log);
+    void logOutput(Logger::Level level, const string &log, const string &procname);
 
     /** set whether to print output */
     void setOutput(bool output) { m_output = output; }
@@ -546,7 +546,7 @@
         attachSync();
         if(m_attached) {
             m_sessionChanged.activate(boost::bind(&RemoteDBusServer::sessionChangedCb, this, _1, _2));
-            m_logOutput.activate(boost::bind(&RemoteDBusServer::logOutputCb, this, _1, _2, _3));
+            m_logOutput.activate(boost::bind(&RemoteDBusServer::logOutputCb, this, _1, _2, _3, _4));
             m_infoReq.activate(boost::bind(&RemoteDBusServer::infoReqCb, this, _1, _2, _3, _4, _5, _6));
         }
     }
@@ -612,12 +612,13 @@
 
 void RemoteDBusServer::logOutputCb(const DBusObject_t &object,
                                    const string &level,
-                                   const string &log)
+                                   const string &log,
+                                   const string &procname)
 {
     if (m_session && 
         (boost::equals(object, getPath()) ||
          boost::equals(object, m_session->getPath()))) {
-        m_session->logOutput(Logger::strToLevel(level.c_str()), log);
+        m_session->logOutput(Logger::strToLevel(level.c_str()), log, procname);
     }
 }
 
@@ -960,9 +961,10 @@
     suspend.start(interruptCb);
 }
 
-void RemoteSession::logOutput(Logger::Level level, const string &log)
+void RemoteSession::logOutput(Logger::Level level, const string &log, const string &procname)
 {
     if(m_output) {
+        ProcNameGuard guard(procname);
         SE_LOG(level, NULL, NULL, "%s", log.c_str());
     }
 }
diff -Nru syncevolution-1.2.99.1/test/ClientTest.cpp syncevolution-1.2.99.2/test/ClientTest.cpp
--- syncevolution-1.2.99.1/test/ClientTest.cpp	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/test/ClientTest.cpp	2012-07-02 17:58:20.000000000 +0200
@@ -440,6 +440,9 @@
             ADD_TEST(LocalTests, testSimpleInsert);
             ADD_TEST(LocalTests, testLocalDeleteAll);
             ADD_TEST(LocalTests, testComplexInsert);
+            if (config.m_insertItem.find("\nUID:") != std::string::npos) {
+                ADD_TEST(LocalTests, testInsertTwice);
+            }
 
             if (!config.m_updateItem.empty()) {
                 ADD_TEST(LocalTests, testLocalUpdate);
@@ -1049,13 +1052,26 @@
     CT_ASSERT_EQUAL(STATUS_NOT_FOUND, status);
 }
 
-// insert one contact without clearing the source first
-void LocalTests::testSimpleInsert() {
+void LocalTests::doInsert(bool withUID)
+{
     // check requirements
     CT_ASSERT(!config.m_insertItem.empty());
     CT_ASSERT(!config.m_createSourceA.empty());
 
-    CT_ASSERT_NO_THROW(insert(createSourceA, config.m_insertItem));
+    std::string item = config.m_insertItem;
+    if (!withUID) {
+        CT_ASSERT_NO_THROW(stripProperty(item, "UID"));
+    }
+    CT_ASSERT_NO_THROW(insert(createSourceA, item));
+}
+
+// insert one contact without clearing the source first
+void LocalTests::testSimpleInsert() {
+    // Insert the item without the UID. There is no guarantee that the
+    // item wasn't already inserted before (database not cleaned by
+    // test) and some backends (CalDAV) will catch an attempt to
+    // add the same item twice.
+    doInsert(false);
 }
 
 // delete all items
@@ -1065,7 +1081,9 @@
     CT_ASSERT(config.m_createSourceA);
 
     // make sure there is something to delete, then delete again
-    CT_ASSERT_NO_THROW(insert(createSourceA, config.m_insertItem));
+    std::string item = config.m_insertItem;
+    CT_ASSERT_NO_THROW(stripProperty(item, "UID"));
+    CT_ASSERT_NO_THROW(insert(createSourceA, item));
     CT_ASSERT_NO_THROW(deleteAll(createSourceA));
 }
 
@@ -1073,10 +1091,46 @@
 void LocalTests::testComplexInsert() {
     CT_ASSERT(config.m_createSourceA);
     CT_ASSERT_NO_THROW(deleteAll(createSourceA));
-    CT_ASSERT_NO_THROW(testSimpleInsert());
+    CT_ASSERT_NO_THROW(doInsert());
     CT_ASSERT_NO_THROW(testIterateTwice());
 }
 
+// insert the same item (identified by UID) twice => either
+// ITEM_NEEDS_MERGE, ITEM_REPLACED or ITEM_MERGED are acceptable
+void LocalTests::testInsertTwice() {
+    CT_ASSERT(config.m_createSourceA);
+    CT_ASSERT(!config.m_insertItem.empty());
+    CT_ASSERT(config.m_insertItem.find("\nUID:") != std::string::npos);
+    CT_ASSERT_NO_THROW(deleteAll(createSourceA));
+
+    // create source
+    TestingSyncSourcePtr source;
+    SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(createSourceA()));
+
+    // mangle data once
+    std::string data = config.m_mangleItem(config.m_insertItem, false);
+
+    // insert new item
+    SyncSourceRaw::InsertItemResult first;
+    SOURCE_ASSERT_NO_FAILURE(source.get(), first = source->insertItemRaw("", data));
+    CT_ASSERT_EQUAL(ITEM_OKAY, first.m_state);
+
+    // and again
+    SyncSourceRaw::InsertItemResult second;
+    SOURCE_ASSERT_NO_FAILURE(source.get(), second = source->insertItemRaw("", data));
+    CLIENT_TEST_LOG("item %s",
+                    second.m_state == ITEM_NEEDS_MERGE ? "needs to be merged" :
+                    second.m_state == ITEM_REPLACED ? "was replaced" :
+                    second.m_state == ITEM_MERGED ? "was merged" :
+                    second.m_state == ITEM_OKAY ? "was added, which is broken!" :
+                    "unknown result ?!");
+    CT_ASSERT(second.m_state == ITEM_NEEDS_MERGE || second.m_state == ITEM_REPLACED || second.m_state == ITEM_MERGED);
+    CT_ASSERT_EQUAL(first.m_luid, second.m_luid);
+    if (second.m_state == ITEM_REPLACED || second.m_state == ITEM_MERGED) {
+        CT_ASSERT(first.m_revision != second.m_revision);
+    }
+}
+
 // clean database, insert item, update it
 void LocalTests::testLocalUpdate() {
     // check additional requirements
@@ -1085,7 +1139,7 @@
 
     CT_ASSERT_NO_THROW(deleteAll(createSourceA));
 
-    CT_ASSERT_NO_THROW(testSimpleInsert());
+    CT_ASSERT_NO_THROW(doInsert());
     CT_ASSERT_NO_THROW(update(createSourceA, config.m_updateItem));
 }
 
@@ -1103,7 +1157,7 @@
     CT_ASSERT_NO_THROW(deleteAll(createSourceA));
 
     CLIENT_TEST_LOG("insert item via source A");
-    CT_ASSERT_NO_THROW(testSimpleInsert());
+    CT_ASSERT_NO_THROW(doInsert());
 
     CLIENT_TEST_LOG("clean changes in sync source B by creating and closing it");
     TestingSyncSourcePtr source;
@@ -1176,7 +1230,7 @@
     CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset());
 
     CLIENT_TEST_LOG("insert another item via source A");
-    CT_ASSERT_NO_THROW(testSimpleInsert());
+    CT_ASSERT_NO_THROW(doInsert());
     CLIENT_TEST_LOG("check for new item via source B");
     SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB()));
     SOURCE_ASSERT_EQUAL(source.get(), 1, countItems(source.get()));
@@ -1222,7 +1276,7 @@
     SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB()));
     CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset());
     CLIENT_TEST_LOG("create and update an item in source A");
-    CT_ASSERT_NO_THROW(testSimpleInsert());
+    CT_ASSERT_NO_THROW(doInsert());
     CT_ASSERT_NO_THROW(update(createSourceA, config.m_updateItem));
     CLIENT_TEST_LOG("should only be listed as new or updated in source B, but not both");
     SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB()));
@@ -1236,9 +1290,9 @@
     SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB()));
     CT_ASSERT_NO_THROW(restart ? source.stopAccess() : source.reset());
     CLIENT_TEST_LOG("create, delete and recreate an item in source A");
-    CT_ASSERT_NO_THROW(testSimpleInsert());
+    CT_ASSERT_NO_THROW(doInsert());
     CT_ASSERT_NO_THROW(deleteAll(createSourceA));
-    CT_ASSERT_NO_THROW(testSimpleInsert());
+    CT_ASSERT_NO_THROW(doInsert());
     CLIENT_TEST_LOG("should only be listed as new or updated in source B, even if\n "
                     "(as for calendar with UID) the same LUID gets reused");
     SOURCE_ASSERT_NO_FAILURE(source.get(), restart ? source.startAccess() : source.reset(createSourceB()));
@@ -1311,7 +1365,7 @@
 
         // insert one item
         CLIENT_TEST_LOG("inserting into %s", main->getSourceName().c_str());
-        CT_ASSERT_NO_THROW(main->testSimpleInsert());
+        CT_ASSERT_NO_THROW(main->doInsert());
         BOOST_FOREACH (LocalTests *test, m_linkedSources) {
             if (test == main) {
                 continue;
@@ -3067,7 +3121,7 @@
 void SyncTests::testRefreshStatus() {
     source_it it;
 
-    CT_ASSERT_NO_THROW(allSourcesInsert());
+    CT_ASSERT_NO_THROW(allSourcesInsert(false));
     CT_ASSERT_NO_THROW(allSourcesDeleteAll());
     CT_ASSERT_NO_THROW(allSourcesInsert());
     doSync(__FILE__, __LINE__,
@@ -3159,7 +3213,7 @@
         boost::bind(boost::function<void ()>(
                                              boost::lambda::if_then(++boost::lambda::var(startCount) == sources.size(),
                                                                     (boost::lambda::bind(log, "inserting one item"),
-                                                                     boost::lambda::bind(&SyncTests::allSourcesInsert, this)))
+                                                                     boost::lambda::bind(&SyncTests::allSourcesInsert, this, true)))
                                              ));
 
     SyncOptions::Callback_t setup =
@@ -4146,6 +4200,17 @@
             }
         }
         out.close();
+
+        // The test used peer-specific test cases, but the actual
+        // result does not depend on the peer because we haven't
+        // received the peer's DevInf at the point where we
+        // import/export the test cases (=> don't apply peer-specific
+        // synccompare workarounds).
+        //
+        // Due to the lack of DevInf, properties and parameters which
+        // need to be enabled via DevInf get lost (= filter them out).
+        ScopedEnvChange env("CLIENT_TEST_SERVER", "");
+        ScopedEnvChange envParams("CLIENT_TEST_STRIP_PARAMETERS", "X-EVOLUTION-UI-SLOT");
         CT_ASSERT(config->m_compare(client, testcases, converted));
     }
 
@@ -4237,17 +4302,20 @@
     accessClientB->doSync(__FILE__, __LINE__, "update", SyncOptions(SYNC_TWO_WAY));
     doSync(__FILE__, __LINE__, "patch", SyncOptions(SYNC_TWO_WAY));
 
-    // compare data in source A against reference data *without* telling synccompare
-    // to ignore known data loss for the server
-    ScopedEnvChange env("CLIENT_TEST_SERVER", "");
-    ScopedEnvChange envParams("CLIENT_TEST_STRIP_PARAMETERS", "X-EVOLUTION-UI-SLOT");
-    ScopedEnvChange envProps("CLIENT_TEST_STRIP_PROPERTIES", "(PHOTO|FN)");
     bool equal = true;
     for (it = sources.begin(); it != sources.end(); ++it) {
         string refDir = getCurrentTest() + "." + it->second->config.m_sourceName + ".ref.dat";
         simplifyFilename(refDir);
         TestingSyncSourcePtr source;
         SOURCE_ASSERT_NO_FAILURE(source.get(), source.reset(it->second->createSourceB()));
+        // Compare data in source A against reference data *without*
+        // telling synccompare to ignore known data loss for the
+        // server. CLIENT_TEST_SERVER is relevant for finding the
+        // right source config and thus setting it must come after the
+        // createSourceB() call.
+        ScopedEnvChange env("CLIENT_TEST_SERVER", "");
+        ScopedEnvChange envParams("CLIENT_TEST_STRIP_PARAMETERS", "X-EVOLUTION-UI-SLOT");
+        ScopedEnvChange envProps("CLIENT_TEST_STRIP_PROPERTIES", "(PHOTO|FN)");
         if (!it->second->compareDatabases(refDir.c_str(), *source, false)) {
             equal = false;
         }
@@ -5830,10 +5898,10 @@
     client.postSync(res, logname);
 }
 
-void SyncTests::allSourcesInsert()
+void SyncTests::allSourcesInsert(bool withUID)
 {
     BOOST_FOREACH(source_array_t::value_type &source_pair, sources)  {
-        CT_ASSERT_NO_THROW(source_pair.second->testSimpleInsert());
+        CT_ASSERT_NO_THROW(source_pair.second->doInsert(withUID));
     }
 }
 
@@ -6307,15 +6375,11 @@
     }
 
     if (getenv("CLIENT_TEST_NO_UID")) {
-        boost::replace_all(item, "UID:1234567890!@#$%^&*()<>@dummy\n", "");
+        stripProperty(item, "UID");
     } else if (getenv("CLIENT_TEST_SIMPLE_UID")) {
         boost::replace_all(item, "UID:1234567890!@#$%^&*()<>@dummy", "UID:1234567890@dummy");
     }
 
-    // Ensure that items of different types do not have the same UID.
-    // They might be stored in the same VCALENDAR.
-    boost::replace_all(item, "@dummy\n", "@dummy" + type + "\n");
-
     if (getenv("CLIENT_TEST_UNIQUE_UID")) {
         // Making UID unique per test to avoid issues
         // when the source already holds older copies.
@@ -6773,7 +6837,7 @@
             "SUMMARY:phone meeting - old\n"
             "DTEND:20060406T163000Z\n"
             "DTSTART:20060406T160000Z\n"
-            "UID:1234567890!@#$%^&*()<>@dummy\n"
+            "UID:1234567890!@#$%^&*()<>@dummyVEVENT\n"
             "DTSTAMP:20060406T211449Z\n"
             "LAST-MODIFIED:20060409T213201Z\n"
             "CREATED:20060409T213201Z\n"
@@ -6792,7 +6856,7 @@
             "SUMMARY:meeting on site - updated\n"
             "DTEND:20060406T163000Z\n"
             "DTSTART:20060406T160000Z\n"
-            "UID:1234567890!@#$%^&*()<>@dummy\n"
+            "UID:1234567890!@#$%^&*()<>@dummyVEVENT\n"
             "DTSTAMP:20060406T211449Z\n"
             "LAST-MODIFIED:20060409T213201Z\n"
             "CREATED:20060409T213201Z\n"
@@ -6812,7 +6876,7 @@
             "SUMMARY:phone meeting\n"
             "DTEND:20060406T163000Z\n"
             "DTSTART:20060406T160000Z\n"
-            "UID:1234567890!@#$%^&*()<>@dummy\n"
+            "UID:1234567890!@#$%^&*()<>@dummyVEVENT\n"
             "DTSTAMP:20060406T211449Z\n"
             "LAST-MODIFIED:20060409T213201Z\n"
             "CREATED:20060409T213201Z\n"
@@ -6837,7 +6901,7 @@
             "SUMMARY:phone meeting\n"
             "DTEND:20060406T163000Z\n"
             "DTSTART:20060406T160000Z\n"
-            "UID:1234567890!@#$%^&*()<>@dummy\n"
+            "UID:1234567890!@#$%^&*()<>@dummyVEVENT\n"
             "DTSTAMP:20060406T211449Z\n"
             "LAST-MODIFIED:20060409T213201Z\n"
             "CREATED:20060409T213201Z\n"
@@ -6882,7 +6946,7 @@
             "END:STANDARD\n"
             "END:VTIMEZONE\n"
             "BEGIN:VEVENT\n"
-            "UID:20080407T193125Z-19554-727-1-50@dummy\n"
+            "UID:20080407T193125Z-19554-727-1-50@dummyVEVENT\n"
             "DTSTAMP:20080407T193125Z\n"
             "DTSTART;TZID=Europe/Berlin:20080406T090000\n"
             "DTEND;TZID=Europe/Berlin:20080406T093000\n"
@@ -6919,7 +6983,7 @@
             "END:STANDARD\n"
             "END:VTIMEZONE\n"
             "BEGIN:VEVENT\n"
-            "UID:20080407T193125Z-19554-727-1-50@dummy\n"
+            "UID:20080407T193125Z-19554-727-1-50@dummyVEVENT\n"
             "DTSTAMP:20080407T193125Z\n"
             "DTSTART;TZID=Europe/Berlin:20080413T090000\n"
             "DTEND;TZID=Europe/Berlin:20080413T093000\n"
@@ -6964,7 +7028,7 @@
                 "END:STANDARD\n"
                 "END:VTIMEZONE\n"
                 "BEGIN:VEVENT\n"
-                "UID:20080407T193125Z-19554-727-1-50@dummy\n"
+                "UID:20080407T193125Z-19554-727-1-50@dummyVEVENT\n"
                 "DTSTAMP:20080407T193125Z\n"
                 "DTSTART;TZID=Europe/Berlin:20080406T090000\n"
                 "DTEND;TZID=Europe/Berlin:20080406T093000\n"
@@ -6986,7 +7050,7 @@
                 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
                 "VERSION:2.0\n"
                 "BEGIN:VEVENT\n"
-                "UID:20080407T193125Z-19554-727-1-50@dummy\n"
+                "UID:20080407T193125Z-19554-727-1-50@dummyVEVENT\n"
                 "DTSTAMP:20080407T193125Z\n"
                 "DTSTART:20080406T070000Z\n"
                 "DTEND:20080406T073000Z\n"
@@ -7005,7 +7069,7 @@
                 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
                 "VERSION:2.0\n"
                 "BEGIN:VEVENT\n"
-                "UID:20080407T193125Z-19554-727-1-50@dummy\n"
+                "UID:20080407T193125Z-19554-727-1-50@dummyVEVENT\n"
                 "DTSTAMP:20080407T193125Z\n"
                 "DTSTART:20080413T070000Z\n"
                 "DTEND:20080413T073000Z\n"
@@ -7028,7 +7092,7 @@
                 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
                 "VERSION:2.0\n"
                 "BEGIN:VEVENT\n"
-                "UID:20080407T193125Z-19554-727-1-50@dummy\n"
+                "UID:20080407T193125Z-19554-727-1-50@dummyVEVENT\n"
                 "DTSTAMP:20080407T193125Z\n"
                 "DTSTART:20080406T070000\n"
                 "DTEND:20080406T073000\n"
@@ -7047,7 +7111,7 @@
                 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
                 "VERSION:2.0\n"
                 "BEGIN:VEVENT\n"
-                "UID:20080407T193125Z-19554-727-1-50@dummy\n"
+                "UID:20080407T193125Z-19554-727-1-50@dummyVEVENT\n"
                 "DTSTAMP:20080407T193125Z\n"
                 "DTSTART:20080413T070000\n"
                 "DTEND:20080413T073000\n"
@@ -7134,7 +7198,7 @@
                 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
                 "VERSION:2.0\n"
                 "BEGIN:VEVENT\n"
-                "UID:20110829T130000Z-19554-727-1-50@dummy\n"
+                "UID:20110829T130000Z-19554-727-1-50@dummyVEVENT\n"
                 "DTSTAMP:20080407T193125Z\n"
                 "DTSTART;VALUE=DATE:20080406\n"
                 "DTEND;VALUE=DATE:20080407\n"
@@ -7164,7 +7228,7 @@
                 "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
                 "VERSION:2.0\n"
                 "BEGIN:VEVENT\n"
-                "UID:20110829T130000Z-19554-727-1-50@dummy\n"
+                "UID:20110829T130000Z-19554-727-1-50@dummyVEVENT\n"
                 "DTSTAMP:20080407T193125Z\n"
                 "DTSTART;VALUE=DATE:20080413\n"
                 "DTEND;VALUE=DATE:20080414\n"
@@ -7393,7 +7457,7 @@
             "SUMMARY:phone meeting\n"
             "DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T163000\n"
             "DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T160000\n"
-            "UID:1234567890!@#$%^&*()<>@dummy\n"
+            "UID:1234567890!@#$%^&*()<>@dummyVEVENT\n"
             "DTSTAMP:20060406T211449Z\n"
             "LAST-MODIFIED:20060409T213201Z\n"
             "CREATED:20060409T213201Z\n"
@@ -7430,7 +7494,7 @@
             "SUMMARY:meeting on site\n"
             "DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T163000\n"
             "DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/Asia/Shanghai:20060406T160000\n"
-            "UID:1234567890!@#$%^&*()<>@dummy\n"
+            "UID:1234567890!@#$%^&*()<>@dummyVEVENT\n"
             "DTSTAMP:20060406T211449Z\n"
             "LAST-MODIFIED:20060409T213201Z\n"
             "CREATED:20060409T213201Z\n"
@@ -7460,7 +7524,7 @@
             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
             "VERSION:2.0\n"
             "BEGIN:VTODO\n"
-            "UID:20060417T173712Z-4360-727-1-2730@dummy\n"
+            "UID:20060417T173712Z-4360-727-1-2730@dummyVTODO\n"
             "DTSTAMP:20060417T173712Z\n"
             "SUMMARY:do me\n"
             "DESCRIPTION:to be done<<REVISION>>\n"
@@ -7475,7 +7539,7 @@
             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
             "VERSION:2.0\n"
             "BEGIN:VTODO\n"
-            "UID:20060417T173712Z-4360-727-1-2730@dummy\n"
+            "UID:20060417T173712Z-4360-727-1-2730@dummyVTODO\n"
             "DTSTAMP:20060417T173712Z\n"
             "SUMMARY:do me ASAP\n"
             "DESCRIPTION:to be done\n"
@@ -7491,7 +7555,7 @@
             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
             "VERSION:2.0\n"
             "BEGIN:VTODO\n"
-            "UID:20060417T173712Z-4360-727-1-2730@dummy\n"
+            "UID:20060417T173712Z-4360-727-1-2730@dummyVTODO\n"
             "DTSTAMP:20060417T173712Z\n"
             "SUMMARY:do me please\\, please\n"
             "DESCRIPTION:to be done\n"
@@ -7506,7 +7570,7 @@
             "PRODID:-//Ximian//NONSGML Evolution Calendar//EN\n"
             "VERSION:2.0\n"
             "BEGIN:VTODO\n"
-            "UID:20060417T173712Z-4360-727-1-2730@dummy\n"
+            "UID:20060417T173712Z-4360-727-1-2730@dummyVTODO\n"
             "DTSTAMP:20060417T173712Z\n"
             "SUMMARY:do me\n"
             "DESCRIPTION:to be done\n"
diff -Nru syncevolution-1.2.99.1/test/ClientTest.h syncevolution-1.2.99.2/test/ClientTest.h
--- syncevolution-1.2.99.1/test/ClientTest.h	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/test/ClientTest.h	2012-07-02 17:58:20.000000000 +0200
@@ -623,9 +623,11 @@
     virtual void testIterateTwice();
     virtual void testDelete404();
     virtual void testReadItem404();
+    void doInsert(bool withUID = true);
     virtual void testSimpleInsert();
     virtual void testLocalDeleteAll();
     virtual void testComplexInsert();
+    virtual void testInsertTwice();
     virtual void testLocalUpdate();
     void doChanges(bool restart);
     virtual void testChanges();
@@ -900,7 +902,7 @@
     virtual void postSync(int res, const std::string &logname);
 
  private:
-    void allSourcesInsert();
+    void allSourcesInsert(bool withUID = true);
     void allSourcesUpdate();
     void allSourcesDeleteAll();
     void allSourcesInsertMany(int startIndex, int numItems,
diff -Nru syncevolution-1.2.99.1/test/client-test-main.cpp syncevolution-1.2.99.2/test/client-test-main.cpp
--- syncevolution-1.2.99.1/test/client-test-main.cpp	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/test/client-test-main.cpp	2012-07-02 17:58:20.000000000 +0200
@@ -172,16 +172,17 @@
             CppUnit::CompilerOutputter formatter(&m_failures, output);
             formatter.printFailureReport();
             failure = output.str();
-            m_failed = true;
+            bool failed = true;
             BOOST_FOREACH (const std::string &re, m_allowedFailures) {
                 if (pcrecpp::RE(re).FullMatch(m_currentTest)) {
                     result = "*** failure ignored ***";
-                    m_failed = false;
+                    failed = false;
                     break;
                 }
             }
-            if (m_failed) {
+            if (failed) {
                 result = "*** failed ***";
+                m_failed = true;
             }
         } else {
             result = "okay";
diff -Nru syncevolution-1.2.99.1/test/runtests.py syncevolution-1.2.99.2/test/runtests.py
--- syncevolution-1.2.99.1/test/runtests.py	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/test/runtests.py	2012-07-02 17:58:20.000000000 +0200
@@ -1022,6 +1022,10 @@
                                   "Client::Source SyncEvolution",
                                   [],
                                   "CLIENT_TEST_FAILURES="
+                                  # testReadItem404 works with some Akonadi versions (Ubuntu Lucid),
+                                  # but not all (Debian Testing). The other tests always fail,
+                                  # the code needs to be fixed.
+                                  "Client::Source::kde_.*::testReadItem404,"
                                   "Client::Source::kde_.*::testDelete404,"
                                   "Client::Source::kde_.*::testImport.*,"
                                   "Client::Source::kde_.*::testRemoveProperties,"
@@ -1274,9 +1278,6 @@
                          # server cannot detect pairs based on UID/RECURRENCE-ID
                          "CLIENT_TEST_ADD_BOTH_SIDES_SERVER_IS_DUMB=1 "
                          "CLIENT_TEST_SKIP="
-                         # testConversion fails because X-EVOLUTION-UI-SLOT is never enabled
-                         # (depends on DevInf from peer, but test runs before parsing that).
-                         "Client::Sync::.*carddav.*::testConversion"
                          ,
                          testPrefix=syncevoPrefix)
 context.add(test)
@@ -1300,9 +1301,6 @@
                          # server supports multiple cycles inside the same session
                          "CLIENT_TEST_PEER_CAN_RESTART=1 "
                          "CLIENT_TEST_SKIP="
-                         # testConversion fails because X-EVOLUTION-UI-SLOT is never enabled
-                         # (depends on DevInf from peer, but test runs before parsing that).
-                         "Client::Sync::.*carddav.*::testConversion"
                          ,
                          testPrefix=syncevoPrefix)
 context.add(test)
diff -Nru syncevolution-1.2.99.1/test/test-dbus.py syncevolution-1.2.99.2/test/test-dbus.py
--- syncevolution-1.2.99.1/test/test-dbus.py	2012-06-22 16:56:20.000000000 +0200
+++ syncevolution-1.2.99.2/test/test-dbus.py	2012-07-02 17:58:20.000000000 +0200
@@ -3620,7 +3620,7 @@
         self.assertEqual(DBusUtil.quit_events, ["session " + self.sessionpath + " done"])
         self.assertEqual(self.lastState, "done")
         self.checkSync(expectedError=20017, expectedResult=20017)
-        self.assertSyncStatus('server', 20017, "error code from SyncEvolution aborted on behalf of user (local, status 20017): failure in local sync child: User did not provide the 'addressbook backend' password.")
+        self.assertSyncStatus('server', 20017, "error code from SyncEvolution aborted on behalf of user (local, status 20017): User did not provide the 'addressbook backend' password.")
 
     @timeout(200)
     def testPasswordRequestTimeout(self):
@@ -3639,7 +3639,7 @@
         self.assertEqual(DBusUtil.quit_events, ["session " + self.sessionpath + " done"])
         self.assertEqual(self.lastState, "request")
         self.checkSync(expectedError=22003, expectedResult=22003)
-        self.assertSyncStatus('server', 22003, "error code from SyncEvolution password request timed out (local, status 22003): failure in local sync child: Could not get the 'addressbook backend' password from user.")
+        self.assertSyncStatus('server', 22003, "error code from SyncEvolution password request timed out (local, status 22003): Could not get the 'addressbook backend' password from user.")
         end = time.time()
         self.assertTrue(abs(120 + (usingValgrind() and 20 or 0) -
                             (end - start)) <
@@ -3725,7 +3725,7 @@
 
         # Sync should have failed with an explanation that it was
         # because of the password.
-        self.assertSyncStatus('server', 22003, "error code from SyncEvolution password request timed out (local, status 22003): failure in local sync child: Could not get the 'addressbook backend' password from user.")
+        self.assertSyncStatus('server', 22003, "error code from SyncEvolution password request timed out (local, status 22003): Could not get the 'addressbook backend' password from user.")
 
     @timeout(200)
     @property("ENV", "SYNCEVOLUTION_LOCAL_CHILD_DELAY=10") # allow killing syncevo-dbus-server in middle of sync
@@ -6669,7 +6669,8 @@
         self.assertEqual(err, None)
         self.assertEqual(0, code)
         out = self.stripSyncTime(out)
-        self.assertEqualDiff('''[INFO @client] @client/addressbook: starting first time sync, two-way (peer is server)
+        self.assertEqualDiff('''[INFO @client] target side of local sync ready
+[INFO @client] @client/addressbook: starting first time sync, two-way (peer is server)
 [INFO @client] creating complete data backup of source addressbook before sync (enabled with dumpData and needed for printChanges)
 @client data changes to be applied during synchronization:
 *** @client/addressbook ***
@@ -6692,7 +6693,7 @@
 
 Synchronization successful.
 
-Changes applied during synchronization:
+Changes applied during synchronization (@client):
 +---------------|-----------------------|-----------------------|-CON-+
 |               |        @client        |       @default        | FLI |
 |        Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS |
@@ -6762,7 +6763,8 @@
         self.assertEqual(err, None)
         self.assertEqual(0, code)
         out = self.stripSyncTime(out)
-        self.assertEqualDiff('''[INFO @client] @client/addressbook: starting normal sync, two-way (peer is server)
+        self.assertEqualDiff('''[INFO @client] target side of local sync ready
+[INFO @client] @client/addressbook: starting normal sync, two-way (peer is server)
 [INFO @client] creating complete data backup of source addressbook before sync (enabled with dumpData and needed for printChanges)
 @client data changes to be applied during synchronization:
 *** @client/addressbook ***
@@ -6782,7 +6784,7 @@
 
 Synchronization successful.
 
-Changes applied during synchronization:
+Changes applied during synchronization (@client):
 +---------------|-----------------------|-----------------------|-CON-+
 |               |        @client        |       @default        | FLI |
 |        Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS |
@@ -6842,7 +6844,8 @@
         self.assertEqual(err, None)
         self.assertEqual(0, code)
         out = self.stripSyncTime(out)
-        self.assertEqualDiff('''[INFO @client] @client/addressbook: starting normal sync, two-way (peer is server)
+        self.assertEqualDiff('''[INFO @client] target side of local sync ready
+[INFO @client] @client/addressbook: starting normal sync, two-way (peer is server)
 [INFO @client] creating complete data backup of source addressbook before sync (enabled with dumpData and needed for printChanges)
 @client data changes to be applied during synchronization:
 *** @client/addressbook ***
@@ -6874,7 +6877,7 @@
 
 Synchronization successful.
 
-Changes applied during synchronization:
+Changes applied during synchronization (@client):
 +---------------|-----------------------|-----------------------|-CON-+
 |               |        @client        |       @default        | FLI |
 |        Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS |
@@ -6937,7 +6940,8 @@
         self.assertEqual(err, None)
         self.assertEqual(0, code)
         out = self.stripSyncTime(out)
-        self.assertEqualDiff('''[INFO @client] @client/addressbook: starting normal sync, two-way (peer is server)
+        self.assertEqualDiff('''[INFO @client] target side of local sync ready
+[INFO @client] @client/addressbook: starting normal sync, two-way (peer is server)
 [INFO @client] creating complete data backup of source addressbook before sync (enabled with dumpData and needed for printChanges)
 @client data changes to be applied during synchronization:
 *** @client/addressbook ***
@@ -6969,7 +6973,7 @@
 
 Synchronization successful.
 
-Changes applied during synchronization:
+Changes applied during synchronization (@client):
 +---------------|-----------------------|-----------------------|-CON-+
 |               |        @client        |       @default        | FLI |
 |        Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS |
@@ -7046,7 +7050,8 @@
         self.assertEqual(err, None)
         self.assertEqual(0, code)
         out = self.stripSyncTime(out)
-        self.assertEqualDiff('''[INFO @client] @client/addressbook: starting first time sync, two-way (peer is server)
+        self.assertEqualDiff('''[INFO @client] target side of local sync ready
+[INFO @client] @client/addressbook: starting first time sync, two-way (peer is server)
 [INFO @client] @client/calendar: starting first time sync, two-way (peer is server)
 [INFO @client] creating complete data backup of source addressbook before sync (enabled with dumpData and needed for printChanges)
 @client data changes to be applied during synchronization:
@@ -7083,7 +7088,7 @@
 
 Synchronization successful.
 
-Changes applied during synchronization:
+Changes applied during synchronization (@client):
 +---------------|-----------------------|-----------------------|-CON-+
 |               |        @client        |       @default        | FLI |
 |        Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS |
@@ -7161,7 +7166,8 @@
         self.assertEqual(err, None)
         self.assertEqual(0, code)
         out = self.stripSyncTime(out)
-        self.assertEqualDiff('''[INFO @client] @client/addressbook: starting normal sync, two-way (peer is server)
+        self.assertEqualDiff('''[INFO @client] target side of local sync ready
+[INFO @client] @client/addressbook: starting normal sync, two-way (peer is server)
 [INFO @client] @client/calendar: starting normal sync, two-way (peer is server)
 [INFO @client] creating complete data backup of source addressbook before sync (enabled with dumpData and needed for printChanges)
 @client data changes to be applied during synchronization:
@@ -7195,7 +7201,7 @@
 
 Synchronization successful.
 
-Changes applied during synchronization:
+Changes applied during synchronization (@client):
 +---------------|-----------------------|-----------------------|-CON-+
 |               |        @client        |       @default        | FLI |
 |        Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS |
@@ -7267,7 +7273,8 @@
         self.assertEqual(err, None)
         self.assertEqual(0, code)
         out = self.stripSyncTime(out)
-        self.assertEqualDiff('''[INFO @client] @client/addressbook: starting normal sync, two-way (peer is server)
+        self.assertEqualDiff('''[INFO @client] target side of local sync ready
+[INFO @client] @client/addressbook: starting normal sync, two-way (peer is server)
 [INFO @client] @client/calendar: starting normal sync, two-way (peer is server)
 [INFO @client] creating complete data backup of source addressbook before sync (enabled with dumpData and needed for printChanges)
 @client data changes to be applied during synchronization:
@@ -7313,7 +7320,7 @@
 
 Synchronization successful.
 
-Changes applied during synchronization:
+Changes applied during synchronization (@client):
 +---------------|-----------------------|-----------------------|-CON-+
 |               |        @client        |       @default        | FLI |
 |        Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS |
@@ -7388,7 +7395,8 @@
         self.assertEqual(err, None)
         self.assertEqual(0, code)
         out = self.stripSyncTime(out)
-        self.assertEqualDiff('''[INFO @client] @client/addressbook: starting normal sync, two-way (peer is server)
+        self.assertEqualDiff('''[INFO @client] target side of local sync ready
+[INFO @client] @client/addressbook: starting normal sync, two-way (peer is server)
 [INFO @client] @client/calendar: starting normal sync, two-way (peer is server)
 [INFO @client] creating complete data backup of source addressbook before sync (enabled with dumpData and needed for printChanges)
 @client data changes to be applied during synchronization:
@@ -7434,7 +7442,7 @@
 
 Synchronization successful.
 
-Changes applied during synchronization:
+Changes applied during synchronization (@client):
 +---------------|-----------------------|-----------------------|-CON-+
 |               |        @client        |       @default        | FLI |
 |        Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS |
@@ -7509,6 +7517,7 @@
         self.assertEqual(0, code)
         out = self.stripSyncTime(out)
         self.assertEqualDiff('''[INFO] @default/calendar: inactive
+[INFO @client] target side of local sync ready
 [INFO @client] @client/calendar: inactive
 [INFO @client] @client/addressbook: starting normal sync, two-way (peer is server)
 [INFO @client] creating complete data backup of source addressbook before sync (enabled with dumpData and needed for printChanges)
@@ -7530,7 +7539,7 @@
 
 Synchronization successful.
 
-Changes applied during synchronization:
+Changes applied during synchronization (@client):
 +---------------|-----------------------|-----------------------|-CON-+
 |               |        @client        |       @default        | FLI |
 |        Source | NEW | MOD | DEL | ERR | NEW | MOD | DEL | ERR | CTS |

Reply to: