cbfafe2077bbe808269ff5fa64ee2af0fb19f512
[platform/upstream/syncevolution.git] / src / synthesis / src / sysync / stdlogicds.cpp
1 /**
2  *  @File     stdlogicds.cpp
3  *
4  *  @Author   Lukas Zeller (luz@synthesis.ch)
5  *
6  *  @brief TStdLogicDS
7  *    Standard database logic implementation, suitable for most (currently all)
8  *    actual DS implementations, but takes as few assumptions about datastore
9  *    so for vastly different sync patterns, this could be replaced by differnt locic
10  *
11  *    Copyright (c) 2001-2009 by Synthesis AG (www.synthesis.ch)
12  *
13  *  @Date 2005-09-15 : luz : created from custdbdatastore
14  */
15 /*
16  */
17
18 // includes
19 #include "prefix_file.h"
20
21 #include "sysync.h"
22 #include "stdlogicds.h"
23 #include "multifielditem.h"
24 #include "multifielditemtype.h"
25
26 using namespace sysync;
27
28 namespace sysync {
29
30 /*
31  * Implementation of TStdLogicDS
32  */
33
34 /* public TStdLogicDS members */
35
36
37 TStdLogicDS::TStdLogicDS(
38   TLocalDSConfig *aDSConfigP,
39   sysync::TSyncSession *aSessionP,
40   const char *aName,
41   long aCommonSyncCapMask
42 ) :
43   TLocalEngineDS(aDSConfigP, aSessionP, aName, aCommonSyncCapMask)
44   #ifdef MULTI_THREAD_DATASTORE
45   ,fStartSyncStatus(aSessionP) // a thread-private status command to store status ocurring during threaded startDataAccessForServer()
46   #endif
47 {
48   InternalResetDataStore();
49   fMultiThread= fSessionP->getSessionConfig()->fMultiThread; // get comfort pointer
50 } // TStdLogicDS::TStdLogicDS
51
52
53 TStdLogicDS::~TStdLogicDS()
54 {
55   InternalResetDataStore();
56 } // TStdLogicDS::~TStdLogicDS
57
58
59 void TStdLogicDS::InternalResetDataStore(void)
60 {
61   // reset
62   fFirstTimeSync=false;
63   fWriteStarted=false;
64   fPreviousSyncTime=0;
65   fCurrentSyncTime=0;
66
67   #ifdef MULTI_THREAD_DATASTORE // combined ifdef/flag
68   if (fMultiThread) {
69     // make sure background processing aborts if it is in progress
70     fStartSyncThread.terminate(); // request soft termination of thread
71     if (!fStartSyncThread.waitfor()) {
72       // has not already terminated
73       PDEBUGPRINTFX(DBG_HOT,("******** Waiting for background thread to terminate"));
74       fStartSyncThread.waitfor(-1); // wait forever or until thread really terminates
75       PDEBUGPRINTFX(DBG_HOT,("******** Background thread terminated"));
76     } // if
77   } // if
78   #endif
79   // we are not initializing
80   fInitializing=false;
81   // no start init request yet
82   fStartInit=false;
83   #if !defined(SYSYNC_CLIENT) || defined(CLIENT_USES_SERVER_DB)
84   // remove all items
85   TSyncItemPContainer::iterator pos;
86   for (pos=fItems.begin(); pos!=fItems.end(); ++pos) {
87     delete *pos;
88   }
89   fItems.clear(); // clear list
90   #endif
91   #ifndef SYSYNC_CLIENT
92   fNumRefOnlyItems=0;
93   #endif
94 } // TStdLogicDS::InternalResetDataStore
95
96
97 // Internal events during sync to access local database
98 // ====================================================
99
100
101 // called to make admin data ready
102 localstatus TStdLogicDS::logicMakeAdminReady(cAppCharP aDataStoreURI, cAppCharP aRemoteDBID)
103 {
104   PDEBUGBLOCKFMTCOLL(("MakeAdminReady","Making Admin Data ready to check sync anchors","localDB=%s|remoteDB=%s",aDataStoreURI,aRemoteDBID));
105   // init local anchor strings, because it will not be set on impl level
106   // (impl level only uses timestamps for local anchor)
107   // These will be derived from timestamps below when implMakeAdminReady() is successful
108   fLastLocalAnchor.erase();
109   fNextLocalAnchor.erase();
110   // Updates the following state variables
111   // - from TLocalEngineDS: fLastRemoteAnchor, fLastLocalAnchor, fResumeAlertCode, fFirstTimeSync
112   //   - for client: fPendingAddMaps
113   //   - for server: fTempGUIDMap
114   // - from TStdLogicDS: fPreviousSyncTime, fCurrentSyncTime
115   // - from derived classes: whatever else belongs to dsSavedAdmin and dsCurrentAdmin state
116   localstatus sta = implMakeAdminReady(
117     fSessionP->getRemoteURI(),          // remote device/server URI (device ID)
118     aDataStoreURI,                      // entire relative URI of local datastore
119     aRemoteDBID                         // database ID of remote device/server (=path as sent by remote)
120   );
121   if (sta==LOCERR_OK) {
122     // TStdLogicDS requires implementation to store timestamps to make the local anchors
123     // - regenerate last session's local anchor string from timestamp
124     TimestampToISO8601Str(fLastLocalAnchor,fPreviousSyncTime,TCTX_UTC,false,false);
125     // - create this session's local anchor string from timestamp
126     TimestampToISO8601Str(fNextLocalAnchor,fCurrentSyncTime,TCTX_UTC,false,false);
127     // - check if config has changed since last sync
128     if (fFirstTimeSync || fPreviousSyncTime<=fSessionP->getRootConfig()->fConfigDate) {
129       // remote should see our (probably changed) devInf
130       PDEBUGPRINTFX(DBG_PROTO,("First time sync or config changed since last sync -> remote should see our devinf"));
131       fSessionP->remoteMustSeeDevinf();
132     }
133     // empty saved anchors if first time sync (should be empty anyway, but...)
134     if (fFirstTimeSync) {
135       fLastLocalAnchor.empty();
136       fLastRemoteAnchor.empty();
137     }
138   }
139   PDEBUGENDBLOCK("MakeAdminReady");
140   return sta;
141 } // TStdLogicDS::logicMakeAdminReady
142
143
144
145 // start writing if not already started
146 localstatus TStdLogicDS::startDataWrite()
147 {
148   localstatus sta = LOCERR_OK;
149
150   if (!fWriteStarted) {
151     sta=implStartDataWrite();
152     fWriteStarted = sta==LOCERR_OK; // must be here to prevent recursion as startDataWrite might be called implicitly below
153     #if !defined(SYSYNC_CLIENT) || defined(CLIENT_USES_SERVER_DB)
154     // server-type DB needs post-processing to update map entries (client and server case)
155     TSyncItemPContainer::iterator pos;
156     if (sta==LOCERR_OK) {
157       // Now allow post-processing of all reported items
158       for (pos=fItems.begin(); pos!=fItems.end(); ++pos) {
159         localstatus sta2 = implReviewReadItem(**pos);
160         if (sta2!=LOCERR_OK) sta=sta2;
161       }
162     }
163     #endif
164     #if defined(SYSYNC_CLIENT) && defined(CLIENT_USES_SERVER_DB)
165     /// @todo we don't need the items to remain that long at all - not even for CLIENT_USES_SERVER_DB case
166     fItems.clear(); // empty list
167     #endif // client using server DB
168   }
169   DEBUGPRINTFX(DBG_DATA,("startDataWrite called, status=%hd", sta));
170   return sta;
171 } // TStdLogicDS::startDataWrite
172
173
174 // end writing
175 localstatus TStdLogicDS::endDataWrite(void)
176 {
177   localstatus sta=LOCERR_OK;
178
179   DEBUGPRINTFX(DBG_DATA,(
180     "endDataWrite called, write %s started",
181     fWriteStarted ? "is" : "not"
182   ));
183   // if we commit a write, the session is ok, and we can clear the resume state
184   if (fWriteStarted) {
185     sta = implEndDataWrite();
186     // ended now
187     fWriteStarted=false;
188   }
189   return sta;
190 } // TStdLogicDS::endDataWrite
191
192
193 // - read specific item from database
194 //   Data and missing ID information is filled in from local database
195 bool TStdLogicDS::logicRetrieveItemByID(
196   TSyncItem &aSyncItem, // item to be filled with data from server. Local or Remote ID must already be set
197   TStatusCommand &aStatusCommand // status, must be set on error or non-200-status
198 )
199 {
200   // simply call implementation
201   return implRetrieveItemByID(aSyncItem,aStatusCommand);
202 } // TLocalEngineDS::logicRetrieveItemByID
203
204
205 /// check is datastore is completely started.
206 /// @param[in] aWait if set, call will not return until either started state is reached
207 ///   or cannot be reached within the maximally allowed request processing time left.
208 bool TStdLogicDS::isStarted(bool aWait)
209 {
210   #ifndef SYSYNC_CLIENT
211   // only server has threaded datastores so far
212   if (aWait && fInitializing) {
213     localstatus sta = startDataAccessForServer();
214     if (sta!=LOCERR_OK) {
215       SYSYNC_THROW(TSyncException("startDataAccessForServer failed (when called from isStarted)", sta));
216     }
217   }
218   #endif
219   // if initialisation could not be completed in the first startDataAccessForServer() call
220   // we are not started.
221   return !fInitializing && inherited::isStarted(aWait);
222 } // TStdLogicDS::isStarted
223
224
225 /// called to mark an already generated (but probably not sent or not yet statused) item
226 /// as "to-be-resumed", by localID or remoteID (latter only in server case).
227 /// @note This must be repeatable without side effects, as server must mark/save suspend state
228 ///       after every request (and not just at end of session)
229 void TStdLogicDS::logicMarkItemForResume(cAppCharP aLocalID, cAppCharP aRemoteID, bool aUnSent)
230 {
231   implMarkItemForResume(aLocalID, aRemoteID, aUnSent);
232 } // TStdLogicDS::logicMarkItemForResume
233
234
235 /// called to mark an already sent item as "to-be-resent", e.g. due to temporary
236 /// error status conditions, by localID or remoteID (latter only in server case).
237 void TStdLogicDS::logicMarkItemForResend(cAppCharP aLocalID, cAppCharP aRemoteID)
238 {
239   implMarkItemForResend(aLocalID, aRemoteID);
240 } // TStdLogicDS::logicMarkItemForResend
241
242
243
244 #ifndef SYSYNC_CLIENT
245
246 // Server case
247 // ===========
248
249 // Actual start sync actions in DB. If server supports threaded init, this will
250 // be called in a sub-thread's context
251 localstatus TStdLogicDS::performStartSync(void)
252 {
253   localstatus sta = LOCERR_OK;
254   TP_DEFIDX(li);
255   TP_SWITCH(li,fSessionP->fTPInfo,TP_database);
256   sta = implStartDataRead();
257   fNumRefOnlyItems=0;
258   if (sta==LOCERR_OK) {
259     // now get data from DB
260     if (!isRefreshOnly() || (isSlowSync() && isResuming())) {
261       // not only updating from client, so read all items now
262       // Note: for a resumed slow updating from client only, we need the
263       //   currently present syncset as well as we need it to detect
264       //   re-sent items
265       bool eof;
266       bool changed;
267       PDEBUGBLOCKFMTCOLL(("GetItems","Read items from DB implementation","datastore=%s",getName()));
268       do {
269         // check if external request to terminate loop
270         if (shouldExitStartSync()) {
271           PDEBUGPRINTFX(DBG_ERROR,("performStartSync aborted by external request"));
272           PDEBUGENDBLOCK("GetItems");
273           TP_START(fSessionP->fTPInfo,li);
274           return 510;
275         }
276         // try to read item
277         TSyncItem *myitemP=NULL;
278         // report all items in syncset, not only changes if we need to filter
279         changed=!fFilteringNeededForAll; // let GetItem
280         // now fetch next item
281         sta = implGetItem(eof,changed,myitemP);
282         if (sta!=LOCERR_OK) {
283           implEndDataRead(); // terminate reading
284           PDEBUGENDBLOCK("GetItems");
285           TP_START(fSessionP->fTPInfo,li);
286           return sta;
287         }
288         else {
289           // read successful, test for eof
290           if (eof) break; // reading done
291           if (fSlowSync) changed=true; // all have changed (just in case GetItem does not return clean result here)
292           // NOTE: sop can be sop_reference_only ONLY in case of server resuming a slowsync
293           TSyncOperation sop=myitemP->getSyncOp();
294           // check if we need to do some filtering to determine final syncop
295           // NOTE: call postFetchFiltering even in case we do not actually
296           //       need filtering (but we might need making item pass acceptance filter!)
297           if (sop!=sop_delete && sop!=sop_soft_delete && sop!=sop_archive_delete) {
298             // we need to post-fetch filter the item first
299             bool passes=postFetchFiltering(myitemP);
300             if (!passes) {
301               // item does not pass = does not belong to sync set per now
302               if (!fSlowSync && (sop==sop_wants_replace)) {
303                 // item already exists on remote but falls out of syncset now: delete
304                 // NOTE: This works only if reviewReadItem() is correctly implemented
305                 //       and checks for items that are deleted after being reported
306                 //       something else to delete their local map entry
307                 sop=sop_delete;
308                 myitemP->cleardata(); // also get rid of unneeded data
309               }
310               else sop=sop_none; // ignore all others (especially adds or slowsync replaces)
311             }
312             else {
313               // item passes = belongs to sync set
314               if (sop==sop_wants_replace && !changed && !fSlowSync) {
315                 // exists but has not changed since last sync
316                 sop=sop_none; // ignore for now
317               }
318             }
319           }
320           // check if we should use that item
321           if (sop==sop_none) {
322             delete myitemP;
323             continue; // try next from DB
324           }
325           // set final sop now
326           myitemP->setSyncOp(sop);
327           // %%% these are just-in-case tests for sloppy db interface
328           // - adjust operation for slowsync
329           if (fSlowSync) {
330             if (sop==sop_delete || sop==sop_soft_delete || sop==sop_archive_delete) {
331               // do not process deleted items during slow sync at all
332               delete myitemP; // forget it
333               continue; // Read next item
334             }
335             else {
336               // must be add or replace, will be an add by default (if unmatched)
337               // - set it to sop_wants_add to signal that this item was not matched yet!
338               myitemP->setRemoteID(""); // forget remote ID, is unknown in slow sync anyway
339               if (sop!=sop_reference_only) // if reference only (resumed slowsync), keep it as is
340                 myitemP->setSyncOp(sop_wants_add); // flag it unmatched
341             }
342           }
343           // - now add it to my local list
344           fItems.push_back(myitemP);
345           if (sop==sop_reference_only)
346             fNumRefOnlyItems++; // count these to avoid them being shown in NOC
347         }
348       } while (true); // exit by break
349       PDEBUGENDBLOCK("GetItems");
350     } // not from client only
351     // end reading
352     sta=implEndDataRead();
353     // show items
354     PDEBUGPRINTFX(DBG_HOT,("%s: number of local items involved in %ssync = %ld",getName(), fSlowSync ? "slow " : "",fItems.size()));
355     CONSOLEPRINTF(("  %ld local items are new/changed/deleted for this sync",fItems.size()));
356     if (PDEBUGTEST(DBG_DATA+DBG_DETAILS)) {
357       PDEBUGBLOCKFMTCOLL(("SyncSet","Items involved in Sync","datastore=%s",getName()));
358       for (TSyncItemPContainer::iterator pos=fItems.begin();pos!=fItems.end();pos++) {
359         TSyncItem *syncitemP = (*pos);
360         PDEBUGPRINTFX(DBG_DATA,(
361           "SyncOp=%-20s: LocalID=%15s RemoteID=%15s",
362           SyncOpNames[syncitemP->getSyncOp()],
363           syncitemP->getLocalID(),
364           syncitemP->getRemoteID()
365         ));
366       }
367       PDEBUGENDBLOCK("SyncSet");
368     }
369     // initiate writing and cause reviewing of items now,
370     // before any of them can be modified by sync process
371     // Notes:
372     // - startDataWrite() includes zapping the sync set
373     //   in slow refresh only sessions (not resumed, see next note)
374     // - In case of resumed slow refresh from remote, the sync set
375     //   will not get zapped again, because some items are already there
376     if (sta==LOCERR_OK)
377       sta = startDataWrite(); // private helper
378     // test if ok
379     if (sta != LOCERR_OK) {
380       // failed
381       engAbortDataStoreSync(sta,true);
382       return sta;
383     }
384     TP_START(fSessionP->fTPInfo,li);
385     return sta;
386   } // startDataRead successful
387   else {
388     TP_START(fSessionP->fTPInfo,li);
389     return sta;
390   }
391 } // TStdLogicDS::performStartSync
392
393
394 #ifdef MULTI_THREAD_DATASTORE
395
396 // function executed by thread
397 static uInt32 StartSyncThreadFunc(TThreadObject *aThreadObject, uInt32 aParam)
398 {
399   // parameter passed is pointer to datastore
400   TStdLogicDS *datastoreP = static_cast<TStdLogicDS *>((void *)aParam);
401   // now call routine that actually performs datastore start
402   uInt32 exitCode = (uInt32) (datastoreP->performStartSync());
403   // thread is about to end (has ended for the Impl and Api levels), inform datastore
404   // and post necessary ThreadMayChangeNow() calls
405   datastoreP->endingThread();
406   // return
407   return exitCode;
408 } // StartSyncThreadFunc
409
410
411 bool TStdLogicDS::threadedStartSync(void)
412 {
413   // do this in a separate thread if requested
414   // Note: ThreadMayChangeNow() has been posted already by startingThread()
415   PDEBUGPRINTFX(DBG_HOT,("******* starting background thread for reading sync set..."));
416   fStartSyncStatus.setStatusCode(200); // assume ok
417   if (!fStartSyncThread.launch(StartSyncThreadFunc,(uInt32)this)) { // pass datastoreP as param
418     // starting thread failed
419     PDEBUGPRINTFX(DBG_ERROR,("******* Failed starting background thread for reading sync set"));
420     return false;
421   }
422   #ifdef SYDEBUG
423   // show link to thread log
424   PDEBUGPRINTFX(DBG_HOT,(
425     "******* started &html;<a href=\"%s_%lu%s\" target=\"_blank\">&html;background thread id=%lu&html;</a>&html; for reading sync set",
426     getDbgLogger()->getDebugFilename(), // href base
427     fStartSyncThread.getid(), // plus thread
428     getDbgLogger()->getDebugExt(), // plus extension
429     fStartSyncThread.getid()
430   ));
431   #endif
432   return true; // started ok
433 } // TStdLogicDS::threadedStartSync
434
435
436 // can be called to check if performStartSync() should be terminated
437 bool TStdLogicDS::shouldExitStartSync(void)
438 {
439   // threaded version, check if termination flag was set
440   return fMultiThread && fStartSyncThread.terminationRequested();
441 } // TStdLogicDS::shouldExitStartSync
442
443 #else
444
445 // can be called to check if performStartSync() should be terminated
446 bool TStdLogicDS::shouldExitStartSync(void)
447 {
448   // nonthread version, is never the case
449   return false;
450 } // TStdLogicDS::shouldExitStartSync
451
452 #endif
453
454
455 // called by dsBeforeStateChange to dssta_dataaccessstarted to make sure datastore is
456 // getting ready for being accessed. Also called by isStarted(true) when starting
457 // up took longer than one request max time for threaded datastores
458 localstatus TStdLogicDS::startDataAccessForServer(void)
459 {
460   localstatus sta = LOCERR_OK;
461
462   DEBUGPRINTFX(DBG_HOT,("TStdLogicDS::startDataAccessForServer"));
463   if (!fInitializing) {
464     // The datastore has not started initializing yet
465     // - start initialisation now
466     fWriteStarted=false;
467     // - read all records from DB right now if server data is used at all
468     DEBUGPRINTFX(DBG_DATA,("- number of items in list before StartDataRead = %ld",fItems.size()));
469     // now we can initialize the conflict resolution mode for this session
470     /// @todo move this to localengineds, at point where we get dssta_syncmodestable
471     fSessionConflictStrategy=getConflictStrategy(fSlowSync,fFirstTimeSync);
472     // prepare for read
473     #ifdef SCRIPT_SUPPORT
474     // - call DB init script, which might add extra filters depending on options and remoterule
475     TScriptContext::execute(
476       fDataStoreScriptContextP,
477       getDSConfig()->fDBInitScript,
478       &DBFuncTable, // context's function table
479       this // datastore pointer needed for context
480     );
481     #endif
482     // - init post fetch filtering, sets fFilteringNeededForAll and fFilteringNeeded correctly
483     initPostFetchFiltering();
484     // - now we can start reading (fFilteringNeededForAll can be checked by StartDataRead)
485     fInitializing=true; // we enter the initialisation phase now
486     fStartInit=true; // and we want to start the init
487   }
488   // try starting init now (eventually repeats until it can be done)
489   if (fStartInit) {
490     PDEBUGPRINTFX(DBG_DATA,( "MultiThread %sabled", fMultiThread ? "en":"dis" ));
491     #ifdef MULTI_THREAD_DATASTORE // combined define and flag
492     if (fMultiThread) {
493       // start init thread
494       if (startingThread()) {
495         // we may start a thread here
496         if (threadedStartSync())
497           fStartInit= false; // starting done now
498       } // if
499     } // if
500     #endif
501     if (!fMultiThread) {
502       // Just perform initialisation
503       sta = performStartSync();
504       fStartInit=false; // starting done now
505       fInitializing=false; // initialisation is already complete here
506     } // if
507   }
508
509   #ifdef MULTI_THREAD_DATASTORE // combined define and flag
510   if (fMultiThread) {
511     // wait for started initialisation to finish within time we have left for this request
512     if (!fStartInit && fInitializing) {
513       // initialisation started but not ended so far: wait for it until done or defined request time passed
514       sInt32 t=fSessionP->RemainingRequestTime();
515       if (fStartSyncThread.waitfor(t<0 ? 0 : t * 1000)) {
516         // background thread has terminated
517         sta = fStartSyncThread.exitcode();
518         PDEBUGPRINTFX(DBG_HOT,("******* background thread for startSync() terminated with exit code=%ld, status sta=%hd", fStartSyncThread.exitcode(),sta));
519         // initialisation is now complete
520         fInitializing=false;
521       } // if
522     } // if
523   } // if
524   #endif
525
526   // now complete initialisation if not still initializing in background
527   if (!fStartInit && !fInitializing) {
528     // finished background processing
529     if (sta==LOCERR_OK) {
530       // quick test: if number of items is > than allowed maxid of remote datatstore,
531       // sync is unlikely to succeed
532       if (getRemoteDatastore()->getMaxID()<fItems.size()) {
533         // this will not work, warn (but no longer abort session, as Siemens S55 guys don't like that)
534         CONSOLEPRINTF((
535           "Warning: Synchronisation involves more items (%ld) than client can possibly manage (%ld",
536           (sInt32)fItems.size(),
537           (sInt32)getRemoteDatastore()->getMaxID()
538         ));
539         PDEBUGPRINTFX(DBG_ERROR,(
540           "Warning: Synchronisation involves more items (%ld) than client can possibly manage (%ld)",
541           (sInt32)fItems.size(),
542           (sInt32)getRemoteDatastore()->getMaxID()
543         ));
544       }
545     }
546     // return status of initialisation
547     return sta;
548   }
549   else {
550     // background processing still in progress
551     // - if we are still processing in background, init is ok so far
552     return LOCERR_OK;
553   }
554 } // TStdLogicDS::startDataAccessForServer
555
556
557 // called to check if conflicting replace or delete command from server exists
558 TSyncItem *TStdLogicDS::getConflictingItemByRemoteID(TSyncItem *syncitemP)
559 {
560   // search for conflicting item by LUID
561   TSyncItemPContainer::iterator pos;
562   for (pos=fItems.begin(); pos!=fItems.end(); ++pos) {
563     if (strcmp((*pos)->getRemoteID(),syncitemP->getRemoteID())==0) {
564       // same LUID exists in data from server
565       PDEBUGPRINTFX(DBG_DATA+DBG_CONFLICT,(
566         "TStdLogicDS::getConflictingItemByRemoteID, found RemoteID='%s', LocalID='%s', syncop=%s",
567         syncitemP->getRemoteID(),
568         syncitemP->getLocalID(),
569         SyncOpNames[syncitemP->getSyncOp()]
570       ));
571       return (*pos); // return pointer to item in question
572     }
573   }
574   PDEBUGPRINTFX(DBG_DATA+DBG_CONFLICT,("TStdLogicDS::getConflictingItemByRemoteID, no conflicting item"));
575   return NULL;
576 } // TStdLogicDS::getConflictingItemByRemoteID
577
578
579 // called to check if content-matching item from server exists for slow sync
580 TSyncItem *TStdLogicDS::getMatchingItem(TSyncItem *syncitemP, TEqualityMode aEqMode)
581 {
582   // search for content matching item
583   TSyncItemPContainer::iterator pos;
584   for (pos=fItems.begin(); pos!=fItems.end(); ++pos) {
585     DEBUGPRINTFX(DBG_DATA+DBG_MATCH+DBG_EXOTIC,(
586       "comparing (this) local item localID='%s' with incoming (other) item remoteID='%s'",
587       (*pos)->getLocalID(),
588       syncitemP->getRemoteID()
589     ));
590     if ((*pos)->compareWith(
591       *syncitemP,aEqMode,this
592       #ifdef SYDEBUG
593       ,PDEBUGTEST(DBG_DATA+DBG_MATCH+DBG_EXOTIC) // only show comparison if exotic AND match is enabled
594       #endif
595     )==0) {
596       // items match in content
597       // - check if item is not already matched
598       if ((*pos)->getSyncOp()!=sop_wants_add && (*pos)->getSyncOp()!=sop_reference_only) {
599         // item has already been matched before, so don't match it again
600         DEBUGPRINTFX(DBG_DATA,(
601           "TStdLogicDS::getMatchingItem, match but already used -> skip it: remoteID='%s' = localID='%s'",
602           syncitemP->getRemoteID(),
603           (*pos)->getLocalID()
604         ));
605       }
606       else {
607         // item has not been matched yet (wannabe add or reference-only), return it now
608         PDEBUGPRINTFX(DBG_DATA+DBG_MATCH+DBG_HOT,(
609           "TStdLogicDS::getMatchingItem, found remoteID='%s' is equal in content with localID='%s'",
610           syncitemP->getRemoteID(),
611           (*pos)->getLocalID()
612         ));
613         return (*pos); // return pointer to item in question
614       }
615     }
616   }
617   PDEBUGPRINTFX(DBG_DATA+DBG_MATCH,("TStdLogicDS::getMatchingItem, no matching item"));
618   return NULL;
619 } // TStdLogicDS::getMatchingItem
620
621
622 // - called to prevent item to be sent to client in subsequent generateSyncCommands()
623 //   item in question should be an item that was returned by getConflictingItemByRemoteID() or getMatchingItem()
624 void TStdLogicDS::dontSendItemAsServer(TSyncItem *syncitemP)
625 {
626   PDEBUGPRINTFX(DBG_DATA+DBG_EXOTIC,("Preventing localID='%s' to be sent to client",syncitemP->getLocalID()));
627   syncitemP->setSyncOp(sop_none); // anyway, set to none
628   // delete from list as we don't need it any more
629   TSyncItemPContainer::iterator pos;
630   for (pos=fItems.begin(); pos!=fItems.end(); ++pos) {
631     if (*pos == syncitemP) {
632       // it is in our list
633       PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Item with localID='%s' will NOT be sent to client (usually due to slowsync match)",syncitemP->getLocalID()));
634       delete *pos; // delete item itself
635       fItems.erase(pos); // remove from list
636       break;
637     }
638   }
639 } // TStdLogicDS::dontSendItemAsServer
640
641
642 // - called when a item in the sync set changes its localID (due to local DB internals)
643 //   Datastore must make sure that eventually cached items get updated
644 // - NOTE: derivates must take care of updating map entries as well!
645 void TStdLogicDS::dsLocalIdHasChanged(const char *aOldID, const char *aNewID)
646 {
647   PDEBUGPRINTFX(DBG_DATA,("TStdLogicDS::dsLocalIdHasChanged"));
648   // update in loaded list of items
649   TSyncItemPContainer::iterator pos;
650   for (pos=fItems.begin(); pos!=fItems.end(); ++pos) {
651     if (strcmp((*pos)->getLocalID(),aOldID)==0) {
652       // found item, change it's local ID now
653       (*pos)->setLocalID(aNewID);
654       // make sure internal dependencies get updated
655       (*pos)->updateLocalIDDependencies();
656       // done
657       break;
658     }
659   }
660   // let base class do what is needed to update the item itself
661   inherited::dsLocalIdHasChanged(aOldID, aNewID);
662 } // TStdLogicDS::dsLocalIdHasChanged
663
664
665
666 // - called to have additional item sent to remote
667 void TStdLogicDS::SendItemAsServer(TSyncItem *aSyncitemP)
668 {
669   // add to list of changes
670   fItems.push_back(aSyncitemP);
671 } // TStdLogicDS::SendItemAsServer
672
673
674 // - end map operation (derived class might want to rollback)
675 bool TStdLogicDS::MapFinishAsServer(
676   bool aDoCommit,                // if not set, entire map operation must be undone
677   TStatusCommand &aStatusCommand // status, must be set on error or non-200-status
678 )
679 {
680   // unsuccessful Map will cause rollback of entire datastore transaction
681   if (!aDoCommit) {
682     // bad, abort session
683     engAbortDataStoreSync(510,true); // data store failed, local problem
684   }
685   return true;
686 } // TStdLogicDS::MapFinishAsServer
687
688
689 // - called for SyncML 1.1 if remote wants number of changes.
690 //   Must return -1 if no NOC value can be returned
691 //   NOTE: we implement it here only for server, as it is not really needed
692 //   for clients normally - if it is needed, client's agent must provide
693 //   it as CustDBDatastore has no own list it can use to count in client case.
694 sInt32 TStdLogicDS::getNumberOfChanges(void)
695 {
696   // for server, number of changes is the number of items in the item list
697   // minus those that are for reference only (in a slow sync resume)
698   return fItems.size()-fNumRefOnlyItems;
699 } // TStdLogicDS::getNumberOfChanges
700
701
702 /// @brief called to have all non-yet-generated sync commands as "to-be-resumed"
703 void TStdLogicDS::logicMarkOnlyUngeneratedForResume(void)
704 {
705   // we do not maintain the map/bookmark list at this level, so
706   // derived class (ODBC, BinFile etc.) must make sure that their list is
707   // clean (no marks from previous sessions) before calling this inherited version
708   implMarkOnlyUngeneratedForResume();
709   // Now add those that we have already received from the implementation
710   TSyncItemPContainer::iterator pos;
711   for (pos = fItems.begin(); pos != fItems.end(); ++pos) {
712     // let datastore mark these unprocessed
713     TSyncItem *syncitemP = (*pos);
714     // mark it for resume by ID
715     logicMarkItemForResume(syncitemP->getLocalID(),syncitemP->getRemoteID(),true); // these are unsent
716   }
717 } // TStdLogicDS::logicMarkOnlyUngeneratedForResume
718
719
720
721 // - called to let server generate sync commands for client
722 //   Returns true if now finished (or aborted) for this datastore
723 //   also sets fState to dss_syncdone when finished
724 bool TStdLogicDS::logicGenerateSyncCommandsAsServer(
725   TSmlCommandPContainer &aNextMessageCommands,
726   TSmlCommand * &aInterruptedCommandP,
727   const char *aLocalIDPrefix
728 )
729 {
730   bool alldone=false;
731   bool ignoreitem;
732   uInt32 itemcount=0;
733   // send as many as possible from list of local modifications
734   // sop_want_replace can only be sent if state is already dss_syncfinish
735   TSyncItemPContainer::iterator pos;
736   pos = fItems.begin(); // first item
737   TSyncItemType *itemtypeP = getRemoteReceiveType();
738   POINTERTEST(itemtypeP,("TStdLogicDS::logicGenerateSyncCommandsAsServer: fRemoteReceiveFromLocalTypeP undefined"));
739   #ifdef SYDEBUG
740   if (fMaxItemCount> 0) {
741     PDEBUGPRINTFX(DBG_DATA+DBG_HOT,(
742       "Info: Max number of items to be sent in this session is limited to %ld (already sent by now=%ld)",
743       fMaxItemCount,
744       fItemsSent
745     ));
746   }
747   #endif
748   while (
749     !isAborted() && // not aborted
750     (getDSConfig()->fMaxItemsPerMessage==0 || itemcount<getDSConfig()->fMaxItemsPerMessage==0) && // max item count per message not reached or not active
751     !fSessionP->outgoingMessageFull() && // message not full
752     aNextMessageCommands.size()==0 // no commands already queued for next message
753   ) {
754     // get item to process
755     if (pos == fItems.end()) {
756       alldone=true;
757       break;
758     }
759     //
760     TSyncItem *syncitemP = (*pos);
761     // get sync op to perform
762     TSyncOperation syncop=syncitemP->getSyncOp();
763     // check if we can send the item now (for replaces, we need ALWAYS to wait until client has finished sending)
764     // Note: usually sync engine will not start generating before dssta_serverseenclientmods anyway, but...
765     if (syncop==sop_wants_replace && !testState(dssta_serverseenclientmods)) {
766       // cannot be sent now, take next
767       pos++;
768       continue;
769     }
770     // check if we should ignore this item
771     ignoreitem = syncop==sop_reference_only; // ignore anyway if reference only
772     // further check if not already ignored
773     if (!ignoreitem) {
774       // - check if adding is still allowed
775       if (fRemoteAddingStopped && (syncop==sop_wants_add || syncop==sop_add)) {
776         // adding to remote has been stopped, discard add items
777         PDEBUGPRINTFX(DBG_DATA,(
778           "Suppressed add for item localID='%s' (fRemoteAddingStopped)",
779           syncitemP->getLocalID()
780         ));
781         ignoreitem=true;
782       }
783       #ifdef SYNCML_TAF_SUPPORT
784       // check other reasons to prevent further adds
785       if (syncop==sop_wants_add || syncop==sop_add) {
786         // - check if max number of items has already been reached
787         if (fMaxItemCount!=0 && fItemsSent>=fMaxItemCount) {
788           PDEBUGPRINTFX(DBG_DATA,(
789             "Suppressed add for item localID='%s' (max item count=%ld reached)",
790             syncitemP->getLocalID(),
791             fMaxItemCount
792           ));
793           ignoreitem=true;
794         }
795         // - check if item passes eventual TAF
796         /// %%% (do not filter replaces, as these would not get reported again in the next session)
797         /// @todo: the above is no longer true as we can now have them re-sent in next session,
798         ///        so this must be changed later!!!
799         if (!(
800           syncitemP->testFilter(fTargetAddressFilter.c_str()) &&
801           syncitemP->testFilter(fIntTargetAddressFilter.c_str())
802         )) {
803           PDEBUGPRINTFX(DBG_DATA+DBG_HOT,(
804             "Item localID='%s' does not pass INCLUSIVE filter (TAF) -> Suppressed adding",
805             syncitemP->getLocalID()
806           ));
807           ignoreitem=true;
808         }
809       }
810       #endif
811     }
812     // Now discard if ignored
813     if (ignoreitem) {
814       // remove item from list
815       TSyncItemPContainer::iterator temp_pos = pos++; // make copy and set iterator to next
816       fItems.erase(temp_pos); // now entry can be deleted (N.M. Josuttis, pg204)
817       // delete item itself
818       delete syncitemP;
819       // test next
820       continue;
821     }
822     // add prefixes to ID
823     if (syncitemP->hasLocalID()) {
824       // make sure GUID (plus prefixes) is not exceeding allowed size
825       adjustLocalIDforSize(syncitemP->fLocalID,getRemoteDatastore()->getMaxGUIDSize(),aLocalIDPrefix ? strlen(aLocalIDPrefix) : 0);
826       // add local ID prefix, if any
827       if (aLocalIDPrefix && *aLocalIDPrefix)
828         syncitemP->fLocalID.insert(0,aLocalIDPrefix);
829     }
830     // create sync op command (may return NULL in case command cannot be created, e.g. for MaxObjSize limitations)
831     TSyncOpCommand *syncopcmdP = newSyncOpCommand(syncitemP,itemtypeP);
832     // erase item from list
833     delete syncitemP;
834     pos = fItems.erase(pos);
835     // issue command now
836     // - Note that when command is split, issuePtr returns true, but we still may NOT generate new commands
837     //   as the message is already full now. That's why the while contains a check for message full and aNextMessageCommands size
838     //   (was not the case before 2.1.0.2, which could cause that the first chunk of a subsequent command
839     //   would be sent before the third..nth chunk of the previous command).
840     TSmlCommand *cmdP = syncopcmdP;
841     syncopcmdP=NULL;
842     // eventually, we have a NULL command here (e.g. in case it could not be generated due to MaxObjSize restrictions)
843     if (cmdP) {
844       if (!fSessionP->issuePtr(cmdP,aNextMessageCommands,aInterruptedCommandP)) {
845         alldone=false; // issue failed (no room in message), not finished so far
846         break;
847       }
848       // count item sent
849       fItemsSent++; // overall counter for statistics
850       itemcount++; // per message counter
851       // send event (but no check for abort)
852       OBJ_PROGRESS_EVENT(fSessionP->getSyncAppBase(),pev_itemsent,getDSConfig(),fItemsSent,getNumberOfChanges(),0);
853     }
854   }; // while not aborted and not message full
855   // we are not done until all aNextMessageCommands are also out
856   // Note: this must be specially checked because we now have SyncML 1.1 chunked commands.
857   //   Those issue() fine, but leave a next chunk in the aNextMessageCommands queue.
858   if (alldone && aNextMessageCommands.size()>0) {
859     alldone=false;
860   }
861   // finished when we have done all
862   return (alldone || isAborted());
863 } // TStdLogicDS::logicGenerateSyncCommandsAsServer
864
865
866 // called for servers when receiving map from client
867 localstatus TStdLogicDS::logicProcessMap(cAppCharP aRemoteID, cAppCharP aLocalID)
868 {
869   // simply call implementation
870   return implProcessMap(aRemoteID, aLocalID);
871 } // TStdLogicDS::logicProcessMap
872
873
874
875 #else
876
877 // Client Case
878 // ===========
879
880 // called by dsBeforeStateChange to dssta_dataaccessstarted to make sure datastore is ready for being accessed.
881 localstatus TStdLogicDS::startDataAccessForClient(void)
882 {
883   DEBUGPRINTFX(DBG_HOT,("TStdLogicDS::startDataAccessForClient"));
884   // init
885   fWriteStarted=false;
886   fEoC=false; // not all changes seen yet
887   // prepare for read
888   #ifdef SCRIPT_SUPPORT
889   // - call DB init script, which might add extra filters depending on options and remoterule
890   TScriptContext::execute(
891     fDataStoreScriptContextP,
892     getDSConfig()->fDBInitScript,
893     &DBFuncTable, // context's function table
894     this // datastore pointer needed for context
895   );
896   #endif
897   // - init post fetch filtering, sets fFilteringNeededForAll and fFilteringNeeded correctly
898   initPostFetchFiltering();
899   // - prepare for read
900   localstatus sta=implStartDataRead();
901   return sta;
902 } // TStdLogicDS::startDataAccessForClient
903
904
905 /// @brief called to have all non-yet-generated sync commands as "to-be-resumed"
906 void TStdLogicDS::logicMarkOnlyUngeneratedForResume(void)
907 {
908   // we do not maintain the map/bookmark list at this level, so
909   // derived class (ODBC, BinFile etc.) must make sure that their list is
910   // clean (no marks from previous sessions) before calling this inherited version
911   implMarkOnlyUngeneratedForResume();
912   // in client case, fItems does not contain ungenerated/unprocessed items
913   // so we don't have anything more do here for now
914 } // TStdLogicDS::logicMarkOnlyUngeneratedForResume
915
916
917
918 // called to generate sync sub-commands as client for remote server
919 // @return true if now finished for this datastore
920 bool TStdLogicDS::logicGenerateSyncCommandsAsClient(
921   TSmlCommandPContainer &aNextMessageCommands,
922   TSmlCommand * &aInterruptedCommandP,
923   const char *aLocalIDPrefix
924 )
925 {
926   localstatus sta = LOCERR_OK;
927   bool alldone=true;
928   // send as many changed items as possible
929   TSyncItemType *itemtypeP = getRemoteReceiveType();
930   POINTERTEST(itemtypeP,("TStdLogicDS::logicGenerateSyncCommandsAsClient: fRemoteReceiveFromLocalTypeP undefined"));
931   while (!fEoC && !isAborted() && !fSessionP->outgoingMessageFull() && aNextMessageCommands.size()==0) {
932     // get next item from DB
933     TSyncItem *syncitemP = NULL;
934     #ifdef OBJECT_FILTERING
935     bool changed=!fFilteringNeededForAll; // set if we need all records for later filtering
936     #else
937     bool changed=true; // without filters, always let DB check if modified
938     #endif
939     sta = implGetItem(fEoC,changed,syncitemP);
940     if (sta!=LOCERR_OK) {
941       // fatal error
942       implEndDataRead(); // terminate reading (error does not matter)
943       engAbortDataStoreSync(sta, true); // local problem
944       return false; // not complete
945     }
946     // read successful, test for EoC (end of changes)
947     if (fEoC)
948       break; // reading done
949     // get sync op to perform
950     TSyncOperation syncop=syncitemP->getSyncOp();
951     #ifdef OBJECT_FILTERING
952     // Filtering
953     // - call this anyway (makes sure item is made conformant to remoteAccept filter, even if
954     //   fFilteringNeeded is not set)
955     if (syncop!=sop_delete && syncop!=sop_soft_delete && syncop!=sop_archive_delete) {
956       bool passes=postFetchFiltering(syncitemP);
957       if (fFilteringNeeded) {
958         if (!passes) {
959                 // item does not pass (current) filter: don't send it.
960           // Note that we DO NOT DELETE items falling out of the sync set by filtering,
961           // as for that we'd need to be able to differentiate adds from replaces.
962           // The use case for client-side filtering is also normally not the "moving-subset-window"
963           // case as for server side filtering, but more static exclusion of certain types of
964           // local entries (e.g. to prevent private stuff going to the server).
965           // - we don't need that sync item
966           delete syncitemP;
967           // - try next
968           continue;
969         }
970       }
971     }
972     #endif
973     // add local ID prefix, if any
974     if (aLocalIDPrefix && *aLocalIDPrefix && syncitemP->hasLocalID())
975       syncitemP->fLocalID.insert(0,aLocalIDPrefix);
976     // create sync op command
977     TSyncOpCommand *syncopcmdP = newSyncOpCommand(syncitemP,itemtypeP);
978     #ifdef CLIENT_USES_SERVER_DB
979     // save item, we need it later for post-processing and Map simulation
980     fItems.push_back(syncitemP);
981     #else
982     // delete item, not used any more
983     delete syncitemP;
984     #endif
985     // issue command now
986     // - Note that when command is split, issuePtr returns true, but we still may NOT generate new commands
987     //   as the message is already full now. That's why the while contains a check for message full and aNextMessageCommands size
988     //   (was not the case before 2.1.0.2, which could cause that the first chunk of a subsequent command
989     //   would be sent before the third..nth chunk of the previous command).
990     TSmlCommand *cmdP = syncopcmdP;
991     syncopcmdP=NULL;
992     // eventually, we have a NULL command here (e.g. in case it could not be generated due to MaxObjSize restrictions)
993     if (cmdP) {
994       if (!fSessionP->issuePtr(cmdP,aNextMessageCommands,aInterruptedCommandP)) {
995         alldone=false; // issue failed (no room in message), not finished so far
996         break;
997       }
998       // count item sent
999       fItemsSent++;
1000       // send event and check for abort
1001       #ifdef PROGRESS_EVENTS
1002       if (!fSessionP->getSyncAppBase()->NotifyProgressEvent(pev_itemsent,getDSConfig(),fItemsSent,getNumberOfChanges())) {
1003         implEndDataRead(); // terminate reading
1004         fSessionP->AbortSession(500,true,LOCERR_USERABORT);
1005         return false; // error
1006       }
1007       // check for "soft" suspension
1008       if (!fSessionP->getSyncAppBase()->NotifyProgressEvent(pev_suspendcheck)) {
1009         fSessionP->SuspendSession(LOCERR_USERSUSPEND);
1010       }
1011       #endif
1012     }
1013   }; // while not aborted
1014   // we are not done until all aNextMessageCommands are also out
1015   // Note: this must be specially checked because we now have SyncML 1.1 chunked commands.
1016   //   Those issue() fine, but leave a next chunk in the aNextMessageCommands queue.
1017   if (alldone && aNextMessageCommands.size()>0) {
1018     alldone=false;
1019   }
1020   // done if we are now ready for sync or if aborted
1021   return ((alldone && fEoC) || isAborted());
1022 } // TStdLogicDS::logicGenerateSyncCommandsAsClient
1023
1024
1025 #endif // client case
1026
1027
1028 // called to process incoming item operation
1029 // Method takes ownership of syncitemP in all cases
1030 bool TStdLogicDS::logicProcessRemoteItem(
1031   TSyncItem *syncitemP,
1032   TStatusCommand &aStatusCommand,
1033   bool &aVisibleInSyncset, // on entry: tells if resulting item SHOULD be visible; on exit: set if processed item remains visible in the sync set.
1034   string *aGUID // GUID is stored here if not NULL
1035 ) {
1036   localstatus sta=LOCERR_OK;
1037   bool irregular=false;
1038   bool shouldbevisible=aVisibleInSyncset;
1039   string datatext;
1040
1041   TP_DEFIDX(li);
1042   TP_SWITCH(li,fSessionP->fTPInfo,TP_database);
1043   SYSYNC_TRY {
1044     // assume item will stay visible in the syncset after processing
1045     aVisibleInSyncset=true;
1046     // start writing if not already started
1047     sta=startDataWrite();
1048     if (sta!=LOCERR_OK) {
1049         aStatusCommand.setStatusCode(sta);
1050     }
1051     else {
1052       // show
1053       DEBUGPRINTFX(DBG_DATA,(
1054         "TStdLogicDS::logicProcessRemoteItem starting, SyncOp=%s, RemoteID='%s', LocalID='%s'",
1055         SyncOpNames[syncitemP->getSyncOp()],
1056         syncitemP->getRemoteID(),
1057         syncitemP->getLocalID()
1058       ));
1059       // now perform action
1060       if (syncitemP->getSyncOp()==sop_replace || syncitemP->getSyncOp()==sop_wants_replace) {
1061         // check if we should read before writing
1062         TMultiFieldItem *mfiP;
1063         GET_CASTED_PTR(mfiP,TMultiFieldItem,syncitemP,"");
1064         bool replacewritesallfields = dsReplaceWritesAllDBFields();
1065         bool mightcontaincutoff=false;
1066         // - see if we would pass sync set filter as is
1067         #ifdef OBJECT_FILTERING
1068         aVisibleInSyncset = mfiP->testFilter(fSyncSetFilter.c_str());
1069         bool invisible =
1070           !getDSConfig()->fInvisibleFilter.empty() && // has an invisible filter
1071           mfiP->testFilter(getDSConfig()->fInvisibleFilter.c_str()); // and passes it -> invisible
1072         bool visibilityok = (aVisibleInSyncset && !invisible) == shouldbevisible; // check if visibility is correct
1073         if (visibilityok && !replacewritesallfields && aVisibleInSyncset) // avoid expensive check if we have to read anyway
1074         #endif
1075           mightcontaincutoff = mfiP->getItemType()->mayContainCutOffData(mfiP->getTargetItemType());
1076         // - if not, we must read item from the DB first and then
1077         //   test again.
1078         if (
1079           #ifdef OBJECT_FILTERING
1080           !visibilityok ||
1081           #endif
1082           replacewritesallfields ||
1083           mightcontaincutoff ||
1084           fIgnoreUpdate // if we may not update items, only add them, then we must check first if item exists in DB
1085         ) {
1086           // the item we are replacing might contain cut-off data or
1087           // needs otherwise to be modified based on current contents
1088           // we should therefore read item from DB first
1089           // - create new empty TMultiFieldItem
1090           TMultiFieldItem *refitemP =
1091             (TMultiFieldItem *) newItemForRemote(ity_multifield);
1092           #ifdef SYSYNC_CLIENT
1093           // Client: retrieve by local ID
1094           refitemP->clearRemoteID(); // not known
1095           refitemP->setLocalID(syncitemP->getLocalID()); // make sure we retrieve by local ID
1096           #else
1097           // Server: retrieve by remote ID
1098           refitemP->clearLocalID(); // make sure we retrieve by remote ID
1099           refitemP->setRemoteID(syncitemP->getRemoteID()); // make sure we retrieve by remote ID
1100           #endif
1101           refitemP->setSyncOp(sop_replace);
1102           #ifdef OBJECT_FILTERING
1103           PDEBUGPRINTFX(DBG_DATA,(
1104             "TStdLogicDS: Need read-modify-write (cause: %s%s%s%s) -> retrieve original item from DB",
1105             !visibilityok ? "visibility_not_ok " : "",
1106             replacewritesallfields ? "replace_writes_all_fields " : "",
1107             mightcontaincutoff ? "might_contain_cutoff_data " : "",
1108             fIgnoreUpdate ? "ignoreUpdate " : ""
1109           ));
1110           #else
1111           PDEBUGPRINTFX(DBG_DATA,(
1112             "TStdLogicDS: Need read-modify-write (cause: %s%s%s) -> retrieve original item from DB",
1113             replacewritesallfields ? "replace_writes_all_fields " : "",
1114             mightcontaincutoff ? "might_contain_cutoff_data " : "",
1115             fIgnoreUpdate ? "ignoreUpdate " : ""
1116           ));
1117           #endif
1118           if (implRetrieveItemByID(*refitemP,aStatusCommand)) {
1119             #ifdef SYDEBUG
1120             if (PDEBUGTEST(DBG_DATA)) {
1121               PDEBUGPRINTFX(DBG_DATA,("TStdLogicDS: Retrieved item"));
1122               if (PDEBUGTEST(DBG_DATA+DBG_DETAILS)) refitemP->debugShowItem(); // show item retrieved
1123             }
1124             #endif
1125             if (fIgnoreUpdate) {
1126               // updates may not be executed at all, and be simply ignored
1127               aStatusCommand.setStatusCode(200); // fake ok.
1128               PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("TStdLogicDS: fIgnoreUpdate set, update command not executed but answered with status 200"));
1129               irregular=true;
1130               sta=LOCERR_OK;
1131               goto processed;
1132             }
1133             // we got the item to be replaced
1134             // - now modify it:
1135             //   - only modify available fields
1136             //   - perform cutoff prevention
1137             //   - do not just copy assigned fields (but all those that are available)
1138             //   - IF replace can write individual fields, then transfer unassigned status
1139             //     (also for non-availables!) to avoid that datastore needs to write back
1140             //     values that are already there.
1141             refitemP->replaceDataFrom(*syncitemP,true,true,false,!replacewritesallfields);
1142             #ifdef SYDEBUG
1143             if (PDEBUGTEST(DBG_DATA)) {
1144               PDEBUGPRINTFX(DBG_DATA,("TStdLogicDS: Item updated with contents from remote"));
1145               if (PDEBUGTEST(DBG_DATA+DBG_EXOTIC)) refitemP->debugShowItem(); // show item retrieved
1146             }
1147             #endif
1148             #ifdef OBJECT_FILTERING
1149             // - make sure item will pass sync set filter NOW
1150             makePassSyncSetFilter(refitemP);
1151             // - make sure item will be visible NOW
1152             makeVisible(refitemP);
1153             #ifdef SYDEBUG
1154             if (PDEBUGTEST(DBG_DATA)) {
1155               PDEBUGPRINTFX(DBG_DATA,("TStdLogicDS: Made visible and pass sync set filter"));
1156               if (PDEBUGTEST(DBG_DATA+DBG_EXOTIC)) refitemP->debugShowItem(); // show item retrieved
1157             }
1158             #endif
1159             #endif
1160             // - get rid of original
1161             delete syncitemP;
1162             // - use new item for further processing
1163             syncitemP=refitemP;
1164           }
1165           else {
1166             // failed retrieving item: switch to add
1167             sta = aStatusCommand.getStatusCode();
1168             if (sta==404 || sta==410) {
1169               // this is an irregularity for a client (but it's perfectly
1170               // normal case for server, as client may use replace for adds+replaces)
1171               #ifdef SYSYNC_CLIENT
1172               if (!syncitemP->hasRemoteID()) {
1173                 // - we cannot handle this properly, we have no remoteID, so report error to server
1174                 PDEBUGPRINTFX(DBG_ERROR,("TStdLogicDS: Item not found, but cannot switch to add because no RemoteID is known, Status=%hd",aStatusCommand.getStatusCode()));
1175               }
1176               else
1177               #endif
1178               {
1179                 if (fPreventAdd) {
1180                   // prevent implicit add -> return sta as is
1181                 }
1182                 else {
1183                   // - switch to add if remote sent remoteID along so we can properly map
1184                   syncitemP->setSyncOp(sop_add);
1185                   PDEBUGPRINTFX(DBG_DATA,("TStdLogicDS: RetrieveItem: not found (Status=%hd) --> adding instead",sta));
1186                   #ifndef SYSYNC_CLIENT
1187                   // - make sure we delete the old item from the map table (as we *know* the item is gone - should it reappear under a different localID, it'll be re-added)
1188                   implProcessMap(syncitemP->getRemoteID(),NULL);
1189                   #endif
1190                   // we can handle it, as we know the remoteID
1191                   #ifdef SYSYNC_CLIENT
1192                   irregular=true; // irregular only for client
1193                   #endif
1194                   sta=LOCERR_OK; // ok for further processing, anyway
1195                 }
1196               }
1197             }
1198             else {
1199               // this is a fatal error, report it
1200               PDEBUGPRINTFX(DBG_ERROR,("TStdLogicDS: RetrieveItem failed, Status=%hd",sta));
1201             }
1202             // get rid of reference item
1203             delete refitemP;
1204           }
1205         }
1206       }
1207       if (sta==LOCERR_OK) {
1208         // make sure that added items will pass sync set filters. If we can't make them pass
1209         // for DS 1.2 exclusive filters we must generate a delete so we must remember the itempassed status)
1210         #ifdef OBJECT_FILTERING
1211         if (syncitemP->getSyncOp()==sop_add || syncitemP->getSyncOp()==sop_wants_add) {
1212           // - make sure new item will pass sync set filter when re-read from DB
1213           aVisibleInSyncset=makePassSyncSetFilter(syncitemP);
1214           // - also make sure new item has correct visibility status
1215           if (shouldbevisible)
1216             makeVisible(syncitemP); // make visible
1217           else
1218             aVisibleInSyncset = !makeInvisible(syncitemP); // make invisible
1219         }
1220         #endif
1221         // Now let derived class process the item
1222         if (!implProcessItem(syncitemP,aStatusCommand))
1223           sta = aStatusCommand.getStatusCode(); // not successful, get error status code
1224       }
1225       // perform special case handling
1226       if (sta!=LOCERR_OK) {
1227         // irregular, special case handling
1228         switch (syncitemP->getSyncOp()) {
1229           case sop_wants_add : // to make sure
1230           case sop_add :
1231             if (sta==418) {
1232               // 418: item already exists, this is kind of a conflict
1233               // (should not happen normally, but can happen if aborted session was not
1234               // completely rolled back by server, so treat it like
1235               // "conflict resolved by client data winning")
1236               PDEBUGPRINTFX(DBG_DATA,("to-be-added item already exists, and incomplete rollbacks in server possible -> trying replace (=conflict resolved by client winning)"));
1237               // - switch to replace
1238               syncitemP->setSyncOp(sop_replace);
1239               irregular=true;
1240               // - process again
1241               if (implProcessItem(syncitemP,aStatusCommand)) {
1242                 aStatusCommand.setStatusCode(208); // client has won
1243               }
1244               else {
1245                 sta = aStatusCommand.getStatusCode();
1246               }
1247             }
1248             break;
1249           case sop_replace :
1250             if (sta==404 || sta==410) {
1251               // this is an irregularity for a client (but it's perfectly
1252               // normal case for server, as client may use replace for adds+replaces)
1253               #ifdef SYSYNC_CLIENT
1254               if (!syncitemP->hasRemoteID()) {
1255                 // - we cannot handle this properly, we have no remoteID, so report error to server
1256                 PDEBUGPRINTFX(DBG_ERROR,("to-be-replaced item not found, but cannot switch to add because no RemoteID is known, Status=%hd",sta));
1257               }
1258               else
1259               #endif
1260               {
1261                 if (fPreventAdd) {
1262                   // prevent implicit add -> return status as is
1263                 }
1264                 else {
1265                   // - switch to add if remote sent remoteID along so we can properly map
1266                   syncitemP->setSyncOp(sop_add);
1267                   #ifndef SYSYNC_CLIENT
1268                   // - make sure we delete the old item from the map table (as we *know* the item is gone - should it reappear under a different localID, it'll be re-added)
1269                   implProcessMap(syncitemP->getRemoteID(),NULL);
1270                   #endif
1271                   // we can handle it, as we know the remoteID
1272                   #ifdef SYSYNC_CLIENT
1273                   irregular=true;
1274                   #endif
1275                   PDEBUGPRINTFX(DBG_DATA,("to-be-replaced item not found (Status=%hd) --> adding instead",sta));
1276                   // - process again (note that we are re-using the status command that might
1277                   //   already have a text item with an OS errir if something failed before)
1278                   sta=LOCERR_OK; // forget previous status
1279                   if (!implProcessItem(syncitemP,aStatusCommand))
1280                     sta=aStatusCommand.getStatusCode(); // not successful, get error status code
1281                 }
1282               }
1283             }
1284             break;
1285           case sop_delete :
1286           case sop_archive_delete :
1287           case sop_soft_delete :
1288             if (sta==404 || sta==410) {
1289               if (fSessionP->getSessionConfig()->fDeletingGoneOK) {
1290                 // 404/410: item not found, could be because previous aborted session has
1291                 // already committed deletion of that item -> behave as if delete was ok
1292                 PDEBUGPRINTFX(DBG_DATA,("to-be-deleted item was not found, but do NOT report %hd",sta));
1293                 aStatusCommand.setStatusCode(200);
1294                 irregular=true;
1295                 sta = LOCERR_OK; // this is ok, item is deleted already
1296               }
1297             }
1298             break;
1299           default :
1300             SYSYNC_THROW(TSyncException("Unknown sync op in TStdLogicDS::logicProcessRemoteItem"));
1301         } // switch
1302       } // if not ok
1303     processed:
1304       if (sta==LOCERR_OK) {
1305         PDEBUGPRINTFX(DBG_DATA,(
1306           "- Operation %s performed (%sregular), Remote ID=%s Local ID=%s, status=%hd",
1307           SyncOpNames[syncitemP->getSyncOp()],
1308           irregular ? "ir" : "",
1309           syncitemP->getRemoteID(),
1310           syncitemP->getLocalID(),
1311           aStatusCommand.getStatusCode()
1312         ));
1313         // return GUID if string ptr was passed
1314         if (aGUID) {
1315           (*aGUID)=syncitemP->getLocalID();
1316         }
1317       }
1318       else {
1319         PDEBUGPRINTFX(DBG_ERROR,(
1320           "- Operation %s failed with SyncML status=%hd",
1321           SyncOpNames[syncitemP->getSyncOp()],
1322           sta
1323         ));
1324       }
1325     } // if startDataWrite ok
1326     // anyway, we are done with this item, delete it now
1327     delete syncitemP;
1328     TP_START(fSessionP->fTPInfo,li);
1329     // done, return regular/irregular status
1330     return (sta==LOCERR_OK) && !irregular;
1331   }
1332   SYSYNC_CATCH (...)
1333     // delete the item
1334     if (syncitemP) delete syncitemP;
1335     TP_START(fSessionP->fTPInfo,li);
1336     // re-throw
1337     SYSYNC_RETHROW;
1338   SYSYNC_ENDCATCH
1339   #ifndef __BORLANDC__
1340   return false; // BCPPB: unreachable code
1341   #endif
1342 } // TStdLogicDS::logicProcessRemoteItem
1343
1344
1345
1346 // Abort datastore sync
1347 void TStdLogicDS::dsAbortDatastoreSync(TSyError aReason, bool aLocalProblem)
1348 {
1349   // call anchestor
1350   inherited::dsAbortDatastoreSync(aReason, aLocalProblem);
1351 } // TStdLogicDS::dsAbortDatastoreSync
1352
1353
1354 // inform logic of coming state change
1355 localstatus TStdLogicDS::dsBeforeStateChange(TLocalEngineDSState aOldState,TLocalEngineDSState aNewState)
1356 {
1357   localstatus sta=LOCERR_OK;
1358
1359   if (aNewState==dssta_dataaccessstarted) {
1360     // start data access
1361     #ifdef SYSYNC_CLIENT
1362     sta = startDataAccessForClient();
1363     #else
1364     sta = startDataAccessForServer();
1365     #endif
1366   }
1367   #ifdef SYSYNC_CLIENT
1368   if (aNewState==dssta_syncgendone) {
1369     // when client has done sync gen, start writing
1370     sta = startDataWrite();
1371   }
1372   #endif
1373   if (aNewState==dssta_completed && !isAborted()) {
1374     // finish writing data now anyway
1375     endDataWrite();
1376     // we must save anchors at the moment we shift from any state to dssta_completed
1377     PDEBUGPRINTFX(DBG_ADMIN,("TStdLogicDS: successfully completed, save anchors now"));
1378     // update our level's state
1379     fPreviousSyncTime = fCurrentSyncTime;
1380     // let implementation update their state and save it
1381     sta=implSaveEndOfSession(true);
1382     if (sta!=LOCERR_OK) {
1383       PDEBUGPRINTFX(DBG_ERROR,("TStdLogicDS: Could not save session completed status, err=%hd",sta));
1384     }
1385   }
1386   if (aNewState==dssta_idle && aOldState>=dssta_syncsetready) {
1387     // again: make sure data is written anyway (if already done this is a NOP)
1388     endDataWrite();
1389   }
1390   // abort on error
1391   if (sta!=LOCERR_OK) return sta;
1392   // let inherited do its stuff as well
1393   return inherited::dsBeforeStateChange(aOldState,aNewState);
1394 } // TStdLogicDS::dsBeforeStateChange
1395
1396
1397 // inform logic of happened state change
1398 localstatus TStdLogicDS::dsAfterStateChange(TLocalEngineDSState aOldState,TLocalEngineDSState aNewState)
1399 {
1400   localstatus sta=LOCERR_OK;
1401
1402   if (aNewState==dssta_dataaccessdone) {
1403     // finish writing data now
1404     endDataWrite();
1405   }
1406   // abort on error
1407   if (sta!=LOCERR_OK) return sta;
1408   // let inherited do its stuff as well
1409   return inherited::dsAfterStateChange(aOldState,aNewState);
1410 } // TStdLogicDS::dsAfterStateChange
1411
1412
1413 /** @deprecated obsolete, replaced by stuff in dsBeforeStateChange()
1414 // called at very end of sync session, when all map commands are done, too
1415 // Note: is also called before deleting a datastore (so aborted sessions
1416 //   can do cleanup and/or statistics display as well)
1417 void TStdLogicDS::endOfSync(bool aRegular)
1418 {
1419   // save new sync anchor now
1420   // NOTE: gets called even if not active
1421   if (fState!=dss_idle) {
1422     PDEBUGPRINTFX(DBG_ADMIN,("TStdLogicDS::endOfSync, %sregular end of sync session",aRegular ? "" : "ir"));
1423     if (!aRegular) fRollback=true; // do not write irregular ends
1424     // datastore was active in sync, end it now
1425     if (!fRollback) {
1426       fRollback=!startWrite();
1427       if (!fRollback) {
1428         fRollback=!SaveAnchor(fNextRemoteAnchor.c_str());
1429       }
1430     }
1431     #ifdef SYDEBUG
1432     if (fRollback)
1433       DEBUGPRINTFX(DBG_ERROR,("************** Datastore error, rolling back transaction"));
1434     #endif
1435     // if session is complete, we can't resume it any more, so clear that status now
1436     if (!fRollback) {
1437       fResumeAlertCode=0; // no resume
1438     }
1439     // end writing now, sync is done
1440     endWrite();
1441   }
1442   // let ancestor do its things
1443   TLocalEngineDS::endOfSync(aRegular);
1444 } // TStdLogicDS::endOfSync
1445
1446 */
1447
1448 } // namespace sysync
1449
1450 /* end of TStdLogicDS implementation */
1451
1452 // eof