On Tue, 2012-08-21 at 11:31 +0200, Patrick Ohly wrote:
> For the PBAP caching mechanism in SyncEvolution I'd like to use the
> Synthesis engine. I think that makes sense because the engine does a few
> things which would have to be done manually otherwise (translate between
> formats, finding pairs). The improvements below would also make sense in
> other use cases.
> 
> At the moment the engine (or rather, SyncML) lacks a few things which I
> need to change. I'm focusing on one-way-from-client syncing because the
> server has a bit more control over what it does and because it matches
> the SyncEvolution use case.
> 
> In SyncML, incremental one-way syncs fall back to a two-way slow sync
> after a failure. If the server has an item which the client no longer
> has, then the item will be recreated on the client, despite the
> intention to only transfer data in one direction. Right?

As Lukas mentioned in another mail thread ("one-way sync + sync tokens
not updated"), the one-way sync modes are in fact meant to be part of a
two-way sync setup. They just temporarily disable sending changes in one
direction. With that intention in mind the current behavior (falling
back to two-way slow sync) makes sense again.

However, SyncEvolution doesn't support these modes properly, because its
change tracking only supports reporting of changes since the last sync.
That's a problem for another time, let's focus on one-way syncing for
PBAP.

> As a first step I would disable sending changes to the client if the
> client asked for a one-way-from-client sync. The server's datastore can
> already be marked read-only (<readonly> config option, SETREADONLY());
> something similar for the peer's datastore would make sense. Then I
> could leave the default behavior as it is and use a script function to
> trigger the desired behavior.
> 
> Actually, there is SETREFRESHONLY(). According to the docs, it is meant
> to be used for turning a two-way sync requested by the client into a
> refresh-from-remote. I need to check whether I can use that or still
> need to add/modify something.

No, SETREFRESHONLY() just changes to traditional one-way sync.

> Suppose a slow sync was done in that modified refresh-only mode. Any
> item that the server has which are not on the client need to be removed
> if the server's storage is meant to mirror the client.
> 
> At the moment, the engine cannot know whether it is meant to mirror the
> data and thus it will leave the extra items on the server unchanged. I
> intend to add a "<mirror>" config option similar to "<readonly>". If
> set, the engine will not only avoid sending changes to the client, it
> will also remove those extra local items.

I've done it a bit differently: instead of adding a config option,
there's now only a macro call. The full code is in the "pbap" branch. I
also pushed the patches which use this feature into the "pbap"
SyncEvolution branch.

I'm attaching the libsynthesis patches for review. Lukas, does this make
sense? Can it get included upstream?

If yes, then I would rebase onto your latest code first. I held back
with that because I wanted to release 1.3 with minimal changes.

> Finally, during a slow sync where a match was found, extra properties in
> the server's copy of an item must be removed to avoid keeping stale
> data. This can already be done by configuring the merging of items
> accordingly (instead of preserving as much data as possible, ensure that
> the client's item is always considered the "winning" one and that its
> version completely overwrites the server's).

Also done and attached.

It will be necessary to revise this handling a bit once more to cover
another PBAP use case: sometimes updating the cache is done with photos,
sometimes without (to reduce the amount of Bluetooth traffic). In the
later case the local PHOTO property must be preserved. Currently it gets
removed.

-- 
Best Regards, Patrick Ohly

The content of this message is my personal opinion only and although
I am an employee of Intel, the statements I make here in no way
represent Intel's position on the issue, nor am I authorized to speak
on behalf of Intel on this matter.

>From 758c470e929594f7f5330758103fe27c285fa657 Mon Sep 17 00:00:00 2001
From: Patrick Ohly <patrick.o...@intel.com>
Date: Thu, 23 Aug 2012 13:50:36 +0200
Subject: [PATCH 1/3] engine: initial support for caching of remote data

This patch introduces support for true one-way syncing ("caching"):
the local datastore is meant to be an exact copy of the data on the
remote side. The assumption is that no modifications are ever made
locally outside of syncing. This is different from one-way sync modes,
which allows local changes and only temporarily disables sending them
to the remote side.

Another goal of the new mode is to avoid data writes as much as
possible.

This new mode only works on the server side of a sync, where the
engine has enough control over the data flow. It has to be enabled in
an <alertscript> with the new SETCACHEDATA() method, similar to the
way how local read-only mode gets enabled with SETREADONLY(). A
CACHEDATA() query method also gets added.

A SyncML client cannot enable caching mode when alerting the server,
nor can the server alert a client in that mode. The intended usage is
that the caching mode is configured in the layer above the engine and
then gets enabled in the engine after initiating a normal slow or
incremental sync.

Once the mode is enabled, the SyncML server behaves differently as
follows:
- The local sync set is always initialized, even in refresh-from-client
  syncs. That's because the data is intended to be reused during
  refresh-from-client syncs.
- A refresh-from-client is treated like a slow sync: the mapping is
  cleared and remote items are matched.
- After receiving and matching all remote items in a slow sync or
  refresh-from-client sync, any local item that was not matched
  (= its status is still "to be added remotely") gets deleted
  because it no longer exists on the remote side.

The behavior of matching items in caching mode still needs to be
changed to achieve the desired behavior. This is in a separate patch.
---
 src/DB_interfaces/api_db/pluginapids.cpp |    4 +++-
 src/sysync/customimplds.cpp              |   15 +++++++++------
 src/sysync/customimplds.h                |    2 ++
 src/sysync/localengineds.cpp             |   23 ++++++++++++++++++++++-
 src/sysync/localengineds.h               |    5 +++++
 src/sysync/stdlogicds.cpp                |   29 ++++++++++++++++++++++++++++-
 src/sysync/stdlogicds.h                  |    2 ++
 7 files changed, 71 insertions(+), 9 deletions(-)

diff --git a/src/DB_interfaces/api_db/pluginapids.cpp b/src/DB_interfaces/api_db/pluginapids.cpp
index 2300dc9..334d242 100755
--- a/src/DB_interfaces/api_db/pluginapids.cpp
+++ b/src/DB_interfaces/api_db/pluginapids.cpp
@@ -1069,7 +1069,7 @@ localstatus TPluginApiDS::apiReadSyncSet(bool aNeedAll)
   // we don't need to load the syncset if we are only refreshing from remote
   // but we also must load it if we can't zap without it on slow refresh, or when we can't retrieve items on non-slow refresh
   // (we won't retrieve anything in case of slow refresh, because after zapping there's nothing left by definition)
-  if (!fRefreshOnly || (fSlowSync && apiNeedSyncSetToZap()) || (!fSlowSync && implNeedSyncSetToRetrieve())) {
+  if (!fRefreshOnly || (fRefreshOnly && fCacheData) || (fSlowSync && apiNeedSyncSetToZap()) || (!fSlowSync && implNeedSyncSetToRetrieve())) {
     SYSYNC_TRY {
       // true for initial ReadNextItem*() call, false later on
       bool firstReadNextItem=true;
@@ -1202,6 +1202,8 @@ localstatus TPluginApiDS::apiReadSyncSet(bool aNeedAll)
     SYSYNC_CATCH (...)
       dberr=LOCERR_EXCEPTION;
     SYSYNC_ENDCATCH
+  } else {
+    PDEBUGPRINTFX(DBG_DATA+DBG_EXOTIC,("skipped reading sync set because of refresh-from-peer sync"));
   } // if we need the syncset at all
 endread:
   // then end read here
diff --git a/src/sysync/customimplds.cpp b/src/sysync/customimplds.cpp
index 069c9ba..fff6625 100755
--- a/src/sysync/customimplds.cpp
+++ b/src/sysync/customimplds.cpp
@@ -1844,7 +1844,7 @@ void TCustomImplDS::implMarkOnlyUngeneratedForResume(void)
           }
         }
       }
-      else if (!isRefreshOnly()) {
+      else if (!isRefreshOnly() || (isRefreshOnly() && isCacheData())) {
         // not slow sync, and not refresh from remote only - mark those that are actually are involved
         if (pos!=fMapTable.end()) {
           // known item, needs a mark only if record is modified (and updates reported at all)
@@ -2110,7 +2110,7 @@ localstatus TCustomImplDS::implGetItem(
   TMultiFieldItem *myitemP=NULL;
 
   // short-cut if refreshing only and not slowsync resuming (then we need the items for comparison)
-  if (isRefreshOnly() && !(isResuming() && isSlowSync()))
+  if (isRefreshOnly() && !isCacheData() && !(isResuming() && isSlowSync()))
     return sta; // aEof is set, return nothing
 
   TP_DEFIDX(li);
@@ -2249,7 +2249,7 @@ localstatus TCustomImplDS::implGetItem(
                 // this item apparently was already slow-sync-matched before the suspend - still show it for reference to avoid re-adding it
                 sop=sop_reference_only;
               }
-              else if (!isRefreshOnly()) {
+              else if (!isRefreshOnly() || (isRefreshOnly() && isCacheData())) {
                 // item is already in map: check if this is an already detected, but unfinished add
                 if (!((*pos).mapflags & mapflag_pendingAddConfirm)) {
                   // is a replace (not an add): changed if mod date newer or resend flagged (AND updates enabled)
@@ -2337,7 +2337,10 @@ localstatus TCustomImplDS::implGetItem(
               // session, so just leave it out for now)
               // Note: this is first-time add detection. If we get this reported as sop_wants_add below, a map with
               //       mapflag_pendingAddConfirm will be created for it.
-              if (isRefreshOnly()) {
+              if (isRefreshOnly() && isCacheData()) {
+                PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,("New item (no map yet) detected during Refresh only -> ignore for now, will be deleted later unless matched against peer item"));
+                sop=sop_none;
+              } else if (isRefreshOnly()) {
                 PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,("New item (no map yet) detected during Refresh only -> ignore for now, will be added in next two-way sync"));
                 sop=sop_none;
               }
@@ -2532,7 +2535,7 @@ localstatus TCustomImplDS::implStartDataWrite()
         // - transaction starts implicitly when first INSERT / UPDATE / DELETE occurs
         // - resumed slow refreshes must NOT zap the sync set again!
         // - prevent zapping when datastore is in readonly mode!
-        if (fRefreshOnly && fSlowSync && !isResuming() && !fReadOnly) {
+        if (fRefreshOnly && !fCacheData && fSlowSync && !isResuming() && !fReadOnly) {
           // - make sure we have at least one pev_deleting event, in case app tracks it to see if session caused changes to DB
           DB_PROGRESS_EVENT(this,pev_deleting,0,0,0);
           // now, we need to zap the DB first
@@ -3163,7 +3166,7 @@ localstatus TCustomImplDS::implSaveEndOfSession(bool aUpdateAnchors)
   PDEBUGBLOCKCOLL("SaveEndOfSession");
   // update TCustomImplDS dsSavedAdmin variables (other levels have already updated their variables
   if (aUpdateAnchors) {
-    if (!fRefreshOnly || fSlowSync) {
+    if (!fRefreshOnly || (fRefreshOnly && fCacheData) || fSlowSync) {
       // This was really a two-way sync or we implicitly know that
       // we are now in sync with remote (like after one-way-from-remote refresh = reload local)
       #ifdef BASED_ON_BINFILE_CLIENT
diff --git a/src/sysync/customimplds.h b/src/sysync/customimplds.h
index de2200f..6f70b0b 100755
--- a/src/sysync/customimplds.h
+++ b/src/sysync/customimplds.h
@@ -688,6 +688,8 @@ protected:
     TSyncItem &aItem,         // the item
     TStatusCommand &aStatusCommand
   );
+  virtual localstatus logicDeleteItemByID(TSyncItem &aSyncItem) { return apiDeleteItem((TMultiFieldItem &)aSyncItem); }
+
   /// process item (according to operation: add/delete/replace - and for future: copy/move)
   virtual bool implProcessItem(
     TSyncItem *aItemP,         // the item
diff --git a/src/sysync/localengineds.cpp b/src/sysync/localengineds.cpp
index 095b327..f6c5be4 100644
--- a/src/sysync/localengineds.cpp
+++ b/src/sysync/localengineds.cpp
@@ -360,6 +360,22 @@ public:
   }; // func_SetRefreshOnly
 
 
+  // integer CACHEDATA()
+  // returns true if sync is refreshing local data in caching mode (without deleting everything beforehand)
+  static void func_CacheData(TItemField *&aTermP, TScriptContext *aFuncContextP)
+  {
+    aTermP->setAsBoolean(
+      static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->isCacheData()
+    );
+  }; // func_CacheData
+  static void func_SetCacheData(TItemField *&aTermP, TScriptContext *aFuncContextP)
+  {
+    static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->engSetCacheData(
+      aFuncContextP->getLocalVar(0)->getAsBoolean()
+    );
+  }; // func_SetCacheData
+
+
   // integer READONLY()
   // returns true if sync is read-only (only reading from local datastore)
   static void func_ReadOnly(TItemField *&aTermP, TScriptContext *aFuncContextP)
@@ -564,6 +580,8 @@ const TBuiltInFuncDef DBFuncDefs[] = {
   { "FORCESLOWSYNC", TLDSfuncs::func_ForceSlowSync, fty_none, 0, NULL },
   { "REFRESHONLY", TLDSfuncs::func_RefreshOnly, fty_integer, 0, NULL },
   { "SETREFRESHONLY", TLDSfuncs::func_SetRefreshOnly, fty_none, 1, param_IntArg },
+  { "CACHEDATA", TLDSfuncs::func_CacheData, fty_integer, 0, NULL },
+  { "SETCACHEDATA", TLDSfuncs::func_SetCacheData, fty_none, 1, param_IntArg },
   { "READONLY", TLDSfuncs::func_ReadOnly, fty_integer, 0, NULL },
   { "SETREADONLY", TLDSfuncs::func_SetReadOnly, fty_none, 1, param_IntArg },
   { "FIRSTTIMESYNC", TLDSfuncs::func_FirstTimeSync, fty_integer, 0, NULL },
@@ -1170,6 +1188,7 @@ void TLocalEngineDS::InternalResetDataStore(void)
   fForceSlowSync=false;
   fSlowSync=false;
   fRefreshOnly=false;
+  fCacheData=false;
   fReadOnly=false;
   fReportUpdates=fDSConfigP->fReportUpdates; // update reporting according to what is configured
   fCanRestart=fDSConfigP->fCanRestart;
@@ -2468,6 +2487,7 @@ TAlertCommand *TLocalEngineDS::engProcessSyncAlert(
     if (IS_SERVER) {
       // server case: initially we are not in refresh only mode. Alert code or alert script could change this
       fRefreshOnly=false;
+      fCacheData=false;
     }
 
     // save it for suspend and reference in scripts
@@ -2630,12 +2650,13 @@ TAlertCommand *TLocalEngineDS::engProcessSyncAlert(
       }
       // - datastore state is now dss_alerted
       PDEBUGPRINTFX(DBG_HOT,(
-        "Alerted (code=%hd) for %s%s %s%s%s ",
+        "Alerted (code=%hd) for %s%s %s%s%s%s ",
         aAlertCode,
         fResuming ? "Resumed " : "",
         SyncModeDescriptions[fSyncMode],
         fSlowSync ? (fSyncMode==smo_twoway ? "Slow Sync" : "Refresh") : "Normal Sync",
         fReadOnly ? " (Readonly)" : "",
+        fCacheData ? " (Cache)" : "",
         fRefreshOnly ? " (Refreshonly)" : ""
       ));
       CONSOLEPRINTF((
diff --git a/src/sysync/localengineds.h b/src/sysync/localengineds.h
index 8a2b748..1a6885d 100755
--- a/src/sysync/localengineds.h
+++ b/src/sysync/localengineds.h
@@ -335,6 +335,9 @@ protected:
   bool fForceSlowSync;          ///< set if external reason wants to force a slow sync even if it is not needed
   bool fSlowSync;               ///< set if slow sync or refresh
   bool fRefreshOnly;            ///< set if local data is refreshed from remote only, that is, no local changes will be sent to remote (can be set independently of apparent fSyncMode)
+  bool fCacheData;              ///< only relevant if fRefreshOnly is also set: instead of throwing away
+                                /// all data at the start of the sync, apply remote changes and remove
+                                /// all local data that the peer doesn't have
   bool fReadOnly;               ///< if set, datastore will not write any user data (but fake successful status to remote)
   bool fReportUpdates;          ///< if NOT set, datastore will not report updates to client (e.g. for email)
   bool fServerAlerted;          ///< set if sync was server alerted
@@ -581,6 +584,7 @@ public:
   bool isSlowSync(void) { return fSlowSync; }; ///< true if slow sync
   bool isResuming(void) { return fResuming; }; ///< true if resuming a previous session
   bool isRefreshOnly(void) { return fRefreshOnly; }; ///< true if only refreshing with data from remote (no send to remote)
+  bool isCacheData(void) { return fCacheData; };
   bool isReadOnly(void) { return fReadOnly; }; ///< true if only reading from local datastore (and ignoring any updates from remote)
   /// get remote datastore related to this datastore
   TRemoteDataStore *getRemoteDatastore(void) { return fRemoteDatastoreP; };
@@ -653,6 +657,7 @@ public:
   void engForceSlowSync(void) { fForceSlowSync=true; };
   /// set refresh only mode (do not send to remote)
   void engSetRefreshOnly(bool b) { fRefreshOnly=b; };
+  void engSetCacheData(bool b) { fCacheData=b; };
   /// set read only mode (do not receive from remote)
   void engSetReadOnly(bool b) { fReadOnly=b; };
   /// can be called to avoid further ADD commands to be sent to remote (device full case, e.g.)
diff --git a/src/sysync/stdlogicds.cpp b/src/sysync/stdlogicds.cpp
index 1020e54..5945cf2 100644
--- a/src/sysync/stdlogicds.cpp
+++ b/src/sysync/stdlogicds.cpp
@@ -302,7 +302,7 @@ localstatus TStdLogicDS::performStartSync(void)
   fNumRefOnlyItems=0;
   if (sta==LOCERR_OK) {
     // now get data from DB
-    if (!isRefreshOnly() || (isSlowSync() && isResuming())) {
+    if (!isRefreshOnly() || (isRefreshOnly() && isCacheData()) || (isSlowSync() && isResuming())) {
       // not only updating from client, so read all items now
       // Note: for a resumed slow updating from client only, we need the
       //   currently present syncset as well as we need it to detect
@@ -1532,6 +1532,33 @@ localstatus TStdLogicDS::dsBeforeStateChange(TLocalEngineDSState aOldState,TLoca
       sta = startDataWrite();
     }
   } // client
+  if (aNewState==dssta_serverseenclientmods) {
+    // Can only happen in server. Implement removal of unmatched items
+    // when in caching mode.
+    if (fCacheData && fSlowSync) {
+      TSyncItemPContainer::iterator pos=fItems.begin();
+      while (pos!=fItems.end()) {
+        TSyncItem *syncitemP = (*pos);
+        if (syncitemP->getSyncOp() == sop_wants_add) {
+          PDEBUGPRINTFX(DBG_DATA,("caching mode: remove unmatched item %s",
+                                  syncitemP->getLocalID()));
+          syncitemP->setSyncOp(sop_delete);
+          localstatus status = logicDeleteItemByID(*syncitemP);
+          if (status != LOCERR_OK) {
+            return status;
+          } else {
+            TSyncItemPContainer::iterator next = pos;
+            ++next;
+            fItems.erase(pos);
+            pos = next;
+            fLocalItemsDeleted++;
+          }
+        } else {
+          ++pos;
+        }
+      }
+    }
+  }
   if (aNewState==dssta_completed && !isAborted()) {
     // finish writing data now anyway
     endDataWrite();
diff --git a/src/sysync/stdlogicds.h b/src/sysync/stdlogicds.h
index a633a98..1470905 100755
--- a/src/sysync/stdlogicds.h
+++ b/src/sysync/stdlogicds.h
@@ -174,6 +174,8 @@ protected:
     TStatusCommand &aStatusCommand ///< status, must be set on error or non-200-status
   );
 
+  /// delete existing item in datastore, returns 211 if not existing any more
+  virtual localstatus logicDeleteItemByID(TSyncItem &aSyncItem) = 0;
   /// @}
 
 
-- 
1.7.10.4

>From 72033969e09934c4630fba20585f8b47c85358e8 Mon Sep 17 00:00:00 2001
From: Patrick Ohly <patrick.o...@intel.com>
Date: Fri, 24 Aug 2012 15:17:40 +0200
Subject: [PATCH 2/3] engine: ignore unassigned entries at end of array

The engine treated an array with unassigned entries at the end as
larger (and thus different) from an array that ends before those
entries, although reading those entries would also have return
"unassigned".

This caused the engine to treat items as different although there were
semantically identical. Found while testing caching mode with
SyncEvolution, where it is critical to minimize writing to the cache.

The new code checks the larger array to see if all the extra entries
are unassigned, in which case it returns "equal" as result instead of
the previous "larger" or "smaller".
---
 src/sysync/itemfield.cpp |   29 +++++++++++++++++++++++++++--
 1 file changed, 27 insertions(+), 2 deletions(-)

diff --git a/src/sysync/itemfield.cpp b/src/sysync/itemfield.cpp
index b7f68ed..be4381b 100644
--- a/src/sysync/itemfield.cpp
+++ b/src/sysync/itemfield.cpp
@@ -466,8 +466,33 @@ sInt16 TArrayField::compareWith(TItemField &aItemField, bool aCaseInsensitive)
   }
   // all compared fields are equal
   if (mysz==othersz) return 0; // same size : equal
-  // larger array is greater
-  return mysz>othersz ? 1 : -1;
+  // Sizes differ, but that only matters if the extra entries are
+  // actually assigned. Without that special case, we end up
+  // with the situation where parsing, encoding and parsing
+  // again leads to different fields:
+  // - EMAIL;TYPE=OTHER:foo -> EMAIL_FLAGS 1 entry "unassigned"
+  // - unassigned -> EMAIL:foo
+  // - EMAIL:foo -> EMAIL_FLAGS 0 entry
+  // - EMAIL_FLAGS 1 entry "unassigned" > EMAIL_FLAGS 0 entry
+  TItemField &largerField = mysz < othersz ? aItemField : *this;
+  sInt16 minsz, maxsz;
+  sInt16 res; // larger array is greater
+  if (mysz < othersz) {
+    minsz = mysz;
+    maxsz = othersz;
+    res = -1;
+  } else {
+    minsz = othersz;
+    maxsz = mysz;
+    res = 1;
+  }
+  for (sInt16 idx=minsz; idx<maxsz; idx++) {
+    if (largerField.getArrayField(idx)->isAssigned())
+      // found real difference
+      return res;
+  }
+  // larger array contains only extra unassigned entries, ignore them
+  return 0;
 } // TArrayField::compareWith
 
 /* end of TArrayField implementation */
-- 
1.7.10.4

>From de7ff9fdc76a443ff87372ca1d91e22ce5e85236 Mon Sep 17 00:00:00 2001
From: Patrick Ohly <patrick.o...@intel.com>
Date: Fri, 24 Aug 2012 15:33:50 +0200
Subject: [PATCH 3/3] engine: override merge options

In caching mode, the local data is meant to be an exact copy of the
client's data. If the client removes fields in an item and then does a
slow sync, no fields of a matching server item are meant to be
preserved (= merged).

This can be achieved with merge=no in the field list. But the field
list is static and shared between data stores, which might not all
operate in caching mode. This makes updating the config cumbersome.

Therefore this patch adds a flag to the merging functions instead
which overrides the configured behavior such that fields are always
copied from one side into the other. This is activated when in caching
mode and also (optionally) via the MERGEITEMS() function.

The comparison is still needed, to determine whether anything changed
at all during a slow sync and prevent unnecessary database writes.

The loosing side is always the server in caching mode, no need to
check (similar to making the local store read-only).

This all applies to "slow sync" and "conflict" code paths.

In caching mode, a <mergescript> is currently ignored in favor of
doing the intended copying directly. Needs to be fixed.
---
 src/sysync/localengineds.cpp      |   20 ++++++++++++++++----
 src/sysync/multifielditem.cpp     |   26 ++++++++++++++++----------
 src/sysync/multifielditem.h       |    4 ++--
 src/sysync/multifielditemtype.cpp |    7 +++++--
 src/sysync/syncitem.h             |    7 ++++++-
 5 files changed, 45 insertions(+), 19 deletions(-)

diff --git a/src/sysync/localengineds.cpp b/src/sysync/localengineds.cpp
index f6c5be4..c81c412 100644
--- a/src/sysync/localengineds.cpp
+++ b/src/sysync/localengineds.cpp
@@ -5652,6 +5652,10 @@ bool TLocalEngineDS::engProcessRemoteItemAsServer(
             PDEBUGPRINTFX(DBG_DATA,("Read-Only or IgnoreUpdate: server always wins"));
             crstrategy=cr_server_wins;
           }
+          else if (fCacheData) {
+            PDEBUGPRINTFX(DBG_DATA,("Caching data: client always wins"));
+            crstrategy=cr_client_wins;
+          }
           else {
             // two-way
             crstrategy = fItemConflictStrategy; // get conflict strategy pre-set for this item
@@ -5849,7 +5853,7 @@ bool TLocalEngineDS::engProcessRemoteItemAsServer(
             aStatusCommand.setStatusCode(208); // client wins
             fConflictsClientWins++;
             // - attempt to merge data from loosing item (accumulating fields)
-            if (!deleteconflict) {
+            if (!fCacheData && !deleteconflict) {
               aSyncItemP->mergeWith(*conflictingItemP,changedincoming,changedexisting,this);
             }
             if (changedincoming) {
@@ -5977,11 +5981,19 @@ bool TLocalEngineDS::engProcessRemoteItemAsServer(
             }
             // if adds prevented, we cannot duplicate, let server win
             if (fPreventAdd && crstrategy==cr_duplicate) crstrategy=cr_server_wins;
+            else if (fCacheData) crstrategy=cr_client_wins;
             // now execute chosen strategy
             if (crstrategy==cr_client_wins) {
-              // - merge server's data into client item
-              PDEBUGPRINTFX(DBG_DATA,("Trying to merge some data from loosing server item into winning client item"));
-              aSyncItemP->mergeWith(*matchingItemP,changedincoming,changedexisting,this);
+              if (fCacheData) {
+                // - merge server's data into client item
+                PDEBUGPRINTFX(DBG_DATA,("Caching: copying winning client item into loosing server item"));
+                aSyncItemP->mergeWith(*matchingItemP,changedincoming,changedexisting,this,
+                                      TSyncItem::MERGE_OPTION_CHANGE_OTHER);
+              } else {
+                // - merge server's data into client item
+                PDEBUGPRINTFX(DBG_DATA,("Trying to merge some data from loosing server item into winning client item"));
+                aSyncItemP->mergeWith(*matchingItemP,changedincoming,changedexisting,this);
+              }
               // only count if server gets updated
               if (changedexisting) fConflictsClientWins++;
               // Note: changedexisting will cause needserverupdate to be set below
diff --git a/src/sysync/multifielditem.cpp b/src/sysync/multifielditem.cpp
index 3436c29..3d00435 100755
--- a/src/sysync/multifielditem.cpp
+++ b/src/sysync/multifielditem.cpp
@@ -1356,15 +1356,15 @@ bool TMultiFieldItem::checkItem(TLocalEngineDS *aDatastoreP)
 // - also updates other item to make sure it is equal to the winning after the merge
 // sets (but does not reset) change status of this and other item.
 // Note that changes of non-relevant fields are not reported here.
-void TMultiFieldItem::mergeWith(TSyncItem &aItem, bool &aChangedThis, bool &aChangedOther, TLocalEngineDS *aDatastoreP)
+void TMultiFieldItem::mergeWith(TSyncItem &aItem, bool &aChangedThis, bool &aChangedOther, TLocalEngineDS *aDatastoreP, int mode)
 {
   TMultiFieldItem *multifielditemP = castToSameTypeP(&aItem);
   if (!multifielditemP) return;
   // do the merge
-  if (fItemTypeP)
+  if (fItemTypeP && mode == MERGE_OPTION_FROM_CONFIG)
     fItemTypeP->mergeItems(*this,*multifielditemP,aChangedThis,aChangedOther,aDatastoreP);
   else
-    standardMergeWith(*multifielditemP,aChangedThis,aChangedOther);
+    standardMergeWith(*multifielditemP,aChangedThis,aChangedOther, mode);
   // show result
   OBJDEBUGPRINTFX(getItemType()->getSession(),DBG_DATA+DBG_CONFLICT,(
     "mergeWith() final status: thisitem: %schanged, otheritem: %schanged (relevant; eqm_none field changes are not indicated)",
@@ -1380,7 +1380,8 @@ void TMultiFieldItem::mergeWith(TSyncItem &aItem, bool &aChangedThis, bool &aCha
 // - also updates other item to make sure it is equal to the winning after the merge
 // returns update status of this and other item. Note that changes of non-relevant fields are
 // not reported here.
-void TMultiFieldItem::standardMergeWith(TMultiFieldItem &aItem, bool &aChangedThis, bool &aChangedOther)
+void TMultiFieldItem::standardMergeWith(TMultiFieldItem &aItem, bool &aChangedThis, bool &aChangedOther,
+                                        int mode)
 {
   // same type of multifield, try to merge
   for (sInt16 i=0; i<fFieldDefinitionsP->numFields(); i++) {
@@ -1401,7 +1402,7 @@ void TMultiFieldItem::standardMergeWith(TMultiFieldItem &aItem, bool &aChangedTh
       bool winning = winningField.isAssigned();
       bool loosing = loosingField.isAssigned();
       // - now decide what to do
-      if (sep!=mem_none) {
+      if (sep!=mem_none && mode == MERGE_OPTION_FROM_CONFIG) {
         // merge enabled
         PDEBUGPRINTFX(DBG_DATA+DBG_CONFLICT,(
           "Field '%s' available and enabled for merging, mode/sep=0x%04hX, %srelevant",
@@ -1483,7 +1484,11 @@ void TMultiFieldItem::standardMergeWith(TMultiFieldItem &aItem, bool &aChangedTh
       //   assignment just passes the proxy)
       if (!mergerelevant) {
         // everything is handled by the field assignment mechanisms
-        loosingField = winningField;
+        if (mode == MERGE_OPTION_CHANGE_THIS) {
+          winningField = loosingField;
+        } else {
+          loosingField = winningField;
+        }
       }
       else if (winningField!=loosingField) {
         // merge relevant fields will get more sophisticated treatment, such
@@ -1498,16 +1503,17 @@ void TMultiFieldItem::standardMergeWith(TMultiFieldItem &aItem, bool &aChangedTh
           FMT_LENGTH_LIMITED(30,wfv.c_str()),FMT_LENGTH_LIMITED(30,lfv.c_str())
         ));
         #endif
-        // update loosing item, too
-        if (loosingField.isShortVers(winningField,fItemTypeP->getFieldOptions(i)->maxsize)) {
+        // update loosing item, too, unless the winning field is shorter or explicitly requested
+        if (mode == MERGE_OPTION_CHANGE_THIS ||
+            loosingField.isShortVers(winningField,fItemTypeP->getFieldOptions(i)->maxsize)) {
           // winning field is short version of loosing field -> loosing field is "better", use it
           winningField=loosingField;
-          if (mergerelevant) aChangedThis=true;
+          aChangedThis=true;
         }
         else {
           // standard case, loosing field is replaced by winning field
           loosingField=winningField;
-          if (mergerelevant) aChangedOther=true;
+          aChangedOther=true;
         }
         // this is some kind of item-level merge as well
         #ifdef SYDEBUG
diff --git a/src/sysync/multifielditem.h b/src/sysync/multifielditem.h
index 0582f66..0cc14bd 100755
--- a/src/sysync/multifielditem.h
+++ b/src/sysync/multifielditem.h
@@ -325,9 +325,9 @@ public:
   // - also updates other item to make sure it is equal to the winning after the merge
   // sets (but does not reset) change status of this and other item.
   // Note that changes of non-relevant fields are not reported here.
-  virtual void mergeWith(TSyncItem &aItem, bool &aChangedThis, bool &aChangedOther, TLocalEngineDS *aDatastoreP);
+  virtual void mergeWith(TSyncItem &aItem, bool &aChangedThis, bool &aChangedOther, TLocalEngineDS *aDatastoreP, int mode = MERGE_OPTION_FROM_CONFIG);
   // standard merge (subset of mergeWith, used if no merge script is defined)
-  void standardMergeWith(TMultiFieldItem &aItem, bool &aChangedThis, bool &aChangedOther);
+  void standardMergeWith(TMultiFieldItem &aItem, bool &aChangedThis, bool &aChangedOther, int mode = MERGE_OPTION_FROM_CONFIG);
   // compare: returns 0 if equal, 1 if this > aItem, -1 if this < aItem
   // SYSYNC_NOT_COMPARABLE if not equal and no ordering known
   virtual sInt16 compareWith(
diff --git a/src/sysync/multifielditemtype.cpp b/src/sysync/multifielditemtype.cpp
index 02d629f..6dd32d9 100755
--- a/src/sysync/multifielditemtype.cpp
+++ b/src/sysync/multifielditemtype.cpp
@@ -189,12 +189,15 @@ public:
   }; // func_IgnoreUpdate
 
 
-  // void MERGEFIELDS()
+  // void MERGEFIELDS(mode = 0)
+  // Optional mode parameter determines the result of the merge.
+  // 0 = according to config, 1 = loosing item is overwritten, 2 = winning item is overwritten
   static void func_MergeFields(TItemField *&aTermP, TScriptContext *aFuncContextP)
   {
     TMultiFieldItemType *mfitP = static_cast<TMultiFieldItemType *>(aFuncContextP->getCallerContext());
     if (mfitP->fFirstItemP)
-      mfitP->fFirstItemP->standardMergeWith(*(mfitP->fSecondItemP),mfitP->fChangedFirst,mfitP->fChangedSecond);
+      mfitP->fFirstItemP->standardMergeWith(*(mfitP->fSecondItemP),mfitP->fChangedFirst,mfitP->fChangedSecond,
+                                            aFuncContextP->getLocalVar(0)->getAsInteger());
   }; // func_MergeFields
 
 
diff --git a/src/sysync/syncitem.h b/src/sysync/syncitem.h
index df40c15..ae455c9 100755
--- a/src/sysync/syncitem.h
+++ b/src/sysync/syncitem.h
@@ -126,13 +126,18 @@ public:
   virtual bool replaceDataFrom(TSyncItem & /* aItem */, bool /* aAvailableOnly */=false, bool /* aDetectCutoffs */=false, bool /* aAssignedOnly */=false, bool /* aTransferUnassigned */=false) { return true; }; // no data -> nop
   // check item before processing it
   virtual bool checkItem(TLocalEngineDS * /* aDatastoreP */) { return true; }; // default is: ok
+  enum {
+    MERGE_OPTION_FROM_CONFIG,   /**< merge as defined in the field list */
+    MERGE_OPTION_CHANGE_OTHER,  /**< ensure that the other item is the same as this item */
+    MERGE_OPTION_CHANGE_THIS    /**< ensure that this items is the same as the other */
+  };
   // merge this item with specified item.
   // Notes:
   // - specified item is treated as loosing item, this item is winning item
   // - also updates other item to make sure it is equal to the winning after the merge
   // sets (but does not reset) change status of this and other item.
   // Note that changes of non-relevant fields are not reported here.
-  virtual void mergeWith(TSyncItem & /* aItem */, bool &aChangedThis, bool &aChangedOther, TLocalEngineDS * /* aDatastoreP */) { aChangedThis=false; aChangedOther=false; }; // nop by default
+  virtual void mergeWith(TSyncItem & /* aItem */, bool &aChangedThis, bool &aChangedOther, TLocalEngineDS * /* aDatastoreP */, int mode = MERGE_OPTION_FROM_CONFIG) { aChangedThis=false; aChangedOther=false; }; // nop by default
   // remote and local ID
   string fRemoteID; // ID in remote party (if this is a server: LUID, GUID otherwise)
   string fLocalID;  // ID in this party (if this is a server: GUID, LUID otherwise)
-- 
1.7.10.4

_______________________________________________
SyncEvolution mailing list
SyncEvolution@syncevolution.org
http://lists.syncevolution.org/listinfo/syncevolution

Reply via email to