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