Imported Upstream version 1.2.99~20120606~SE~ff65aef~SYSYNC~2728cb4
[platform/upstream/syncevolution.git] / src / synthesis / src / sysync / localengineds.cpp
1 /**
2  *  @File     localengineds.cpp
3  *
4  *  @Author   Lukas Zeller (luz@plan44.ch)
5  *
6  *  @brief TLocalEngineDS
7  *    Abstraction of the local datastore - interface class to the
8  *    sync engine.
9  *
10  *    Copyright (c) 2001-2011 by Synthesis AG + plan44.ch
11  *
12  *  @Date 2005-09-15 : luz : created from localdatastore
13  */
14
15 // includes
16 #include "prefix_file.h"
17 #include "sysync.h"
18 #include "localengineds.h"
19 #include "syncappbase.h"
20 #include "scriptcontext.h"
21 #include "superdatastore.h"
22 #include "syncagent.h"
23
24
25 using namespace sysync;
26
27 namespace sysync {
28
29 #ifdef SYDEBUG
30
31 cAppCharP const LocalDSStateNames[numDSStates] = {
32   "idle",
33   "client_initialized",
34   "admin_ready",
35   "client_sent_alert",
36   "server_alerted",
37   "server_answered_alert",
38   "client_alert_statused",
39   "client_alerted",
40   "sync_mode_stable",
41   "data_access_started",
42   "sync_set_ready",
43   "client_sync_gen_started",
44   "server_seen_client_mods",
45   "server_sync_gen_started",
46   "sync_gen_done",
47   "data_access_done",
48   "client_maps_sent",
49   "admin_done",
50   "completed"
51 };
52 #endif
53
54
55 #ifdef OBJECT_FILTERING
56
57 // add a new expression to an existing filter
58 static void addToFilter(const char *aNewFilter, string &aFilter, bool aORChain=false)
59 {
60   if (aNewFilter && *aNewFilter) {
61     // just assign if current filter expression is empty
62     if (aFilter.empty())
63       aFilter = aNewFilter;
64     else {
65       // construct new filter
66       string newFilter;
67       StringObjPrintf(newFilter,"(%s)%c(%s)",aFilter.c_str(),aORChain ? '|' : '&',aNewFilter);
68       aFilter = newFilter;
69     }
70   }
71 } // addToFilter
72
73 #endif
74
75 #ifdef SCRIPT_SUPPORT
76
77 // Script functions
78 // ================
79
80 class TLDSfuncs {
81 public:
82
83   #ifdef OBJECT_FILTERING
84   #ifdef SYNCML_TAF_SUPPORT
85
86   // string GETCGITARGETFILTER()
87   // returns current CGI-specified target address filter expression
88   static void func_GetCGITargetFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
89   {
90     TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
91     aTermP->setAsString(dsP->fTargetAddressFilter.c_str());
92   }; // func_GetCGITargetFilter
93
94
95   // string GETTARGETFILTER()
96   // returns current internal target address filter expression
97   static void func_GetTargetFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
98   {
99     TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
100     aTermP->setAsString(dsP->fIntTargetAddressFilter.c_str());
101   }; // func_GetTargetFilter
102
103
104   // SETTARGETFILTER(string filter)
105   // sets (overwrites) the internal target address filter
106   static void func_SetTargetFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
107   {
108     TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
109     aFuncContextP->getLocalVar(0)->getAsString(dsP->fIntTargetAddressFilter);
110   }; // func_SetTargetFilter
111
112
113   // ADDTARGETFILTER(string filter)
114   // adds a filter expression to the existing internal targetfilter (automatically paranthesizing and adding AND)
115   static void func_AddTargetFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
116   {
117     string f;
118     TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
119     aFuncContextP->getLocalVar(0)->getAsString(f);
120     addToFilter(f.c_str(),dsP->fIntTargetAddressFilter,false); // AND-chaining
121   }; // func_AddTargetFilter
122
123   #endif // SYNCML_TAF_SUPPORT
124
125
126   // string GETFILTER()
127   // returns current sync set filter expression
128   static void func_GetFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
129   {
130     TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
131     aTermP->setAsString(dsP->fSyncSetFilter.c_str());
132   }; // func_GetFilter
133
134
135   // SETFILTER(string filter)
136   // sets (overwrites) the current sync set filter
137   static void func_SetFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
138   {
139     TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
140     aFuncContextP->getLocalVar(0)->getAsString(dsP->fSyncSetFilter);
141     dsP->engFilteredFetchesFromDB(true); // update filter dependencies
142   }; // func_SetFilter
143
144
145   // ADDFILTER(string filter)
146   // adds a filter expression to the existing (dynamic) targetfilter (automatically paranthesizing and adding AND)
147   static void func_AddFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
148   {
149     string f;
150     TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
151     aFuncContextP->getLocalVar(0)->getAsString(f);
152     addToFilter(f.c_str(),dsP->fSyncSetFilter,false); // AND-chaining
153     dsP->engFilteredFetchesFromDB(true); // update filter dependencies
154   }; // func_AddFilter
155
156
157   // ADDSTATICFILTER(string filter)
158   // adds a filter expression to the existing (static) localdbfilter (automatically paranthesizing and adding AND)
159   static void func_AddStaticFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
160   {
161     string f;
162     TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
163     aFuncContextP->getLocalVar(0)->getAsString(f);
164     addToFilter(f.c_str(),dsP->fLocalDBFilter,false); // AND-chaining
165     dsP->engFilteredFetchesFromDB(true); // update filter dependencies
166   }; // func_AddStaticFilter
167
168   #endif // OBJECT_FILTERING
169
170   #ifdef SYSYNC_TARGET_OPTIONS
171
172   // string DBOPTIONS()
173   // returns current DB options
174   static void func_DBOptions(TItemField *&aTermP, TScriptContext *aFuncContextP)
175   {
176     TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
177     aTermP->setAsString(dsP->fDBOptions.c_str());
178   }; // func_DBOptions
179
180
181   // integer DBHANDLESOPTS()
182   // returns true if database can completely handle options like /dr() and /li during fetching
183   static void func_DBHandlesOpts(TItemField *&aTermP, TScriptContext *aFuncContextP)
184   {
185     aTermP->setAsBoolean(
186       static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->dsOptionFilterFetchesFromDB()
187     );
188   }; // func_DBHandlesOpts
189
190
191   // timestamp STARTDATE()
192   // returns startdate if one is set in datastore
193   static void func_StartDate(TItemField *&aTermP, TScriptContext *aFuncContextP)
194   {
195     lineartime_t d = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fDateRangeStart;
196     TTimestampField *resP = static_cast<TTimestampField *>(aTermP);
197     if (d==noLinearTime)
198       resP->assignEmpty();
199     else
200       resP->setTimestampAndContext(d,TCTX_UTC);
201   }; // func_StartDate
202
203
204   // SETSTARTDATE(timestamp startdate)
205   // sets startdate for datastore
206   static void func_SetStartDate(TItemField *&aTermP, TScriptContext *aFuncContextP)
207   {
208     TTimestampField *tsP = static_cast<TTimestampField *>(aFuncContextP->getLocalVar(0));
209     timecontext_t tctx;
210
211     static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fDateRangeStart =
212       tsP->getTimestampAs(TCTX_UTC,&tctx); // floating will also be treated as UTC
213   }; // func_SetStartDate
214
215
216   // timestamp ENDDATE()
217   // returns enddate if one is set in datastore
218   static void func_EndDate(TItemField *&aTermP, TScriptContext *aFuncContextP)
219   {
220     lineartime_t d = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fDateRangeEnd;
221     TTimestampField *resP = static_cast<TTimestampField *>(aTermP);
222     if (d==noLinearTime)
223       resP->assignEmpty();
224     else
225       resP->setTimestampAndContext(d,TCTX_UTC);
226   }; // func_EndDate
227
228
229   // SETENDDATE(timestamp startdate)
230   // sets enddate for datastore
231   static void func_SetEndDate(TItemField *&aTermP, TScriptContext *aFuncContextP)
232   {
233     TTimestampField *tsP = static_cast<TTimestampField *>(aFuncContextP->getLocalVar(0));
234     timecontext_t tctx;
235
236     static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fDateRangeEnd =
237       tsP->getTimestampAs(TCTX_UTC,&tctx); // floating will also be treated as UTC
238   }; // func_SetEndDate
239
240
241   // integer DEFAULTSIZELIMIT()
242   // returns limit set for all items in this datastore (the /li(xxx) CGI option value)
243   static void func_DefaultLimit(TItemField *&aTermP, TScriptContext *aFuncContextP)
244   {
245     fieldinteger_t i = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fSizeLimit;
246     if (i<0)
247       aTermP->unAssign(); // no limit
248     else
249       aTermP->setAsInteger(i);
250   }; // func_DefaultLimit
251
252
253   // SETDEFAULTSIZELIMIT(integer limit)
254   // sets limit for all items in this datastore (the /li(xxx) CGI option value)
255   static void func_SetDefaultLimit(TItemField *&aTermP, TScriptContext *aFuncContextP)
256   {
257     static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fSizeLimit =
258       aFuncContextP->getLocalVar(0)->getAsInteger();
259   }; // func_SetDefaultLimit
260
261
262   // integer NOATTACHMENTS()
263   // returns true if attachments should be suppressed (/na CGI option)
264   static void func_NoAttachments(TItemField *&aTermP, TScriptContext *aFuncContextP)
265   {
266     aTermP->setAsBoolean(
267       static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fNoAttachments
268     );
269   }; // func_NoAttachments
270
271
272   // SETNOATTACHMENTS(integer flag)
273   // if true, attachments will be suppressed (/na CGI option)
274   static void func_SetNoAttachments(TItemField *&aTermP, TScriptContext *aFuncContextP)
275   {
276     static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fNoAttachments =
277       aFuncContextP->getLocalVar(0)->getAsBoolean();
278   }; // func_SetNoAttachments
279
280
281   // integer MAXITEMCOUNT()
282   // returns item count limit (0=none) as set by /max(n) CGI option
283   static void func_MaxItemCount(TItemField *&aTermP, TScriptContext *aFuncContextP)
284   {
285     aTermP->setAsInteger(
286       static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fMaxItemCount
287     );
288   }; // func_MaxItemCount
289
290
291   // SETMAXITEMCOUNT(integer maxcount)
292   // set item count limit (0=none) as set by /max(n) CGI option
293   static void func_SetMaxItemCount(TItemField *&aTermP, TScriptContext *aFuncContextP)
294   {
295     static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fMaxItemCount =
296       aFuncContextP->getLocalVar(0)->getAsInteger();
297   }; // func_SetMaxItemCount
298
299   #endif // SYSYNC_TARGET_OPTIONS
300
301
302   // integer SLOWSYNC()
303   // returns true if we are in slow sync
304   static void func_SlowSync(TItemField *&aTermP, TScriptContext *aFuncContextP)
305   {
306     aTermP->setAsBoolean(
307       static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->isSlowSync()
308     );
309   }; // func_SlowSync
310
311
312   // FORCESLOWSYNC()
313   // force a slow sync (like with /na CGI option)
314   static void func_ForceSlowSync(TItemField *&aTermP, TScriptContext *aFuncContextP)
315   {
316     static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->engForceSlowSync();
317   }; // func_ForceSlowSync
318
319
320
321   // integer ALERTCODE()
322   // returns the alert code as currently know by datastore (might change from normal to slow while processing)
323   static void func_AlertCode(TItemField *&aTermP, TScriptContext *aFuncContextP)
324   {
325     aTermP->setAsInteger(
326       static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fAlertCode
327     );
328   }; // func_AlertCode
329
330
331   // SETALERTCODE(integer maxcount)
332   // set the alert code (makes sense in alertscript to modify the incoming code to something different)
333   static void func_SetAlertCode(TItemField *&aTermP, TScriptContext *aFuncContextP)
334   {
335     static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fAlertCode =
336       aFuncContextP->getLocalVar(0)->getAsInteger();
337   }; // func_SetAlertCode
338
339
340
341   // integer REFRESHONLY()
342   // returns true if sync is only refreshing local (note that alert code might be different, as local
343   // refresh can take place without telling the remote so, for compatibility with clients that do not support the mode)
344   static void func_RefreshOnly(TItemField *&aTermP, TScriptContext *aFuncContextP)
345   {
346     aTermP->setAsBoolean(
347       static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->isRefreshOnly()
348     );
349   }; // func_RefreshOnly
350
351
352   // SETREFRESHONLY(integer flag)
353   // modifies the refresh only flag (one way sync from remote to local only)
354   // Note that clearing this flag when a client has alerted one-way will probably lead to an error
355   static void func_SetRefreshOnly(TItemField *&aTermP, TScriptContext *aFuncContextP)
356   {
357     static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->engSetRefreshOnly(
358       aFuncContextP->getLocalVar(0)->getAsBoolean()
359     );
360   }; // func_SetRefreshOnly
361
362
363   // integer READONLY()
364   // returns true if sync is read-only (only reading from local datastore)
365   static void func_ReadOnly(TItemField *&aTermP, TScriptContext *aFuncContextP)
366   {
367     aTermP->setAsBoolean(
368       static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->isReadOnly()
369     );
370   }; // func_ReadOnly
371
372
373   // SETREADONLY(integer flag)
374   // modifies the read only flag (only reading from local datastore)
375   static void func_SetReadOnly(TItemField *&aTermP, TScriptContext *aFuncContextP)
376   {
377     static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->engSetReadOnly(
378       aFuncContextP->getLocalVar(0)->getAsBoolean()
379     );
380   }; // func_SetReadOnly
381
382
383
384   // integer FIRSTTIMESYNC()
385   // returns true if we are in first time slow sync
386   static void func_FirstTimeSync(TItemField *&aTermP, TScriptContext *aFuncContextP)
387   {
388     aTermP->setAsBoolean(
389       static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->isFirstTimeSync()
390     );
391   }; // func_FirstTimeSync
392
393
394   // void SETCONFLICTSTRATEGY(string strategy)
395   // sets conflict strategy for this session
396   static void func_SetConflictStrategy(TItemField *&aTermP, TScriptContext *aFuncContextP)
397   {
398     // convert to syncop
399     string s;
400     aFuncContextP->getLocalVar(0)->getAsString(s);
401     sInt16 strategy;
402     StrToEnum(conflictStrategyNames,numConflictStrategies, strategy, s.c_str());
403     static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fSessionConflictStrategy =
404       (TConflictResolution) strategy;
405   }; // func_SetConflictStrategy
406
407
408   // string DBNAME()
409   // returns name of DB
410   static void func_DBName(TItemField *&aTermP, TScriptContext *aFuncContextP)
411   {
412     aTermP->setAsString(
413       static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->getName()
414     );
415   }; // func_DBName
416
417   // void ABORTDATASTORE(integer statuscode)
418   static void func_AbortDatastore(TItemField *&aTermP, TScriptContext *aFuncContextP)
419   {
420     static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->engAbortDataStoreSync(aFuncContextP->getLocalVar(0)->getAsInteger(),true); // we cause the abort locally
421   } // func_AbortDatastore
422
423   // string LOCALDBNAME()
424   // returns name of local DB with which it was identified for the sync
425   static void func_LocalDBName(TItemField *&aTermP, TScriptContext *aFuncContextP)
426   {
427     aTermP->setAsString(
428       static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->getIdentifyingName()
429     );
430   }; // func_LocalDBName
431
432
433   // string REMOTEDBNAME()
434   // returns remote datastore's full name (as used by the remote in <sync> command, may contain subpath and CGI)
435   static void func_RemoteDBName(TItemField *&aTermP, TScriptContext *aFuncContextP)
436   {
437     TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
438     aTermP->setAsString(dsP->getRemoteDatastore()->getFullName());
439   }; // func_RemoteDBName
440
441
442   #ifdef SYSYNC_CLIENT
443
444   // ADDTARGETCGI(string cgi)
445   // adds CGI to the target URI. If target URI already contains a ?, string will be just
446   // appended, otherwise a ? is added, then the new CGI.
447   // Note: if string to be added is already contained, it will not be added again
448   static void func_AddTargetCGI(TItemField *&aTermP, TScriptContext *aFuncContextP)
449   {
450     TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
451     // Add extra CGI specified
452     string cgi;
453     aFuncContextP->getLocalVar(0)->getAsString(cgi);
454     addCGItoString(dsP->fRemoteDBPath,cgi.c_str(),true);
455   }; // func_AddTargetCGI
456
457
458   // SETRECORDFILTER(string filter, boolean inclusive)
459   // Sets record level filter expression for remote
460   static void func_SetRecordFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
461   {
462     TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
463     // put into record level filter
464     if (dsP->getSession()->getSyncMLVersion()>=syncml_vers_1_2) {
465       // DS 1.2: use <filter>
466       aFuncContextP->getLocalVar(0)->getAsString(dsP->fRemoteRecordFilterQuery);
467       dsP->fRemoteFilterInclusive = aFuncContextP->getLocalVar(1)->getAsBoolean();
468     }
469     else if (!aFuncContextP->getLocalVar(0)->isEmpty()) {
470       // DS 1.1 and below and not empty filter: add as cgi
471       string filtercgi;
472       if (aFuncContextP->getLocalVar(1)->getAsBoolean())
473         filtercgi = "/tf("; // exclusive, use TAF
474       else
475         filtercgi = "/fi("; // inclusive, use sync set filter
476       aFuncContextP->getLocalVar(0)->appendToString(filtercgi);
477       filtercgi += ')';
478       addCGItoString(dsP->fRemoteDBPath,filtercgi.c_str(),true);
479     }
480   }; // func_SetRecordFilter
481
482
483   // SETDAYSRANGE(integer daysbefore, integer daysafter)
484   // Sets type of record filter
485   static void func_SetDaysRange(TItemField *&aTermP, TScriptContext *aFuncContextP)
486   {
487     TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
488     // get params
489     int daysbefore = aFuncContextP->getLocalVar(0)->getAsInteger();
490     int daysafter = aFuncContextP->getLocalVar(1)->getAsInteger();
491     // depending on SyncML version, create a SINCE/BEFORE filter or use the /dr(x,y) syntax
492     if (dsP->getSession()->getSyncMLVersion()>=syncml_vers_1_2 && static_cast<TSyncAgent *>(dsP->getSession())->fServerHasSINCEBEFORE) {
493       // use the SINCE/BEFORE syntax
494       // BEFORE&EQ;20070808T000000Z&AND;SINCE&EQ;20070807T000000Z
495       lineartime_t now = getSystemNowAs(TCTX_UTC,aFuncContextP->getSessionZones());
496       string ts;
497       // AND-chain with possibly existing filter
498       cAppCharP sep = "";
499       if (!dsP->fRemoteRecordFilterQuery.empty())
500         sep = "&AND;";
501       if (daysbefore>=0) {
502         dsP->fRemoteRecordFilterQuery += sep;
503         dsP->fRemoteRecordFilterQuery += "SINCE&EQ;";
504         TimestampToISO8601Str(ts,now-daysbefore*linearDateToTimeFactor,TCTX_UTC,false,false);
505         dsP->fRemoteRecordFilterQuery += ts;
506         sep = "&AND;";
507       }
508       if (daysafter>=0) {
509         dsP->fRemoteRecordFilterQuery += sep;
510         dsP->fRemoteRecordFilterQuery += "BEFORE&EQ;";
511         TimestampToISO8601Str(ts,now+daysafter*linearDateToTimeFactor,TCTX_UTC,false,false);
512         dsP->fRemoteRecordFilterQuery += ts;
513       }
514     }
515     else {
516       // use the /dr(-x,y) syntax
517       string rangecgi;
518       StringObjPrintf(rangecgi,"/dr(%ld,%ld)",(long int)(-daysbefore),(long int)(daysafter));
519       addCGItoString(dsP->fRemoteDBPath,rangecgi.c_str(),true);
520     }
521   }; // func_SetDaysRange
522
523
524   #endif // SYSYNC_CLIENT
525
526 }; // TLDSfuncs
527
528 const uInt8 param_FilterArg[] = { VAL(fty_string) };
529 const uInt8 param_DateArg[] = { VAL(fty_timestamp) };
530 const uInt8 param_IntArg[] = { VAL(fty_integer) };
531 const uInt8 param_StrArg[] = { VAL(fty_string) };
532 const uInt8 param_OneInteger[] = { VAL(fty_integer) };
533
534 const TBuiltInFuncDef DBFuncDefs[] = {
535   #ifdef OBJECT_FILTERING
536   #ifdef SYNCML_TAF_SUPPORT
537   { "GETCGITARGETFILTER", TLDSfuncs::func_GetCGITargetFilter, fty_string, 0, NULL },
538   { "GETTARGETFILTER", TLDSfuncs::func_GetTargetFilter, fty_string, 0, NULL },
539   { "SETTARGETFILTER", TLDSfuncs::func_SetTargetFilter, fty_none, 1, param_FilterArg },
540   { "ADDTARGETFILTER", TLDSfuncs::func_AddTargetFilter, fty_none, 1, param_FilterArg },
541   #endif
542   { "GETFILTER", TLDSfuncs::func_GetFilter, fty_string, 0, NULL },
543   { "SETFILTER", TLDSfuncs::func_SetFilter, fty_none, 1, param_FilterArg },
544   { "ADDFILTER", TLDSfuncs::func_AddFilter, fty_none, 1, param_FilterArg },
545   { "ADDSTATICFILTER", TLDSfuncs::func_AddStaticFilter, fty_none, 1, param_FilterArg },
546   #endif
547   #ifdef SYSYNC_TARGET_OPTIONS
548   { "DBOPTIONS", TLDSfuncs::func_DBOptions, fty_string, 0, NULL },
549   { "STARTDATE", TLDSfuncs::func_StartDate, fty_timestamp, 0, NULL },
550   { "ENDDATE", TLDSfuncs::func_EndDate, fty_timestamp, 0, NULL },
551   { "SETSTARTDATE", TLDSfuncs::func_SetStartDate, fty_none, 1, param_DateArg },
552   { "SETENDDATE", TLDSfuncs::func_SetEndDate, fty_none, 1, param_DateArg },
553   { "MAXITEMCOUNT", TLDSfuncs::func_MaxItemCount, fty_integer, 0, NULL },
554   { "SETMAXITEMCOUNT", TLDSfuncs::func_SetMaxItemCount, fty_none, 1, param_IntArg },
555   { "NOATTACHMENTS", TLDSfuncs::func_NoAttachments, fty_integer, 0, NULL },
556   { "SETNOATTACHMENTS", TLDSfuncs::func_SetNoAttachments, fty_none, 1, param_IntArg },
557   { "DEFAULTSIZELIMIT", TLDSfuncs::func_DefaultLimit, fty_integer, 0, NULL },
558   { "SETDEFAULTSIZELIMIT", TLDSfuncs::func_SetDefaultLimit, fty_none, 1, param_IntArg },
559   { "DBHANDLESOPTS", TLDSfuncs::func_DBHandlesOpts, fty_integer, 0, NULL },
560   #endif
561   { "ALERTCODE", TLDSfuncs::func_AlertCode, fty_integer, 0, NULL },
562   { "SETALERTCODE", TLDSfuncs::func_SetAlertCode, fty_none, 1, param_IntArg },
563   { "SLOWSYNC", TLDSfuncs::func_SlowSync, fty_integer, 0, NULL },
564   { "FORCESLOWSYNC", TLDSfuncs::func_ForceSlowSync, fty_none, 0, NULL },
565   { "REFRESHONLY", TLDSfuncs::func_RefreshOnly, fty_integer, 0, NULL },
566   { "SETREFRESHONLY", TLDSfuncs::func_SetRefreshOnly, fty_none, 1, param_IntArg },
567   { "READONLY", TLDSfuncs::func_ReadOnly, fty_integer, 0, NULL },
568   { "SETREADONLY", TLDSfuncs::func_SetReadOnly, fty_none, 1, param_IntArg },
569   { "FIRSTTIMESYNC", TLDSfuncs::func_FirstTimeSync, fty_integer, 0, NULL },
570   { "SETCONFLICTSTRATEGY", TLDSfuncs::func_SetConflictStrategy, fty_none, 1, param_StrArg },
571   { "DBNAME", TLDSfuncs::func_DBName, fty_string, 0, NULL },
572   { "LOCALDBNAME", TLDSfuncs::func_LocalDBName, fty_string, 0, NULL },
573   { "REMOTEDBNAME", TLDSfuncs::func_RemoteDBName, fty_string, 0, NULL },
574   { "ABORTDATASTORE", TLDSfuncs::func_AbortDatastore, fty_none, 1, param_OneInteger },
575 };
576
577 // functions for all datastores
578 const TFuncTable DBFuncTable = {
579   sizeof(DBFuncDefs) / sizeof(TBuiltInFuncDef), // size of table
580   DBFuncDefs, // table pointer
581   NULL // no chain func
582 };
583
584
585 #ifdef SYSYNC_CLIENT
586
587 const uInt8 param_OneStr[] = { VAL(fty_string) };
588 const uInt8 param_OneInt[] = { VAL(fty_integer) };
589 const uInt8 param_TwoInt[] = { VAL(fty_integer), VAL(fty_integer) };
590 const uInt8 param_SetRecordFilter[] = { VAL(fty_string), VAL(fty_integer) };
591
592 const TBuiltInFuncDef ClientDBFuncDefs[] = {
593   { "ADDTARGETCGI", TLDSfuncs::func_AddTargetCGI, fty_none, 1, param_OneStr },
594   { "SETRECORDFILTER", TLDSfuncs::func_SetRecordFilter, fty_none, 2, param_SetRecordFilter },
595   { "SETDAYSRANGE", TLDSfuncs::func_SetDaysRange, fty_none, 2, param_TwoInt },
596 };
597
598
599 // chain to general DB functions
600 static void *ClientDBChainFunc(void *&aCtx)
601 {
602   // caller context remains unchanged
603   // -> no change needed
604   // next table is general DS func table
605   return (void *)&DBFuncTable;
606 } // ClientDBChainFunc
607
608
609 // function table for client-only script functions
610 const TFuncTable ClientDBFuncTable = {
611   sizeof(ClientDBFuncDefs) / sizeof(TBuiltInFuncDef), // size of table
612   ClientDBFuncDefs, // table pointer
613   ClientDBChainFunc // chain to general agent funcs.
614 };
615
616
617 #endif // SYSYNC_CLIENT
618
619 #endif // SCRIPT_SUPPORT
620
621
622
623
624 // config
625 // ======
626
627 // conflict strategy names
628 // bfo: Problems with XCode (expicit qualification), already within namespace ?
629 //const char * const sysync::conflictStrategyNames[numConflictStrategies] = {
630 const char * const conflictStrategyNames[numConflictStrategies] = {
631   "duplicate",    // add conflicting counterpart to both databases
632   "newer-wins",   // newer version wins (if date/version comparison is possible, like sst_duplicate otherwise)
633   "server-wins",  // server version wins (and is written to client)
634   "client-wins"   // client version wins (and is written to server)
635 };
636
637
638 // type support config
639 TTypeSupportConfig::TTypeSupportConfig(const char* aName, TConfigElement *aParentElement) :
640   TConfigElement(aName,aParentElement)
641 {
642   clear();
643 } // TTypeSupportConfig::TTypeSupportConfig
644
645
646 TTypeSupportConfig::~TTypeSupportConfig()
647 {
648   clear();
649 } // TTypeSupportConfig::~TTypeSupportConfig
650
651
652 // init defaults
653 void TTypeSupportConfig::clear(void)
654 {
655   // init defaults
656   fPreferredTx = NULL;
657   fPreferredRx = NULL;
658   fPreferredLegacy = NULL;
659   fAdditionalTypes.clear();
660   #ifndef NO_REMOTE_RULES
661   fRuleMatchTypes.clear();
662   #endif
663   // clear inherited
664   inherited::clear();
665 } // TTypeSupportConfig::clear
666
667
668 #ifdef HARDCODED_CONFIG
669
670 // add type support
671 bool TTypeSupportConfig::addTypeSupport(
672   cAppCharP aTypeName,
673   bool aForRead,
674   bool aForWrite,
675   bool aPreferred,
676   cAppCharP aVariant,
677   cAppCharP aRuleMatch
678 ) {
679   // search datatype
680   TDataTypeConfig *typecfgP =
681     static_cast<TRootConfig *>(getRootElement())->fDatatypesConfigP->getDataType(aTypeName);
682   if (!typecfgP) return false;
683   // search variant
684   TTypeVariantDescriptor variantDescP = NULL;
685   if (aVariant && *aVariant)
686     variantDescP = typecfgP->getVariantDescriptor(aVariant);
687   // now add datatype
688   if (aPreferred) {
689     // - preferred
690     if (aForRead) {
691       if (!fPreferredRx) {
692         fPreferredRx=typecfgP; // set it
693         fPrefRxVariantDescP=variantDescP;
694       }
695     }
696     if (aForWrite) {
697       if (!fPreferredTx) {
698         fPreferredTx=typecfgP; // set it
699         fPrefTxVariantDescP=variantDescP;
700       }
701     }
702   } // if preferred
703   else {
704     // - additional
705     TAdditionalDataType adt;
706     adt.datatypeconfig=typecfgP;
707     adt.forRead=aForRead;
708     adt.forWrite=aForWrite;
709     adt.variantDescP=variantDescP; // variant of that type
710     #ifndef NO_REMOTE_RULES
711     if (aRuleMatch) {
712       // this is a rulematch type (which overrides normal type selection mechanism)
713       AssignString(atd.remoteRuleMatch,aRuleMatch); // remote rule match string
714       fRuleMatchTypes.push_back(adt); // save it in the list
715     }
716     else
717     #endif
718     {
719       // standard type
720       fAdditionalTypes.push_back(adt); // save it in the list
721     }
722   }
723   return true;
724 } // TTypeSupportConfig::addTypeSupport
725
726 #else
727
728 // config element parsing
729 bool TTypeSupportConfig::localStartElement(const char *aElementName, const char **aAttributes, sInt32 aLine)
730 {
731   // checking the elements
732   if (strucmp(aElementName,"use")==0) {
733     expectEmpty(); // datatype usage specs may not have
734     // process arguments
735     const char* nam = getAttr(aAttributes,"datatype");
736     if (!nam)
737       return fail("use must have 'datatype' attribute");
738     // search datatype
739     TDataTypeConfig *typecfgP =
740       static_cast<TRootConfig *>(getRootElement())->fDatatypesConfigP->getDataType(nam);
741     if (!typecfgP)
742       return fail("unknown datatype '%s' specified",nam);
743     #ifndef NO_REMOTE_RULES
744     // get rulematch string, if any
745     cAppCharP ruleMatch = getAttr(aAttributes,"rulematch");
746     #endif
747     // convert variant
748     TTypeVariantDescriptor variantDescP=NULL; // no variant descriptor by default
749     cAppCharP variant = getAttr(aAttributes,"variant");
750     if (variant) {
751       // get a type-specific descriptor which describes the variant of a type to be used with this datastore
752       variantDescP = typecfgP->getVariantDescriptor(variant);
753       if (!variantDescP)
754         return fail("unknown variant '%s' specified",variant);
755     }
756     // convert mode
757     bool rd=true,wr=true;
758     const char* mode = getAttr(aAttributes,"mode");
759     if (mode) {
760       rd=false;
761       wr=false;
762       while (*mode) {
763         if (tolower(*mode)=='r') rd=true;
764         else if (tolower(*mode)=='w') wr=true;
765         else {
766           ReportError(true,"invalid mode '%c'",*mode);
767           return true;
768         }
769         // next char
770         mode++;
771       }
772       if (!rd && !wr)
773         return fail("mode must specify 'r', 'w' or 'rw' at least");
774     }
775     // get preferred
776     bool preferred=false;
777     const char* pref = getAttr(aAttributes,"preferred");
778     if (pref) {
779       if (!StrToBool(pref, preferred)) {
780         if (strucmp(pref,"legacy")==0) {
781           // this is the preferred type for blind and legacy mode sync attempts
782           fPreferredLegacy=typecfgP; // remember (note that there is only ONE preferred type, mode is ignored)
783           preferred=false; // not officially preferred
784         }
785         else
786           return fail("bad value for 'preferred'");
787       }
788     }
789     // now add datatype
790     if (preferred) {
791       // - preferred
792       if (rd) {
793         if (fPreferredRx)
794           return fail("preferred read type already defined");
795         else {
796           fPreferredRx=typecfgP; // set it
797           fPrefRxVariantDescP=variantDescP;
798         }
799       }
800       if (wr) {
801         if (fPreferredTx)
802           return fail("preferred write type already defined");
803         else {
804           fPreferredTx=typecfgP; // set it
805           fPrefTxVariantDescP=variantDescP;
806         }
807       }
808     } // if preferred
809     else {
810       // - additional
811       TAdditionalDataType adt;
812       adt.datatypeconfig=typecfgP;
813       adt.forRead=rd;
814       adt.forWrite=wr;
815       adt.variantDescP=variantDescP;
816       #ifndef NO_REMOTE_RULES
817       if (ruleMatch) {
818         // this is a rulematch type (which overrides normal type selection mechanism)
819         AssignString(adt.remoteRuleMatch,ruleMatch); // remote rule match string
820         fRuleMatchTypes.push_back(adt); // save it in the list
821       }
822       else
823       #endif
824       {
825         // standard type
826         fAdditionalTypes.push_back(adt); // save it in the list
827       }
828     }
829   }
830   // - none known here
831   else
832     return inherited::localStartElement(aElementName,aAttributes,aLine);
833   // ok
834   return true;
835 } // TTypeSupportConfig::localStartElement
836
837 #endif
838
839
840 // resolve
841 void TTypeSupportConfig::localResolve(bool aLastPass)
842 {
843   #ifndef HARDCODED_CONFIG
844   if (aLastPass) {
845     // check for required settings
846     if (!fPreferredTx || !fPreferredRx)
847       SYSYNC_THROW(TConfigParseException("'typesupport' must contain at least one preferred type for read and write"));
848   }
849   #endif
850   // resolve inherited
851   inherited::localResolve(aLastPass);
852 } // TTypeSupportConfig::localResolve
853
854
855
856
857 // datastore config
858 TLocalDSConfig::TLocalDSConfig(const char* aName, TConfigElement *aParentElement) :
859   TConfigElement(aName,aParentElement),
860   fTypeSupport("typesupport",this)
861 {
862   clear();
863 } // TLocalDSConfig::TLocalDSConfig
864
865
866 TLocalDSConfig::~TLocalDSConfig()
867 {
868   // nop so far
869 } // TLocalDSConfig::~TLocalDSConfig
870
871
872 // init defaults
873 void TLocalDSConfig::clear(void)
874 {
875   // init defaults
876   // - conflict resolution strategy
877   fConflictStrategy=cr_newer_wins;
878   fSlowSyncStrategy=cr_newer_wins;
879   fFirstTimeStrategy=cr_newer_wins;
880   // options
881   fLocalDBTypeID=0;
882   fReadOnly=false;
883   fCanRestart=false;
884   fReportUpdates=true;
885   fDeleteWins=false; // replace wins over delete by default
886   fResendFailing=true; // resend failing items in next session by default
887   #ifdef SYSYNC_SERVER
888   fTryUpdateDeleted=false; // no attempt to update already deleted items (assuming they are invisible only)
889   fAlwaysSendLocalID=false; // off as it used to be not SCTS conformant (but would give clients chances to remap IDs)
890   #endif
891   fMaxItemsPerMessage=0; // no limit
892   #ifdef OBJECT_FILTERING
893   // - filters
894   fRemoteAcceptFilter.erase();
895   fSilentlyDiscardUnaccepted=false;
896   fLocalDBFilterConf.erase();
897   fMakePassFilter.erase();
898   fInvisibleFilter.erase();
899   fMakeVisibleFilter.erase();
900   // - DS 1.2 Filter support (<filter> allowed in Alert, <filter-rx>/<filterCap> shown in devInf)
901   fDS12FilterSupport=false; // off by default, as clients usually don't have it
902   // - Set if date range support is available in this datastore
903   fDateRangeSupported=false;
904   #endif
905   #ifdef SCRIPT_SUPPORT
906   fDBInitScript.erase();
907   fSentItemStatusScript.erase();
908   fReceivedItemStatusScript.erase();
909   fAlertScript.erase();
910   #ifdef SYSYNC_CLIENT
911   fAlertPrepScript.erase();
912   #endif
913   fDBFinishScript.erase();
914   #endif
915   // clear embedded
916   fTypeSupport.clear();
917   // clear inherited
918   inherited::clear();
919 } // TLocalDSConfig::clear
920
921
922 #ifndef HARDCODED_CONFIG
923
924 // config element parsing
925 bool TLocalDSConfig::localStartElement(const char *aElementName, const char **aAttributes, sInt32 aLine)
926 {
927   // checking the elements
928   if (strucmp(aElementName,"dbtypeid")==0)
929     expectUInt32(fLocalDBTypeID);
930   else if (strucmp(aElementName,"typesupport")==0)
931     expectChildParsing(fTypeSupport);
932   else if (strucmp(aElementName,"conflictstrategy")==0)
933     expectEnum(sizeof(fConflictStrategy),&fConflictStrategy,conflictStrategyNames,numConflictStrategies);
934   else if (strucmp(aElementName,"slowsyncstrategy")==0)
935     expectEnum(sizeof(fSlowSyncStrategy),&fSlowSyncStrategy,conflictStrategyNames,numConflictStrategies);
936   else if (strucmp(aElementName,"firsttimestrategy")==0)
937     expectEnum(sizeof(fFirstTimeStrategy),&fFirstTimeStrategy,conflictStrategyNames,numConflictStrategies);
938   else if (strucmp(aElementName,"readonly")==0)
939     expectBool(fReadOnly);
940   else if (strucmp(aElementName,"canrestart")==0)
941     expectBool(fCanRestart);
942   else if (strucmp(aElementName,"syncmode")==0) {
943     if (!fSyncModeBuffer.empty()) {
944       fSyncModes.insert(fSyncModeBuffer);
945       fSyncModeBuffer.clear();
946     }
947     expectString(fSyncModeBuffer);
948   } else if (strucmp(aElementName,"reportupdates")==0)
949     expectBool(fReportUpdates);
950   else if (strucmp(aElementName,"deletewins")==0)
951     expectBool(fDeleteWins);
952   else if (strucmp(aElementName,"resendfailing")==0)
953     expectBool(fResendFailing);
954   #ifdef SYSYNC_SERVER
955   else if (strucmp(aElementName,"tryupdatedeleted")==0)
956     expectBool(fTryUpdateDeleted);
957   else if (strucmp(aElementName,"alwayssendlocalid")==0)
958     expectBool(fAlwaysSendLocalID);
959   else if (strucmp(aElementName,"alias")==0) {
960     // get a name
961     string name;
962     if (!getAttrExpanded(aAttributes, "name", name, true))
963       return fail("Missing 'name' attribute in 'alias'");
964     fAliasNames.push_back(name);
965     expectEmpty();
966   }
967   #endif
968   else if (strucmp(aElementName,"maxitemspermessage")==0)
969     expectUInt32(fMaxItemsPerMessage);
970   #ifdef OBJECT_FILTERING
971   // filtering
972   else if (strucmp(aElementName,"acceptfilter")==0)
973     expectString(fRemoteAcceptFilter);
974   else if (strucmp(aElementName,"silentdiscard")==0)
975     expectBool(fSilentlyDiscardUnaccepted);
976   else if (strucmp(aElementName,"localdbfilter")==0)
977     expectString(fLocalDBFilterConf);
978   else if (strucmp(aElementName,"makepassfilter")==0)
979     expectString(fMakePassFilter);
980   else if (strucmp(aElementName,"invisiblefilter")==0)
981     expectString(fInvisibleFilter);
982   else if (strucmp(aElementName,"makevisiblefilter")==0)
983     expectString(fMakeVisibleFilter);
984   else if (strucmp(aElementName,"ds12filters")==0)
985     expectBool(fDS12FilterSupport);
986   else if (strucmp(aElementName,"daterangesupport")==0)
987     expectBool(fDateRangeSupported);
988   #endif
989   #ifdef SCRIPT_SUPPORT
990   else if (strucmp(aElementName,"datastoreinitscript")==0)
991     expectScript(fDBInitScript,aLine,&DBFuncTable);
992   else if (strucmp(aElementName,"sentitemstatusscript")==0)
993     expectScript(fSentItemStatusScript,aLine,&ErrorFuncTable);
994   else if (strucmp(aElementName,"receiveditemstatusscript")==0)
995     expectScript(fReceivedItemStatusScript,aLine,&ErrorFuncTable);
996   else if (strucmp(aElementName,"alertscript")==0)
997     expectScript(fAlertScript,aLine,&DBFuncTable);
998   #ifdef SYSYNC_CLIENT
999   else if (strucmp(aElementName,"alertprepscript")==0)
1000     expectScript(fAlertPrepScript,aLine,getClientDBFuncTable());
1001   #endif
1002   else if (strucmp(aElementName,"datastorefinishscript")==0)
1003     expectScript(fDBFinishScript,aLine,&DBFuncTable);
1004   #endif
1005   #ifndef MINIMAL_CODE
1006   else if (strucmp(aElementName,"displayname")==0)
1007     expectString(fDisplayName);
1008   #endif
1009   // - none known here
1010   else
1011     return inherited::localStartElement(aElementName,aAttributes,aLine);
1012   // ok
1013   return true;
1014 } // TLocalDSConfig::localStartElement
1015
1016 #endif
1017
1018 // resolve
1019 void TLocalDSConfig::localResolve(bool aLastPass)
1020 {
1021   if (!fSyncModeBuffer.empty()) {
1022     fSyncModes.insert(fSyncModeBuffer);
1023     fSyncModeBuffer.clear();
1024   }
1025
1026   if (aLastPass) {
1027     #ifdef SCRIPT_SUPPORT
1028     TScriptContext *sccP = NULL;
1029     SYSYNC_TRY {
1030       // resolve all scripts in same context
1031       // - first script needed (when alert is created)
1032       #ifdef SYSYNC_CLIENT
1033       TScriptContext::resolveScript(getSyncAppBase(),fAlertPrepScript,sccP,NULL);
1034       #endif
1035       // - scripts needed when database is made ready
1036       TScriptContext::resolveScript(getSyncAppBase(),fDBInitScript,sccP,NULL);
1037       TScriptContext::resolveScript(getSyncAppBase(),fSentItemStatusScript,sccP,NULL);
1038       TScriptContext::resolveScript(getSyncAppBase(),fReceivedItemStatusScript,sccP,NULL);
1039       TScriptContext::resolveScript(getSyncAppBase(),fAlertScript,sccP,NULL);
1040       TScriptContext::resolveScript(getSyncAppBase(),fDBFinishScript,sccP,NULL);
1041       // - forget this context
1042       if (sccP) delete sccP;
1043     }
1044     SYSYNC_CATCH (...)
1045       if (sccP) delete sccP;
1046       SYSYNC_RETHROW;
1047     SYSYNC_ENDCATCH
1048     #endif
1049   }
1050   // resolve embedded
1051   fTypeSupport.Resolve(aLastPass);
1052   // resolve inherited
1053   inherited::localResolve(aLastPass);
1054 } // TLocalDSConfig::localResolve
1055
1056
1057 // - add type support to datatstore from config
1058 void TLocalDSConfig::addTypes(TLocalEngineDS *aDatastore, TSyncSession *aSessionP)
1059 {
1060   TSyncItemType *typeP;
1061   TSyncItemType *writetypeP;
1062
1063   // preferred types, create instances only if not already existing
1064   // - preferred receive
1065   typeP = aSessionP->findLocalType(fTypeSupport.fPreferredRx);
1066   if (!typeP) {
1067     typeP = fTypeSupport.fPreferredRx->newSyncItemType(aSessionP,NULL); // local types are never exclusively related to a datastore
1068     aSessionP->addLocalItemType(typeP); // add to session
1069   }
1070   // - preferred send
1071   writetypeP = aSessionP->findLocalType(fTypeSupport.fPreferredTx);
1072   if (!writetypeP) {
1073     writetypeP = fTypeSupport.fPreferredTx->newSyncItemType(aSessionP,NULL); // local types are never exclusively related to a datastore
1074     aSessionP->addLocalItemType(writetypeP);
1075   }
1076   // - set preferred types
1077   aDatastore->setPreferredTypes(typeP,writetypeP);
1078   // additional types
1079   TAdditionalTypesList::iterator pos;
1080   for (pos=fTypeSupport.fAdditionalTypes.begin(); pos!=fTypeSupport.fAdditionalTypes.end(); pos++) {
1081     // - search for type already created from same config item
1082     typeP = aSessionP->findLocalType((*pos).datatypeconfig);
1083     if (!typeP) {
1084       // - does not exist yet, create the type
1085       typeP = (*pos).datatypeconfig->newSyncItemType(aSessionP,NULL); // local types are never exclusively related to a datastore
1086       // - add type to session types
1087       aSessionP->addLocalItemType(typeP);
1088     }
1089     // - add type to datastore's supported types
1090     aDatastore->addTypeSupport(typeP,(*pos).forRead,(*pos).forWrite);
1091   }
1092   #ifndef NO_REMOTE_RULES
1093   // rulematch types
1094   for (pos=fTypeSupport.fRuleMatchTypes.begin(); pos!=fTypeSupport.fRuleMatchTypes.end(); pos++) {
1095     // - search for type already created from same config item
1096     typeP = aSessionP->findLocalType((*pos).datatypeconfig);
1097     if (!typeP) {
1098       // - does not exist yet, create the type
1099       typeP = (*pos).datatypeconfig->newSyncItemType(aSessionP,NULL); // local types are never exclusively related to a datastore
1100       // - add type to session types
1101       aSessionP->addLocalItemType(typeP);
1102     }
1103     // - add type to datastore's rulematch types
1104     aDatastore->addRuleMatchTypeSupport(typeP,(*pos).remoteRuleMatch.c_str());
1105   }
1106   #endif
1107   // now apply type limits
1108   // Note: this is usually derived, as limits are often defined within the datastore,
1109   //       not the type itself (however, for hardcoded template-based fieldlists,
1110   //       the limits are taken from the template, see  TLocalDSConfig::addTypeLimits()
1111   addTypeLimits(aDatastore, aSessionP);
1112 } // TLocalDSConfig::addTypes
1113
1114
1115 // Add (probably datastore-specific) limits such as MaxSize and NoTruncate to types
1116 void TLocalDSConfig::addTypeLimits(TLocalEngineDS *aLocalDatastoreP, TSyncSession *aSessionP)
1117 {
1118   // add field size limitations from map to all types
1119   TSyncItemTypePContainer::iterator pos;
1120   TSyncItemTypePContainer *typesP = &(aLocalDatastoreP->fRxItemTypes);
1121   for (uInt8 i=0; i<2; i++) {
1122     for (pos=typesP->begin(); pos!=typesP->end(); pos++) {
1123       // apply default limits to type (e.g. from hard-coded template in config)
1124       (*pos)->addDefaultTypeLimits();
1125     }
1126     typesP = &(aLocalDatastoreP->fTxItemTypes);
1127   }
1128 } // TLocalDSConfig::addTypeLimits
1129
1130
1131 // Check for alias names
1132 uInt16 TLocalDSConfig::isDatastoreAlias(cAppCharP aDatastoreURI)
1133 {
1134   // only servers have (and may need) aliases
1135   #ifdef SYSYNC_SERVER
1136   for (TStringList::iterator pos = fAliasNames.begin(); pos!=fAliasNames.end(); pos++) {
1137     if (*pos == aDatastoreURI)
1138       return (*pos).size(); // return size of match
1139   }
1140   #endif
1141   return 0;
1142 } // TLocalDSConfig::isDatastoreAlias
1143
1144
1145
1146
1147 /*
1148  * Implementation of TLocalEngineDS
1149  */
1150
1151
1152 /// @Note InternalResetDataStore() must also be callable from destructor
1153 ///  (care not to call other objects which will refer to the already
1154 ///  half-destructed datastore!)
1155 void TLocalEngineDS::InternalResetDataStore(void)
1156 {
1157   // possibly complete, if not already done (should be, by engFinishDataStoreSync() !)
1158   if (fLocalDSState>dssta_idle)
1159     changeState(dssta_completed); // complete NOW, opportunity to show stats, etc.
1160   // switch down to idle
1161   changeState(dssta_idle);
1162   /// @todo obsolete: fState=dss_idle;
1163   fAbortStatusCode=LOCERR_OK; // not aborted yet
1164   fLocalAbortCause=true; // assume local cause
1165   fRemoteAddingStopped=false;
1166   fAlertCode=0; // not yet alerted
1167
1168   /// Init Sync mode @ref dsSyncMode
1169   fSyncMode=smo_twoway; // default to twoway
1170   fForceSlowSync=false;
1171   fSlowSync=false;
1172   fRefreshOnly=false;
1173   fReadOnly=false;
1174   fReportUpdates=fDSConfigP->fReportUpdates; // update reporting according to what is configured
1175   fCanRestart=fDSConfigP->fCanRestart;
1176   fSyncModes=fDSConfigP->fSyncModes;
1177   fServerAlerted=false;
1178   fResuming=false;
1179   #ifdef SUPERDATASTORES
1180   fAsSubDatastoreOf=NULL; // is not a subdatastore
1181   #endif
1182
1183
1184   /// Init administrative data to defaults @ref dsAdminData
1185   // - last
1186   fLastRemoteAnchor.erase();
1187   fLastLocalAnchor.erase();
1188   // - current
1189   fNextRemoteAnchor.erase();
1190   fNextLocalAnchor.erase();
1191   // suspend state
1192   fResumeAlertCode=0; // none
1193   fPreventResuming=false;
1194   // suspend of chunked items
1195   fPartialItemState=pi_state_none;
1196   fLastSourceURI.erase();
1197   fLastTargetURI.erase();
1198   fLastItemStatus=0;
1199   fPITotalSize=0;
1200   fPIStoredSize=0;
1201   fPIUnconfirmedSize=0;
1202   if (fPIStoredDataAllocated) {
1203     smlLibFree(fPIStoredDataP);
1204     fPIStoredDataAllocated=false;
1205   }
1206   fPIStoredDataP=NULL;
1207
1208   // other state info
1209   fFirstTimeSync=false; // not first sync by default
1210
1211   #ifdef SYSYNC_CLIENT
1212   // - maps for add commands
1213   fPendingAddMaps.clear();
1214   fUnconfirmedMaps.clear();
1215   fLastSessionMaps.clear();
1216   #endif
1217   #ifdef SYSYNC_SERVER
1218   PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,(
1219     "fTempGUIDMap: removing %ld items", (long)fTempGUIDMap.size()
1220   ));
1221   fTempGUIDMap.clear();
1222   #endif
1223
1224   /// Init type negotiation
1225   /// - for sending data
1226   fLocalSendToRemoteTypeP = NULL;
1227   fRemoteReceiveFromLocalTypeP = NULL;
1228   /// - for receiving data
1229   fLocalReceiveFromRemoteTypeP = NULL;
1230   fRemoteSendToLocalTypeP = NULL;
1231
1232   /// Init Filtering @ref dsFiltering
1233   resetFiltering();
1234
1235   /// Init item processing @ref dsItemProcessing
1236   fSessionConflictStrategy=cr_duplicate; // will be updated later when sync mode is known
1237   fItemSizeLimit=-1; // no limit yet
1238   fCurrentSyncOp = sop_none; // will be set at engProcessItem()
1239   fEchoItemOp = sop_none; // will be set at engProcessItem()
1240   fItemConflictStrategy=fSessionConflictStrategy; // will be set at engProcessItem()
1241   fForceConflict = false; // will be set at engProcessItem()
1242   fDeleteWins = false; // will be set at engProcessItem()
1243   fRejectStatus = -1; // will be set at engProcessItem()
1244   #ifdef SCRIPT_SUPPORT
1245   // - delete the script context if any
1246   if (fSendingTypeScriptContextP) {
1247     delete fSendingTypeScriptContextP;
1248     fSendingTypeScriptContextP=NULL;
1249   }
1250   if (fReceivingTypeScriptContextP) {
1251     delete fReceivingTypeScriptContextP;
1252     fReceivingTypeScriptContextP=NULL;
1253   }
1254   if (fDataStoreScriptContextP) {
1255     delete fDataStoreScriptContextP;
1256     fDataStoreScriptContextP=NULL;
1257   }
1258   #endif
1259
1260   /// Init other vars @ref dsOther
1261
1262
1263   /// Init Counters and statistics @ref dsCountStats
1264   // - NOC from remote
1265   fRemoteNumberOfChanges=-1; // none known yet
1266   // - data transferred
1267   fIncomingDataBytes=0;
1268   fOutgoingDataBytes=0;
1269   // - locally performed ops
1270   fLocalItemsAdded=0;
1271   fLocalItemsUpdated=0;
1272   fLocalItemsDeleted=0;
1273   fLocalItemsError=0;
1274   // - remotely performed ops
1275   fRemoteItemsAdded=0;
1276   fRemoteItemsUpdated=0;
1277   fRemoteItemsDeleted=0;
1278   fRemoteItemsError=0;
1279   #ifdef SYSYNC_SERVER
1280   // - conflicts
1281   fConflictsServerWins=0;
1282   fConflictsClientWins=0;
1283   fConflictsDuplicated=0;
1284   // - slow sync matches
1285   fSlowSyncMatches=0;
1286   #endif
1287
1288   // Override defaults from ancestor
1289   // - generally, limit GUID size to reasonable size (even if we can
1290   //   theoretically handle unlimited GUIDs in client, except for
1291   //   some DEBUGPRINTF statements that will crash for example with
1292   //   the brain-damaged GUIDs that Exchange server uses.
1293   fMaxGUIDSize = 64;
1294 } // TLocalEngineDS::InternalResetDataStore
1295
1296
1297 /// constructor
1298 TLocalEngineDS::TLocalEngineDS(TLocalDSConfig *aDSConfigP, TSyncSession *aSessionP, const char *aName, uInt32 aCommonSyncCapMask) :
1299   TSyncDataStore(aSessionP, aName, aCommonSyncCapMask)
1300   ,fPIStoredDataP(NULL)
1301   ,fPIStoredDataAllocated(false)
1302   #ifdef SCRIPT_SUPPORT
1303   ,fSendingTypeScriptContextP(NULL) // no associated script context
1304   ,fReceivingTypeScriptContextP(NULL) // no associated script context
1305   ,fDataStoreScriptContextP(NULL) // no datastore level context
1306   #endif
1307   ,fRemoteDatastoreP(NULL) // no associated remote
1308 {
1309   // set config ptr
1310   fDSConfigP = aDSConfigP;
1311   if (!fDSConfigP)
1312     SYSYNC_THROW(TSyncException(DEBUGTEXT("TLocalEngineDS::TLocalEngineDS called with NULL config","lds1")));
1313   /// Init Sync state @ref dsSyncState
1314   fLocalDSState=dssta_idle; // idle to begin with
1315   // now reset
1316   InternalResetDataStore();
1317 } // TLocalEngineDS::TLocalEngineDS
1318
1319
1320
1321
1322 TLocalEngineDS::~TLocalEngineDS()
1323 {
1324   // reset everything
1325   InternalResetDataStore();
1326 } // TLocalEngineDS::~TLocalEngineDS
1327
1328
1329 #ifdef SYDEBUG
1330
1331 // return datastore state name
1332 cAppCharP TLocalEngineDS::getDSStateName(void)
1333 {
1334   return LocalDSStateNames[fLocalDSState];
1335 } // TLocalEngineDS::getDSStateName
1336
1337
1338 // return datastore state name
1339 cAppCharP TLocalEngineDS::getDSStateName(TLocalEngineDSState aState)
1340 {
1341   return LocalDSStateNames[aState];
1342 } // TLocalEngineDS::getDSStateName
1343
1344 #endif
1345
1346 // reset datastore (after use)
1347 void TLocalEngineDS::engResetDataStore(void)
1348 {
1349   // now reset
1350   // - logic layer and above
1351   dsResetDataStore();
1352   // - myself
1353   InternalResetDataStore();
1354   // - anchestors
1355   inherited::engResetDataStore();
1356 } // TLocalEngineDS::engResetDataStore
1357
1358
1359
1360 // check if this datastore is accessible with given URI
1361 // NOTE: By default, local datastore type is addressed with
1362 //       first path element of URI, rest of path might be used
1363 //       by derivates to subselect data folders etc.
1364 uInt16 TLocalEngineDS::isDatastore(const char *aDatastoreURI)
1365 {
1366   // extract base name
1367   string basename;
1368   analyzeName(aDatastoreURI,&basename);
1369   // compare only base name
1370   // - compare with main name
1371   int res = inherited::isDatastore(basename.c_str());
1372   if (res==0) {
1373     // Not main name: compare with aliases
1374     res = fDSConfigP->isDatastoreAlias(basename.c_str());
1375   }
1376   return res;
1377 } // TLocalEngineDS::isDatastore
1378
1379
1380
1381 /// get DB specific error message text for dbg log, or empty string if none
1382 /// @return platform specific DB error text
1383 string TLocalEngineDS::lastDBErrorText(void)
1384 {
1385   string s;
1386   s.erase();
1387   uInt32 err = lastDBError();
1388   if (isDBError(err)) {
1389     StringObjPrintf(s," (DB specific error code = %ld)",(long)lastDBError());
1390   }
1391   return s;
1392 } // TLocalEngineDS::lastDBErrorText
1393
1394
1395 #ifdef SYSYNC_CLIENT
1396
1397 // - init Sync Parameters (client case)
1398 //   Derivates might override this to pre-process and modify parameters
1399 //   (such as adding client settings as CGI to remoteDBPath)
1400 bool TLocalEngineDS::dsSetClientSyncParams(
1401   TSyncModes aSyncMode,
1402   bool aSlowSync,
1403   const char *aRemoteDBPath,
1404   const char *aDBUser,
1405   const char *aDBPassword,
1406   const char *aLocalPathExtension,
1407   const char *aRecordFilterQuery,
1408   bool aFilterInclusive
1409 )
1410 {
1411   // - set remote params
1412   fRemoteDBPath=aRemoteDBPath;
1413   AssignString(fDBUser,aDBUser);
1414   AssignString(fDBPassword,aDBPassword);
1415   // check for running under control of a superdatastore
1416   // - aRemoteDBPath might contain a special prefix: "<super>remote", with "super" specifying the
1417   //   name of a local superdatastore to run the sync with
1418   string opts;
1419   if (!fRemoteDBPath.empty() && fRemoteDBPath.at(0)=='<') {
1420     // we have an option prefix
1421     size_t pfxe = fRemoteDBPath.find('>', 1);
1422     if (pfxe!=string::npos) {
1423       // extract options
1424       opts.assign(fRemoteDBPath, 1, pfxe-1);
1425       // store remote path cleaned from options
1426       fRemoteDBPath.erase(0,pfxe+1);
1427     }
1428   }
1429   if (!opts.empty()) {
1430     #ifdef SUPERDATASTORES
1431     // For now, the only option withing angle brackets is the name of the superdatastore, so opts==superdatastorename
1432     // - look for superdatastore having the specified name
1433     TSuperDSConfig *superdscfgP = static_cast<TSuperDSConfig *>(getSession()->getSessionConfig()->getLocalDS(opts.c_str()));
1434     if (superdscfgP && superdscfgP->isAbstractDatastore()) {
1435       // see if we have an instance of this already
1436       fAsSubDatastoreOf = static_cast<TSuperDataStore *>(getSession()->findLocalDataStore(superdscfgP));
1437       if (fAsSubDatastoreOf) {
1438         // that superdatastore already exists, just override client sync params with those already set
1439         aSyncMode = fAsSubDatastoreOf->fSyncMode;
1440         aSlowSync = fAsSubDatastoreOf->fSlowSync;
1441         aRecordFilterQuery = fAsSubDatastoreOf->fRemoteRecordFilterQuery.c_str();
1442       }
1443       else {
1444         // instantiate new superdatastore
1445         fAsSubDatastoreOf = static_cast<TSuperDataStore *>(superdscfgP->newLocalDataStore(getSession()));
1446         if (fAsSubDatastoreOf) {
1447           fSessionP->fLocalDataStores.push_back(fAsSubDatastoreOf);
1448           // configure it with the same parameters as the subdatastore
1449           if (!fAsSubDatastoreOf->dsSetClientSyncParams(
1450             aSyncMode,
1451             aSlowSync,
1452             fRemoteDBPath.c_str(), // already cleaned from <xxx> prefix
1453             aDBUser,
1454             aDBPassword,
1455             aLocalPathExtension,
1456             aRecordFilterQuery,
1457             aFilterInclusive
1458           ))
1459             return false; // failed
1460         }
1461       }
1462       if (fAsSubDatastoreOf) {
1463         // find link config for this superdatastore
1464         TSubDSLinkConfig *lcfgP = NULL;
1465         TSubDSConfigList::iterator pos;
1466         for(pos=superdscfgP->fSubDatastores.begin();pos!=superdscfgP->fSubDatastores.end();pos++) {
1467           if ((*pos)->fLinkedDSConfigP==fDSConfigP) {
1468             // this is the link
1469             lcfgP = *pos;
1470             break;
1471           }
1472         }
1473         if (lcfgP) {
1474           // now link into superdatastore
1475           fAsSubDatastoreOf->addSubDatastoreLink(lcfgP,this);
1476         }
1477         else {
1478           PDEBUGPRINTFX(DBG_ERROR,("Warning: '%s' is not a subdatastore of '%s'", getName(), opts.c_str()));
1479           return false; // failed
1480         }
1481       }
1482     }
1483     else {
1484       PDEBUGPRINTFX(DBG_ERROR,("Warning: No superdatastore name '%s' exists -> can't run '%s' under superdatastore control", opts.c_str(), getName()));
1485       return false; // failed
1486     }
1487     #endif // SUPERDATASTORES
1488   }
1489   // sync mode
1490   fSyncMode=aSyncMode;
1491   fSlowSync=aSlowSync;
1492   fRefreshOnly=fSyncMode==smo_fromserver; // here to make sure, should be set by setSyncMode(FromAlertCode) later anyway
1493   // DS 1.2 filters
1494   AssignString(fRemoteRecordFilterQuery,aRecordFilterQuery);
1495   fRemoteFilterInclusive=aFilterInclusive;
1496   // params
1497   // - build local path
1498   fLocalDBPath=URI_RELPREFIX;
1499   fLocalDBPath+=getName();
1500   if (aLocalPathExtension && *aLocalPathExtension) {
1501     fLocalDBPath+='/';
1502     fLocalDBPath+=aLocalPathExtension;
1503   }
1504   // - we have the params for syncing now
1505   return changeState(dssta_clientparamset)==LOCERR_OK;
1506 } // TLocalEngineDS::dsSetClientSyncParams
1507
1508 #endif // SYSYNC_CLIENT
1509
1510
1511
1512 // add support for more data types
1513 // (for programatically creating local datastores from specialized TSyncSession derivates)
1514 void TLocalEngineDS::addTypeSupport(TSyncItemType *aItemTypeP,bool aForRx, bool aForTx)
1515 {
1516   if (aForRx) fRxItemTypes.push_back(aItemTypeP);
1517   if (aForTx) fTxItemTypes.push_back(aItemTypeP);
1518 } // TLocalEngineDS::addTypeSupport
1519
1520
1521 #ifndef NO_REMOTE_RULES
1522 // add data type that overrides normal type selection if string matches active remote rule
1523 void TLocalEngineDS::addRuleMatchTypeSupport(TSyncItemType *aItemTypeP,cAppCharP aRuleMatchString)
1524 {
1525   TRuleMatchTypeEntry rme;
1526   rme.itemTypeP = aItemTypeP;
1527   rme.ruleMatchString = aRuleMatchString;
1528   fRuleMatchItemTypes.push_back(rme);
1529 } // TLocalEngineDS::addRuleMatchTypeSupport
1530 #endif
1531
1532
1533 TTypeVariantDescriptor TLocalEngineDS::getVariantDescForType(TSyncItemType *aItemTypeP)
1534 {
1535   // search in config for specific variant descriptor
1536   // - first check preferred rx
1537   if (fDSConfigP->fTypeSupport.fPreferredRx == aItemTypeP->getTypeConfig())
1538     return fDSConfigP->fTypeSupport.fPrefRxVariantDescP;
1539   // - then check preferred tx
1540   if (fDSConfigP->fTypeSupport.fPreferredTx == aItemTypeP->getTypeConfig())
1541     return fDSConfigP->fTypeSupport.fPrefTxVariantDescP;
1542   // - then check additional types
1543   TAdditionalTypesList::iterator pos;
1544   for (pos=fDSConfigP->fTypeSupport.fAdditionalTypes.begin(); pos!=fDSConfigP->fTypeSupport.fAdditionalTypes.end(); pos++) {
1545     if ((*pos).datatypeconfig == aItemTypeP->getTypeConfig())
1546       return (*pos).variantDescP;
1547   }
1548   // none found
1549   return NULL;
1550 } // TLocalEngineDS::getVariantDescForType
1551
1552
1553
1554
1555 // - called when a item in the sync set changes its localID (due to local DB internals)
1556 void TLocalEngineDS::dsLocalIdHasChanged(const char *aOldID, const char *aNewID)
1557 {
1558   #ifdef SYSYNC_SERVER
1559   if (IS_SERVER) {
1560     // make sure remapped localIDs get updated as well
1561     TStringToStringMap::iterator pos;
1562     for (pos=fTempGUIDMap.begin(); pos!=fTempGUIDMap.end(); pos++) {
1563       if (pos->second == aOldID) {
1564         // update ID
1565         PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,(
1566           "fTempGUIDMap: updating mapping of %s from %s to %s",
1567           pos->first.c_str(),
1568           aOldID,
1569           aNewID
1570         ));
1571         pos->second = aNewID;
1572         break;
1573       }
1574     }
1575   }
1576   #endif // SYSYNC_SERVER
1577 } // TLocalEngineDS::dsLocalIdHasChanged
1578
1579
1580 #ifdef SYSYNC_SERVER
1581
1582 // for received GUIDs (Map command), obtain real GUID (might be temp GUID due to maxguidsize restrictions)
1583 void TLocalEngineDS::obtainRealLocalID(string &aLocalID)
1584 {
1585   if (aLocalID.size()>0 && aLocalID[0]=='#') {
1586     // seems to be a temp GUID
1587     TStringToStringMap::iterator pos =
1588       fTempGUIDMap.find(aLocalID);
1589     if (pos!=fTempGUIDMap.end()) {
1590       // found temp GUID mapping, replace it
1591       PDEBUGPRINTFX(DBG_DATA,(
1592         "translated tempLocalID='%s' back to real localID='%s'",
1593         aLocalID.c_str(),
1594         (*pos).second.c_str()
1595       ));
1596       aLocalID = (*pos).second;
1597     }
1598     else {
1599       PDEBUGPRINTFX(DBG_ERROR,("No realLocalID found for tempLocalID='%s'",aLocalID.c_str()));
1600     }
1601   }
1602 } // TLocalEngineDS::obtainRealLocalID
1603
1604
1605 // for sending GUIDs (Add command), generate temp GUID which conforms to maxguidsize of remote datastore if needed
1606 void TLocalEngineDS::adjustLocalIDforSize(string &aLocalID, sInt32 maxguidsize, sInt32 prefixsize)
1607 {
1608   if (maxguidsize>0) {
1609     if (aLocalID.length()+prefixsize>(uInt32)maxguidsize) { //BCPPB needed unsigned cast
1610       // real GUID is too long, we need to create a temp
1611       #if SYDEBUG>1
1612       // first check if there is already a mapping for it,
1613       // because on-disk storage can only hold one; also
1614       // saves space
1615       // TODO: implement this more efficiently than this O(N) search
1616       for (TStringToStringMap::const_iterator it = fTempGUIDMap.begin();
1617            it != fTempGUIDMap.end();
1618            ++it) {
1619         if (it->second == aLocalID) {
1620           // found an existing mapping!
1621           PDEBUGPRINTFX(DBG_ERROR,(
1622             "fTempGUIDMap: translated realLocalID='%s' to tempLocalID='%s' (reused?!)",
1623             aLocalID.c_str(),
1624             it->first.c_str()
1625           ));
1626           aLocalID = it->first;
1627           return;
1628         }
1629       }
1630       string tempguid;
1631       long counter = fTempGUIDMap.size(); // as list only grows, we have unique tempuids for sure
1632       while (true) {
1633         counter++;
1634         StringObjPrintf(tempguid,"#%ld",counter);
1635         if (fTempGUIDMap.find(tempguid) != fTempGUIDMap.end()) {
1636           PDEBUGPRINTFX(DBG_ERROR,(
1637             "fTempGUIDMap: '%s' not new?!",
1638             tempguid.c_str()
1639           ));
1640         } else {
1641           break;
1642         }
1643       }
1644       #else
1645       // rely on tempguid list only growing (which still holds true)
1646       string tempguid;
1647       StringObjPrintf(tempguid,"#%ld",(long)fTempGUIDMap.size()+1); // as list only grows, we have unique tempuids for sure
1648       #endif
1649       fTempGUIDMap[tempguid]=aLocalID;
1650       PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,(
1651         "fTempGUIDMap: translated realLocalID='%s' to tempLocalID='%s'",
1652         aLocalID.c_str(),
1653         tempguid.c_str()
1654       ));
1655       aLocalID=tempguid;
1656     }
1657   }
1658 } // TLocalEngineDS::adjustLocalIDforSize
1659
1660 #endif // SYSYNC_SERVER
1661
1662
1663 // set Sync types needed for sending local data to remote DB
1664 void TLocalEngineDS::setSendTypeInfo(
1665   TSyncItemType *aLocalSendToRemoteTypeP,
1666   TSyncItemType *aRemoteReceiveFromLocalTypeP
1667 )
1668 {
1669   fLocalSendToRemoteTypeP=aLocalSendToRemoteTypeP;
1670   fRemoteReceiveFromLocalTypeP=aRemoteReceiveFromLocalTypeP;
1671 } // TLocalEngineDS::setSendTypeInfo
1672
1673
1674 // set Sync types needed for receiving remote data in local DB
1675 void TLocalEngineDS::setReceiveTypeInfo(
1676   TSyncItemType *aLocalReceiveFromRemoteTypeP,
1677   TSyncItemType *aRemoteSendToLocalTypeP
1678 )
1679 {
1680   fLocalReceiveFromRemoteTypeP=aLocalReceiveFromRemoteTypeP;
1681   fRemoteSendToLocalTypeP=aRemoteSendToLocalTypeP;
1682 } // TLocalEngineDS::setReceiveTypeInfo
1683
1684
1685 // - init usage of datatypes set with setSendTypeInfo/setReceiveTypeInfo
1686 localstatus TLocalEngineDS::initDataTypeUse(void)
1687 {
1688   localstatus sta = LOCERR_OK;
1689
1690   // check compatibility
1691   if (
1692     !fLocalSendToRemoteTypeP || !fLocalReceiveFromRemoteTypeP ||
1693     !fLocalSendToRemoteTypeP->isCompatibleWith(fLocalReceiveFromRemoteTypeP)
1694   ) {
1695     // send and receive types not compatible
1696     sta = 415;
1697     PDEBUGPRINTFX(DBG_ERROR,("Incompatible send and receive types -> cannot sync (415)"));
1698     engAbortDataStoreSync(sta,true,false); // do not proceed with sync of this datastore, local problem, not resumable
1699     return sta;
1700   }
1701   #ifdef SCRIPT_SUPPORT
1702   // let types initialize themselves for being used by this datastore
1703   // - optimization: if both types are same, initialize only once
1704   if (fLocalSendToRemoteTypeP == fLocalReceiveFromRemoteTypeP)
1705     fLocalReceiveFromRemoteTypeP->initDataTypeUse(this,true,true); // send and receive
1706   else {
1707     fLocalSendToRemoteTypeP->initDataTypeUse(this,true,false); // for sending, not receiving
1708     fLocalReceiveFromRemoteTypeP->initDataTypeUse(this,false,true); // not sending, for receiving
1709   }
1710   #endif
1711   // ok
1712   return sta;
1713 } // TLocalEngineDS::initDataTypeUse
1714
1715
1716
1717 // conflict resolution strategy. Conservative here
1718 TConflictResolution TLocalEngineDS::getConflictStrategy(bool aForSlowSync, bool aForFirstTime)
1719 {
1720   return aForSlowSync ?
1721     (aForFirstTime ? fDSConfigP->fFirstTimeStrategy : fDSConfigP->fSlowSyncStrategy) :
1722     fDSConfigP->fConflictStrategy;
1723 } // TLocalEngineDS::getConflictStrategy
1724
1725
1726
1727 // add filter keywords and property names to filterCap
1728 void TLocalEngineDS::addFilterCapPropsAndKeywords(SmlPcdataListPtr_t &aFilterKeywords, SmlPcdataListPtr_t &aFilterProps)
1729 {
1730   #ifdef OBJECT_FILTERING
1731   // add my own properties
1732   if (fDSConfigP->fDateRangeSupported) {
1733     addPCDataStringToList("BEFORE", &aFilterKeywords);
1734     addPCDataStringToList("SINCE", &aFilterKeywords);
1735   }
1736   // get default send type
1737   TSyncItemType *itemTypeP = fSessionP->findLocalType(fDSConfigP->fTypeSupport.fPreferredTx);
1738   TTypeVariantDescriptor variantDesc = NULL;
1739   doesUseType(itemTypeP, &variantDesc);
1740   // have it add it's keywords and properties
1741   itemTypeP->addFilterCapPropsAndKeywords(aFilterKeywords,aFilterProps,variantDesc);
1742   #endif
1743 } // TLocalEngineDS::addFilterCapPropsAndKeywords
1744
1745
1746
1747
1748
1749 // reset all filter settings
1750 void TLocalEngineDS::resetFiltering(void)
1751 {
1752   #ifdef OBJECT_FILTERING
1753   // - dynamic sync set filter
1754   fSyncSetFilter.erase();
1755   // - static filter
1756   fLocalDBFilter=fDSConfigP->fLocalDBFilterConf; // use configured localDB filter
1757   // - TAF filters
1758   #ifdef SYNCML_TAF_SUPPORT
1759   fTargetAddressFilter.erase(); // none from <sync> yet
1760   fIntTargetAddressFilter.erase(); // none from internal source (script)
1761   #endif
1762   #ifdef SYSYNC_TARGET_OPTIONS
1763   // - Other filtering options
1764   fDateRangeStart=0; // no date range
1765   fDateRangeEnd=0;
1766   fSizeLimit=-1; // no size limit
1767   fMaxItemCount=0; // no item count limit
1768   fNoAttachments=false; // attachments not suppressed
1769   fDBOptions.erase(); // no options
1770   #endif
1771   // - Filtering requirements
1772   fTypeFilteringNeeded=false;
1773   fFilteringNeededForAll=false;
1774   fFilteringNeeded=false;
1775   #endif // OBJECT_FILTERING
1776 } // TLocalEngineDS::resetFiltering
1777
1778
1779
1780 #ifdef OBJECT_FILTERING
1781
1782 /// @brief check single filter term for DS 1.2 filterkeywords.
1783 /// @return true if term still needs to be added to normal filter expression, false if term will be handled otherwise
1784 bool TLocalEngineDS::checkFilterkeywordTerm(
1785   cAppCharP aIdent, bool aAssignToMakeTrue,
1786   cAppCharP aOp, bool aCaseInsensitive,
1787   cAppCharP aVal, bool aSpecialValue,
1788   TSyncItemType *aItemTypeP
1789 )
1790 {
1791   // show it to the datatype (if any)
1792   if (aItemTypeP) {
1793     if (!aItemTypeP->checkFilterkeywordTerm(aIdent, aAssignToMakeTrue, aOp, aCaseInsensitive, aVal, aSpecialValue))
1794       return false; // type fully handles it, no need to check it further or add it to the filter expression
1795   }
1796   // we generally implement BEFORE and SINCE on the datastore level
1797   // This might not make sense depending on the actual datatype, but does not harm either
1798   #ifdef SYSYNC_TARGET_OPTIONS
1799   timecontext_t tctx;
1800
1801   if (strucmp(aIdent,"BEFORE")==0) {
1802     if (ISO8601StrToTimestamp(aVal,fDateRangeEnd,tctx)) {
1803       TzConvertTimestamp(fDateRangeEnd,tctx,TCTX_UTC,getSessionZones(),fSessionP->fUserTimeContext);
1804     }
1805     else {
1806       PDEBUGPRINTFX(DBG_ERROR,("invalid ISO datetime for BEFORE: '%s'",aVal));
1807       return true; // add it to filter, possibly this is not meant to be a filterkeyword
1808     }
1809   }
1810   else if (strucmp(aIdent,"SINCE")==0) {
1811     if (ISO8601StrToTimestamp(aVal,fDateRangeStart,tctx)) {
1812       TzConvertTimestamp(fDateRangeStart,tctx,TCTX_UTC,getSessionZones(),fSessionP->fUserTimeContext);
1813     }
1814     else {
1815       PDEBUGPRINTFX(DBG_ERROR,("invalid ISO datetime for SINCE: '%s'",aVal));
1816       return true; // add it to filter, possibly this is not meant to be a filterkeyword
1817     }
1818   }
1819   else if (strucmp(aIdent,"MAXSIZE")==0) {
1820     if (StrToFieldinteger(aVal,fSizeLimit)==0) {
1821       PDEBUGPRINTFX(DBG_ERROR,("invalid integer for MAXSIZE: '%s'",aVal));
1822       return true; // add it to filter, possibly this is not meant to be a filterkeyword
1823     }
1824   }
1825   else if (strucmp(aIdent,"MAXCOUNT")==0) {
1826     if (StrToULong(aVal,fMaxItemCount)==0) {
1827       PDEBUGPRINTFX(DBG_ERROR,("invalid integer for MAXSIZE: '%s'",aVal));
1828       return true; // add it to filter, possibly this is not meant to be a filterkeyword
1829     }
1830   }
1831   else if (strucmp(aIdent,"NOATT")==0) {
1832     if (!StrToBool(aVal,fNoAttachments)==0) {
1833       PDEBUGPRINTFX(DBG_ERROR,("invalid boolean for NOATT: '%s'",aVal));
1834       return true; // add it to filter, possibly this is not meant to be a filterkeyword
1835     }
1836   }
1837   else if (strucmp(aIdent,"DBOPTIONS")==0) {
1838     fDBOptions = aVal; // just get DB options
1839   }
1840   #endif
1841   else {
1842     // unknown identifier, add to filter expression
1843     return true;
1844   }
1845   // this term will be processed by special mechanism like fDateRangeStart/fDateRangeEnd
1846   // or fSizeLimit, so there is no need for normal filtering
1847   return false; // do not include into filter
1848 } // TLocalEngineDS::checkFilterkeywordTerm
1849
1850
1851 /// @brief parse "syncml:filtertype-cgi" filter, convert into internal filter syntax
1852 ///  and possibly sets some special filter options (fDateRangeStart, fDateRangeEnd)
1853 ///  based on "filterkeywords" available for the type passed (DS 1.2).
1854 ///  For parsing DS 1.1/1.0 TAF-style filters, aItemType can be NULL, no type-specific
1855 ///  filterkeywords can be parsed then.
1856 /// @return pointer to next character after processing (usually points to terminator)
1857 /// @param[in] aCGI the NUL-terminated filter string
1858 /// @param[in] aItemTypeP if not NULL, this is the item type the filter applies to
1859 const char *TLocalEngineDS::parseFilterCGI(cAppCharP aCGI, TSyncItemType *aItemTypeP, string &aFilter)
1860 {
1861   const char *p=aCGI, *q;
1862   sInt16 paraNest=0; // nested paranthesis
1863   string ident;
1864   char op[3];
1865   char logop;
1866   string val;
1867   bool termtofilter;
1868   bool assigntomaketrue;
1869   bool specialvalue;
1870   bool caseinsensitive;
1871
1872   PDEBUGPRINTFX(DBG_FILTER+DBG_EXOTIC,("Parsing syncml:filtertype-cgi filter: %s",aCGI));
1873   aFilter.erase();
1874   logop=0;
1875   while (p && *p) {
1876     if (aFilter.empty()) logop=0; // ignore logical operation that would be at beginning of an expression
1877     // skip spaces
1878     while (isspace(*p)) p++;
1879     // now we need an ident or paranthesis
1880     if (*p=='(') {
1881       if (logop) aFilter+=logop; // expression continues, we need the logop now
1882       logop=0; // now consumed
1883       paraNest++;
1884       aFilter+='(';
1885       p++;
1886     }
1887     else {
1888       // must be term: ident op val
1889       // - check special case pseudo-identifiers
1890       if (strucmp(p,"&LUID;",6)==0) {
1891         ident="LUID";
1892         p+=6;
1893       }
1894       else {
1895         // normal identifier
1896         // - search end
1897         q=p;
1898         while (isalnum(*q) || *q=='[' || *q==']' || *q=='.' || *q=='_') q++;
1899         // - assign
1900         if (q==p) {
1901           PDEBUGPRINTFX(DBG_ERROR,("Expected identifier but found '%s'",p));
1902           break;
1903         }
1904         ident.assign(p,q-p);
1905         p=q;
1906       }
1907       // skip spaces
1908       while (isspace(*p)) p++;
1909       // next must be comparison operator, possibly preceeded by modifiers
1910       op[0]=0; op[1]=0; op[2]=0;
1911       assigntomaketrue=false;
1912       specialvalue=false;
1913       caseinsensitive=false;
1914       // - check modifiers first
1915       if (*p==':') { assigntomaketrue=true; p++; }
1916       if (*p=='*') { specialvalue=true; p++; }
1917       if (*p=='^') { caseinsensitive=true; p++; }
1918       // - now OP either in internal form or as pseudo-entity
1919       if (*p=='>' || *p=='<') {
1920         // possible two-char ops (>=, <=, <>)
1921         op[0]=*p++;
1922         if (*p=='>' || *p=='=') {
1923           op[1]=*p++;
1924         }
1925       }
1926       else if (*p=='=' || *p=='%' || *p=='$') {
1927         // single char ops, just use them as is
1928         op[0]=*p++;
1929       }
1930       else if (*p=='&') {
1931         p++;
1932         if (tolower(*p)=='i') { caseinsensitive=true; p++; }
1933         if (strucmp(p,"eq;",3)==0) { op[0]='='; p+=3; }
1934         else if (strucmp(p,"gt;",3)==0) { op[0]='>'; p+=3; }
1935         else if (strucmp(p,"ge;",3)==0) { op[0]='>'; op[1]='='; p+=3; }
1936         else if (strucmp(p,"lt;",3)==0) { op[0]='<'; p+=3; }
1937         else if (strucmp(p,"le;",3)==0) { op[0]='<'; op[1]='='; p+=3; }
1938         else if (strucmp(p,"ne;",3)==0) { op[0]='<'; op[1]='>'; p+=3; }
1939         else if (strucmp(p,"con;",4)==0) { op[0]='%'; p+=4; }
1940         else if (strucmp(p,"ncon;",5)==0) { op[0]='$'; p+=5; }
1941         else {
1942           PDEBUGPRINTFX(DBG_ERROR,("Expected comparison operator pseudo-entity but found '%s'",p-1));
1943           break;
1944         }
1945       }
1946       else {
1947         PDEBUGPRINTFX(DBG_ERROR,("Expected comparison operator but found '%s'",p));
1948         break;
1949       }
1950       // next must be value
1951       // - check for special value cases
1952       if (strucmp(p,"&NULL;",6)==0) {
1953         // SyncML DS 1.2
1954         p+=6;
1955         val='E';
1956         specialvalue=true;
1957       }
1958       else if (strucmp(p,"&UNASSIGNED;",12)==0) {
1959         // Synthesis extension
1960         p+=12;
1961         val='U';
1962         specialvalue=true;
1963       }
1964       else {
1965         val.erase();
1966       }
1967       // - get value chars
1968       while (*p && *p!='&' && *p!='|' && *p!=')') {
1969         // value char, possibly hex escaped
1970         uInt16 c;
1971         if (*p=='%') {
1972           // convert from hex
1973           if (HexStrToUShort(p+1,c,2)==2) {
1974             p+=3;
1975           }
1976           else
1977             c=*p++;
1978         }
1979         else
1980           c=*p++;
1981         // add to value
1982         val += (char)c;
1983       } // value
1984       // now we have identifier, op and value
1985       // - check and possibly sort out filterkeyword terms
1986       termtofilter = checkFilterkeywordTerm(ident.c_str(),assigntomaketrue,op,caseinsensitive,val.c_str(),specialvalue,aItemTypeP);
1987       // - add to filter if not handled already by other mechanism
1988       if (termtofilter) {
1989         if (logop) aFilter+=logop; // if this is a continuation, add logical operator now
1990         aFilter += ident;
1991         if (assigntomaketrue) aFilter+=':';
1992         if (specialvalue) aFilter+='*';
1993         if (caseinsensitive) aFilter+='^';
1994         aFilter += op;
1995         aFilter += val;
1996       }
1997       else {
1998         PDEBUGPRINTFX(DBG_FILTER+DBG_EXOTIC,(
1999           "checkFilterkeywordTerm(%s,%hd,%s,%hd,%s,%hd) prevents adding to filter",
2000           ident.c_str(),(uInt16)assigntomaketrue,op,(uInt16)caseinsensitive,val.c_str(),(uInt16)specialvalue
2001         ));
2002         if (logop) {
2003           PDEBUGPRINTFX(DBG_FILTER,("Ignored logical operation '%c' due to always-ANDed filterkeyword",logop));
2004         }
2005       }
2006       // now check for continuation: optional closing paranthesis plus logical op
2007       // - closing paranthesis
2008       do {
2009         // skip spaces
2010         while (isspace(*p)) p++;
2011         if (*p!=')') break;
2012         if (paraNest==0) {
2013           // as we might parse filters as part of /fi() or /tf() options,
2014           // this is not an error but only means end of filter expression
2015           goto endFilter;
2016         }
2017         aFilter+=')';
2018         paraNest--;
2019         p++;
2020       } while (true);
2021       // - logical op
2022       if (*p==0)
2023         break; // done
2024       else if (*p=='&') {
2025         logop=*p++; // if no entity matches, & by itself is treated as AND
2026         if (strucmp(p,"amp;",4)==0) { logop='&'; p+=4; }
2027         else if (strucmp(p,"and;",4)==0) { logop='&'; p+=4; }
2028         else if (strucmp(p,"or;",3)==0) { logop='|'; p+=3; }
2029       }
2030       else if (*p=='|') {
2031         logop='|';
2032       }
2033       else {
2034         PDEBUGPRINTFX(DBG_ERROR,("Expected logical operator or end of filter but found '%s'",p));
2035         break;
2036       }
2037     } // not opening paranthesis
2038   } // while not end of filter
2039 endFilter:
2040   PDEBUGPRINTFX(DBG_FILTER+DBG_EXOTIC,("Resulting internal filter: %s",aFilter.c_str()));
2041   // return pointer to terminating character
2042   return p;
2043 } // TLocalEngineDS::parseFilterCGI
2044
2045
2046 #endif
2047
2048
2049 // analyze database name
2050 void TLocalEngineDS::analyzeName(
2051   const char *aDatastoreURI,
2052   string *aBaseNameP,
2053   string *aTableNameP,
2054   string *aCGIP
2055 )
2056 {
2057   const char *p,*q=NULL, *r;
2058   r=strchr(aDatastoreURI,'?');
2059   p=strchr(aDatastoreURI,'/');
2060   if (r && p>r) p=NULL; // if slash is in CGI, ignore it
2061   else q=p+1; // slash exclusive
2062   if (p!=NULL) {
2063     // we have more than just the first element
2064     if (aBaseNameP) aBaseNameP->assign(aDatastoreURI,p-aDatastoreURI);
2065     // rest is table name and probably CGI
2066     if (aTableNameP) {
2067       if (r) aTableNameP->assign(q,r-q); // we have CGI
2068       else *aTableNameP=q; // entire rest is tablename
2069     }
2070   }
2071   else {
2072     // no second path element, but possibly CGI
2073     // - assign base name
2074     if (aBaseNameP) {
2075       if (r)
2076         (*aBaseNameP).assign(aDatastoreURI,r-aDatastoreURI); // only up to CGI
2077       else
2078         (*aBaseNameP)=aDatastoreURI; // complete name
2079     }
2080     // - there is no table name
2081     if (aTableNameP) aTableNameP->erase();
2082   }
2083   // return CGI (w/o question mark) if any
2084   if (aCGIP) {
2085     if (r) *aCGIP=r+1; else aCGIP->erase();
2086   }
2087 } // TLocalEngineDS::analyzeName
2088
2089
2090 #ifdef SYSYNC_TARGET_OPTIONS
2091
2092 // parses single option, returns pointer to terminating char of argument string
2093 // or NULL on error
2094 // Note: if aArguments is passed NULL, this is an option without arguments,
2095 // and an arbitrary non-NULL will be returned if parsing is ok
2096 const char *TLocalEngineDS::parseOption(
2097   const char *aOptName,
2098   const char *aArguments,
2099   bool aFromSyncCommand
2100 )
2101 {
2102   #ifdef OBJECT_FILTERING
2103   if (strucmp(aOptName,"fi")==0) {
2104     if (!aArguments) return NULL;
2105     // make sync set filter expression
2106     string f;
2107     aArguments=parseFilterCGI(aArguments,fLocalSendToRemoteTypeP,f); // if type being used for sending to remote is known here, use it
2108     if (!aFromSyncCommand) {
2109       addToFilter(f.c_str(),fSyncSetFilter,false); // AND chaining
2110       // call this once to give derivate a chance to see if it can filter the now set fSyncSetFilter
2111       engFilteredFetchesFromDB(true);
2112     }
2113     return aArguments; // end of filter pattern
2114   }
2115   #ifdef SYNCML_TAF_SUPPORT
2116   else if (strucmp(aOptName,"tf")==0) {
2117     if (!aArguments) return NULL;
2118     // make temporary filter (or TAF) expression
2119     aArguments=parseFilterCGI(aArguments,fLocalSendToRemoteTypeP,fTargetAddressFilter); // if type being used for sending to remote is known here, use it
2120     // Note: TAF filters are always evaluated internally as we need all SyncSet records
2121     //       regardless of possible TAF suppression (for slowsync matching etc.)
2122     return aArguments; // end of filter pattern
2123   }
2124   #endif
2125   else if (aArguments && strucmp(aOptName,"dr")==0) {
2126     // date range limit
2127     sInt16 dstart,dend;
2128     if (sscanf(aArguments,"%hd,%hd",&dstart,&dend)==2) {
2129       // - find end of arguments
2130       aArguments=strchr(aArguments,')');
2131       // - calculate start and end
2132       fDateRangeStart=getSystemNowAs(TCTX_UTC,getSessionZones());
2133       fDateRangeEnd=fDateRangeStart;
2134       // - now use offsets
2135       fDateRangeStart+=dstart*linearDateToTimeFactor;
2136       fDateRangeEnd+=dend*linearDateToTimeFactor;
2137       return aArguments;
2138     }
2139     else return NULL;
2140   }
2141   else if (aArguments && strucmp(aOptName,"li")==0) {
2142     // size limit
2143     sInt16 n=StrToFieldinteger(aArguments,fSizeLimit);
2144     if (n>0) {
2145       // - find end of arguments
2146       aArguments+=n;
2147       return aArguments;
2148     }
2149     else return NULL;
2150   }
2151   else
2152   if (!aArguments && strucmp(aOptName,"na")==0) {
2153     // no attachments
2154     fNoAttachments=true;
2155     return (const char *)1; // non-zero
2156   }
2157   else
2158   if (aArguments && strucmp(aOptName,"max")==0) {
2159     // maximum number of items (for email for example)
2160     sInt16 n=StrToULong(aArguments,fMaxItemCount);
2161     if (n>0) {
2162       // - find end of arguments
2163       aArguments+=n;
2164       return aArguments;
2165     }
2166     else return NULL;
2167   }
2168   else
2169   #endif
2170   #ifdef SYSYNC_SERVER
2171   if (IS_SERVER && !aArguments && strucmp(aOptName,"slow")==0) {
2172     // force a slow sync
2173     PDEBUGPRINTFX(DBG_HOT,("Slowsync forced by CGI-option in db path"));
2174     fForceSlowSync=true;
2175     return (const char *)1; // non-zero
2176   }
2177   else
2178   #endif // SYSYNC_SERVER
2179   if (aArguments && strucmp(aOptName,"o")==0) {
2180     // datastore options
2181     // - find end of arguments
2182     const char *p=strchr(aArguments,')');
2183     if (p) fDBOptions.assign(aArguments,p-aArguments);
2184     return p;
2185   }
2186   else
2187     return NULL; // not parsed
2188 } // TLocalEngineDS::parseOption
2189
2190 #endif
2191
2192 // parse options
2193 localstatus TLocalEngineDS::engParseOptions(
2194   const char *aTargetURIOptions,      // option string contained in target URI
2195   bool aFromSyncCommand               // must be set when parsing options from <sync> target URI
2196 )
2197 {
2198   localstatus sta=LOCERR_OK;
2199   if (aTargetURIOptions) {
2200     const char *p = aTargetURIOptions;
2201     #ifdef SYSYNC_TARGET_OPTIONS
2202     const char *q;
2203     #endif
2204     char c;
2205     string taf; // official TAF
2206     while ((c=*p)) {
2207       #ifdef SYSYNC_TARGET_OPTIONS
2208       if (c=='/') {
2209         // proprietary option lead-in
2210         // - get option name
2211         string optname;
2212         optname.erase();
2213         while(isalnum(c=*(++p)))
2214           optname+=c;
2215         // - get arguments
2216         if (c=='(') {
2217           q=p; // save
2218           p++; // skip "("
2219           p=parseOption(optname.c_str(),p,aFromSyncCommand);
2220           if (!p) {
2221             // unrecognized or badly formatted option, just add it to TAF
2222             taf+='/';
2223             taf+=optname;
2224             p=q; // restart after option name
2225             continue;
2226           }
2227           if (*p!=')') {
2228             sta=406;
2229             PDEBUGPRINTFX(DBG_ERROR,("Syntax error in target options"));
2230             break;
2231           }
2232         }
2233         else {
2234           // option without arguments
2235           if (!parseOption(optname.c_str(),NULL,aFromSyncCommand)) {
2236             sta=406;
2237             PDEBUGPRINTFX(DBG_ERROR,("Unknown target option"));
2238             break;
2239           } // error, not parsed
2240           p--; // will be incremented once again below
2241         }
2242       }
2243       else
2244       #endif
2245       {
2246         // char not part of an option
2247         taf+=c;
2248       }
2249       // next
2250       p++;
2251     }
2252     // check if we have TAF
2253     if (taf.size()>0) {
2254       #if defined(TAF_AS_SYNCSETFILTER) && defined(SYSYNC_TARGET_OPTIONS)
2255       // treat as "fi(<filterexpression>)" option like before 1.0.8.10
2256       if (!parseOption("fi",taf.c_str(),aFromSyncCommand)) { sta=406; } // error, not parsed
2257       #else
2258       #ifdef SYNCML_TAF_SUPPORT
2259       // treat as "tf(<filterexpression>)" = real TAF
2260       if (!parseOption("tf",taf.c_str(),aFromSyncCommand)) { sta=406; } // error, not parsed
2261       #else
2262       sta=406;
2263       PDEBUGPRINTFX(DBG_ERROR,("TAF not supported"));
2264       #endif
2265       #endif
2266     }
2267   }
2268   // return status
2269   return sta;
2270 } //  TLocalEngineDS::engParseOptions
2271
2272
2273 // process SyncML 1.2 style filter
2274 localstatus TLocalEngineDS::engProcessDS12Filter(SmlFilterPtr_t aTargetFilter)
2275 {
2276   localstatus sta=LOCERR_OK;
2277
2278   if (aTargetFilter) {
2279     // check general availability
2280     #ifdef OBJECT_FILTERING
2281     if (!fDSConfigP->fDS12FilterSupport)
2282     #endif
2283     {
2284       PDEBUGPRINTFX(DBG_ERROR,("DS 1.2 style filtering is not available or disabled in config (<ds12filters>)"));
2285       sta=406;
2286       goto error;
2287     }
2288     // check filter
2289     TSyncItemType *itemTypeP=NULL; // no associated type so far
2290     bool inclusiveFilter=false; // default is EXCLUSIVE
2291     // - meta
2292     if (aTargetFilter->meta) {
2293       SmlMetInfMetInfPtr_t metaP = smlPCDataToMetInfP(aTargetFilter->meta);
2294       const char *typestr = smlMetaTypeToCharP(metaP);
2295       // get sync item type for it
2296       // - filter mostly applies to items SENT, so we search these first
2297       itemTypeP = getSendType(typestr,NULL);
2298       if (!itemTypeP)
2299         itemTypeP = getReceiveType(typestr,NULL);
2300       PDEBUGPRINTFX(DBG_FILTER,("DS12 <Filter> <Type> is '%s' -> %sfound",typestr,itemTypeP ? "" : "NOT "));
2301       if (!itemTypeP) {
2302         sta=415;
2303         goto error;
2304       }
2305     }
2306     // - filtertype
2307     if (aTargetFilter->filtertype) {
2308       const char *ftystr = smlPCDataToCharP(aTargetFilter->filtertype);
2309       if (strucmp(ftystr,SYNCML_FILTERTYPE_INCLUSIVE)==0) {
2310         inclusiveFilter=true;
2311       }
2312       else if (strucmp(ftystr,SYNCML_FILTERTYPE_EXCLUSIVE)==0) {
2313         inclusiveFilter=false;
2314       }
2315       else {
2316         PDEBUGPRINTFX(DBG_ERROR,("Invalid <FilterType> '%s'",ftystr));
2317         sta=422;
2318         goto error;
2319       }
2320     }
2321     // - field level filter
2322     if (aTargetFilter->field) {
2323       /// @todo %%% to be implemented
2324       PDEBUGPRINTFX(DBG_ERROR,("Field-level filtering not supported"));
2325       sta=406;
2326       goto error;
2327     }
2328     // - record level filter
2329     if (aTargetFilter->record) {
2330       #ifdef OBJECT_FILTERING
2331       SmlItemPtr_t recordItemP = aTargetFilter->record->item;
2332       if (recordItemP) {
2333         // - check grammar
2334         const char *grammarstr = smlMetaTypeToCharP(smlPCDataToMetInfP(recordItemP->meta));
2335         if (strucmp(grammarstr,SYNCML_FILTERTYPE_CGI)!=0) {
2336           PDEBUGPRINTFX(DBG_ERROR,("Invalid filter grammar '%s'",grammarstr));
2337           sta=422;
2338           goto error;
2339         }
2340         // now get the actual filter string
2341         const char *filterstring = smlPCDataToCharP(recordItemP->data);
2342         PDEBUGPRINTFX(DBG_HOT,(
2343           "Remote specified %sCLUSIVE filter query: '%s'",
2344           inclusiveFilter ? "IN" : "EX",
2345           filterstring
2346         ));
2347         if (*filterstring) {
2348           string f;
2349           // parse it
2350           filterstring = parseFilterCGI(filterstring,itemTypeP,f);
2351           if (*filterstring) {
2352             // not read to end
2353             PDEBUGPRINTFX(DBG_ERROR,("filter query syntax error at: '%s'",filterstring));
2354             sta=422;
2355             goto error;
2356           }
2357           /// @todo: %%% check if this is correct interpretation
2358           // - exclusive is what we used to call "sync set" filtering
2359           // - inclusive seems to be former TAF
2360           if (inclusiveFilter) {
2361             // INCLUSIVE
2362             #ifdef SYNCML_TAF_SUPPORT
2363             fTargetAddressFilter=f;
2364             #else
2365             PDEBUGPRINTFX(DBG_ERROR,("This SyncML engine version has no INCLUSIVE filter support"));
2366             #endif
2367           }
2368           else {
2369             // EXCLUSIVE
2370             addToFilter(f.c_str(),fSyncSetFilter,false); // AND chaining
2371             PDEBUGPRINTFX(DBG_FILTER,("complete sync set filter is now: '%s'",fSyncSetFilter.c_str()));
2372             // call this once to give derivate a chance to see if it can filter the now set fSyncSetFilter
2373             engFilteredFetchesFromDB(true);
2374           }
2375         }
2376       } // if item
2377       #else
2378       // no object filtering
2379       PDEBUGPRINTFX(DBG_ERROR,("This SyncML engine version has no filter support (only PRO has)"));
2380       sta=406;
2381       goto error;
2382       #endif
2383     } // record
2384   } // filter at all
2385 error:
2386   return sta;
2387 } // TLocalEngineDS::engProcessDS12Filter
2388
2389
2390 // process Sync alert from remote party: check if alert code is supported,
2391 // check if slow sync is needed due to anchor mismatch
2392 // - server case: also generate appropriate Alert acknowledge command
2393 TAlertCommand *TLocalEngineDS::engProcessSyncAlert(
2394   TSuperDataStore *aAsSubDatastoreOf, // if acting as subdatastore
2395   uInt16 aAlertCode,                  // the alert code
2396   const char *aLastRemoteAnchor,      // last anchor of remote
2397   const char *aNextRemoteAnchor,      // next anchor of remote
2398   const char *aTargetURI,             // target URI as sent by remote, no processing at all
2399   const char *aIdentifyingTargetURI,  // target URI that was used to identify datastore
2400   const char *aTargetURIOptions,      // option string contained in target URI
2401   SmlFilterPtr_t aTargetFilter,       // DS 1.2 filter, NULL if none
2402   const char *aSourceURI,             // source URI
2403   TStatusCommand &aStatusCommand      // status that might be modified
2404 )
2405 {
2406   TAlertCommand *alertcmdP=NULL;
2407   localstatus sta=LOCERR_OK;
2408
2409   SYSYNC_TRY {
2410     if (IS_SERVER) {
2411       // save the identifying URI
2412       fIdentifyingDBName = aIdentifyingTargetURI;
2413     }
2414     // determine status of read-only option
2415     fReadOnly=
2416       fSessionP->getReadOnly() || // session level read-only flag (probably set by login)
2417       fDSConfigP->fReadOnly; // or datastore config
2418     #ifdef SUPERDATASTORES
2419     // if running as subdatastore of a superdatastore already, this call mus be from a superdatastore as well (aAsSubDatastoreOf!=NULL)
2420     // Note: On a client, fAsSubDatastoreOf is set earlier in dsSetClientSyncParams()
2421     //       On a server, fAsSubDatastoreOf will be set now to avoid alerting as sub- and normal datastore at the same time.
2422     if (fAsSubDatastoreOf && !aAsSubDatastoreOf) {
2423       // bad, cannot be alerted directly AND as subdatastore
2424       aStatusCommand.setStatusCode(400);
2425       ADDDEBUGITEM(aStatusCommand,"trying to alert already alerted subdatastore");
2426       PDEBUGPRINTFX(DBG_ERROR,("Already alerted as subdatastore of '%s'",fAsSubDatastoreOf->getName()));
2427       return NULL;
2428     }
2429     // set subdatastore mode
2430     fAsSubDatastoreOf = aAsSubDatastoreOf;
2431     #endif
2432     // reset type info
2433     fLocalSendToRemoteTypeP = NULL;
2434     fLocalReceiveFromRemoteTypeP = NULL;
2435     fRemoteReceiveFromLocalTypeP=NULL;
2436     fRemoteSendToLocalTypeP=NULL;
2437     // prepare database-level scripts
2438     // NOTE: in client case, alertprepscript is already rebuilt here!
2439     #ifdef SCRIPT_SUPPORT
2440     TScriptContext::rebuildContext(fSessionP->getSyncAppBase(),fDSConfigP->fDBInitScript,fDataStoreScriptContextP,fSessionP);
2441     TScriptContext::rebuildContext(fSessionP->getSyncAppBase(),fDSConfigP->fSentItemStatusScript,fDataStoreScriptContextP,fSessionP);
2442     TScriptContext::rebuildContext(fSessionP->getSyncAppBase(),fDSConfigP->fReceivedItemStatusScript,fDataStoreScriptContextP,fSessionP);
2443     TScriptContext::rebuildContext(fSessionP->getSyncAppBase(),fDSConfigP->fAlertScript,fDataStoreScriptContextP,fSessionP);
2444     TScriptContext::rebuildContext(fSessionP->getSyncAppBase(),fDSConfigP->fDBFinishScript,fDataStoreScriptContextP,fSessionP,true); // now instantiate vars
2445     #endif
2446     // NOTE for client case:
2447     //   ALL instantiated datastores have already sent an Alert to the server by now here
2448
2449     // check DS 1.2 <filter>
2450     sta = engProcessDS12Filter(aTargetFilter);
2451     if (sta != LOCERR_OK) {
2452       aStatusCommand.setStatusCode(sta);
2453       ADDDEBUGITEM(aStatusCommand,"Invalid <Filter> in target options");
2454       return NULL; // error in options
2455     }
2456
2457     // Filter CGI is now a combination of TAF and Synthesis-Style
2458     // extras (options).
2459     if (aTargetURIOptions && *aTargetURIOptions) {
2460       // there are target address options (such as filter CGI and TAF)
2461       sta = engParseOptions(aTargetURIOptions,false);
2462       if (sta != LOCERR_OK) {
2463         aStatusCommand.setStatusCode(sta);
2464         ADDDEBUGITEM(aStatusCommand,"Invalid CGI target URI options");
2465         return NULL; // error in options
2466       }
2467     }
2468     if (IS_SERVER) {
2469       // server case: initially we are not in refresh only mode. Alert code or alert script could change this
2470       fRefreshOnly=false;
2471     }
2472
2473     // save it for suspend and reference in scripts
2474     fAlertCode=aAlertCode;
2475     #ifdef SCRIPT_SUPPORT
2476     // call the alert script, which might want to force a slow sync and/or a server sync set zap
2477     TScriptContext::execute(
2478       fDataStoreScriptContextP,
2479       fDSConfigP->fAlertScript,
2480       &DBFuncTable,
2481       this // caller context
2482     );
2483     aAlertCode=fAlertCode; // get possibly modified version back (SETALERTCODE)
2484     #endif
2485     // if we process a sync alert now, we haven't started sync or map generation
2486     #ifdef SYSYNC_SERVER
2487     if (IS_SERVER) {
2488       // server case: forget Temp GUID mapping
2489       // make sure we are not carrying forward any left-overs. Last sessions's tempGUID mappings that are
2490       // needed for "early map" resolution might be loaded by the call to engInitSyncAnchors below.
2491       // IMPORTANT NOTE: the tempGUIDs that might get loaded will become invalid as soon as <Sync>
2492       // starts - so fTempGUIDMap needs to be cleared again as soon as the first <Sync> command arrives from the client.
2493       fTempGUIDMap.clear();
2494     }
2495     #endif
2496     // save remote's next anchor for saving at end of session
2497     fNextRemoteAnchor = aNextRemoteAnchor;
2498     // get target info in case we are server
2499     #ifdef SYSYNC_SERVER
2500     if (IS_SERVER) {
2501       // now get anchor info out of database
2502       // - make sure other anchor variables are set
2503       sta = engInitSyncAnchors(
2504         aIdentifyingTargetURI, // use processed form, not as sent by remote
2505         aSourceURI
2506       );
2507       if (sta!=LOCERR_OK) {
2508         // error getting anchors
2509         aStatusCommand.setStatusCode(syncmlError(sta));
2510         PDEBUGPRINTFX(DBG_ERROR,("Could not get Sync Anchor info, status=%hd",sta));
2511         return NULL; // no alert to send back
2512       }
2513       // Server ok until here
2514       PDEBUGPRINTFX(DBG_PROTO,(
2515         "Saved Last Remote Client Anchor='%s', received <last> Remote Client Anchor='%s' (must match for normal sync)",
2516         fLastRemoteAnchor.c_str(),
2517         aLastRemoteAnchor
2518       ));
2519       PDEBUGPRINTFX(DBG_PROTO,(
2520         "Received <next> Remote Client Anchor='%s' (to be compared with <last> in NEXT session)",
2521         fNextRemoteAnchor.c_str()
2522       ));
2523       PDEBUGPRINTFX(DBG_PROTO,(
2524         "(Saved) Last Local Server Anchor='%s', (generated) Next Local Server Anchor='%s' (sent to client as <last>/<next> in <alert>)",
2525         fLastLocalAnchor.c_str(),
2526         fNextLocalAnchor.c_str()
2527       ));
2528     }
2529     #endif
2530     #ifdef SYSYNC_CLIENT
2531     if (IS_CLIENT) {
2532       // Client ok until here
2533       PDEBUGPRINTFX(DBG_PROTO,(
2534         "Saved Last Remote Server Anchor='%s', received <last> Remote Server Anchor='%s' (must match for normal sync)",
2535         fLastRemoteAnchor.c_str(),
2536         aLastRemoteAnchor
2537       ));
2538       PDEBUGPRINTFX(DBG_PROTO,(
2539         "Received <next> Remote Server Anchor='%s' (to be compared with <last> in NEXT session)",
2540         fNextRemoteAnchor.c_str()
2541       ));
2542     }
2543     #endif
2544     PDEBUGPRINTFX(DBG_PROTO,(
2545       "(Saved) fResumeAlertCode = %hd (valid for >DS 1.2 only)",
2546       fResumeAlertCode
2547     ));
2548     // Now check for resume
2549     // - default to what was actually alerted
2550     uInt16 effectiveAlertCode=aAlertCode;
2551     #ifdef SYSYNC_SERVER
2552     if (IS_SERVER) {
2553       // - check if resuming server session
2554       fResuming=false;
2555       if (aAlertCode==225) {
2556         if (fSessionP->getSyncMLVersion()<syncml_vers_1_2) {
2557           aStatusCommand.setStatusCode(406);
2558           ADDDEBUGITEM(aStatusCommand,"Resume not supported in SyncML prior to 1.2");
2559           PDEBUGPRINTFX(DBG_ERROR,("Resume not supported in SyncML prior to 1.2"));
2560           return NULL;
2561         }
2562         // Resume requested
2563         if (fResumeAlertCode==0 || !dsResumeSupportedInDB()) {
2564           // cannot resume, suggest a normal sync (in case anchors do not match, this will become a 508 below)
2565           aStatusCommand.setStatusCode(509); // cannot resume, override
2566           effectiveAlertCode=200; // suggest normal sync
2567           ADDDEBUGITEM(aStatusCommand,"Cannot resume, suggesting a normal sync");
2568           PDEBUGPRINTFX(DBG_ERROR,("Cannot resume, suggesting a normal sync"));
2569         }
2570         else {
2571           // we can resume, use the saved alert code
2572           effectiveAlertCode=fResumeAlertCode;
2573           PDEBUGPRINTFX(DBG_HOT,("Alerted to resume previous session, Switching to alert Code = %hd",fResumeAlertCode));
2574           fResuming=true;
2575         }
2576       }
2577     }
2578     #endif
2579     // now do the actual alert internally
2580     if (sta==LOCERR_OK) {
2581       // check if we can process the alert
2582       // NOTE: for client this might cause a change in Sync mode, if server
2583       //       alerts something different than client alerted before.
2584       //       Note that a client will keep fromserver mode even if server
2585       //       changes to two-way, as it might be that we have sent the server
2586       //       a two-way alert even if we want fromserver due to compatibility with
2587       //       servers that cannot do fromserver.
2588       if (IS_SERVER) {
2589         // - Server always obeys what client requests (that is, if alertscript does not modify it)
2590         sta = setSyncModeFromAlertCode(effectiveAlertCode,false); // as server
2591       } // server
2592       else {
2593         // - for client, check that server can't switch to a client writing mode
2594         //   (we had a case when mobical did that for a user and erased all his data)
2595         TSyncModes prevMode = fSyncMode; // remember previous mode
2596         sta = setSyncModeFromAlertCode(effectiveAlertCode,true); // as client
2597         if (prevMode==smo_fromclient && fSyncMode!=smo_fromclient) {
2598           // server tries to switch to a mode that could be writing data to the client
2599           // - forbidden
2600           sta=403;
2601           aStatusCommand.setStatusCode(syncmlError(sta));
2602           ADDDEBUGITEM(aStatusCommand,"Server may not write to client");
2603           PDEBUGPRINTFX(DBG_ERROR,("From-Client only: Server may not alert mode that writes client data (%hd) - blocked",effectiveAlertCode));
2604           // - also abort sync of this datastore without chance to resume, as this problem might
2605           //   originate from a resume attempt, which the server
2606           //   tried to convert to a normal or slow sync. With cancelling the resume here, we make sure
2607           //   next sync will start over and sending the desired (one-way) sync mode again.
2608           engAbortDataStoreSync(sta, false, false); // remote problem, not resumable
2609         }
2610       } // client
2611       if (!isAborted()) {
2612         if (sta!=LOCERR_OK) {
2613           // Sync type not supported
2614           // - go back to idle
2615           changeState(dssta_idle,true); // force to idle
2616           aStatusCommand.setStatusCode(syncmlError(sta));
2617           ADDDEBUGITEM(aStatusCommand,"Sync type not supported");
2618           PDEBUGPRINTFX(DBG_ERROR,("Sync type (Alert code %hd) not supported, err=%hd",effectiveAlertCode,sta));
2619         }
2620       }
2621     }
2622     if (sta==LOCERR_OK) {
2623       // Sync type supported
2624       // - set new state to alerted
2625       if (IS_CLIENT) {
2626         changeState(dssta_clientalerted,true); // force it
2627       }
2628       else {
2629         changeState(dssta_serveralerted,true); // force it
2630       }
2631       // - datastore state is now dss_alerted
2632       PDEBUGPRINTFX(DBG_HOT,(
2633         "Alerted (code=%hd) for %s%s %s%s%s ",
2634         aAlertCode,
2635         fResuming ? "Resumed " : "",
2636         SyncModeDescriptions[fSyncMode],
2637         fSlowSync ? (fSyncMode==smo_twoway ? "Slow Sync" : "Refresh") : "Normal Sync",
2638         fReadOnly ? " (Readonly)" : "",
2639         fRefreshOnly ? " (Refreshonly)" : ""
2640       ));
2641       CONSOLEPRINTF((
2642         "- Remote alerts (%hd): %s%s %s%s%s for '%s' (source='%s')",
2643         aAlertCode,
2644         fResuming ? "Resumed " : "",
2645         SyncModeDescriptions[fSyncMode],
2646         fSlowSync ? (fSyncMode==smo_twoway ? "Slow Sync" : "Refresh") : "Normal Sync",
2647         fReadOnly ? " (Readonly)" : "",
2648         fRefreshOnly ? " (Refreshonly)" : "",
2649         aTargetURI,
2650         aSourceURI
2651       ));
2652       // - now test if the sync state is same in client & server
2653       //   (if saved anchor is empty, slow sync is needed anyway)
2654       if (
2655         (
2656           (
2657             (!fLastRemoteAnchor.empty() &&
2658               ( (fLastRemoteAnchor==aLastRemoteAnchor)
2659                 #ifdef SYSYNC_CLIENT
2660                 || (fSessionP->fLenientMode && IS_CLIENT)
2661                 #endif
2662               )
2663             ) || // either anchors must match (or lenient mode for client)...
2664             (fResuming && *aLastRemoteAnchor==0) // ...or in case of resume, remote not sending anchor is ok as well
2665           )
2666           && !fForceSlowSync // ...but no force for slowsync may be set internally
2667         )
2668         || fSlowSync // if slow sync is requested by the remote anyway, we don't need to be in sync anyway, so just go on
2669       ) {
2670         if (!(fLastRemoteAnchor==aLastRemoteAnchor) && fSessionP->fLenientMode) {
2671           PDEBUGPRINTFX(DBG_ERROR,("Warning - remote anchor mismatch but tolerated in lenient mode"));
2672         }
2673         // sync state ok or Slow sync requested anyway:
2674         #ifdef SYSYNC_SERVER
2675         if (IS_SERVER) {
2676           // we can generate Alert with same code as sent
2677           // %%% Note: this is not entirely clear, as SCTS sends
2678           //     corresponding SERVER ALERTED code back.
2679           //     Specs suggest that we send the code back unmodified
2680           uInt16 alertCode = getSyncStateAlertCode(fServerAlerted);
2681           alertcmdP = new TAlertCommand(fSessionP,this,alertCode);
2682           fAlertCode=alertCode; // save it for reference in scripts and for suspend/resume
2683         }
2684         #endif
2685       }
2686       else {
2687         // switch to slow sync
2688         fSlowSync=true;
2689         PDEBUGPRINTFX(DBG_HOT,("Switched to SlowSync because of Anchor mismatch or server-side user option"));
2690         CONSOLEPRINTF(("- switched to SlowSync because of Sync Anchor mismatch"));
2691         // sync state not ok, we need slow sync
2692         aStatusCommand.setStatusCode(508); // Refresh required
2693         // update effective alert code
2694         uInt16 alertCode = getSyncStateAlertCode(false);
2695         fAlertCode=alertCode; // save it for reference in scripts and for suspend
2696         // NOTE: if client detected slow-sync not before here, status 508 alone
2697         //       (without another Alert 201 sent to the server) is sufficient for
2698         //       server to switch to slow sync.
2699         if (IS_SERVER) {
2700           // generate Alert for Slow sync
2701           alertcmdP = new TAlertCommand(fSessionP,this,alertCode);
2702         }
2703       }
2704       // Now we are alerted for a sync
2705       // - reset item counters
2706       fItemsSent = 0;
2707       fItemsReceived = 0;
2708       #ifdef SYSYNC_SERVER
2709       if (IS_SERVER) {
2710         // Server case
2711         // - show info
2712         PDEBUGPRINTFX(DBG_HOT,(
2713           "ALERTED from client for %s%s%s Sync",
2714           fResuming ? "resumed " : "",
2715           fSlowSync ? "slow" : "normal",
2716           fFirstTimeSync ? " first time" : ""
2717         ));
2718         // server: add Item with Anchors and URIs
2719         SmlItemPtr_t itemP = newItem();
2720         // - anchors
2721         itemP->meta=newMetaAnchor(fNextLocalAnchor.c_str(),fLastLocalAnchor.c_str());
2722         // - MaxObjSize here again to make SCTS happy
2723         if (
2724           (fSessionP->getRootConfig()->fLocalMaxObjSize>0) &&
2725           (fSessionP->getSyncMLVersion()>=syncml_vers_1_1)
2726         ) {
2727           // SyncML 1.1 has object size and we need to put it here for SCTS
2728           smlPCDataToMetInfP(itemP->meta)->maxobjsize=newPCDataLong(
2729             fSessionP->getRootConfig()->fLocalMaxObjSize
2730           );
2731         }
2732         // - URIs (reversed from what was received in Alert)
2733         itemP->source=newLocation(aTargetURI); // use unprocessed form as sent by remote
2734         itemP->target=newLocation(aSourceURI);
2735         // - add to alert command
2736         alertcmdP->addItem(itemP);
2737         // - set new state, alert now answered
2738         changeState(dssta_serveransweredalert,true); // force it
2739       } // server case
2740       #endif // SYSYNC_SERVER
2741       #ifdef SYSYNC_CLIENT
2742       if (IS_CLIENT) {
2743         // Client case
2744         // - now sync mode is stable (late switch to slowsync has now occurred if any)
2745         changeState(dssta_syncmodestable,true);
2746         // - show info
2747         PDEBUGPRINTFX(DBG_HOT,(
2748           "ALERTED from server for %s%s%s Sync",
2749           fResuming ? "resumed " : "",
2750           fSlowSync ? "slow" : "normal",
2751           fFirstTimeSync ? " first time" : ""
2752         ));
2753       } // client Case
2754       #endif // SYSYNC_CLIENT
2755     }
2756     // clear partial item if we definitely know we are not resuming
2757     if (!fResuming) {
2758       // not resuming - prevent that partial item is used in TSyncOpCommand
2759       fPartialItemState=pi_state_none;
2760       // free this space early (would be freed at session end anyway, but we don't need it any more now)
2761       if (fPIStoredDataAllocated) {
2762         smlLibFree(fPIStoredDataP);
2763         fPIStoredDataAllocated=false;
2764       }
2765       fPIStoredDataP=NULL;
2766     }
2767     // save name how remote adresses local database
2768     // (for sending same URI back in own Sync)
2769     fRemoteViewOfLocalURI = aTargetURI; // save it
2770     if (IS_SERVER) {
2771       fRemoteDBPath = aSourceURI;
2772     }
2773     if (sta!=LOCERR_OK) {
2774       // no alert command
2775       if (alertcmdP) delete alertcmdP;
2776       alertcmdP=NULL;
2777       aStatusCommand.setStatusCode(syncmlError(sta));
2778       PDEBUGPRINTFX(DBG_HOT,("engProcessSyncAlert failed with status=%hd",sta));
2779     }
2780   }
2781   SYSYNC_CATCH (...)
2782     // clean up locally owned objects
2783     if (alertcmdP) delete alertcmdP;
2784     SYSYNC_RETHROW;
2785   SYSYNC_ENDCATCH
2786   // return alert command, if any
2787   return alertcmdP;
2788 } // TLocalEngineDS::engProcessSyncAlert
2789
2790
2791 // process status received for sync alert
2792 bool TLocalEngineDS::engHandleAlertStatus(TSyError aStatusCode)
2793 {
2794   bool handled=false;
2795   if (IS_CLIENT) {
2796     // for client, make sure we have just sent the alert
2797     if (!testState(dssta_clientsentalert,true)) return false; // cannot switch if server not alerted
2798     // anyway, we have seen the status
2799     changeState(dssta_clientalertstatused,true); // force it
2800   }
2801   else {
2802     // for server, check if client did combined init&sync
2803     if (fLocalDSState>=dssta_syncmodestable) {
2804       // must be combined init&sync
2805       if (aStatusCode!=200) {
2806         // everything except ok is not allowed here
2807         PDEBUGPRINTFX(DBG_ERROR,("In combined init&sync, Alert status must be ok (but is %hd)",aStatusCode));
2808         dsAbortDatastoreSync(400,false); // remote problem
2809       }
2810       // aborted or not, status is handled
2811       return true;
2812     }
2813     // normal case with separate init: we need to have answered the alert here
2814     if (!testState(dssta_serveransweredalert,true)) return false; // cannot switch if server not alerted
2815   } // server case
2816   // now check status code
2817   if (aStatusCode==508) {
2818     // remote party needs slow sync
2819     PDEBUGPRINTFX(DBG_HOT,("engHandleAlertStatus: Remote party needs SlowSync, switching to slowsync (AFTER alert, cancelling possible Resume)"));
2820     // Note: in server and client cases, this mode change may happen AFTER alert command exchange
2821     // - switch to slow sync
2822     fSlowSync=true;
2823     // - if we are late-forced to slow sync, this means that this cannot be a resume
2824     fResuming=false;
2825     // - update effective alert code that will be saved when this session gets suspended
2826     fAlertCode=getSyncStateAlertCode(fServerAlerted);
2827     handled=true;
2828   }
2829   else if (aStatusCode==200) {
2830     handled=true;
2831   }
2832   if (IS_CLIENT) {
2833     // check for resume override by server
2834     if (!handled && fResuming) {
2835       // we have requested resume
2836       if (aStatusCode==509) {
2837         // resume not accepted by server, but overridden by another sync type
2838         fResuming=false;
2839         PDEBUGPRINTFX(DBG_ERROR,("engHandleAlertStatus: Server rejected Resume"));
2840         handled=true;
2841       }
2842     }
2843   }
2844   else {
2845     // if we have handled it here, sync mode is now stable
2846     if (handled) {
2847       // if we get that far, sync mode for server is now stable AND we can receive cached maps
2848       changeState(dssta_syncmodestable,true); // force it, sync mode is now stable, no further changes are possible
2849     }
2850   }
2851   // no other status codes are supported at the datastore level
2852   if (!handled && aStatusCode>=400) {
2853     engAbortDataStoreSync(aStatusCode, false); // remote problem
2854     handled=true;
2855   }
2856   return handled; // status handled
2857 } // TLocalEngineDS::engHandleAlertStatus
2858
2859
2860 // initialize reception of syncop commands for datastore
2861 // Note: previously, this was implemented as initLocalDatastoreSync in syncsession
2862 localstatus TLocalEngineDS::engInitForSyncOps(
2863   const char *aRemoteDatastoreURI // URI of remote datastore
2864 )
2865 {
2866   localstatus sta = LOCERR_OK;
2867
2868   // no default types
2869   TSyncItemType *LocalSendToRemoteTypeP=NULL;       // used by local to send to remote
2870   TSyncItemType *RemoteReceiveFromLocalTypeP=NULL;  // used by remote to receive from local
2871   TSyncItemType *LocalReceiveFromRemoteTypeP=NULL;  // used by local to receive from remote
2872   TSyncItemType *RemoteSendToLocalTypeP=NULL;       // used by remote to send to local
2873
2874   // Now determine remote datastore
2875   // Note: It might be that this was called already earlier in the session, so
2876   //       the link between local and remote datastore might already exist
2877   if (fRemoteDatastoreP==NULL) {
2878     // try to locate it by name and set it - in case of superdatastore, it will be set in all subdatastores
2879     engSetRemoteDatastore(fSessionP->findRemoteDataStore(aRemoteDatastoreURI));
2880   }
2881   else {
2882     // There is a remote datastore already associated
2883     #ifdef SYDEBUG
2884     // - make a sanity check to see if sepcified remote URI matches
2885     if(fRemoteDatastoreP!=fSessionP->findRemoteDataStore(aRemoteDatastoreURI)) {
2886       PDEBUGPRINTFX(DBG_ERROR,(
2887         "Warning: Received remote DS LocURI '%s' does not match already associated DS '%s'. We use the associated DS.",
2888         aRemoteDatastoreURI,
2889         fRemoteDatastoreP->getName()
2890       ));
2891     }
2892     #endif
2893   }
2894   // Now create a dummy remote data store for a blind sync attempt
2895   if (!fRemoteDatastoreP) {
2896     // no such remote datastore for this local datastore known, create one (or fail)
2897     #ifdef REMOTE_DS_MUST_BE_IN_DEVINF
2898     if (fSessionP->fRemoteDataStoresKnown) {
2899       // we have received devinf, but still can't find remote data store: error
2900       // Note: we had to disable this because of bugs in smartner server
2901       PDEBUGPRINTFX(DBG_ERROR,("Remote datastore name '%s' not found in received DevInf",aRemoteDatastoreURI));
2902       return 404;
2903     }
2904     else
2905     #else
2906     if (fSessionP->fRemoteDataStoresKnown) {
2907       // we have received devinf, but still can't find remote data store:
2908       // just show in log, but continue as if there was no devInf received at all
2909       PDEBUGPRINTFX(DBG_ERROR,("Warning: Remote datastore name '%s' not found in received DevInf.",aRemoteDatastoreURI));
2910     }
2911     #endif
2912     {
2913       // We couldn't retrieve DevInf (or !REMOTE_DS_MUST_BE_IN_DEVINF), so we have to try blind
2914       // - check remote specifics here if we had no devinf (there might be default remote
2915       //   rules to apply or checking license restrictions
2916       // - this is executed only once per session, after that, we'll be fRemoteDevInfLock-ed
2917       if (!fSessionP->fRemoteDevInfKnown && !fSessionP->fRemoteDevInfLock) {
2918         // detect client specific server behaviour if needed
2919         sta = fSessionP->checkRemoteSpecifics(NULL, NULL);
2920         fSessionP->remoteAnalyzed(); // analyzed now (accepted or not does not matter)
2921         if (sta!=LOCERR_OK)
2922           return sta; // not ok, device rejected
2923       }
2924       // default data types are those preferred by local datastore (or explicitly marked for blind sync attempts)
2925       if (getDSConfig()->fTypeSupport.fPreferredLegacy) {
2926         // we have a preferred type for blind sync attempts
2927         LocalSendToRemoteTypeP = getSession()->findLocalType(getDSConfig()->fTypeSupport.fPreferredLegacy);
2928         LocalReceiveFromRemoteTypeP = LocalSendToRemoteTypeP;
2929       }
2930       else {
2931         // no specific "blind" preference, use my own normally preferred types
2932         LocalSendToRemoteTypeP = getPreferredTxItemType(); // send in preferred tx type of local datastore
2933         LocalReceiveFromRemoteTypeP = getPreferredRxItemType(); // receive in preferred rx type of local datastore
2934       }
2935       // same type on both end (as only local type exists)
2936       RemoteReceiveFromLocalTypeP = LocalSendToRemoteTypeP; // same on both end
2937       RemoteSendToLocalTypeP = LocalReceiveFromRemoteTypeP; // same on both end (as only local type exists)
2938       // create "remote" datastore with matching properties to local one
2939       PDEBUGPRINTFX(DBG_ERROR,("Warning: No DevInf for remote datastore, running blind sync attempt"));
2940       TRemoteDataStore *remDsP;
2941       MP_NEW(remDsP,DBG_OBJINST,"TRemoteDataStore",TRemoteDataStore(
2942         fSessionP,
2943         aRemoteDatastoreURI, // remote name of datastore
2944         0 // standard Sync caps
2945       ));
2946       // - set it (in case of superdatastore in all subdatastores as well)
2947       engSetRemoteDatastore(remDsP);
2948       // add type support
2949       fRemoteDatastoreP->setPreferredTypes(
2950         RemoteReceiveFromLocalTypeP, // remote receives in preferred tx type of local datastore
2951         RemoteSendToLocalTypeP // remote sends in preferred rx type of local datastore
2952       );
2953       // add it to the remote datastore list
2954       fSessionP->fRemoteDataStores.push_back(fRemoteDatastoreP);
2955       // make sure late devInf arriving won't supersede our artificially created remote datastore any more
2956       fSessionP->fRemoteDevInfLock=true;
2957     }
2958   }
2959   else {
2960     // found remote DB, determine default data exchange types
2961     // - common types for sending data to remote
2962     LocalSendToRemoteTypeP=getTypesForTxTo(fRemoteDatastoreP,&RemoteReceiveFromLocalTypeP);
2963     // - common types for receiving data from remote
2964     LocalReceiveFromRemoteTypeP=getTypesForRxFrom(fRemoteDatastoreP,&RemoteSendToLocalTypeP);
2965   }
2966   #ifndef NO_REMOTE_RULES
2967   // check if rule match type will override what we found so far
2968   if (!fSessionP->fActiveRemoteRules.empty()) {
2969     // have a look at our rulematch types
2970     TRuleMatchTypesContainer::iterator pos;
2971     TSyncItemType *ruleMatchTypeP = NULL;
2972     for (pos=fRuleMatchItemTypes.begin();pos!=fRuleMatchItemTypes.end();++pos) {
2973       // there is a rule applied
2974       // - parse match string in format "rule[,rule]..." with * and ? wildcards allowed in "rule"
2975       cAppCharP p=(*pos).ruleMatchString;
2976       while (*p!=0) {
2977         // split at commas
2978         cAppCharP e=strchr(p,',');
2979         size_t n;
2980         if (e) {
2981           n=e-p;
2982           e++;
2983         }
2984         else {
2985           n=strlen(p);
2986           e=p+n;
2987         }
2988         // see if that matches with any of the active rules
2989         TRemoteRulesList::iterator apos;
2990         for(apos=fSessionP->fActiveRemoteRules.begin();apos!=fSessionP->fActiveRemoteRules.end();apos++) {
2991           if (strwildcmp((*apos)->getName(), p, 0, n)==0) {
2992             ruleMatchTypeP=(*pos).itemTypeP; // get the matching type
2993             break;
2994           }
2995         }
2996         if (ruleMatchTypeP) break; // found a rule match type
2997         // test next match target
2998         p=e;
2999       }
3000       // apply if found one already
3001       if (ruleMatchTypeP) {
3002         // use this instead of normal types
3003         // - local types
3004         LocalSendToRemoteTypeP=ruleMatchTypeP;       // used by local to send to remote
3005         LocalReceiveFromRemoteTypeP=ruleMatchTypeP;  // used by local to receive from remote
3006         // Find matching remote types
3007         // - first look for existing remote type with same config as local one
3008         TSyncItemType *remCorrTypeP = fSessionP->findRemoteType(ruleMatchTypeP->getTypeConfig(),fRemoteDatastoreP);
3009         // - if none found, create one and have it inherit the CTCap options of the generic version that is already there
3010         if (!remCorrTypeP) {
3011           // none found: need to create one
3012           remCorrTypeP = ruleMatchTypeP->newCopyForSameType(fSessionP,fRemoteDatastoreP);
3013           if (remCorrTypeP) {
3014             // - get generic remote type (the one that might have received CTCap already)
3015             TSyncItemType *remGenericTypeP = fRemoteDatastoreP->getSendType(ruleMatchTypeP);
3016             // - copy options
3017             if (remGenericTypeP) remCorrTypeP->copyCTCapInfoFrom(*remGenericTypeP);
3018           }
3019         }
3020         // now assign
3021         RemoteReceiveFromLocalTypeP=remCorrTypeP;
3022         RemoteSendToLocalTypeP=remCorrTypeP;
3023         // Show that we are using ruleMatch type
3024         PDEBUGPRINTFX(DBG_DATA+DBG_HOT,(
3025           "An active remote rule overrides default type usage - forcing type '%s' for send and receive",
3026           ruleMatchTypeP->getTypeConfig()->getName()
3027         ));
3028         // done
3029         break;
3030       }
3031     }
3032   }
3033   #endif
3034   // check if we are sync compatible (common type for both directions)
3035   if (LocalSendToRemoteTypeP && LocalReceiveFromRemoteTypeP && RemoteReceiveFromLocalTypeP && RemoteSendToLocalTypeP) {
3036     // avoid further changes in remote devInf (e.g. by late result of GET, sent *after* first <sync>)
3037     fSessionP->fRemoteDevInfLock=true;
3038     // there is a common data type for each of both directions
3039     // - show local types
3040     PDEBUGPRINTFX(DBG_DATA,(
3041       "Local Datastore '%s' - Types: tx to remote: '%s': %s (%s), rx from remote: '%s': %s (%s)",
3042       getName(),
3043       LocalSendToRemoteTypeP->getTypeConfig()->getName(),LocalSendToRemoteTypeP->getTypeName(), LocalSendToRemoteTypeP->getTypeVers(),
3044       LocalReceiveFromRemoteTypeP->getTypeConfig()->getName(),LocalReceiveFromRemoteTypeP->getTypeName(), LocalReceiveFromRemoteTypeP->getTypeVers()
3045     ));
3046     // - show remote types
3047     PDEBUGPRINTFX(DBG_DATA+DBG_DETAILS,(
3048       "Remote Datastore '%s' - Types: tx to local: '%s': %s (%s), rx from local: '%s': %s (%s)",
3049       fRemoteDatastoreP->getName(),
3050       RemoteSendToLocalTypeP->getTypeConfig()->getName(),RemoteSendToLocalTypeP->getTypeName(), RemoteSendToLocalTypeP->getTypeVers(),
3051       RemoteReceiveFromLocalTypeP->getTypeConfig()->getName(),RemoteReceiveFromLocalTypeP->getTypeName(), RemoteReceiveFromLocalTypeP->getTypeVers()
3052     ));
3053   }
3054   else {
3055     // datastores are not sync compatible
3056     sta=415;
3057     PDEBUGPRINTFX(DBG_ERROR,("No common datastore formats -> cannot sync (415)"));
3058     PDEBUGPRINTFX(DBG_EXOTIC,("- LocalSendToRemoteTypeP      = '%s'", LocalSendToRemoteTypeP ? LocalSendToRemoteTypeP->getTypeName() : "<missing>"));
3059     PDEBUGPRINTFX(DBG_EXOTIC,("- LocalReceiveFromRemoteTypeP = '%s'", LocalReceiveFromRemoteTypeP ? LocalReceiveFromRemoteTypeP->getTypeName() : "<missing>"));
3060     PDEBUGPRINTFX(DBG_EXOTIC,("- RemoteSendToLocalTypeP      = '%s'", RemoteSendToLocalTypeP ? RemoteSendToLocalTypeP->getTypeName() : "<missing>"));
3061     PDEBUGPRINTFX(DBG_EXOTIC,("- RemoteReceiveFromLocalTypeP = '%s'", RemoteReceiveFromLocalTypeP ? RemoteReceiveFromLocalTypeP->getTypeName() : "<missing>"));
3062     engAbortDataStoreSync(sta,true,false); // do not proceed with sync of this datastore, local problem, not resumable
3063     return sta;
3064   }
3065   // set type info in local datastore
3066   setSendTypeInfo(LocalSendToRemoteTypeP,RemoteReceiveFromLocalTypeP);
3067   setReceiveTypeInfo(LocalReceiveFromRemoteTypeP,RemoteSendToLocalTypeP);
3068   // - initialize usage of types (checks compatibility as well)
3069   return initDataTypeUse();
3070 } // TLocalEngineDS::engInitForSyncOps
3071
3072
3073 // called from <sync> command to generate sync sub-commands to be sent to remote
3074 // Returns true if now finished for this datastore
3075 // also changes state to dssta_syncgendone when all sync commands have been generated
3076 bool TLocalEngineDS::engGenerateSyncCommands(
3077   TSmlCommandPContainer &aNextMessageCommands,
3078   TSmlCommand * &aInterruptedCommandP,
3079   const char *aLocalIDPrefix
3080 )
3081 {
3082   PDEBUGBLOCKFMT(("SyncGen","Now generating sync commands","datastore=%s",getName()));
3083   bool finished=false;
3084   #ifdef SYSYNC_CLIENT
3085   if (IS_CLIENT)
3086     finished = logicGenerateSyncCommandsAsClient(aNextMessageCommands, aInterruptedCommandP, aLocalIDPrefix);
3087   #endif
3088   #ifdef SYSYNC_SERVER
3089   if (IS_SERVER)
3090     finished = logicGenerateSyncCommandsAsServer(aNextMessageCommands, aInterruptedCommandP, aLocalIDPrefix);
3091   #endif
3092   // change state when finished
3093   if (finished) {
3094     changeState(dssta_syncgendone,true);
3095     if (IS_CLIENT) {
3096       // from client only skips to clientmapssent without any server communication
3097       // (except if we are in old synthesis-compatible mode which runs from-client-only
3098       // with empty sync-from-server and map phases.
3099       if (getSyncMode()==smo_fromclient && !fSessionP->fCompleteFromClientOnly) {
3100         // data access ends with all sync commands generated in from-client-only
3101         PDEBUGPRINTFX(DBG_PROTO,("From-Client-Only sync: skipping directly to end of map phase now"));
3102         changeState(dssta_dataaccessdone,true);
3103         changeState(dssta_clientmapssent,true);
3104       }
3105     }
3106   }
3107   PDEBUGPRINTFX(DBG_DATA,(
3108     "engGenerateSyncCommands ended, state='%s', sync generation %sdone",
3109     getDSStateName(),
3110     fLocalDSState>=dssta_syncgendone ? "" : "NOT "
3111   ));
3112   PDEBUGENDBLOCK("SyncGen");
3113   return finished;
3114 } // TLocalEngineDS::engGenerateSyncCommands
3115
3116
3117 // called to confirm a sync operation's completion (status from remote received)
3118 // @note aSyncOp passed not necessarily reflects what was sent to remote, but what actually happened
3119 void TLocalEngineDS::dsConfirmItemOp(TSyncOperation aSyncOp, cAppCharP aLocalID, cAppCharP aRemoteID, bool aSuccess, localstatus aErrorStatus)
3120 {
3121   // commands failed with "cancelled" should be re-sent for resume
3122   if (!aSuccess && aErrorStatus==514 && dsResumeSupportedInDB() && fSessionP->isSuspending()) {
3123     // cancelled syncop as result of explicit suspend: mark for resume as it was never really processed at the other end
3124     PDEBUGPRINTFX(DBG_DATA+DBG_EXOTIC,("Cancelled SyncOp during suspend -> mark for resume"));
3125     engMarkItemForResume(aLocalID,aRemoteID,true);
3126   }
3127   PDEBUGPRINTFX(DBG_DATA+DBG_EXOTIC,(
3128     "dsConfirmItemOp completed, syncop=%s, localID='%s', remoteID='%s', %s, errorstatus=%hd",
3129     SyncOpNames[aSyncOp],
3130     aLocalID ? aLocalID : "<none>",
3131     aRemoteID ? aRemoteID : "<none>",
3132     aSuccess ? "SUCCESS" : "FAILURE",
3133     aErrorStatus
3134   ));
3135 } // TLocalEngineDS::dsConfirmItemOp
3136
3137
3138
3139 // handle status of sync operation
3140 // Note: in case of superdatastore, status is always directed to the originating subdatastore, as
3141 //       the fDataStoreP of the SyncOpCommand is set to subdatastore when generating the SyncOps.
3142 bool TLocalEngineDS::engHandleSyncOpStatus(TStatusCommand *aStatusCmdP,TSyncOpCommand *aSyncOpCmdP)
3143 {
3144   TSyError statuscode = aStatusCmdP->getStatusCode();
3145   // we can make it simple here because we KNOW that we do not send multiple items per SyncOp, so we
3146   // just need to look at the first item's target and source
3147   const char *localID = aSyncOpCmdP->getSourceLocalID();
3148   const char *remoteID = aSyncOpCmdP->getTargetRemoteID();
3149   #ifdef SYSYNC_SERVER
3150   string realLocID;
3151   #endif
3152   if (localID) {
3153     #ifdef SUPERDATASTORES
3154     // remove possible prefix if this item was sent in the <sync> command context of a superdatastore
3155     if (fAsSubDatastoreOf) {
3156       // let superdatastore remove the prefix for me
3157       localID = fAsSubDatastoreOf->removeSubDSPrefix(localID,this);
3158     }
3159     #endif
3160     #ifdef SYSYNC_SERVER
3161     if (IS_SERVER) {
3162       // for server only: convert to internal representation
3163       realLocID=localID;
3164       obtainRealLocalID(realLocID);
3165       localID=realLocID.c_str();
3166     }
3167     #endif
3168   }
3169   // handle special cases for Add/Replace/Delete
3170   TSyncOperation sop = aSyncOpCmdP->getSyncOp();
3171   switch (sop) {
3172     case sop_wants_add:
3173     case sop_add:
3174       if (statuscode<300 || statuscode==419) {
3175         // All ok status 2xx as well as special "merged" 419 is ok for an add:
3176         // Whatever remote said, I know this is an add and so I counts this as such
3177         // (even if the remote somehow merged it with existing data,
3178         // it is obviously a new item in my sync set with this remote)
3179         fRemoteItemsAdded++;
3180         dsConfirmItemOp(sop_add,localID,remoteID,true); // ok added
3181       }
3182       else if (
3183         statuscode==418 &&
3184         (isResuming()
3185         || (isSlowSync() && IS_CLIENT)
3186         )
3187       ) {
3188         // "Already exists"/418 is acceptable...
3189         // ... in slow sync as client, as some servers use it instead of 200/419 for slow sync match
3190         // ... during resumed sync as server with clients like Symbian which
3191         //     can detect duplicate adds themselves. Should not generally
3192         //     occur, as we shouldn't re-send them as long as we haven't seen
3193         //     a map. But symbian cannot send early maps - it instead does
3194         //     it's own duplicate checking.
3195         // ... during resumed sync as client (as servers might issue 418 for
3196         //     items sent a second time after an implicit suspend)
3197         PDEBUGPRINTFX(DBG_ERROR,("Warning: received 418 status for add in resumed/slowsync session -> treat it as ok (200)"));
3198         dsConfirmItemOp(sop_replace,localID,remoteID,true); // kind of ok
3199         statuscode=200; // convert to ok (but no count incremented, as nothing changed)
3200       }
3201       else {
3202         dsConfirmItemOp(sop_add,localID,remoteID,false,statuscode); // failed add
3203       }
3204       // adding with 420 error: device full
3205       if (statuscode==420) {
3206         // special case: device indicates that it is full, so stop adding in this session
3207         PDEBUGPRINTFX(DBG_ERROR,("Warning: Status %hd: Remote device full -> preventing further adds in this session",statuscode));
3208         engStopAddingToRemote();
3209         fRemoteItemsError++; // this is considered a remote item error
3210       }
3211       break;
3212     // case sop_copy: break;
3213     case sop_wants_replace:
3214     case sop_replace:
3215       #ifdef SYSYNC_SERVER
3216       if (IS_SERVER && (statuscode==404 || statuscode==410)) {
3217         // obviously, remote item that we wanted to change does not exist any more.
3218         // Instead of aborting the session we'll just remove the map item for that
3219         // server item, such that it will be re-added in the next sync session
3220         PDEBUGPRINTFX(DBG_DATA,("Status %hd: Replace target not found on client -> silently ignore but remove map in server (item will be added in next session), ",statuscode));
3221         // remove map for remote item(s), targetRef contain remoteIDs
3222         SmlTargetRefListPtr_t targetrefP = aStatusCmdP->getStatusElement()->targetRefList;
3223         while (targetrefP) {
3224           // target ref available
3225           engProcessMap(smlPCDataToCharP(targetrefP->targetRef),NULL);
3226           // next
3227           targetrefP=targetrefP->next;
3228         }
3229         statuscode=410; // always use "gone" status (even if we might have received a 404)
3230         dsConfirmItemOp(sop_replace,localID,remoteID,false,statuscode);
3231         break;
3232       }
3233       else
3234       #endif
3235       if (statuscode==201) {
3236         fRemoteItemsAdded++;
3237         dsConfirmItemOp(sop_add,localID,remoteID,true); // ok as add
3238       }
3239       else if (statuscode<300 || statuscode==419) { // conflict resolved counts as ok as well
3240         fRemoteItemsUpdated++;
3241         dsConfirmItemOp(sop_replace,localID,remoteID,true); // ok as replace
3242       }
3243       #ifdef SYSYNC_CLIENT
3244       else if (IS_CLIENT && (isSlowSync() && statuscode==418)) {
3245         // "Already exists"/418 is acceptable as client in slow sync because some
3246         // servers use it instead of 200/419 for slow sync match
3247         PDEBUGPRINTFX(DBG_ERROR,("Warning: received 418 for for replace during slow sync - treat it as ok (200), but don't count as update"));
3248         dsConfirmItemOp(sop_replace,localID,remoteID,true); // 418 is acceptable in slow sync (not really conformant, but e.g. happening with Scheduleworld)
3249         statuscode=200; // always use "gone" status (even if we might have received a 404)
3250       }
3251       #endif // SYSYNC_CLIENT
3252       else {
3253         dsConfirmItemOp(sop_replace,localID,remoteID,false,statuscode); // failed replace
3254       }
3255       break;
3256     case sop_archive_delete:
3257     case sop_soft_delete:
3258     case sop_delete:
3259       if (statuscode<211) fRemoteItemsDeleted++;
3260       // allow 211 and 404 for delete - after all, the record is not there
3261       // any more on the remote
3262       if (statuscode==404 || statuscode==211) {
3263         PDEBUGPRINTFX(DBG_DATA,("Status: %hd: To-be-deleted item not found, but accepted this (changed status to 200)",statuscode));
3264         statuscode=200;
3265       }
3266       // if ok (explicit or implicit), we can confirm the delete
3267       dsConfirmItemOp(sop_delete,localID,remoteID,statuscode<300,statuscode); // counts as ok delete
3268       break;
3269     default: break;
3270   } // switch
3271   // check if we want to mark failed items for resend in the next session or abort
3272   bool resend = fDSConfigP->fResendFailing; // get default from config
3273   #ifdef SCRIPT_SUPPORT
3274   // let script check status code
3275   TErrorFuncContext errctx;
3276   errctx.statuscode = statuscode;
3277   errctx.resend = resend;
3278   errctx.newstatuscode = statuscode;
3279   errctx.syncop = sop;
3280   errctx.datastoreP = this;
3281   // - first check datastore level
3282   if (
3283     TScriptContext::executeTest(
3284       false, // assume script does NOT handle status entirely
3285       fDataStoreScriptContextP,
3286       fDSConfigP->fSentItemStatusScript,
3287       &ErrorFuncTable,
3288       &errctx // caller context
3289     )
3290   ) {
3291     // completely handled
3292     PDEBUGPRINTFX(DBG_ERROR,("Status: %hd: Handled by datastore script (original op was %s)",statuscode,SyncOpNames[sop]));
3293     return true;
3294   }
3295   errctx.statuscode = errctx.newstatuscode;
3296   // - then check session level
3297   if (
3298     TScriptContext::executeTest(
3299       false, // assume script does NOT handle status entirely
3300       fSessionP->fSessionScriptContextP,
3301       fSessionP->getSessionConfig()->fSentItemStatusScript,
3302       &ErrorFuncTable,
3303       &errctx // caller context
3304     )
3305   ) {
3306     // completely handled
3307     PDEBUGPRINTFX(DBG_ERROR,("Status: %hd: Handled by session script (original op was %s)",statuscode,SyncOpNames[sop]));
3308     return true;
3309   }
3310   // not completely handled, use possibly modified status code
3311   #ifdef SYDEBUG
3312   if (statuscode != errctx.newstatuscode) {
3313     PDEBUGPRINTFX(DBG_ERROR,("Status: Script changed original status=%hd to %hd (original op was %s)",statuscode,errctx.newstatuscode,SyncOpNames[errctx.syncop]));
3314   }
3315   #endif
3316   statuscode = errctx.newstatuscode;
3317   resend = errctx.resend;
3318   #endif
3319   // now perform default action according to status code
3320   switch (statuscode) {
3321     case 200:
3322       break;
3323     case 201:
3324       PDEBUGPRINTFX(DBG_PROTO,("Status: %hd: Item added (original op was %s)",statuscode,SyncOpNames[sop]));
3325       break;
3326     case 204:
3327       PDEBUGPRINTFX(DBG_PROTO,("Status: %hd: No content (original op was %s)",statuscode,SyncOpNames[sop]));
3328       break;
3329     case 207:
3330     case 208:
3331     case 209:
3332     case 419:
3333       PDEBUGPRINTFX(DBG_HOT,("Status: %hd: Conflict resolved (original op was %s)",statuscode,SyncOpNames[sop]));
3334       break;
3335     case 210:
3336       PDEBUGPRINTFX(DBG_HOT,("Status: %hd: Delete without archive (original op was %s)",statuscode,SyncOpNames[sop]));
3337       break;
3338     case 211:
3339       PDEBUGPRINTFX(DBG_ERROR,("Status: %hd: nothing deleted, item not found (original op was %s)",statuscode,SyncOpNames[sop]));
3340       break;
3341     case 410: // gone
3342     case 420: // device full
3343       // these have been handled above and are considered ok now
3344       break;
3345     case 514: // cancelled
3346       // ignore cancelling while suspending, as these are CAUSED by the suspend
3347       if (fSessionP->isSuspending() && dsResumeSupportedInDB()) {
3348         // don't do anything here - we'll be suspended later (but process commands until then)
3349         // dsConfirmItemOp() has already caused the item to be marked for resume
3350         break;
3351       }
3352       // for non-DS-1.2 sessions, we treat 514 like the other errors below (that is - retry might help)
3353     case 424: // size mismatch (e.g. due to faild partial item resume attempt -> retry will help)
3354     case 417: // retry later (remote says that retry will probably work)
3355     case 506: // processing error, retry later (remote says that retry will probably work)
3356     case 404: // not found (retry is not likely to help, but does not harm too much, either)
3357     case 408: // timeout (if that happens on a single item, retry probably helps)
3358     case 415: // bad type (retry is not likely to help, but does not harm too much, either)
3359     case 510: // datastore failure (too unspecific to know if retry might help, but why not?)
3360     case 500: // general failure (too unspecific to know if retry might help, but why not?)
3361       // these errors cause either a resend in a later session
3362       // or only abort the datastore, but not the session
3363       if (resend && dsResumeSupportedInDB()) {
3364         PDEBUGPRINTFX(DBG_ERROR,("Status: General error %hd (original op was %s) -> marking item for resend in next session",statuscode,SyncOpNames[sop]));
3365         engMarkItemForResend(localID,remoteID); // Note: includes incrementing fRemoteItemsError
3366       }
3367       else {
3368         PDEBUGPRINTFX(DBG_ERROR,("Status: General error %hd (original op was %s) -> aborting sync with this datastore",statuscode,SyncOpNames[sop]));
3369         engAbortDataStoreSync(statuscode,false); // remote problem
3370       }
3371       break;
3372     default:
3373       // let command handle it
3374       return false;
3375       //break;
3376   }
3377   // status handled
3378   return true; // handled status
3379 } // TLocalEngineDS::engHandleSyncOpStatus
3380
3381
3382 /// Internal events during sync for derived classes
3383 /// @Note local DB authorisation must be established already before calling these
3384 /// - cause loading of all session anchoring info and other admin data (logicMakeAdminReady())
3385 ///   fLastRemoteAnchor,fLastLocalAnchor,fNextLocalAnchor; isFirstTimeSync() will be valid after the call
3386 /// - in case of superdatastore, consolidates the anchor information from the subdatastores
3387 localstatus TLocalEngineDS::engInitSyncAnchors(
3388   cAppCharP aDatastoreURI,      ///< local datastore URI
3389   cAppCharP aRemoteDBID         ///< ID of remote datastore (to find session information in local DB)
3390 )
3391 {
3392   // nothing more to do than making admin data ready
3393   // - this will fill all dsSavedAdminData members here and in all derived classes
3394   localstatus sta=logicMakeAdminReady(aDatastoreURI, aRemoteDBID);
3395   if (sta==LOCERR_OK) {
3396     changeState(dssta_adminready); // admin data is now ready
3397   }
3398   // return on error
3399   return sta;
3400 } // TLocalEngineDS::engInitSyncAnchors
3401
3402
3403 #ifdef SYSYNC_CLIENT
3404
3405 // initialize Sync alert for datastore according to Parameters set with dsSetClientSyncParams()
3406 localstatus TLocalEngineDS::engPrepareClientSyncAlert(void)
3407 {
3408   #ifdef SUPERDATASTORES
3409   // no operation here if running under control of a superdatastore.
3410   // superdatastore's engPrepareClientSyncAlert() will call engPrepareClientRealDSSyncAlert of all subdatastores at the right time
3411   if (fAsSubDatastoreOf)
3412     return LOCERR_OK;
3413   #endif
3414   // this is a real datastore
3415   return engPrepareClientDSForAlert();
3416 } // TLocalEngineDS::engPrepareClientSyncAlert
3417
3418
3419
3420 // initialize Sync alert for datastore according to Parameters set with dsSetClientSyncParams()
3421 localstatus TLocalEngineDS::engPrepareClientDSForAlert(void)
3422 {
3423   localstatus sta;
3424
3425   // reset the filters that might be added to in alertprepscript
3426   // (as they might have been half set-up in a previous failed alert, they must be cleared and re-constructed here)
3427   resetFiltering();
3428
3429   #ifdef SCRIPT_SUPPORT
3430   // AlertPrepareScript to add filters and CGI
3431   // - rebuild early (before all of the other DS scripts in makeAdminReady caused by engInitSyncAnchors below!)
3432   TScriptContext::rebuildContext(fSessionP->getSyncAppBase(),fDSConfigP->fAlertPrepScript,fDataStoreScriptContextP,fSessionP);
3433   // - add custom DS 1.2 filters and/or custom CGI to fRemoteDBPath
3434   TScriptContext::execute(
3435     fDataStoreScriptContextP,
3436     fDSConfigP->fAlertPrepScript,
3437     fDSConfigP->getClientDBFuncTable(), // function table with extra
3438     this // datastore pointer needed for context
3439   );
3440   #endif
3441   // - save the identifying name of the DB
3442   fIdentifyingDBName = fLocalDBPath;
3443   // - get information about last session out of database
3444   sta = engInitSyncAnchors(
3445     relativeURI(fLocalDBPath.c_str()),
3446     fRemoteDBPath.c_str()
3447   );
3448   if (sta!=LOCERR_OK) {
3449     // error getting anchors
3450     PDEBUGPRINTFX(DBG_ERROR,("Could not get Sync Anchor info"));
3451     return localError(sta);
3452   }
3453   // check if we are forced to slowsync (otherwise, fSlowSync is pre-set from dsSetClientSyncParams()
3454   fSlowSync = fSlowSync || fLastLocalAnchor.empty() || fFirstTimeSync;
3455   // check for resume
3456   if (fResumeAlertCode!=0 && fSessionP->getSyncMLVersion()>=syncml_vers_1_2) {
3457     // we have a suspended session, try to resume
3458     PDEBUGPRINTFX(DBG_PROTO,("Found suspended session with Alert Code = %hd",fResumeAlertCode));
3459     fResuming = true;
3460   }
3461   return LOCERR_OK; // ok
3462 } // TLocalEngineDS::engPrepareClientDSForAlert
3463
3464
3465 // generate Sync alert for datastore after initialisation with engPrepareClientSyncAlert()
3466 // Note: this could be repeatedly called due to auth failures at beginning of session
3467 // Note: this is a NOP for subDatastores (should not be called in this case, anyway)
3468 localstatus TLocalEngineDS::engGenerateClientSyncAlert(
3469   TAlertCommand *&aAlertCommandP
3470 )
3471 {
3472   aAlertCommandP=NULL;
3473   #ifdef SUPERDATASTORES
3474   if (fAsSubDatastoreOf) return LOCERR_OK; // NOP, ok, only superdatastore creates an alert!
3475   #endif
3476
3477   PDEBUGPRINTFX(DBG_PROTO,(
3478     "(Saved) Last Local Client Anchor='%s', (generated) Next Local Client Anchor='%s' (sent to server as <last>/<next> in <alert>)",
3479     fLastLocalAnchor.c_str(),
3480     fNextLocalAnchor.c_str()
3481   ));
3482   // create appropriate initial alert command
3483   uInt16 alertCode = getSyncStateAlertCode(fServerAlerted,true);
3484   // check for resume
3485   if (fResuming) {
3486     // check if what we resume is same as what we wanted to do
3487     if (alertCode != fResumeAlertCode) {
3488       // this is ok for client, just show in log
3489       PDEBUGPRINTFX(DBG_PROTO,(
3490         "Sync mode seems to have changed (alert code = %hd) since last Suspend (alert code = %hd)",
3491         alertCode,
3492         fResumeAlertCode
3493       ));
3494     }
3495     // resume
3496     alertCode=225; // resume
3497     PDEBUGPRINTFX(DBG_PROTO,(
3498       "Alerting resume of last sync session (original alert code = %hd)",
3499       fResumeAlertCode
3500     ));
3501   }
3502   aAlertCommandP = new TAlertCommand(fSessionP,this,alertCode);
3503   PDEBUGPRINTFX(DBG_HOT,(
3504     "%s: ALERTING server for %s%s%s Sync",
3505     getName(),
3506     fResuming ? "RESUMED " : "",
3507     fSlowSync ? "slow" : "normal",
3508     fFirstTimeSync ? " first time" : ""
3509   ));
3510   // add Item with Anchors and URIs
3511   SmlItemPtr_t itemP = newItem();
3512   // - anchors
3513   itemP->meta=newMetaAnchor(fNextLocalAnchor.c_str(),fLastLocalAnchor.c_str());
3514   // - MaxObjSize here again to make SCTS happy
3515   if (
3516     (fSessionP->getSyncMLVersion()>=syncml_vers_1_1) &&
3517     (fSessionP->getRootConfig()->fLocalMaxObjSize>0)
3518   ) {
3519     // SyncML 1.1 has object size and we need to put it here for SCTS
3520     smlPCDataToMetInfP(itemP->meta)->maxobjsize=newPCDataLong(
3521       fSessionP->getRootConfig()->fLocalMaxObjSize
3522     );
3523   }
3524   // - URIs
3525   itemP->source=newLocation(fLocalDBPath.c_str()); // local DB ID
3526   itemP->target=newLocation(fRemoteDBPath.c_str()); // use remote path as configured in client settings
3527   // - add DS 1.2 filters
3528   if (!fRemoteRecordFilterQuery.empty() || false /* %%% field level filter */) {
3529     if (fSessionP->getSyncMLVersion()<syncml_vers_1_2) {
3530       PDEBUGPRINTFX(DBG_ERROR,("Filter specified, but SyncML version is < 1.2"));
3531       engAbortDataStoreSync(406, true, false); // can't continue sync
3532       return 406; // feature not supported
3533     }
3534     SmlFilterPtr_t filterP = SML_NEW(SmlFilter_t);
3535     memset(filterP,0,sizeof(SmlFilter_t));
3536     if (filterP) {
3537       // Must have a meta with the content type
3538       // - get the preferred receive type
3539       TSyncItemType *itemTypeP = fSessionP->findLocalType(fDSConfigP->fTypeSupport.fPreferredRx);
3540       if (itemTypeP) {
3541         // add meta type
3542         filterP->meta = newMetaType(itemTypeP->getTypeName());
3543       }
3544       // add filtertype if needed (=not EXCLUSIVE)
3545       if (fRemoteFilterInclusive) {
3546         filterP->filtertype=newPCDataString(SYNCML_FILTERTYPE_INCLUSIVE);
3547       }
3548       // record level
3549       if (!fRemoteRecordFilterQuery.empty()) {
3550         // add <record>
3551         filterP->record = SML_NEW(SmlRecordOrFieldFilter_t);
3552         // - add item with data=filterquery
3553         filterP->record->item = newStringDataItem(fRemoteRecordFilterQuery.c_str());
3554         // - add meta type with grammar
3555         filterP->record->item->meta = newMetaType(SYNCML_FILTERTYPE_CGI);
3556         PDEBUGPRINTFX(DBG_HOT,(
3557           "Alerting with %sCLUSIVE Record Level Filter Query = '%s'",
3558           fRemoteFilterInclusive ? "IN" : "EX",
3559           fRemoteRecordFilterQuery.c_str()
3560         ));
3561       }
3562       // field level
3563       /// @todo %%% to be implemented
3564       if (false) {
3565         // !!! remember to add real check (now: false) in outer if-statement as well!!!!!
3566         // %%% tbd
3567       }
3568     }
3569     // add it to item
3570     itemP->target->filter = filterP;
3571   }
3572   // - add to alert command
3573   aAlertCommandP->addItem(itemP);
3574   // we have now produced the client alert command, change state
3575   return changeState(dssta_clientsentalert);
3576 } // TLocalEngineDS::engGenerateClientSyncAlert
3577
3578
3579 // Init engine for client sync
3580 // - determine types to exchange
3581 // - make sync set ready
3582 localstatus TLocalEngineDS::engInitForClientSync(void)
3583 {
3584   #ifdef SUPERDATASTORES
3585   // no init in case we are under control of a superdatastore -> the superdatastore will do that
3586   if (fAsSubDatastoreOf)
3587     return LOCERR_OK;
3588   #endif
3589   return engInitDSForClientSync();
3590 } // TLocalEngineDS::engInitForClientSync
3591
3592
3593
3594 // Init engine for client sync
3595 // - determine types to exchange
3596 // - make sync set ready
3597 localstatus TLocalEngineDS::engInitDSForClientSync(void)
3598 {
3599   // make ready for syncops
3600   localstatus sta = engInitForSyncOps(getRemoteDBPath());
3601   if (sta==LOCERR_OK) {
3602     // - let local datastore (derived DB-specific class) prepare for sync
3603     sta = changeState(dssta_dataaccessstarted);
3604     if (sta==LOCERR_OK && isStarted(false)) {
3605       // already started now, change state
3606       sta = changeState(dssta_syncsetready);
3607     }
3608   }
3609   return sta;
3610 } // TLocalEngineDS::engInitDSForClientSync
3611
3612
3613 #endif // Client
3614
3615
3616 // get Alert code for current Sync State
3617 uInt16 TLocalEngineDS::getSyncStateAlertCode(bool aServerAlerted, bool aClientMinimal)
3618 {
3619   uInt16 alertcode=0;
3620
3621   switch (fSyncMode) {
3622     case smo_twoway :
3623       alertcode = aServerAlerted ? 206 : 200;
3624       break;
3625     case smo_fromclient :
3626       alertcode = aServerAlerted ? 207 : 202; // fully specified
3627       break;
3628     case smo_fromserver :
3629       if (aClientMinimal) {
3630         // refresh from server is just client not sending any data, so we can alert like two-way
3631         alertcode = aServerAlerted ? 206 : 200;
3632       }
3633       else {
3634         // correctly alert it
3635         alertcode = aServerAlerted ? 209 : 204;
3636       }
3637       break;
3638   case numSyncModes:
3639       // invalid
3640       break;
3641   }
3642   // slowsync/refresh variants are always plus one, except 206 --> 201 (same as client initiated slow sync)
3643   if (fSlowSync) alertcode = (alertcode!=206 ? alertcode+1 : 201);
3644   return alertcode;
3645 } // TLocalEngineDS::getSyncStateAlertCode
3646
3647
3648 /// initializes Sync state variables and returns false if alert is not supported
3649 localstatus TLocalEngineDS::setSyncModeFromAlertCode(uInt16 aAlertCode, bool aAsClient)
3650 {
3651   localstatus sta;
3652   TSyncModes syncMode;
3653   bool slowSync, serverAlerted;
3654   // - translate into mode and flags
3655   sta=getSyncModeFromAlertCode(aAlertCode,syncMode,slowSync,serverAlerted);
3656   if (sta==LOCERR_OK) {
3657     // - set them
3658     sta=setSyncMode(aAsClient,syncMode,slowSync,serverAlerted);
3659   }
3660   return sta;
3661 } // TLocalEngineDS::setSyncModeFromAlertCode
3662
3663
3664 /// initializes Sync mode variables
3665 localstatus TLocalEngineDS::setSyncMode(bool aAsClient, TSyncModes aSyncMode, bool aIsSlowSync, bool aIsServerAlerted)
3666 {
3667   // get sync caps of this datastore
3668   uInt32 synccapmask=getSyncCapMask();
3669   // check if mode supported
3670   if (aIsServerAlerted) {
3671     // check if we support server alerted modes, SyncCap/Bit=7
3672     if (~synccapmask & (1<<7)) return 406; // not supported
3673   }
3674   switch(aSyncMode) {
3675     case smo_twoway:
3676       // Two-way Sync, SyncCap/Bit=1
3677       // or Two-way slow Sync, SyncCap/Bit=2
3678       if (~synccapmask & (1<< (aIsSlowSync ? 2 : 1))) return 406; // not supported
3679       if (fSyncMode==smo_fromserver && aAsClient)
3680         aSyncMode=smo_fromserver; // for client, if already fromserver mode set, keep it
3681       break;
3682     case smo_fromclient:
3683       // One-way from client, SyncCap/Bit=3
3684       // or Refresh (=slow one-way) from client, SyncCap/Bit=4
3685       if (~synccapmask & (1<< (aIsSlowSync ? 4 : 3))) return 406; // not supported
3686       if (!aAsClient) fRefreshOnly=true; // as server, we are in refresh-only-mode if we get one-way from client
3687       break;
3688     case smo_fromserver:
3689       // One-way from server, SyncCap/Bit=5
3690       // or Refresh (=slow one-way) from server, SyncCap/Bit=6
3691       if (~synccapmask & (1<< (aIsSlowSync ? 6 : 5))) return 406; // not supported
3692       if (aAsClient) fRefreshOnly=true; // as client, we are in refresh-only-mode if we get one-way fromm server
3693       break;
3694     default:
3695       return 400; // bad request
3696   }
3697   // now set mode and flags (possibly adjusted above)
3698   fSyncMode=aSyncMode;
3699   fSlowSync=aIsSlowSync;
3700   fServerAlerted=aIsServerAlerted;
3701   // ok
3702   return LOCERR_OK;
3703 } // TLocalEngineDS::setSyncMode
3704
3705
3706 /// get Sync mode variables from given alert code
3707 localstatus TLocalEngineDS::getSyncModeFromAlertCode(uInt16 aAlertCode, TSyncModes &aSyncMode, bool &aIsSlowSync, bool &aIsServerAlerted)
3708 {
3709   // these might be pre-defined)
3710   /// @deprecated state change does not belong here
3711   aIsSlowSync=false;
3712   aIsServerAlerted=false;
3713   aSyncMode=smo_twoway; // to make sure it is valid
3714   // first test if server-alerted
3715   if (aAlertCode>=206 && aAlertCode<210) {
3716     // Server alerted modes
3717     aIsServerAlerted=true;
3718   }
3719   // test for compatibility with alert code
3720   switch(aAlertCode) {
3721     case 200:
3722     case 206:
3723       // Two-way Sync
3724       aSyncMode=smo_twoway;
3725       aIsSlowSync=false;
3726       break;
3727     case 201:
3728       // Two-way slow Sync
3729       aSyncMode=smo_twoway;
3730       aIsSlowSync=true;
3731       break;
3732     case 202:
3733     case 207:
3734       // One-way from client
3735       aSyncMode=smo_fromclient;
3736       aIsSlowSync=false;
3737       break;
3738     case 203:
3739     case 208:
3740       // refresh (=slow one-way) from client
3741       aSyncMode=smo_fromclient;
3742       aIsSlowSync=true;
3743       break;
3744     case 204:
3745     case 209:
3746       // One-way from server
3747       aSyncMode=smo_fromserver;
3748       aIsSlowSync=false;
3749       break;
3750     case 205:
3751     case 210:
3752       // refresh (=slow one-way) from server
3753       aSyncMode=smo_fromserver;
3754       aIsSlowSync=true;
3755       break;
3756     default:
3757       // bad alert
3758       return 400;
3759   }
3760   return LOCERR_OK;
3761 } // TLocalEngineDS::getSyncModeFromAlertCode
3762
3763
3764 // create new Sync capabilities info from capabilities mask
3765 // Bit0=reserved, Bit1..Bitn = SyncType 1..n available
3766 // Note: derived classes might add special sync codes and/or mask standard ones
3767 SmlDevInfSyncCapPtr_t TLocalEngineDS::newDevInfSyncCap(uInt32 aSyncCapMask)
3768 {
3769   SmlDevInfSyncCapPtr_t synccapP;
3770   SmlPcdataPtr_t synctypeP;
3771
3772   // new synccap structure
3773   synccapP = SML_NEW(SmlDevInfSyncCap_t);
3774   // synccap list is empty
3775   synccapP->synctype=NULL;
3776   // now add standard synccaps
3777   for (sInt16 k=0; k<32; k++) {
3778     if (aSyncCapMask & (1<<k)) {
3779       // capability available
3780       synctypeP=newPCDataLong(k);
3781       addPCDataToList(synctypeP,&(synccapP->synctype));
3782     }
3783   }
3784   // Now add non-standard synccaps.
3785   // From the spec: "Other values can also be specified."
3786   // Values are PCDATA, so we can use plain strings.
3787   //
3788   // But the Funambol server expects integer numbers and
3789   // throws a parser error when sent a string. So better
3790   // stick to a semi-random number (hopefully no-one else
3791   // is using it).
3792   //
3793   // Worse, Nokia phones cancel direct sync sessions with an
3794   // OBEX error ("Forbidden") when non-standard sync modes
3795   // are included in the SyncCap. As a workaround for that
3796   // we use the following logic:
3797   // - libsynthesis in a SyncML client will always send
3798   //   all the extended sync modes; with the Funambol
3799   //   workaround in place that works
3800   // - libsynthesis in a SyncML server will only send the
3801   //   extended sync modes if the client has sent any
3802   //   extended sync modes itself; the 390002 mode is
3803   //   sent unconditionally for that purpose
3804   //
3805   // Corresponding code in TRemoteDataStore::setDatastoreDevInf().
3806   //
3807   if (!IS_SERVER ||
3808       fSessionP->receivedSyncModeExtensions()) {
3809     if (canRestart()) {
3810       synctypeP=newPCDataString("390001");
3811       addPCDataToList(synctypeP,&(synccapP->synctype));
3812     }
3813     synctypeP=newPCDataString("390002");
3814     addPCDataToList(synctypeP,&(synccapP->synctype));
3815
3816     // Finally add non-standard synccaps that are outside of the
3817     // engine's control.
3818     set<string> modes;
3819     getSyncModes(modes);
3820     for (set<string>::const_iterator it = modes.begin();
3821          it != modes.end();
3822          ++it) {
3823       synctypeP=newPCDataString(*it);
3824       addPCDataToList(synctypeP,&(synccapP->synctype));
3825     }
3826   }
3827
3828   // return it
3829   return synccapP;
3830 } // TLocalEngineDS::newDevInfSyncCap
3831
3832
3833 // obtain new datastore info, returns NULL if none available
3834 SmlDevInfDatastorePtr_t TLocalEngineDS::newDevInfDatastore(bool aAsServer, bool aWithoutCTCapProps)
3835 {
3836   SmlDevInfDatastorePtr_t datastoreP;
3837
3838   // set only basic info, details must be added in derived class
3839   // - sourceref is the name of the datastore,
3840   //   or for server, if already alerted, the name used in the alert
3841   //   (this is to allow /dsname/foldername with clients that expect the
3842   //   devInf to contain exactly the same full path as name, like newer Nokias)
3843   string dotname;
3844   #ifdef SYSYNC_SERVER
3845   if (IS_SERVER && testState(dssta_serveralerted,false) && fSessionP->fDSPathInDevInf) {
3846     // server and already alerted
3847     // - don't include sub-datastores
3848     if (fAsSubDatastoreOf) {
3849       return NULL;
3850     }
3851
3852     // - use datastore spec as sent from remote, minus CGI, as relative spec
3853     dotname = URI_RELPREFIX;
3854     dotname += fSessionP->SessionRelativeURI(fRemoteViewOfLocalURI.c_str());
3855     if (!fSessionP->fDSCgiInDevInf) {
3856       // remove CGI
3857       string::size_type n=dotname.find("?");
3858       if (n!=string::npos)
3859         dotname.resize(n); // remove CGI
3860     }
3861   }
3862   else
3863   #endif
3864   {
3865     // client or not yet alerted - just use datastore base name
3866     StringObjPrintf(dotname,URI_RELPREFIX "%s",fName.c_str());
3867   }
3868
3869   // create new
3870   datastoreP=SML_NEW(SmlDevInfDatastore_t);
3871   datastoreP->sourceref=newPCDataString(dotname);
3872   #ifndef MINIMAL_CODE
3873   // - Optional display name
3874   datastoreP->displayname=newPCDataOptString(getDisplayName());
3875   #else
3876   datastoreP->displayname=NULL;
3877   #endif
3878   // - MaxGUIDsize (for client only)
3879   if (aAsServer)
3880     datastoreP->maxguidsize=NULL;
3881   else
3882     datastoreP->maxguidsize=newPCDataLong(fMaxGUIDSize);
3883   // - check for legacy mode type (that is to be used as "preferred" instead of normal preferred)
3884   TSyncItemType *legacyTypeP = NULL;
3885   if (getSession()->fLegacyMode && getDSConfig()->fTypeSupport.fPreferredLegacy) {
3886     // get the type marked as blind
3887     legacyTypeP = getSession()->findLocalType(getDSConfig()->fTypeSupport.fPreferredLegacy);
3888   }
3889   // - RxPref
3890   if (!fRxPrefItemTypeP) SYSYNC_THROW(TStructException("Datastore has no RxPref ItemType"));
3891   datastoreP->rxpref = (legacyTypeP ? legacyTypeP : fRxPrefItemTypeP)->newXMitDevInf();
3892   // - Rx (excluding the type we report as preferred)
3893   datastoreP->rx=TSyncItemType::newXMitListDevInf(fRxItemTypes,legacyTypeP ? legacyTypeP : fRxPrefItemTypeP);
3894   // - TxPref
3895   if (!fTxPrefItemTypeP) SYSYNC_THROW(TStructException("Datastore has no TxPref ItemType"));
3896   datastoreP->txpref = (legacyTypeP ? legacyTypeP : fTxPrefItemTypeP)->newXMitDevInf();
3897   // - Tx (excluding the type we report as preferred)
3898   datastoreP->tx=TSyncItemType::newXMitListDevInf(fTxItemTypes,legacyTypeP ? legacyTypeP : fTxPrefItemTypeP);
3899   // - DSMem
3900   /// @todo %%% tbd: add dsmem
3901   datastoreP->dsmem=NULL;
3902   // - SyncML DS 1.2 datastore-local CTCap
3903   if (fSessionP->getSyncMLVersion()>=syncml_vers_1_2) {
3904     // CTCap is local to datastore, get only CTCaps relevant for this datastore, but independently from alerted state
3905     datastoreP->ctcap = fSessionP->newLocalCTCapList(false, this, aWithoutCTCapProps);
3906   }
3907   else
3908     datastoreP->ctcap=NULL; // before SyncML 1.2, there was no datastore-local CTCap
3909   // - SyncML DS 1.2 flags (SmlDevInfHierarchical_f)
3910   /// @todo %%% tbd: add SmlDevInfHierarchical_f
3911   datastoreP->flags=0;
3912   // - SyncML DS 1.2 filters
3913   datastoreP->filterrx=NULL;
3914   datastoreP->filtercap=NULL;
3915   #ifdef OBJECT_FILTERING
3916   if (IS_SERVER && fDSConfigP->fDS12FilterSupport && fSessionP->getSyncMLVersion()>=syncml_vers_1_2) {
3917     // Show Filter info in 1.2 devInf if this is not a client
3918     // - FilterRx constant
3919     datastoreP->filterrx = SML_NEW(SmlDevInfXmitList_t);
3920     datastoreP->filterrx->next = NULL;
3921     datastoreP->filterrx->data = SML_NEW(SmlDevInfXmit_t);
3922     datastoreP->filterrx->data->cttype=newPCDataString(SYNCML_FILTERTYPE_CGI);
3923     datastoreP->filterrx->data->verct=newPCDataString(SYNCML_FILTERTYPE_CGI_VERS);
3924     // build filtercap
3925     SmlPcdataListPtr_t filterkeywords = NULL;
3926     SmlPcdataListPtr_t filterprops = NULL;
3927     // - fill the lists from types
3928     addFilterCapPropsAndKeywords(filterkeywords,filterprops);
3929     // - if we have something, actually build a filtercap
3930     if (filterkeywords || filterprops) {
3931       // we have filtercaps, add them
3932       // - FilterCap list
3933       datastoreP->filtercap = SML_NEW(SmlDevInfFilterCapList_t);
3934       datastoreP->filtercap->next = NULL;
3935       datastoreP->filtercap->data = SML_NEW(SmlDevInfFilterCap_t);
3936       datastoreP->filtercap->data->cttype=newPCDataString(SYNCML_FILTERTYPE_CGI);
3937       datastoreP->filtercap->data->verct=newPCDataString(SYNCML_FILTERTYPE_CGI_VERS);
3938       // - add list we've got
3939       datastoreP->filtercap->data->filterkeyword=filterkeywords;
3940       datastoreP->filtercap->data->propname=filterprops;
3941     }
3942   }
3943   #endif // OBJECT_FILTERING
3944   // - Sync capabilities of this datastore
3945   datastoreP->synccap=newDevInfSyncCap(getSyncCapMask());
3946   // return it
3947   return datastoreP;
3948 } // TLocalEngineDS::newDevInfDatastore
3949
3950
3951 // Set remote datastore for local
3952 void TLocalEngineDS::engSetRemoteDatastore(
3953   TRemoteDataStore *aRemoteDatastoreP  // the remote datastore involved
3954 )
3955 {
3956   // save link to remote datastore
3957   if (fRemoteDatastoreP) {
3958     if (fRemoteDatastoreP!=aRemoteDatastoreP)
3959       SYSYNC_THROW(TSyncException("Sync continues with different datastore"));
3960   }
3961   fRemoteDatastoreP=aRemoteDatastoreP;
3962 } // TLocalEngineDS::engSetRemoteDatastore
3963
3964
3965 // SYNC command bracket start (check credentials if needed)
3966 bool TLocalEngineDS::engProcessSyncCmd(
3967   SmlSyncPtr_t aSyncP,                // the Sync element
3968   TStatusCommand &aStatusCommand,     // status that might be modified
3969   bool &aQueueForLater // will be set if command must be queued for later (re-)execution
3970 )
3971 {
3972   // get number of changes info if available
3973   if (aSyncP->noc) {
3974     StrToLong(smlPCDataToCharP(aSyncP->noc),fRemoteNumberOfChanges);
3975   }
3976   // check if datastore is aborted
3977   if (CheckAborted(aStatusCommand)) return false;
3978   // check
3979   if (!fRemoteDatastoreP)
3980     SYSYNC_THROW(TSyncException("No remote datastore linked"));
3981   // let remote datastore process it first
3982   if (!fRemoteDatastoreP->remoteProcessSyncCmd(aSyncP,aStatusCommand,aQueueForLater)) {
3983     PDEBUGPRINTFX(DBG_ERROR,("TLocalEngineDS::engProcessSyncCmd: remote datastore failed processing <sync>"));
3984     changeState(dssta_idle,true); // force it
3985     return false;
3986   }
3987   // check for combined init&sync
3988   if (!testState(dssta_syncmodestable,false)) {
3989     // <sync> encountered before sync mode stable: could be combined init&sync session
3990     if (fLocalDSState>=dssta_serveransweredalert) {
3991       // ok for switching to combined init&sync
3992       PDEBUGPRINTFX(DBG_HOT,("TLocalEngineDS::engProcessSyncCmd: detected combined init&sync, freeze sync mode already now"));
3993       // - freeze sync mode as it is now
3994       changeState(dssta_syncmodestable,true); // force it, sync mode is now stable, no further changes are possible
3995     }
3996   }
3997   // now init if this is the first <sync> command
3998   bool startingNow = false; // assume start already initiated
3999   if (testState(dssta_syncmodestable,true)) {
4000     // all alert and alert status must be done by now, sync mode must be stable
4001     CONSOLEPRINTF(("- Starting Sync with Datastore '%s', %s sync",fRemoteViewOfLocalURI.c_str(),fSlowSync ? "slow" : "normal"));
4002     startingNow = true; // initiating start now
4003     #ifdef SYSYNC_SERVER
4004     if (IS_SERVER) {
4005       // at this point, all temporary GUIDs become invalid (no "early map" possible any more which might refer to last session's tempGUIDs)
4006       fTempGUIDMap.clear(); // forget all previous session's temp GUID mappings
4007       // let local datastore (derived DB-specific class) prepare for sync
4008       localstatus sta = changeState(dssta_dataaccessstarted);
4009       if (sta!=LOCERR_OK) {
4010         // abort session (old comment: %%% aborting datastore only does not work, will loop, why? %%%)
4011         aStatusCommand.setStatusCode(syncmlError(sta));
4012         PDEBUGPRINTFX(DBG_ERROR,("TLocalEngineDS::engProcessSyncCmd: could not change state to dsssta_dataaccessstarted -> abort"));
4013         engAbortDataStoreSync(sta,true); // local problem
4014         return false;
4015       }
4016     }
4017     #endif
4018   }
4019   // if data access started (finished or not), check start status
4020   // for every execution and re-execution of the sync command
4021   if (testState(dssta_dataaccessstarted)) {
4022     // queue <sync> command if datastore is not yet started already
4023     if (engIsStarted(!startingNow)) { // wait only if start was already initiated
4024       // - is now initialized
4025       if (IS_SERVER) {
4026         // - for server, make the sync set ready now (as engine is now started)
4027         changeState(dssta_syncsetready,true); // force it
4028       }
4029       else {
4030         // - for client, we need at least dssta_syncgendone (sync set has been ready long ago, we've already sent changes to server!)
4031         if (!testState(dssta_syncgendone)) {
4032           // bad sequence of commands (<sync> from server too early!)
4033           aStatusCommand.setStatusCode(400);
4034           PDEBUGPRINTFX(DBG_ERROR,("TLocalEngineDS::engProcessSyncCmd: client received SYNC before sending SYNC complete"));
4035           engAbortDataStoreSync(400,false,false); // remote problem, not resumable
4036           return false;
4037         }
4038       }
4039       PDEBUGPRINTFX(DBG_HOT,(
4040         "- Started %s Sync (first <sync> command)",
4041         fSlowSync ? "slow" : "normal"
4042       ));
4043       if (fRemoteNumberOfChanges>=0) PDEBUGPRINTFX(DBG_HOT,("- NumberOfChanges announced by remote = %ld",(long)fRemoteNumberOfChanges));
4044       DB_PROGRESS_EVENT(this,pev_syncstart,0,0,0);
4045     }
4046     else {
4047       // - not yet started
4048       PDEBUGPRINTFX(DBG_HOT,(
4049         "- Starting sync not yet complete - re-execute <sync> command again in next message"
4050       ));
4051       aQueueForLater=true;
4052       return true; // ok so far
4053     }
4054   }
4055   // must be syncready here (otherwise we return before we reach this)
4056   if (!testState(dssta_syncsetready)) {
4057     aStatusCommand.setStatusCode(403); // forbidden
4058     ADDDEBUGITEM(aStatusCommand,"SYNC received too early");
4059     PDEBUGPRINTFX(DBG_ERROR,("TLocalEngineDS::engProcessSyncCmd: SYNC received too early"));
4060     engAbortDataStoreSync(403,false,false); // remote problem, not resumable
4061     return false;
4062   }
4063   // state is now syncing
4064   /// @deprecated - dssta_syncsetready is enough
4065   //fState=dss_syncing;
4066   return true;
4067 } // TLocalEngineDS::engProcessSyncCmd
4068
4069
4070 // SYNC command bracket end (but another might follow in next message)
4071 bool TLocalEngineDS::engProcessSyncCmdEnd(bool &aQueueForLater)
4072 {
4073   bool ok=true;
4074   // queue it for later as long as datastore is not ready now
4075   if (!engIsStarted(false)) { // not waiting if not started
4076     // no state change, just postpone execution
4077     aQueueForLater=true;
4078   }
4079   // also inform remote
4080   if (fRemoteDatastoreP) ok=fRemoteDatastoreP->remoteProcessSyncCmdEnd();
4081   return ok;
4082 } // TLocalEngineDS::engProcessSyncCmdEnd
4083
4084
4085 #ifdef SYSYNC_SERVER
4086
4087 // server case: called whenever outgoing Message of Sync Package starts
4088 void TLocalEngineDS::engServerStartOfSyncMessage(void)
4089 {
4090   // this is where we might start our own <Sync> command (all
4091   // received commands are now processed)
4092   // - Note that this might be a subdatastore -> if so, do NOT
4093   //   start a sync (superdatastore will handle this)
4094   // - Note that this will be called even if current message is
4095   //   already full, so it could well be that this sync command
4096   //   is queued.
4097   if (!fSessionP->fCompleteFromClientOnly && testState(dssta_serverseenclientmods) && getSyncMode()==smo_fromclient) {
4098     // from-client only does not send back a <sync> command, simply end data access here
4099     PDEBUGPRINTFX(DBG_PROTO,("from-client-only:do not send <sync> command back to client, data access ends here"));
4100     changeState(dssta_syncgendone,true);
4101     changeState(dssta_dataaccessdone,true);
4102   }
4103   // ### SyncFest #5, found with Tactel Jazz Client:
4104   // - do not send anything when remote datastore is not known
4105   else if (fRemoteDatastoreP) {
4106     if (!testState(dssta_serversyncgenstarted) && testState(dssta_serverseenclientmods)) {
4107       changeState(dssta_serversyncgenstarted,true);
4108       if (!isSubDatastore()) {
4109         // - Note: if sync command was already started, the
4110         //   finished(), continueIssue() mechanism will make sure that
4111         //   more commands are generated
4112         // - Note2: if all sync commands can be sent at once,
4113         //   fState will be modified by issuing <sync>, so
4114         //   it must be ok for issuing syncops here already!
4115         TSyncCommand *synccmdP =
4116           new TSyncCommand(
4117             fSessionP,
4118             this,               // local datastore
4119             fRemoteDatastoreP   // remote datastore
4120           );
4121         // issue
4122         ISSUE_COMMAND_ROOT(fSessionP,synccmdP);
4123       }
4124     }
4125   }
4126   else {
4127     changeState(dssta_idle,true); // force it
4128   }
4129 } // TLocalEngineDS::engServerStartOfSyncMessage
4130
4131
4132 #endif // server only
4133
4134
4135 // called whenever Message of Sync Package ends or after last queued Sync command is executed
4136 // - aEndOfAllSyncCommands is true when at end of Sync-data-from-remote packet
4137 //   AND all possibly queued sync/syncop commands have been processed.
4138 void TLocalEngineDS::engEndOfSyncFromRemote(bool aEndOfAllSyncCommands)
4139 {
4140   // is called for all local datastores, including superdatastore, even inactive ones, so check state first
4141   if (testState(dssta_syncsetready)) {
4142     if (aEndOfAllSyncCommands) {
4143       // we are at end of sync-data-from-remote for THIS datastore
4144       if (IS_CLIENT) {
4145         // - we are done with <Sync> from server, that is, data access is done now
4146         changeState(dssta_dataaccessdone,true); // force it
4147       }
4148       else {
4149         // - server has seen all client modifications now
4150         // Note: in case of the simulated-empty-sync-hack in action, we
4151         //       allow that we are already in server_sync_gen_started and
4152         //       won't try to force down to dssta_serverseenclientmods
4153         if (!fSessionP->fFakeFinalFlag || getDSState()<dssta_serverseenclientmods) {
4154           // under normal circumstances, wee need dssta_serverseenclientmods here
4155           changeState(dssta_serverseenclientmods,true); // force it
4156         }
4157         else {
4158           // hack exception
4159           PDEBUGPRINTFX(DBG_ERROR,("Warning: simulated </final> active - allowing state>server_seen_client_mods"));
4160         }
4161       }
4162     }
4163     #ifdef SYSYNC_SERVER
4164     if (IS_SERVER) {
4165       engServerStartOfSyncMessage();
4166     }
4167     #endif
4168     // now do final things
4169     if (testState(dssta_dataaccessdone,true)) {
4170       // @todo: I think, as long as we need to send maps, we're not done yet!!!
4171       // sync packets in both directions done, forget remote datastore
4172       fRemoteDatastoreP=NULL;
4173     }
4174   } // dssta_syncsetready
4175 } // TLocalEngineDS::engEndOfSyncFromRemote
4176
4177
4178 // - must return true if this datastore is finished with <sync>
4179 //   (if all datastores return true,
4180 //   session is allowed to finish sync packet with outgoing message
4181 bool TLocalEngineDS::isSyncDone(void)
4182 {
4183   // is called for all local datastores, even inactive ones, which must signal sync done, too
4184   // - only datastores currently receiving or sending <sync> commands are not done with sync
4185   // - aborted datastores are also done with sync, no matter what status they have
4186   return (
4187     fAbortStatusCode!=0 ||
4188     //(fState!=dss_syncsend && fState!=dss_syncing && fState!=dss_syncready && fState!=dss_syncfinish)
4189     !testState(dssta_clientsentalert) || // nothing really happened yet...
4190     testState(dssta_syncgendone) // ...or already completed generating <sync>
4191   );
4192 } // TLocalEngineDS::isSyncDone
4193
4194
4195 // test datastore state for minimal or exact state
4196 bool TLocalEngineDS::testState(TLocalEngineDSState aMinState, bool aNeedExactMatch)
4197 {
4198   bool result =
4199     (!aNeedExactMatch || (fLocalDSState==aMinState)) &&
4200     (fLocalDSState>=aMinState);
4201   DEBUGPRINTFX(DBG_EXOTIC,(
4202     "%s: testState=%s - expected state%c='%s', found state=='%s'",
4203     getName(),
4204     result ? "TRUE" : "FALSE",
4205     aNeedExactMatch ? '=' : '>',
4206     getDSStateName(aMinState),
4207     getDSStateName()
4208   ));
4209   return result;
4210 } // TLocalEngineDS::testState
4211
4212
4213 // change datastore state, calls logic layer before and after change
4214 localstatus TLocalEngineDS::changeState(TLocalEngineDSState aNewState, bool aForceOnError)
4215 {
4216   localstatus err1,err2;
4217   TLocalEngineDSState oldState = fLocalDSState;
4218
4219   // nop if no change in state
4220   if (aNewState==oldState) return LOCERR_OK;
4221   // state cannot be decremented except down to adminready and below
4222   if ((aNewState<oldState) && (aNewState>dssta_adminready)) {
4223     PDEBUGPRINTFX(DBG_ERROR,(
4224       "%s: Internal error: attempt to reduce state from '%s' to '%s' - aborting",
4225       getName(),
4226       getDSStateName(oldState),
4227       getDSStateName(aNewState)
4228     ));
4229     err1 = 500;
4230     dsAbortDatastoreSync(err1,true);
4231     return err1;
4232   }
4233   // give logic opportunity to react before state changes
4234   PDEBUGBLOCKFMT((
4235     "DSStateChange",
4236     "Datastore changes state",
4237     "datastore=%s|oldstate=%s|newstate=%s",
4238     getName(),
4239     getDSStateName(oldState),
4240     getDSStateName(aNewState)
4241   ));
4242   err2 = LOCERR_OK;
4243   err1 = dsBeforeStateChange(oldState,aNewState);
4244   if (!aForceOnError && err1) goto endchange;
4245   // switch state
4246   fLocalDSState = aNewState;
4247
4248   if (aNewState == dssta_syncmodestable) {
4249     // There are multiple places where the sync mode is frozen.  Ensure
4250     // that this change is reported in all of them by putting the code
4251     // here.
4252     PDEBUGPRINTFX(DBG_HOT,(
4253                          "executing %s%s%s Sync%s",
4254                          fResuming ? "resumed " : "",
4255                          fSlowSync ? "slow" : "normal",
4256                          fFirstTimeSync ? " first time" : "",
4257                          fSyncMode == smo_twoway ? ", two-way" :
4258                          fSyncMode == smo_fromclient ? " from client" :
4259                          fSyncMode == smo_fromserver ? " from server" :
4260                          " in unknown direction?!"
4261                            ));
4262 #ifdef PROGRESS_EVENTS
4263     // progress event
4264     DB_PROGRESS_EVENT(this,
4265                       pev_alerted,
4266                       fSlowSync ? (fFirstTimeSync ? 2 : 1) : 0,
4267                       fResuming ? 1 : 0,
4268                       fSyncMode
4269                       );
4270 #endif // PROGRESS_EVENTS
4271   }
4272
4273   // now give logic opportunity to react again
4274   err2 = dsAfterStateChange(oldState,aNewState);
4275 endchange:
4276   PDEBUGENDBLOCK("DSStateChange");
4277   // return most recent error
4278   return err2 ? err2 : err1;
4279 } // TLocalEngineDS::changeState
4280
4281
4282
4283 // test datastore abort status
4284 // datastore is aborted when
4285 // - it was explicitly aborted (engAbortDataStoreSync() called, fAbortStatusCode set)
4286 // - session is suspending and the datastore has not yet completed sync up to sending
4287 //   maps (client) or admin already saved (server+client).
4288 //   If client has sent maps, all that MIGHT be missing would be map status, and
4289 //   if that hasn't arrived, the pendingMaps mechanism will make sure these get
4290 //   sent in the next session.
4291 bool TLocalEngineDS::isAborted(void)
4292 {
4293   return fAbortStatusCode!=0 || (fSessionP->isSuspending() && !testState(dssta_clientmapssent));
4294 } // TLocalEngineDS::isAborted
4295
4296
4297 // abort sync with this datastore
4298 void TLocalEngineDS::engAbortDataStoreSync(TSyError aStatusCode, bool aLocalProblem, bool aResumable)
4299 {
4300   if (fLocalDSState!=dssta_idle && !fAbortStatusCode) {
4301     // prepare status
4302     fAbortStatusCode = aStatusCode ? aStatusCode : 514; // make sure we have a non-zero fAbortStatusCode
4303     fLocalAbortCause = aLocalProblem;
4304     if (!aResumable) preventResuming(); // prevent resuming
4305     PDEBUGBLOCKFMT((
4306       "DSAbort","Aborting datastore sync","abortStatusCode=%hd|localProblem=%s|resumable=%s",
4307       aStatusCode,
4308       aLocalProblem ? "yes" : "no",
4309       aResumable ? "yes" : "no"
4310     ));
4311     // tell that to the session
4312     fSessionP->DatastoreFailed(aStatusCode,aLocalProblem);
4313     // as soon as sync set is ready, we have potentially started the sync and resume makes sense
4314     // NOTE: before we have made the sync set ready, WE MUST NOT resume, because making the sync
4315     //   set ready includes zapping it on slow refreshes, and this is only done when not resuming
4316     //   (so saving a suspend state before dssta_syncsetready would cause that the zapping is
4317     //   possibly skipped)
4318     if (!testState(dssta_syncsetready)) preventResuming(); // prevent resuming before sync set is ready
4319     // save resume (or non-resumable!) status only if this is NOT A TIMEOUT, because if it is a
4320     // (server) timeout, suspend state was saved at end of last request, and writing again here would destroy
4321     // the state.
4322     if (aStatusCode!=408) {
4323       engSaveSuspendState(true); // save even if already aborted
4324     }
4325     // let derivates know
4326     dsAbortDatastoreSync(aStatusCode, aLocalProblem);
4327     // show abort
4328     PDEBUGPRINTFX(DBG_ERROR,(
4329       "*************** Warning: Datastore flagged aborted (after %ld sec. request processing, %ld sec. total) with %s Status %hd",
4330       (long)((getSession()->getSystemNowAs(TCTX_UTC)-fSessionP->getLastRequestStarted()) / secondToLinearTimeFactor),
4331       (long)((getSession()->getSystemNowAs(TCTX_UTC)-fSessionP->getSessionStarted()) / secondToLinearTimeFactor),
4332       aLocalProblem ? "LOCAL" : "REMOTE",
4333       aStatusCode
4334     ));
4335     DB_PROGRESS_EVENT(
4336       this,
4337       pev_syncend,
4338       getAbortStatusCode(),
4339       fSlowSync ? (fFirstTimeSync ? 2 : 1) : 0,
4340       fResuming ? 1 : 0
4341     );
4342     PDEBUGENDBLOCK("DSAbort");
4343   }
4344 } // TLocalEngineDS::engAbortDataStoreSync
4345
4346
4347 // check if aborted, set status to abort reason code if yes
4348 bool TLocalEngineDS::CheckAborted(TStatusCommand &aStatusCommand)
4349 {
4350   if (fAbortStatusCode!=0) {
4351     aStatusCommand.setStatusCode(
4352       fSessionP->getSyncMLVersion()>=syncml_vers_1_1 ? 514 : // cancelled
4353         (fAbortStatusCode<LOCAL_STATUS_CODE ? fAbortStatusCode : 512) // sync failed
4354     );
4355     PDEBUGPRINTFX(DBG_DATA,("This datastore is in aborted state, rejects all commands with %hd",aStatusCommand.getStatusCode()));
4356     return true; // aborted, status set
4357   }
4358   return false; // not aborted
4359 } // TLocalEngineDS::CheckAborted
4360
4361
4362
4363 // Do common logfile substitutions
4364 void TLocalEngineDS::DoLogSubstitutions(string &aLog,bool aPlaintext)
4365 {
4366   #ifndef MINIMAL_CODE
4367   string s;
4368
4369   if (aPlaintext) {
4370     StringObjTimestamp(s,fEndOfSyncTime);
4371     // %T Time of sync (in derived datastores, this is the point of reference for newer/older comparisons) as plain text
4372     StringSubst(aLog,"%T",s,2);
4373     // %seT Time of session end (with this datastore) as plain text
4374     StringSubst(aLog,"%seT",s,4);
4375     // %ssT Time of session start as plain text
4376     StringObjTimestamp(s,fSessionP->getSessionStarted());
4377     StringSubst(aLog,"%ssT",s,4);
4378   }
4379   // %sdT sync duration (in seconds) for this datastore (start of session until datastore finished)
4380   StringSubst(aLog,"%sdT",((sInt32)(fEndOfSyncTime-fSessionP->getSessionStarted())/secondToLinearTimeFactor),4);
4381   // %nD  Datastore name
4382   StringSubst(aLog,"%nD",getName(),3);
4383   // %rD  Datastore remote path
4384   StringSubst(aLog,"%rD",fRemoteDBPath,3);
4385   // %lD  Datastore local path (complete with all CGI)
4386   StringSubst(aLog,"%lD",fRemoteViewOfLocalURI,3);
4387   // %iR  Remote Device ID (URI)
4388   StringSubst(aLog,"%iR",fSessionP->getRemoteURI(),3);
4389   // %nR  Remote name: [Manufacturer ]Model")
4390   StringSubst(aLog,"%nR",fSessionP->getRemoteDescName(),3);
4391   // %vR  Remote Device Version Info ("Type (HWV, FWV, SWV) Oem")
4392   StringSubst(aLog,"%vR",fSessionP->getRemoteInfoString(),3);
4393   // %U User Name
4394   StringSubst(aLog,"%U",fSessionP->getSyncUserName(),2);
4395   // %iS local Session ID
4396   StringSubst(aLog,"%iS",fSessionP->getLocalSessionID(),3);
4397   // %sS  Status code (0 if successful)
4398   StringSubst(aLog,"%sS",fAbortStatusCode,3);
4399   // %ssS Session Status code (0 if successful)
4400   StringSubst(aLog,"%ssS",fSessionP->getAbortReasonStatus(),4);
4401   // %syV SyncML version (as text) of session
4402   StringSubst(aLog,"%syV",SyncMLVerDTDNames[fSessionP->getSyncMLVersion()],4);
4403   // %syV SyncML version numeric (0=unknown, 1=1.0, 2=1.1, 3=1.2) of session
4404   StringSubst(aLog,"%syVn",(long)fSessionP->getSyncMLVersion(),5);
4405   // %mS  Syncmode (0=twoway, 1=fromclient 2=fromserver)
4406   StringSubst(aLog,"%mS",(sInt32)fSyncMode,3);
4407   // %tS  Synctype (0=normal,1=slow,2=firsttime slow, +10 if resumed session)
4408   StringSubst(aLog,"%tS",(fSlowSync ? (fFirstTimeSync ? 2 : 1) : 0) + (isResuming() ? 10 : 0),3);
4409   // %laI locally added Items
4410   StringSubst(aLog,"%laI",fLocalItemsAdded,4);
4411   // %raI remotely added Items
4412   StringSubst(aLog,"%raI",fRemoteItemsAdded,4);
4413   // %ldI locally deleted Items
4414   StringSubst(aLog,"%ldI",fLocalItemsDeleted,4);
4415   // %rdI remotely deleted Items
4416   StringSubst(aLog,"%rdI",fRemoteItemsDeleted,4);
4417   // %luI locally updated Items
4418   StringSubst(aLog,"%luI",fLocalItemsUpdated,4);
4419   // %ruI remotely updated Items
4420   StringSubst(aLog,"%ruI",fRemoteItemsUpdated,4);
4421   // %reI locally not accepted Items (sent error to remote, remote MAY resend them or abort the session)
4422   StringSubst(aLog,"%leI",fLocalItemsError,4);
4423   // %leI remotely not accepted Items (got error from remote, local will resend them later)
4424   StringSubst(aLog,"%reI",fRemoteItemsError,4);
4425   #ifdef SYSYNC_SERVER
4426   if (IS_SERVER) {
4427     // %smI Slowsync matched Items
4428     StringSubst(aLog,"%smI",fSlowSyncMatches,4);
4429     // %scI Server won Conflicts
4430     StringSubst(aLog,"%scI",fConflictsServerWins,4);
4431     // %ccI Client won Conflicts
4432     StringSubst(aLog,"%ccI",fConflictsClientWins,4);
4433     // %dcI Conflicts with duplications
4434     StringSubst(aLog,"%dcI",fConflictsDuplicated,4);
4435     // %tiB total incoming bytes
4436     StringSubst(aLog,"%tiB",fSessionP->getIncomingBytes(),4);
4437     // %toB total outgoing bytes
4438     StringSubst(aLog,"%toB",fSessionP->getOutgoingBytes(),4);
4439   }
4440   #endif
4441   // %niB net incoming data bytes for this datastore
4442   StringSubst(aLog,"%diB",fIncomingDataBytes,4);
4443   // %noB net incoming data bytes for this datastore
4444   StringSubst(aLog,"%doB",fOutgoingDataBytes,4);
4445   #endif
4446 } // TLocalEngineDS::DoLogSubstitutions
4447
4448
4449 // log datastore sync result
4450 // - Called at end of sync with this datastore
4451 void TLocalEngineDS::dsLogSyncResult(void)
4452 {
4453   #ifndef MINIMAL_CODE
4454   if (fSessionP->logEnabled()) {
4455     string logtext;
4456     logtext=fSessionP->getSessionConfig()->fLogFileFormat;
4457     if (!logtext.empty()) {
4458       // substitute
4459       DoLogSubstitutions(logtext,true); // plaintext
4460       // show
4461       fSessionP->WriteLogLine(logtext.c_str());
4462     }
4463   }
4464   #endif
4465 } // TLocalEngineDS::dsLogSyncResult
4466
4467
4468
4469 // Terminate all activity with this datastore
4470 // Note: may be called repeatedly, must only execute relevant shutdown code once
4471 void TLocalEngineDS::engTerminateDatastore(localstatus aAbortStatusCode)
4472 {
4473   // now abort (if not already aborted), then finish activities
4474   engFinishDataStoreSync(aAbortStatusCode);
4475   // and finally reset completely
4476   engResetDataStore();
4477 } // TLocalEngineDS::TerminateDatastore
4478
4479
4480 // called at very end of sync session, when everything is done
4481 // Note: is also called before deleting a datastore (so aborted sessions
4482 //   can do cleanup and/or statistics display as well)
4483 void TLocalEngineDS::engFinishDataStoreSync(localstatus aErrorStatus)
4484 {
4485   // set end of sync time
4486   fEndOfSyncTime = getSession()->getSystemNowAs(TCTX_UTC);
4487   // check if we have something to do at all
4488   if (fLocalDSState!=dssta_idle && fLocalDSState!=dssta_completed) {
4489     if (aErrorStatus==LOCERR_OK) {
4490       // Check if we need to abort now due to failed items only
4491       if (fRemoteItemsError>0) {
4492         // remote reported errors
4493         if (fSlowSync && fRemoteItemsAdded==0 && fRemoteItemsDeleted==0 && fRemoteItemsUpdated==0 && fSessionP->getSessionConfig()->fAbortOnAllItemsFailed) {
4494           PDEBUGPRINTFX(DBG_ERROR+DBG_DETAILS,("All remote item operations failed -> abort sync"));
4495           engAbortDataStoreSync(512,false,false); // remote problems (only failed items in a slow sync) caused sync to fail, not resumable
4496         }
4497         else
4498           fSessionP->DatastoreHadErrors(); // at least SOME items were successful, so it's not a completely unsuccessful sync
4499       }
4500     }
4501     // abort, if requested from caller or only-failed-items
4502     if (aErrorStatus!=LOCERR_OK)
4503       engAbortDataStoreSync(aErrorStatus,true); // if we have an error here, this is considered a local problem
4504     else {
4505       DB_PROGRESS_EVENT(
4506         this,
4507         pev_syncend,
4508         fAbortStatusCode,
4509         fSlowSync ? (fFirstTimeSync ? 2 : 1) : 0,
4510         fResuming ? 1 : 0
4511       );
4512     }
4513     #ifdef SUPERDATASTORES
4514     // if this is part of a superdatastore, include its statistics into mine, as
4515     // superdatastore can not save any statistics.
4516     // This ensures that the result sum over all subdatastores is correct,
4517     // however the assignment of error and byte counts is not (all non-related
4518     // counts go to first subdatastores with the following code)
4519     if (fAsSubDatastoreOf) {
4520       fOutgoingDataBytes += fAsSubDatastoreOf->fOutgoingDataBytes;
4521       fIncomingDataBytes += fAsSubDatastoreOf->fIncomingDataBytes;
4522       fRemoteItemsError += fAsSubDatastoreOf->fRemoteItemsError;
4523       fLocalItemsError += fAsSubDatastoreOf->fLocalItemsError;
4524       // consumed now, clear in superdatastore
4525       fAsSubDatastoreOf->fOutgoingDataBytes=0;
4526       fAsSubDatastoreOf->fIncomingDataBytes=0;
4527       fAsSubDatastoreOf->fRemoteItemsError=0;
4528       fAsSubDatastoreOf->fLocalItemsError=0;
4529     }
4530     #endif
4531     // make log entry
4532     dsLogSyncResult();
4533     // update my session state vars for successful sessions
4534     if (aErrorStatus==LOCERR_OK) {
4535       // update anchor
4536       fLastRemoteAnchor=fNextRemoteAnchor;
4537       fLastLocalAnchor=fNextLocalAnchor; // note: when using TStdLogicDS, this is not saved, but re-generated at next sync from timestamp
4538       // no resume
4539       fResumeAlertCode=0;
4540       // no resume item (just to make sure we don't get strange effects later)
4541       fLastItemStatus = 0;
4542       fLastSourceURI.erase();
4543       fLastTargetURI.erase();
4544       fPartialItemState = pi_state_none;
4545       fPIStoredSize = 0;
4546     }
4547     // now shift state to complete, let logic and implementation save the state
4548     changeState(dssta_completed,true);
4549     #ifdef SCRIPT_SUPPORT
4550     // - call DB finish script
4551     TScriptContext::execute(
4552       fDataStoreScriptContextP,
4553       fDSConfigP->fDBFinishScript,
4554       &DBFuncTable, // context's function table
4555       this // datastore pointer needed for context
4556     );
4557     #endif
4558   }
4559   // in any case: idle now again (note: could be shift from dssta_completed to dssta_idle)
4560   changeState(dssta_idle,true);
4561 } // TLocalEngineDS::engFinishDataStoreSync
4562
4563
4564 /// inform everyone of coming state change
4565 localstatus TLocalEngineDS::dsBeforeStateChange(TLocalEngineDSState aOldState,TLocalEngineDSState aNewState)
4566 {
4567   localstatus sta = LOCERR_OK;
4568   return sta;
4569 } // TLocalEngineDS::dsBeforeStateChange
4570
4571
4572 /// inform everyone of happened state change
4573 localstatus TLocalEngineDS::dsAfterStateChange(TLocalEngineDSState aOldState,TLocalEngineDSState aNewState)
4574 {
4575   localstatus sta = LOCERR_OK;
4576   if (aOldState>dssta_idle && aNewState==dssta_completed) {
4577     // we are going from a non-idle state to completed
4578     // - show statistics
4579     showStatistics();
4580   }
4581   return sta;
4582 } // TLocalEngineDS::dsAfterStateChange
4583
4584
4585 // show statistics or error of current sync
4586 void TLocalEngineDS::showStatistics(void)
4587 {
4588   // Console
4589   CONSOLEPRINTF((""));
4590   CONSOLEPRINTF(("- Sync Statistics for '%s' (%s), %s sync",
4591     getName(),
4592     fRemoteViewOfLocalURI.c_str(),
4593     fSlowSync ? "slow" : "normal"
4594   ));
4595   // now show results
4596   if (isAborted()) {
4597     // failed
4598     CONSOLEPRINTF(("  ************ Failed with status code=%hd",fAbortStatusCode));
4599   }
4600   else {
4601     // successful: show statistics on console
4602     CONSOLEPRINTF(("  =================================================="));
4603     if (IS_SERVER) {
4604       CONSOLEPRINTF(("                               on Server   on Client"));
4605     }
4606     else {
4607       CONSOLEPRINTF(("                               on Client   on Server"));
4608     }
4609     CONSOLEPRINTF(("  Added:                       %9ld   %9ld",(long)fLocalItemsAdded,(long)fRemoteItemsAdded));
4610     CONSOLEPRINTF(("  Deleted:                     %9ld   %9ld",(long)fLocalItemsDeleted,(long)fRemoteItemsDeleted));
4611     CONSOLEPRINTF(("  Updated:                     %9ld   %9ld",(long)fLocalItemsUpdated,(long)fRemoteItemsUpdated));
4612     CONSOLEPRINTF(("  Rejected with error:         %9ld   %9ld",(long)fLocalItemsError,(long)fRemoteItemsError));
4613     #ifdef SYSYNC_SERVER
4614     if (IS_SERVER) {
4615       CONSOLEPRINTF(("  SlowSync Matches:            %9ld",(long)fSlowSyncMatches));
4616       CONSOLEPRINTF(("  Server won Conflicts:        %9ld",(long)fConflictsServerWins));
4617       CONSOLEPRINTF(("  Client won Conflicts:        %9ld",(long)fConflictsClientWins));
4618       CONSOLEPRINTF(("  Conflicts with Duplication:  %9ld",(long)fConflictsDuplicated));
4619     }
4620     #endif
4621   }
4622   CONSOLEPRINTF((""));
4623   // Always provide statistics as events
4624   DB_PROGRESS_EVENT(this,pev_dsstats_l,fLocalItemsAdded,fLocalItemsUpdated,fLocalItemsDeleted);
4625   DB_PROGRESS_EVENT(this,pev_dsstats_r,fRemoteItemsAdded,fRemoteItemsUpdated,fRemoteItemsDeleted);
4626   DB_PROGRESS_EVENT(this,pev_dsstats_e,fLocalItemsError,fRemoteItemsError,0);
4627   #ifdef SYSYNC_SERVER
4628   if (IS_SERVER) {
4629     DB_PROGRESS_EVENT(this,pev_dsstats_s,fSlowSyncMatches,0,0);
4630     DB_PROGRESS_EVENT(this,pev_dsstats_c,fConflictsServerWins,fConflictsClientWins,fConflictsDuplicated);
4631   }
4632   #endif
4633   // NOTE: pev_dsstats_d should remain the last log data event sent (as it terminates collecting data in some GUIs)
4634   DB_PROGRESS_EVENT(this,pev_dsstats_d,fOutgoingDataBytes,fIncomingDataBytes,fRemoteItemsError);
4635   // Always show statistics in debug log
4636   #ifdef SYDEBUG
4637   PDEBUGPRINTFX(DBG_HOT,("Sync Statistics for '%s' (%s), %s sync",
4638     getName(),
4639     fRemoteViewOfLocalURI.c_str(),
4640     fSlowSync ? "slow" : "normal"
4641   ));
4642   if (PDEBUGTEST(DBG_HOT)) {
4643     string stats =
4644              "==================================================\n";
4645     if (IS_SERVER) {
4646       stats += "                             on Server   on Client\n";
4647     }
4648     else {
4649       stats += "                             on Client   on Server\n";
4650     }
4651     StringObjAppendPrintf(stats,"Added:                       %9ld   %9ld\n",(long)fLocalItemsAdded,(long)fRemoteItemsAdded);
4652     StringObjAppendPrintf(stats,"Deleted:                     %9ld   %9ld\n",(long)fLocalItemsDeleted,(long)fRemoteItemsDeleted);
4653     StringObjAppendPrintf(stats,"Updated:                     %9ld   %9ld\n",(long)fLocalItemsUpdated,(long)fRemoteItemsUpdated);
4654     StringObjAppendPrintf(stats,"Rejected with error:         %9ld   %9ld\n\n",(long)fLocalItemsError,(long)fRemoteItemsError);
4655     #ifdef SYSYNC_SERVER
4656     if (IS_SERVER) {
4657       StringObjAppendPrintf(stats,"SlowSync Matches:            %9ld\n",(long)fSlowSyncMatches);
4658       StringObjAppendPrintf(stats,"Server won Conflicts:        %9ld\n",(long)fConflictsServerWins);
4659       StringObjAppendPrintf(stats,"Client won Conflicts:        %9ld\n",(long)fConflictsClientWins);
4660       StringObjAppendPrintf(stats,"Conflicts with Duplication:  %9ld\n\n",(long)fConflictsDuplicated);
4661     }
4662     #endif
4663     StringObjAppendPrintf(stats,"Content Data Bytes sent:     %9ld\n",(long)fOutgoingDataBytes);
4664     StringObjAppendPrintf(stats,"Content Data Bytes received: %9ld\n\n",(long)fIncomingDataBytes);
4665     StringObjAppendPrintf(stats,"Duration of sync [seconds]:  %9ld\n",(long)((fEndOfSyncTime-fSessionP->getSessionStarted())/secondToLinearTimeFactor));
4666     PDEBUGPUTSXX(DBG_HOT,stats.c_str(),0,true);
4667   }
4668   if (isAborted()) {
4669     // failed
4670     PDEBUGPRINTFX(DBG_ERROR,("Warning: Failed with status code=%hd, statistics are incomplete!!",fAbortStatusCode));
4671   }
4672   #endif
4673 } // TLocalEngineDS::showStatistics
4674
4675
4676
4677 // create a new syncop command for sending to remote
4678 TSyncOpCommand *TLocalEngineDS::newSyncOpCommand(
4679   TSyncItem *aSyncItemP, // the sync item
4680   TSyncItemType *aSyncItemTypeP,  // the sync item type
4681   cAppCharP aLocalIDPrefix
4682 )
4683 {
4684   // get operation
4685   TSyncOperation syncop=aSyncItemP->getSyncOp();
4686   // obtain meta
4687   SmlPcdataPtr_t metaP = newMetaType(aSyncItemTypeP->getTypeName());
4688   // create command
4689   TSyncOpCommand *syncopcmdP = new TSyncOpCommand(fSessionP,this,syncop,metaP);
4690   // make sure item does not have stuff it is not allowed to have
4691   // %%% SCTS does not like SourceURI in Replace and Delete commands sent to Client
4692   // there are the only ones allowed to carry a GUID
4693   if (IS_SERVER) {
4694     #ifdef SYSYNC_SERVER
4695     // Server: commands only have remote IDs, except add which only has target ID
4696     if (syncop==sop_add || syncop==sop_wants_add)
4697       aSyncItemP->clearRemoteID(); // no remote ID
4698     else {
4699       if (!fDSConfigP->fAlwaysSendLocalID &&
4700           aSyncItemP->hasRemoteID()) {
4701         // only if localID may not be included in all syncops,
4702         // and not if the item has no remote ID yet
4703         //
4704         // The second case had to be added to solve an issue
4705         // during suspended syncs:
4706         // - server tries to add a new item and uses the Replace op for it
4707         // - pending Replace is added to map
4708         // - next sync resends the Replace, but with empty IDs and thus
4709         //   cannot be processed by client
4710         //
4711         // Log from such a failed sync:
4712         // Item localID='328' already has map entry: remoteid='', mapflags=0x1, changed=0, deleted=0, added=0, markforresume=0, savedmark=1
4713         // Resuming and found marked-for-resume -> send replace
4714         // ...
4715         // Command 'Replace': is 1-th counted cmd, cmdsize(+tags needed to end msg)=371, available=130664 (maxfree=299132, freeaftersend=298761, notUsableBufferBytes()=168468)
4716         // Item remoteID='', localID='', datasize=334
4717         // Replace: issued as (outgoing MsgID=2, CmdID=4), now queueing for status
4718         // ...
4719         // Status 404: Replace target not found on client -> silently ignore but remove map in server (item will be added in next session),
4720         aSyncItemP->clearLocalID(); // no local ID
4721       }
4722     }
4723     #endif
4724   }
4725   else {
4726     // Client: all commands only have local IDs
4727     aSyncItemP->clearRemoteID(); // no remote ID
4728   }
4729   // add the localID prefix if we do have a localID to send
4730   if (aSyncItemP->hasLocalID()) {
4731     if (IS_SERVER) {
4732       #ifdef SYSYNC_SERVER
4733       // make sure GUID (plus prefixes) is not exceeding allowed size
4734       adjustLocalIDforSize(aSyncItemP->fLocalID,getRemoteDatastore()->getMaxGUIDSize(),aLocalIDPrefix ? strlen(aLocalIDPrefix) : 0);
4735       #endif
4736     }
4737     // add local ID prefix, if any
4738     if (aLocalIDPrefix && *aLocalIDPrefix)
4739       aSyncItemP->fLocalID.insert(0,aLocalIDPrefix);
4740   }
4741   #ifdef SYSYNC_TARGET_OPTIONS
4742   // init item generation variables
4743   fItemSizeLimit=fSizeLimit;
4744   #else
4745   fItemSizeLimit=-1; // no limit
4746   #endif
4747   // now add item
4748   SmlItemPtr_t itemP = aSyncItemTypeP->newSmlItem(aSyncItemP,this);
4749   // check if data size is ok
4750   if (itemP && fSessionP->fMaxOutgoingObjSize) {
4751     if (itemP->data && itemP->data->content && itemP->data->length) {
4752       // there is data, check if size is ok
4753       if (itemP->data->length > fSessionP->fMaxOutgoingObjSize) {
4754         // too large, suppress it
4755         PDEBUGPRINTFX(DBG_ERROR,(
4756           "WARNING: outgoing item is larger (%ld) than MaxObjSize (%ld) of remote -> suppress now/mark for resend",
4757           (long)itemP->data->length,
4758           (long)fSessionP->fMaxOutgoingObjSize
4759         ));
4760         smlFreeItemPtr(itemP);
4761         itemP=NULL;
4762         // mark item for resend
4763         // For datastores without resume support, this will just have no effect at all
4764         engMarkItemForResend(aSyncItemP->getLocalID(),aSyncItemP->getRemoteID());
4765       }
4766     }
4767   }
4768   if (itemP) {
4769     // add it to the command
4770     syncopcmdP->addItem(itemP);
4771   }
4772   else {
4773     // no item - command is invalid, delete it
4774     delete syncopcmdP;
4775     syncopcmdP=NULL;
4776   }
4777   // return command
4778   return syncopcmdP;
4779 } // TLocalEngineDS::newSyncOpCommand
4780
4781
4782 // create SyncItem suitable for being sent from local to remote
4783 TSyncItem *TLocalEngineDS::newItemForRemote(
4784   uInt16 aExpectedTypeID    // typeid of expected type
4785 )
4786 {
4787   // safety
4788   if (!canCreateItemForRemote())
4789     SYSYNC_THROW(TSyncException("newItemForRemote called without sufficient type information ready"));
4790   // create
4791   TSyncItem *itemP = fLocalSendToRemoteTypeP->newSyncItem(fRemoteReceiveFromLocalTypeP,this);
4792   if (!itemP)
4793     SYSYNC_THROW(TSyncException("newItemForRemote could not create item"));
4794   // check type
4795   if (!itemP->isBasedOn(aExpectedTypeID)) {
4796     PDEBUGPRINTFX(DBG_ERROR,(
4797       "newItemForRemote created item of typeID %hd, caller expects %hd",
4798       itemP->getTypeID(),
4799       aExpectedTypeID
4800     ));
4801     SYSYNC_THROW(TSyncException("newItemForRemote created wrong item type"));
4802   }
4803   return itemP;
4804 } // TLocalEngineDS::newItemForRemote
4805
4806
4807 // return pure relative (item) URI (removes absolute part or ./ prefix)
4808 const char *TLocalEngineDS::DatastoreRelativeURI(const char *aURI)
4809 {
4810   return relativeURI(relativeURI(aURI,fSessionP->getLocalURI()),getName());
4811 } // TLocalEngineDS::DatastoreRelativeURI
4812
4813
4814
4815 // - init filtering and check if needed (sets fTypeFilteringNeeded, fFilteringNeeded and fFilteringNeededForAll)
4816 void TLocalEngineDS::initPostFetchFiltering(void)
4817 {
4818   #ifdef OBJECT_FILTERING
4819   if (!fLocalSendToRemoteTypeP) {
4820     fTypeFilteringNeeded=false;
4821     fFilteringNeeded=false;
4822     fFilteringNeededForAll=false;
4823   }
4824   else {
4825     // get basic settings from type
4826     fLocalSendToRemoteTypeP->initPostFetchFiltering(fTypeFilteringNeeded,fFilteringNeededForAll,this);
4827     fFilteringNeeded=fTypeFilteringNeeded;
4828     // NOTE: if type filtering is needed, it's the responsibility of initPostFetchFiltering() of
4829     //       the type to check (using the DBHANDLESOPTS() script func) if DB does already handle
4830     //       the range filters and such and possibly avoid type filtering then.
4831     // then check for standard filter requirements
4832     #ifdef SYDEBUG
4833     #ifdef SYNCML_TAF_SUPPORT
4834     if (!fTargetAddressFilter.empty()) PDEBUGPRINTFX(DBG_DATA,("using (dynamic, temporary) TAF expression from CGI : %s",fTargetAddressFilter.c_str()));
4835     if (!fIntTargetAddressFilter.empty()) PDEBUGPRINTFX(DBG_DATA,("using (dynamic, temporary) internally set TAF expression : %s",fIntTargetAddressFilter.c_str()));
4836     #endif // SYNCML_TAF_SUPPORT
4837     if (!fSyncSetFilter.empty()) PDEBUGPRINTFX(DBG_DATA,("using (dynamic) sync set filter expression : %s",fSyncSetFilter.c_str()));
4838     if (!fLocalDBFilter.empty()) PDEBUGPRINTFX(DBG_DATA,("using (static) local db filter expression : %s",fLocalDBFilter.c_str()));
4839     #endif // SYDEBUG
4840     // - if DB does the standard filters, we don't need to check them here again
4841     if (!engFilteredFetchesFromDB(true)) {
4842       // If DB does NOT do the standard filters, we have to do them here
4843       // - this is the case if we have an (old-style) sync set filter, but not filtered by DB
4844       //   we need to filter all because sync set filter can be dynamic
4845       if (!fSyncSetFilter.empty())
4846         fFilteringNeededForAll=true;
4847       // always return true if there is something to filter at all
4848       if (
4849         !fLocalDBFilter.empty() ||
4850         !fDSConfigP->fInvisibleFilter.empty() ||
4851         !fSyncSetFilter.empty() ||
4852         !fDSConfigP->fRemoteAcceptFilter.empty()
4853       )
4854         fFilteringNeeded=true;
4855     }
4856   }
4857   PDEBUGPRINTFX(DBG_FILTER+DBG_HOT,(
4858     "Datastore-level postfetch filtering %sneeded%s",
4859     fFilteringNeeded ? "" : "NOT ",
4860     fFilteringNeeded ? (fFilteringNeededForAll ? " and to be applied to all records" : " only for changed records") : ""
4861   ));
4862   #endif
4863 } // TLocalEngineDS::initPostFetchFiltering
4864
4865
4866 // filter fetched record
4867 bool TLocalEngineDS::postFetchFiltering(TSyncItem *aSyncItemP)
4868 {
4869   #ifndef OBJECT_FILTERING
4870   return true; // no filters, always pass
4871   #else
4872   if (!aSyncItemP) return false; // null item does not pass
4873   // first do standard filters
4874   // - if DB has filtered the
4875   bool passes=true;
4876   if (fFilteringNeeded) {
4877     // - first make sure outgoing object has all properties set
4878     //   such that it would pass the acceptance filter (for example KIND for calendar...)
4879     if (!aSyncItemP->makePassFilter(fDSConfigP->fRemoteAcceptFilter.c_str())) {
4880       // we could not make item pass acceptance filters
4881       PDEBUGPRINTFX(DBG_ERROR,("- item localid='%s' cannot be made passing <acceptfilter> -> ignored",aSyncItemP->getLocalID()));
4882       passes=false;
4883     }
4884     // now check for field-level filters
4885     if (passes && !engFilteredFetchesFromDB()) {
4886       // DB has not already filtered these, so we need to do it here
4887       // - "moving target" first
4888       passes=fSyncSetFilter.empty() || aSyncItemP->testFilter(fSyncSetFilter.c_str());
4889       if (passes) {
4890         // - static filters
4891         passes =
4892           aSyncItemP->testFilter(fLocalDBFilter.c_str()) && // local filter
4893           (
4894             fDSConfigP->fInvisibleFilter.empty() || // and either no invisibility defined...
4895             !aSyncItemP->testFilter(fDSConfigP->fInvisibleFilter.c_str()) // ...or NOT passed
4896           );
4897       }
4898     }
4899     if (passes && fTypeFilteringNeeded) {
4900       // finally, apply type's filter
4901       passes=aSyncItemP->postFetchFiltering(this);
4902     }
4903   }
4904   else {
4905     // no filtering needed, DB has already filtered out those that would not pass
4906     // BUT: make sure outgoing items WILL pass the acceptance filter. If this
4907     //      cannot be done, item will be filtered out.
4908     if (!aSyncItemP->makePassFilter(fDSConfigP->fRemoteAcceptFilter.c_str())) {
4909       // we could not make item pass acceptance filters
4910       PDEBUGPRINTFX(DBG_ERROR,("- item localid='%s' cannot be made passing <acceptfilter> -> ignored",aSyncItemP->getLocalID()));
4911       passes=false;
4912     }
4913   }
4914   #ifdef SYDEBUG
4915   if (!passes) {
4916     PDEBUGPRINTFX(DBG_DATA,("- item localid='%s' does not pass filters -> ignored",aSyncItemP->getLocalID()));
4917   }
4918   #endif
4919   // return result
4920   return passes;
4921   #endif
4922 } // TLocalEngineDS::postFetchFiltering
4923
4924
4925 #ifdef OBJECT_FILTERING
4926
4927 // - called to check if incoming item passes acception filters
4928 bool TLocalEngineDS::isAcceptable(TSyncItem *aSyncItemP, TStatusCommand &aStatusCommand)
4929 {
4930   // test acceptance
4931   if (aSyncItemP->testFilter(fDSConfigP->fRemoteAcceptFilter.c_str())) return true; // ok
4932   // not accepted, set 415 error
4933   if (!fDSConfigP->fSilentlyDiscardUnaccepted)
4934     aStatusCommand.setStatusCode(415);
4935   ADDDEBUGITEM(aStatusCommand,"Received item does not pass acceptance filter");
4936   PDEBUGPRINTFX(DBG_ERROR,(
4937     "Received item does not pass acceptance filter: %s",
4938     fDSConfigP->fRemoteAcceptFilter.c_str()
4939   ));
4940   return false;
4941 } // TLocalEngineDS::isAcceptable
4942
4943
4944 /// @brief called to make incoming item visible
4945 /// @return true if now visible
4946 bool TLocalEngineDS::makeVisible(TSyncItem *aSyncItemP)
4947 {
4948   bool invisible=false;
4949   if (!fDSConfigP->fInvisibleFilter.empty()) {
4950     invisible=aSyncItemP->testFilter(fDSConfigP->fInvisibleFilter.c_str());
4951   }
4952   if (invisible) {
4953     return aSyncItemP->makePassFilter(fDSConfigP->fMakeVisibleFilter.c_str());
4954   }
4955   return true; // is already visible
4956 } // TLocalEngineDS::makeVisible
4957
4958
4959 /// @brief called to make incoming item INvisible
4960 /// @return true if now INvisible
4961 bool TLocalEngineDS::makeInvisible(TSyncItem *aSyncItemP)
4962 {
4963   // return true if could make invisible or already was invisible
4964   if (fDSConfigP->fInvisibleFilter.empty())
4965     return false; // no invisible filter, cannot make invisible
4966   // make pass invisible filter - if successful, we're now invisible
4967   return aSyncItemP->makePassFilter(fDSConfigP->fInvisibleFilter.c_str()); // try to make invisible (and return result)
4968 } // TLocalEngineDS::makeInvisible
4969
4970
4971
4972 // - called to make incoming item pass sync set filtering
4973 bool TLocalEngineDS::makePassSyncSetFilter(TSyncItem *aSyncItemP)
4974 {
4975   bool pass=true;
4976
4977   // make sure we pass sync set filtering and stay visible
4978   if (!fSyncSetFilter.empty()) {
4979     // try to make pass sync set filter (modifies item only if it would not pass otherwise)
4980     pass=aSyncItemP->makePassFilter(fSyncSetFilter.c_str());
4981   }
4982   if (!pass || fSyncSetFilter.empty()) {
4983     // specified sync set filter cannot make item pass, or no sync set filter at all:
4984     // - apply makePassFilter default expression
4985     if (!fDSConfigP->fMakePassFilter.empty()) {
4986       pass=aSyncItemP->makePassFilter(fDSConfigP->fMakePassFilter.c_str());
4987       if (pass) {
4988         // check again to check if item would pass the syncset filter now
4989         pass=aSyncItemP->testFilter(fSyncSetFilter.c_str());
4990       }
4991     }
4992   }
4993   return pass;
4994 } // TLocalEngineDS::makePassSyncSetFilter
4995
4996 #endif
4997
4998
4999 // process remote item
5000 bool TLocalEngineDS::engProcessRemoteItem(
5001   TSyncItem *syncitemP,
5002   TStatusCommand &aStatusCommand
5003 )
5004 {
5005   #ifdef SYSYNC_CLIENT
5006   if (IS_CLIENT)
5007     return engProcessRemoteItemAsClient(syncitemP,aStatusCommand); // status, must be set to correct status code (ok / error)
5008   #endif
5009   #ifdef SYSYNC_SERVER
5010   if (IS_SERVER)
5011     return engProcessRemoteItemAsServer(syncitemP,aStatusCommand); // status, must be set to correct status code (ok / error)
5012   #endif
5013   // neither
5014   return false;
5015 } // TLocalEngineDS::engProcessRemoteItem
5016
5017
5018 // process SyncML SyncOp command for this datastore
5019 bool TLocalEngineDS::engProcessSyncOpItem(
5020   TSyncOperation aSyncOp,        // the operation
5021   SmlItemPtr_t aItemP,           // the item to be processed
5022   SmlMetInfMetInfPtr_t aMetaP,   // command-wide meta, if any
5023   TStatusCommand &aStatusCommand // pre-set 200 status, can be modified in case of errors
5024 )
5025 {
5026   bool regular = false;
5027   // determine SyncItemType that can handle this item data
5028   if (fRemoteDatastoreP==NULL) {
5029     PDEBUGPRINTFX(DBG_ERROR,("engProcessSyncOpItem: Remote Datastore not known"));
5030     aStatusCommand.setStatusCode(500);
5031   }
5032   // - start with default
5033   TSyncItemType *remoteTypeP=getRemoteSendType();
5034   TSyncItemType *localTypeP=getLocalReceiveType();
5035   // - see if command-wide meta plus item contents specify another type
5036   //   (item meta, if present, overrides command wide meta)
5037   // see if item itself or command meta specify a type name or format
5038   SmlMetInfMetInfPtr_t itemmetaP = smlPCDataToMetInfP(aItemP->meta);
5039   // - format
5040   TFmtTypes fmt=fmt_chr;
5041   if (itemmetaP && itemmetaP->format)
5042     smlPCDataToFormat(itemmetaP->format,fmt); // use type name from item's meta
5043   else if (aMetaP && aMetaP->format)
5044     smlPCDataToFormat(aMetaP->format,fmt); // use type name from command-wide meta
5045   // - type
5046   string versstr;
5047   const char *typestr = NULL;
5048   if (itemmetaP && itemmetaP->type)
5049     typestr = smlPCDataToCharP(itemmetaP->type); // use type name from item's meta
5050   else if (aMetaP && aMetaP->type)
5051     typestr = smlPCDataToCharP(aMetaP->type); // use type name from command-wide meta
5052   // check if there is a type specified
5053   if (typestr) {
5054     PDEBUGPRINTFX(DBG_DATA,("Explicit type '%s' specified in command or item meta",typestr));
5055     if (strcmp(remoteTypeP->getTypeName(),typestr)!=0) {
5056       // specified type is NOT default type: search appropriate remote type
5057       remoteTypeP=fRemoteDatastoreP->getSendType(typestr,NULL); // no version known so far
5058       if (!remoteTypeP) {
5059         // specified type is not a remote type listed in remote's devInf.
5060         // But as remote is actually using it, we can assume it does support it, so use local type of same name instead
5061         PDEBUGPRINTFX(DBG_ERROR,("According to remote devInf, '%s' is not supported, but obviously it is used here so we try to handle it",typestr));
5062         // look it up in local datastore's list
5063         remoteTypeP=getReceiveType(typestr,NULL);
5064       }
5065     }
5066     if (remoteTypeP) {
5067       #ifdef APP_CAN_EXPIRE
5068       // get modified date of item
5069       lineardate_t moddat=0; // IMPORTANT, must be initialized in case expiryFromData returns nothing!
5070       bool ok = remoteTypeP->expiryFromData(aItemP,moddat)<=MAX_EXPIRY_DIFF+5;
5071       // ok==true: we are within hard expiry
5072       // ok==false: we are out of hard expiry
5073       #ifdef SYSER_REGISTRATION
5074       if (getSession()->getSyncAppBase()->fRegOK) {
5075         // we have a license (permanent or timed) --> hard expiry is irrelevant
5076         // (so override ok according to validity of current license)
5077         ok=true; // assume ok
5078         // check if license is timed, and if so, check if mod date is within timed range
5079         // (if not, set ok to false)
5080         uInt8 rd = getSession()->getSyncAppBase()->fRegDuration;
5081         if (rd) {
5082           lineardate_t ending = date2lineardate(rd/12+2000,rd%12+1,1);
5083           ok = ending>=moddat; // ok if not modified after end of license period
5084         }
5085       }
5086       #endif
5087       // when we have no license (neither permanent nor timed), hard expiry decides as is
5088       // (so just use ok as is)
5089       if (!ok) {
5090         aStatusCommand.setStatusCode(403); // forbidden to hack this expiry stuff!
5091         fSessionP->AbortSession(403,true); // local problem
5092         return false;
5093       }
5094       #endif // APP_CAN_EXPIRE
5095       // we have a type, which should be able to determine version from data
5096       if (remoteTypeP->versionFromData(aItemP,versstr)) {
5097         // version found, Make sure version matches as well
5098         PDEBUGPRINTFX(DBG_DATA,("Version '%s' obtained from item data",versstr.c_str()));
5099         // check if current remotetype already has correct version (and type, but we know this already)
5100         if (!remoteTypeP->supportsType(remoteTypeP->getTypeName(),versstr.c_str(),true)) {
5101           // no, type/vers do not match, search again
5102           remoteTypeP=fRemoteDatastoreP->getSendType(typestr,versstr.c_str());
5103           if (!remoteTypeP) {
5104             // specified type is not a remote type listed in remote's devInf.
5105             // But as remote is actually using it, we can assume it does support it, so use local type of same name instead
5106             PDEBUGPRINTFX(DBG_ERROR,("According to remote devInf, '%s' version '%s' is not supported, but obviously it is used here so we try to handle it",typestr,versstr.c_str()));
5107             // look it up in local datastore's list
5108             remoteTypeP=getReceiveType(typestr,versstr.c_str());
5109           }
5110         }
5111       }
5112       else {
5113         PDEBUGPRINTFX(DBG_HOT,("Version could not be obtained from item data"));
5114       }
5115     }
5116     if (!remoteTypeP) {
5117       // no matching remote type: fail
5118       aStatusCommand.setStatusCode(415);
5119       ADDDEBUGITEM(aStatusCommand,"Incompatible content type specified in command or item meta");
5120       PDEBUGPRINTFX(DBG_ERROR,(
5121         "Incompatible content type '%s' version '%s' specified in command or item meta",
5122         typestr,
5123         versstr.empty() ? "[none]" : versstr.c_str()
5124       ));
5125       return false; // irregular
5126     }
5127     else {
5128       // we have the remote type, now determine matching local type
5129       // - first check if this is compatible with the existing localTypeP (which
5130       //   was possibly selected by remote rule match
5131       if (!localTypeP->supportsType(remoteTypeP->getTypeName(),remoteTypeP->getTypeVers(),false)) {
5132         // current default local type does not support specified remote type
5133         // - find a matching local type
5134         localTypeP=getReceiveType(remoteTypeP);
5135         #ifdef SYDEBUG
5136         if (localTypeP) {
5137           PDEBUGPRINTFX(DBG_DATA+DBG_HOT,(
5138             "Explicit type '%s' does not match default type -> switching to local type '%s' for processing item",
5139             typestr,
5140             localTypeP->getTypeConfig()->getName()
5141           ));
5142         }
5143         #endif
5144       }
5145
5146     }
5147   }
5148   // now process
5149   if (localTypeP && remoteTypeP) {
5150     TSyncItem *syncitemP = NULL;
5151     // create the item (might have empty data in case of delete)
5152     syncitemP=remoteTypeP->newSyncItem(aItemP,aSyncOp,fmt,localTypeP,this,aStatusCommand);
5153     if (!syncitemP) {
5154       // failed to create item
5155       return false; // irregular
5156     }
5157     // Now start the real processing
5158     PDEBUGBLOCKFMT(("Process_Item","processing remote item",
5159       "SyncOp=%s|LocalID=%s|RemoteID=%s",
5160       SyncOpNames[syncitemP->getSyncOp()],
5161       syncitemP->getLocalID(),
5162       syncitemP->getRemoteID()
5163     ));
5164     #ifdef SCRIPT_SUPPORT
5165     TErrorFuncContext errctx;
5166     errctx.syncop = syncitemP->getSyncOp();
5167     #endif
5168     SYSYNC_TRY {
5169       // this call frees the item
5170       regular =
5171         engProcessRemoteItem(syncitemP,aStatusCommand);
5172       syncitemP = NULL;
5173       PDEBUGENDBLOCK("Process_Item");
5174     }
5175     SYSYNC_CATCH (...)
5176       // Hmm, was the item freed? Not sure, so assume that it was freed.
5177       PDEBUGENDBLOCK("Process_Item");
5178       SYSYNC_RETHROW;
5179     SYSYNC_ENDCATCH
5180     // Check for datastore level scripts that might change the status code and/or regular status
5181     #ifdef SCRIPT_SUPPORT
5182     errctx.statuscode = aStatusCommand.getStatusCode();
5183     errctx.newstatuscode = errctx.statuscode;
5184     errctx.datastoreP = this;
5185     // call script
5186     regular =
5187       TScriptContext::executeTest(
5188         regular, // pass through regular status
5189         fDataStoreScriptContextP,
5190         fDSConfigP->fReceivedItemStatusScript,
5191         &ErrorFuncTable,
5192         &errctx // caller context
5193       );
5194     // use possibly modified status code
5195     #ifdef SYDEBUG
5196     if (aStatusCommand.getStatusCode() != errctx.newstatuscode) {
5197       PDEBUGPRINTFX(DBG_ERROR,("Status: Datastore script changed original status=%hd to %hd (original op was %s)",aStatusCommand.getStatusCode(),errctx.newstatuscode,SyncOpNames[errctx.syncop]));
5198     }
5199     #endif
5200     aStatusCommand.setStatusCode(errctx.newstatuscode);
5201     #endif
5202     if (regular) {
5203       // item 100% successfully processed
5204       // - set new defaults to same type as current item
5205       setReceiveTypeInfo(localTypeP,remoteTypeP);
5206     }
5207   }
5208   else {
5209     // missing remote or local type: fail
5210     aStatusCommand.setStatusCode(415);
5211     ADDDEBUGITEM(aStatusCommand,"Unknown content type");
5212     PDEBUGPRINTFX(DBG_ERROR,(
5213       "Missing remote or local SyncItemType"
5214     ));
5215     regular=false; // irregular
5216   }
5217   return regular;
5218 } // TLocalEngineDS::engProcessSyncOpItem
5219
5220
5221 #ifdef SYSYNC_SERVER
5222
5223
5224 // Server Case
5225 // ===========
5226
5227 // helper to cause database version of an item (as identified by aSyncItemP's ID) to be sent to client
5228 // (aka "force a conflict")
5229 TSyncItem *TLocalEngineDS::SendDBVersionOfItemAsServer(TSyncItem *aSyncItemP)
5230 {
5231   TStatusCommand dummy(fSessionP);
5232   // - create new item
5233   TSyncItem *conflictingItemP =
5234     newItemForRemote(aSyncItemP->getTypeID());
5235   if (!conflictingItemP) return NULL;
5236   // - set IDs
5237   conflictingItemP->setLocalID(aSyncItemP->getLocalID());
5238   conflictingItemP->setRemoteID(aSyncItemP->getRemoteID());
5239   // - this is always a replace conflict (item exists in DB)
5240   conflictingItemP->setSyncOp(sop_wants_replace);
5241   // - try to get from DB
5242   bool ok=logicRetrieveItemByID(*conflictingItemP,dummy);
5243   if (ok && dummy.getStatusCode()!=404) {
5244     // item found in DB, add it to the sync set so it can be sent to remote
5245     // if not cancelled by dontSendItemAsServer()
5246     SendItemAsServer(conflictingItemP);
5247     PDEBUGPRINTFX(DBG_DATA,("Forced conflict with corresponding item from server DB"));
5248   }
5249   else {
5250     // no item found, we cannot force a conflict
5251     delete conflictingItemP;
5252     conflictingItemP=NULL;
5253   }
5254   return conflictingItemP;
5255 } // TLocalEngineDS::SendDBVersionOfItemAsServer
5256
5257
5258
5259 // process map
5260 localstatus TLocalEngineDS::engProcessMap(cAppCharP aRemoteID, cAppCharP aLocalID)
5261 {
5262   if (!testState(dssta_syncmodestable)) {
5263     // Map received when not appropriate
5264     PDEBUGPRINTFX(DBG_ERROR,("Map not allowed in this stage of sync"));
5265     return 403;
5266   }
5267   // pre-process localID
5268   string realLocalID;
5269   if (aLocalID && *aLocalID) {
5270     // Note: Map must be ready to have either empty local or remote ID to delete an entry
5271     // perform reverse lookup of received GUID to real GUID
5272     realLocalID = aLocalID;
5273     obtainRealLocalID(realLocalID);
5274     aLocalID=realLocalID.c_str();
5275   }
5276   else {
5277     aLocalID=NULL;
5278   }
5279   // pre-process remoteID
5280   if (!aRemoteID || *aRemoteID==0)
5281     aRemoteID=NULL;
5282   // let implementation process the map command
5283   return logicProcessMap(aRemoteID, aLocalID);
5284 } // TLocalEngineDS::engProcessMap
5285
5286
5287
5288 // process sync operation from client with specified sync item
5289 // (according to current sync mode of local datastore)
5290 // - returns true (and unmodified or non-200-successful status) if
5291 //   operation could be processed regularily
5292 // - returns false (but probably still successful status) if
5293 //   operation was processed with internal irregularities, such as
5294 //   trying to delete non-existant item in datastore with
5295 //   incomplete Rollbacks (which returns status 200 in this case!).
5296 bool TLocalEngineDS::engProcessRemoteItemAsServer(
5297   TSyncItem *aSyncItemP,
5298   TStatusCommand &aStatusCommand // status, must be set to correct status code (ok / error)
5299 )
5300 {
5301   TSyncItem *conflictingItemP=NULL;
5302   TSyncItem *echoItemP=NULL;
5303   TSyncItem *delitemP=NULL;
5304   bool changedincoming=false;
5305   bool changedexisting=false;
5306   bool remainsvisible=true; // usually, we want the item to remain visible in the sync set
5307   TStatusCommand dummy(fSessionP);
5308
5309   // get some info out of item (we might need it after item is already consumed)
5310   TSyncOperation syncop=aSyncItemP->getSyncOp();
5311   uInt16 itemtypeid=aSyncItemP->getTypeID();
5312   string remoteid=aSyncItemP->getRemoteID();
5313   // check if datastore is aborted
5314   if(CheckAborted(aStatusCommand))
5315     return false;
5316   // send event (but no abort checking)
5317   DB_PROGRESS_EVENT(this,pev_itemreceived,++fItemsReceived,fRemoteNumberOfChanges,0);
5318   fPreventAdd = false;
5319   fIgnoreUpdate = false;
5320   // show
5321   PDEBUGPRINTFX(DBG_DATA,("%s item operation received",SyncOpNames[syncop]));
5322   // check if receiving commands is allowed at all
5323   if (fSyncMode==smo_fromserver) {
5324     // Modifications from client not allowed during update from server only
5325     aStatusCommand.setStatusCode(403);
5326     ADDDEBUGITEM(aStatusCommand,"Client command not allowed in one-way/refresh from server");
5327     PDEBUGPRINTFX(DBG_ERROR,("Client command not allowed in one-way/refresh from server"));
5328     delete aSyncItemP;
5329     return false;
5330   }
5331   // let item check itself to catch special cases
5332   // - init variables which are used/modified by item checking
5333   #ifdef SYSYNC_TARGET_OPTIONS
5334   // init item generation variables
5335   fItemSizeLimit=fSizeLimit;
5336   #else
5337   fItemSizeLimit=-1; // no limit
5338   #endif
5339   fCurrentSyncOp = syncop;
5340   fEchoItemOp = sop_none;
5341   fItemConflictStrategy=fSessionConflictStrategy; // get default strategy for this item
5342   fForceConflict = false;
5343   fDeleteWins = fDSConfigP->fDeleteWins; // default to configured value
5344   fRejectStatus = -1; // no rejection
5345   // - now check
5346   //   check reads and possibly modifies:
5347   //   - fEchoItemOp : if not sop_none, the incoming item is echoed back to the remote with the specified syncop
5348   //   - fItemConflictStrategy : might be changed from the pre-set datastore default
5349   //   - fForceConflict : if set, a conflict is forced by adding the corresponding local item (if any) to the list of items to be sent
5350   //   - fDeleteWins : if set, in a replace/delete conflict delete will win (regardless of strategy)
5351   //   - fPreventAdd : if set, attempt to add item from remote (even implicitly trough replace) will cause no add but delete of remote item
5352   //   - fIgnoreUpdate : if set, attempt to update item from remote will be ignored, only adds (also implicit ones) are executed
5353   //   - fRejectStatus : if set>=0, incoming item is irgnored silently(==0) or with error(!=0)
5354   aSyncItemP->checkItem(this);
5355   // - create echo item if we need one
5356   if (fEchoItemOp!=sop_none) {
5357     // Note: sop_add makes no sense at all.
5358     // Note: If echo is enabled, conflicts are not checked, as echo makes only sense in
5359     //       cases where we know that a conflict cannot occur or is irrelevant
5360     // - artifically create a "conflicting" item, that is, one to be sent back to remote
5361     echoItemP=newItemForRemote(aSyncItemP->getTypeID());
5362     // - assign data from incoming item if echo is not a delete
5363     if (fEchoItemOp!=sop_delete && fEchoItemOp!=sop_archive_delete && fEchoItemOp!=sop_soft_delete)
5364       echoItemP->replaceDataFrom(*aSyncItemP);
5365     // - set remote ID (note again: sop_add makes no sense here)
5366     echoItemP->setRemoteID(aSyncItemP->getRemoteID());
5367     // - set sop
5368     echoItemP->setSyncOp(fEchoItemOp);
5369     // - now check for possible conflict
5370     if (!fSlowSync) {
5371       conflictingItemP = getConflictingItemByRemoteID(aSyncItemP);
5372       // remove item if there is one that would conflict with the echo
5373       if (conflictingItemP) dontSendItemAsServer(conflictingItemP);
5374       conflictingItemP = NULL;
5375     }
5376     // - add echo to the list of items to be sent (DB takes ownership)
5377     SendItemAsServer(echoItemP);
5378     PDEBUGPRINTFX(DBG_DATA,("Echoed item back to remote with sop=%s",SyncOpNames[fEchoItemOp]));
5379     // process item normally (except that we don't check for LUID conflicts)
5380   }
5381   // - check if incoming item should be processed at all
5382   if (fRejectStatus>=0) {
5383     // Note: a forced conflict can still occur even if item is rejected
5384     // (this has the effect of unconditionally letting the server item win)
5385     if (fForceConflict && syncop!=sop_add) {
5386       conflictingItemP = SendDBVersionOfItemAsServer(aSyncItemP);
5387       // Note: conflictingitem is always a replace
5388       if (conflictingItemP) {
5389         if (syncop==sop_delete) {
5390           // original was delete, forced conflict means re-adding to remote
5391           conflictingItemP->setSyncOp(sop_wants_add);
5392         }
5393         else {
5394           // merge here because we'll not process the item further
5395           conflictingItemP->mergeWith(*aSyncItemP,changedexisting,changedincoming,this);
5396         }
5397       }
5398     }
5399     // now discard the incoming item
5400     delete aSyncItemP;
5401     PDEBUGPRINTFX(fRejectStatus>=400 ? DBG_ERROR : DBG_DATA,("Item rejected with Status=%hd",fRejectStatus));
5402     if (fRejectStatus>0) {
5403       // rejected with status code (not necessarily error)
5404       aStatusCommand.setStatusCode(fRejectStatus);
5405       if (fRejectStatus>=300) {
5406         // non 200-codes are errors
5407         ADDDEBUGITEM(aStatusCommand,"Item rejected");
5408         return false;
5409       }
5410     }
5411     // silently rejected
5412     return true;
5413   }
5414   // now perform requested operation
5415   bool ok=false;
5416   localstatus sta;
5417   switch (syncop) {
5418     readonly_delete:
5419       // read-only handling of delete is like soft delete: remove map entry, but nothing else
5420       PDEBUGPRINTFX(DBG_DATA,("Read-Only Datastore: Prevented actual deletion, just removing map entry"));
5421     case sop_soft_delete:
5422       // Readonly: allowed, as only map is touched
5423       // soft delete from client is treated as an indication that the item was
5424       // removed from the client's datastore, but is still in the set
5425       // of sync data for that client.
5426       // This means that the map item must be removed.
5427       // - when the item is hard-deleted on the server, nothing will happen at next sync
5428       // - when the item is modified on the server, it will be re-added to the client at next sync
5429       // - when slow sync is performed, the item will be re-added, too.
5430       // %%%%% Note that this does NOT work as it is now, as adds also occur for non-modified
5431       //       items that have no map AND are visible under current targetFilter.
5432       //       probably we should use a map entry with no remoteID for soft-deleted items later....
5433       // Delete Map entry by remote ID
5434       aSyncItemP->clearLocalID(); // none
5435       sta=engProcessMap(aSyncItemP->getRemoteID(),NULL);
5436       ok=sta==LOCERR_OK;
5437       aStatusCommand.setStatusCode(ok ? 200 : sta);
5438       break;
5439     case sop_archive_delete:
5440       if (fReadOnly) goto readonly_delete; // register removal of item in map, but do nothing to data itself
5441       #ifdef OBJECT_FILTERING
5442       if (!fDSConfigP->fInvisibleFilter.empty()) {
5443         // turn into replace with all fields unavailable but made to pass invisible filter
5444         // - make sure that no data field is assigned
5445         aSyncItemP->cleardata();
5446         // - make item pass "invisible" filter
5447         if (aSyncItemP->makePassFilter(fDSConfigP->fInvisibleFilter.c_str())) {
5448           // item now passes invisible rule, that is, it is invisible -> replace in DB
5449           goto archive_delete;
5450         }
5451       }
5452       // fall trough, no archive delete supported
5453       #endif
5454       // No archive delete support if there is no filter to detect/generate invisibles
5455       // before SyncML 1.1 : we could return 210 here and still process the delete op.
5456       // SyncML 1.1 : we must return 501 (not implemented) here
5457       aStatusCommand.setStatusCode(501);
5458       PDEBUGPRINTFX(DBG_ERROR,("Datastore does not support Archive-Delete, error status = 501"));
5459       delete aSyncItemP;
5460       ok=false;
5461       break;
5462     case sop_delete:
5463       // delete item by LUID
5464       if (fSlowSync) {
5465         aStatusCommand.setStatusCode(403);
5466         ADDDEBUGITEM(aStatusCommand,"Delete during slow sync not allowed");
5467         PDEBUGPRINTFX(DBG_ERROR,("Delete during slow sync not allowed"));
5468         delete aSyncItemP;
5469         ok=false;
5470         break;
5471       }
5472       // check for conflict with replace from server
5473       // Note: conflict cases do not change local DB, so they are allowed before checking fReadOnly
5474       if (!echoItemP) conflictingItemP = getConflictingItemByRemoteID(aSyncItemP); // do not check conflicts if we have already created an echo
5475       // - check if we must force the conflict
5476       if (!conflictingItemP && fForceConflict) {
5477         conflictingItemP=SendDBVersionOfItemAsServer(aSyncItemP);
5478       }
5479       if (conflictingItemP) {
5480         // conflict only if other party has replace
5481         if (conflictingItemP->getSyncOp()==sop_replace || conflictingItemP->getSyncOp()==sop_wants_replace) {
5482           if (!fDeleteWins) {
5483             // act as if successfully deleted and cause re-adding of still existing server item
5484             // - discard deletion
5485             delete aSyncItemP;
5486             // - remove map entry for this item (it no longer exists on the client)
5487             sta = engProcessMap(NULL,conflictingItemP->getLocalID());
5488             aStatusCommand.setStatusCode(sta==LOCERR_OK ? 200 : sta);
5489             // - change replace to add (as to-be-replaced item is already deleted on remote)
5490             conflictingItemP->setSyncOp(sop_add);
5491             // - remove remote ID (will be assigned a new ID because the item is now re-added)
5492             conflictingItemP->setRemoteID("");
5493             // - no server operation needed
5494             PDEBUGPRINTFX(DBG_DATA,("Conflict of Client Delete with Server replace -> discarded delete, re-added server item to client"));
5495             ok=true;
5496             break;
5497           }
5498           else {
5499             // delete preceedes replace
5500             // - avoid sending item from server
5501             dontSendItemAsServer(conflictingItemP);
5502             // - let delete happen
5503           }
5504         }
5505         // if both have deleted the item, we should remove the map
5506         // and avoid sending a delete to the client
5507         else if (conflictingItemP->getSyncOp()==sop_delete) {
5508           // - discard deletion
5509           delete aSyncItemP;
5510           // - remove map entry for this item (it no longer exists)
5511           sta = engProcessMap(NULL,conflictingItemP->getLocalID());
5512           aStatusCommand.setStatusCode(sta==LOCERR_OK ? 200 : sta);
5513           // - make sure delete from server is not sent
5514           dontSendItemAsServer(conflictingItemP);
5515           PDEBUGPRINTFX(DBG_DATA,("Client and Server have deleted same item -> just removed map entry"));
5516           ok=true;
5517           break;
5518         }
5519       }
5520       // real delete is discarded silently when fReadOnly is set
5521       if (fReadOnly) goto readonly_delete; // register removal of item in map, but do nothing to data itself
5522       // really delete
5523       fLocalItemsDeleted++;
5524       remainsvisible=false; // deleted not visible any more
5525       ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // delete in local database NOW
5526       break;
5527     case sop_copy:
5528       if (fReadOnly) {
5529         delete aSyncItemP; // we don't need it
5530         aStatusCommand.setStatusCode(200);
5531         PDEBUGPRINTFX(DBG_DATA,("Read-Only: copy command silently discarded"));
5532         ok=true;
5533         break;
5534       }
5535       // %%% note: this would belong into specific datastore implementation, but is here
5536       //     now for simplicity as copy isn't used heavily het
5537       // retrieve data from local datastore
5538       if (!logicRetrieveItemByID(*aSyncItemP,aStatusCommand)) { ok=false; break; }
5539       // process like add
5540       /// @todo %%%%%%%%%%%%%%%% NOTE: MISSING SENDING BACK MAP COMMAND for new GUID created
5541       goto normal_add;
5542     case sop_add:
5543       // test for slow sync
5544       if (fSlowSync) goto sop_slow_add; // add in slow sync is like replace
5545     normal_add:
5546       // add as new item to server DB
5547       aStatusCommand.setStatusCode(201); // item added (if no error occurs)
5548       if (fReadOnly) {
5549         delete aSyncItemP; // we don't need it
5550         PDEBUGPRINTFX(DBG_DATA,("Read-Only: add command silently discarded"));
5551         ok=true;
5552         break;
5553       }
5554       // check if adds are prevented
5555       if (!fPreventAdd) {
5556         // add allowed
5557         fLocalItemsAdded++;
5558         #ifdef OBJECT_FILTERING
5559         // test if acceptable
5560         if (!isAcceptable(aSyncItemP,aStatusCommand)) { ok=false; break; } // cannot be accepted
5561         // Note: making item to pass sync set filter is implemented in derived DB implementation
5562         //   as criteria for passing might be in data that must first be read from the DB
5563         #endif
5564         remainsvisible=true; // should remain visible
5565         ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // add to local database NOW
5566         if (!remainsvisible && fSessionP->getSyncMLVersion()>=syncml_vers_1_2) {
5567           PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Added item is not visible under current filters -> remove it on client"));
5568           goto removefromremoteandsyncset;
5569         }
5570         break;
5571       }
5572       goto preventadd;
5573     archive_delete:
5574     sop_slow_add:
5575       aSyncItemP->setSyncOp(sop_replace); // set correct op
5576       // ...and process like replace
5577     case sop_reference_only:
5578     case sop_replace:
5579       #ifdef OBJECT_FILTERING
5580       // test if acceptable
5581       if (!isAcceptable(aSyncItemP,aStatusCommand)) { ok=false; break; } // cannot be accepted
5582       // Note: making item to pass sync set filter is implemented in derived DB implementation
5583       //   as criteria for passing might be in data that must first be read from the DB
5584       #endif
5585       // check for conflict with server side modifications
5586       if (!fSlowSync) {
5587         if (!echoItemP) conflictingItemP = getConflictingItemByRemoteID(aSyncItemP);
5588         // - check if we must force the conflict
5589         if (!conflictingItemP && fForceConflict) {
5590           conflictingItemP=SendDBVersionOfItemAsServer(aSyncItemP);
5591         }
5592         bool deleteconflict=false;
5593         if (conflictingItemP) {
5594           // Note: if there is a conflict, this replace cannot be an
5595           //       implicit add, so we don't need to check for fPreventAdd
5596           //       here.
5597           // Note: if we are in ignoreUpdate mode, the only conflict resolution
5598           //       possible is unconditional server win
5599           sInt16 cmpRes = SYSYNC_NOT_COMPARABLE;
5600           // assume we can resolve the conflict
5601           aStatusCommand.setStatusCode(419); // default to server win
5602           ADDDEBUGITEM(aStatusCommand,"Conflict resolved by server");
5603           PDEBUGPRINTFX(DBG_HOT,(
5604             "Conflict: Remote <%s> with remoteID=%s <--> Local <%s> with localID=%s, remoteID=%s",
5605             SyncOpNames[aSyncItemP->getSyncOp()],
5606             aSyncItemP->getRemoteID(),
5607             SyncOpNames[conflictingItemP->getSyncOp()],
5608             conflictingItemP->getLocalID(),
5609             conflictingItemP->getRemoteID()
5610           ));
5611           // we have a conflict, decide what to do
5612           TConflictResolution crstrategy;
5613           if (fReadOnly || fIgnoreUpdate) {
5614             // server always wins and overwrites modified client version
5615             PDEBUGPRINTFX(DBG_DATA,("Read-Only or IgnoreUpdate: server always wins"));
5616             crstrategy=cr_server_wins;
5617           }
5618           else {
5619             // two-way
5620             crstrategy = fItemConflictStrategy; // get conflict strategy pre-set for this item
5621             if (conflictingItemP->getSyncOp()==sop_delete) {
5622               // server wants to delete item, client wants to replace
5623               if (fDSConfigP->fTryUpdateDeleted) {
5624                 // if items are not really deleted, but only made invisible,
5625                 // we can assume we can update the "deleted" item
5626                 // BUT ONLY if the conflict strategy is not "server always wins"
5627                 if (crstrategy==cr_server_wins) {
5628                   PDEBUGPRINTFX(DBG_PROTO+DBG_HOT,("Conflict of Client Replace with Server delete and strategy is server-wins -> delete from client"));
5629                   aStatusCommand.setStatusCode(419); // server wins, client command ignored
5630                   break; // done
5631                 }
5632                 else {
5633                   PDEBUGPRINTFX(DBG_PROTO+DBG_HOT,("Conflict of Client Replace with Server delete -> try to update already deleted item (as it might still exist in syncset)"));
5634                   // apply replace (and in case of !fDeleteWins, possible implicit add)
5635                   fPreventAdd=fDeleteWins; // we want implicit add only if delete cannot win
5636                   remainsvisible=!fDeleteWins; // we want to see the item in the sync set if delete does not win!
5637                   ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible);
5638                 }
5639                 if (fDeleteWins) {
5640                   if (!ok) {
5641                     // could not update already deleted item
5642                     PDEBUGPRINTFX(DBG_PROTO,("Could not update already deleted server item (seems to be really deleted, not just invisible)"));
5643                     aStatusCommand.setStatusCode(419); // server wins, client command ignored
5644                   }
5645                   else {
5646                     // update of invisible item successful, but it will still be deleted from client
5647                     // Note: possibly, the update was apparently successful, but only because an UPDATE with no
5648                     //   target does not report an error. So effectively, no update might have happened.
5649                     PDEBUGPRINTFX(DBG_PROTO,("Updated already deleted server item, but delete still wins -> client item will be deleted"));
5650                     fLocalItemsUpdated++;
5651                     aStatusCommand.setStatusCode(200); // client command successful (but same item will still be deleted)
5652                   }
5653                   // nothing more to do, let delete happen on the client (conflictingItemP delete will be sent)
5654                   ok=true;
5655                 }
5656                 else {
5657                   // not fDeleteWins - item failed, updated or implicitly added
5658                   if (ok) {
5659                     // update (or implicit add) successful
5660                     if (aStatusCommand.getStatusCode()==201) {
5661                       PDEBUGPRINTFX(DBG_PROTO,("Client Update wins and has re-added already deleted server item -> prevent delete on client"));
5662                       fLocalItemsAdded++;
5663                     }
5664                     else {
5665                       PDEBUGPRINTFX(DBG_PROTO,("Client Update wins and has updated still existing server item -> prevent delete on client"));
5666                       fLocalItemsUpdated++;
5667                     }
5668                     // and client item wins - prevent sending delete to client
5669                     // - don't send delete to client
5670                     conflictingItemP->setSyncOp(sop_none); // just in case...
5671                     dontSendItemAsServer(conflictingItemP);
5672                   }
5673                 }
5674                 // done
5675                 break;
5676               }
5677               else {
5678                 // Normal delete conflict processing (assuming deleted items REALLY deleted)
5679                 if (!fDeleteWins) {
5680                   // - client always wins (replace over delete)
5681                   crstrategy=cr_client_wins;
5682                   deleteconflict=true; // flag condition for processing below
5683                   // - change from replace to add, because item is already deleted in server and must be re-added
5684                   fLocalItemsAdded++;
5685                   aSyncItemP->setSyncOp(sop_add);
5686                   PDEBUGPRINTFX(DBG_PROTO,("Conflict of Client Replace with Server delete -> client wins, client item is re-added to server"));
5687                 }
5688                 else {
5689                   // delete wins, just discard incoming item
5690                   delete aSyncItemP;
5691                   PDEBUGPRINTFX(DBG_PROTO,("Conflict of Client Replace with Server delete -> DELETEWINS() set -> ignore client replace"));
5692                   ok=true;
5693                   break;
5694                 }
5695               }
5696             }
5697             else {
5698               // replace from client conflicts with replace from server
5699               // - compare items for further conflict resolution
5700               //   NOTE: it is serveritem.compareWith(clientitem)
5701               cmpRes = conflictingItemP->compareWith(
5702                 *aSyncItemP,eqm_conflict,this
5703                 #ifdef SYDEBUG
5704                 ,PDEBUGTEST(DBG_CONFLICT) // show conflict comparisons in normal sync if conflict details are enabled
5705                 #endif
5706               );
5707               PDEBUGPRINTFX(DBG_DATA,(
5708                 "Compared conflicting items with eqm_conflict: remoteItem %s localItem",
5709                 cmpRes==0 ? "==" : (cmpRes>0 ? "<" : ">")
5710               ));
5711               // see if we can determine newer item
5712               if (crstrategy==cr_newer_wins) {
5713                 if (cmpRes!=0 && conflictingItemP->sortable(*aSyncItemP)) {
5714                   // newer item wins
5715                   // (comparison was: serveritem.compareWith(clientitem), so
5716                   // cmpRes<0 means that client is newer
5717                   PDEBUGPRINTFX(DBG_PROTO,("Conflict resolved by identifying newer item"));
5718                   if (cmpRes > 0)
5719                     crstrategy=cr_server_wins; // server has newer item
5720                   else
5721                     crstrategy=cr_client_wins; // client has newer item
5722                 }
5723                 else {
5724                   // newer item cannot be determined, duplicate items
5725                   crstrategy=cr_duplicate;
5726                 }
5727                 PDEBUGPRINTFX(DBG_DATA,(
5728                   "Newer item %sdetermined: %s",
5729                   crstrategy==cr_duplicate ? "NOT " : "",
5730                   crstrategy==cr_client_wins ? "Client item is newer and wins" :
5731                     (crstrategy==cr_server_wins ? "Server item is newer ans wins" : "item is duplicated if different")
5732                 ));
5733               }
5734             }
5735             // modify strategy based on compare
5736             if (cmpRes==0 && crstrategy==cr_duplicate) {
5737               // items are equal by definition of item comparison,
5738               // but obviously both changed, this means that changes should be
5739               // mergeable
5740               // So, by deciding arbitrarily that server has won, we will not loose any data
5741               crstrategy=cr_server_wins; // does not matter, because merge will be attempted
5742               PDEBUGPRINTFX(DBG_DATA,("Duplication avoided because items are equal by their own definition, just merge"));
5743             }
5744             // if adds prevented, we cannot duplicate, let server win
5745             if (fPreventAdd && crstrategy==cr_duplicate) crstrategy=cr_server_wins;
5746           } // not fReadOnly
5747           // now apply strategy
5748           if (crstrategy==cr_duplicate) {
5749             // add items vice versa
5750             PDEBUGPRINTFX(DBG_PROTO,("Conflict resolved by duplicating items in both databases"));
5751             aStatusCommand.setStatusCode(209);
5752             fConflictsDuplicated++;
5753             // - set server item such that it will be added as new item to client DB
5754             conflictingItemP->setSyncOp(sop_add);
5755             // - break up mapping between client and server item BEFORE adding to server
5756             //   because else adding of item with already existing remoteID can fail.
5757             //   In addition, item now being sent to client may not have a map before
5758             //   it receives a map command from the client!
5759             sta = engProcessMap(NULL,conflictingItemP->getLocalID());
5760             if(sta!=LOCERR_OK) {
5761               PDEBUGPRINTFX(DBG_ERROR,(
5762                 "Problem (status=%hd) removing map entry for LocalID='%s'",
5763                  sta,
5764                  conflictingItemP->getLocalID()
5765               ));
5766             }
5767             // - add client item as new item to server DB
5768             fLocalItemsAdded++;
5769             aSyncItemP->setSyncOp(sop_add); // set correct op
5770             remainsvisible=true; // should remain visible
5771             ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // add to local database NOW
5772             break;
5773           }
5774           else if (crstrategy==cr_server_wins) {
5775             // Note: for fReadOnly, this is always the case!
5776             // server item wins and is sent to client
5777             PDEBUGPRINTFX(DBG_PROTO,("Conflict resolved: server item replaces client item"));
5778             aStatusCommand.setStatusCode(419); // server wins, client command ignored
5779             fConflictsServerWins++;
5780             // - make sure item is set to replace data in client
5781             conflictingItemP->setSyncOp(sop_replace);
5782             // - attempt to merge data from loosing item (accumulating fields)
5783             if (!fReadOnly) {
5784               conflictingItemP->mergeWith(*aSyncItemP,changedexisting,changedincoming,this);
5785             }
5786             if (fIgnoreUpdate) changedexisting=false; // never try to update existing item
5787             if (changedexisting) {
5788               // we have merged something, so server must be updated, too
5789               // Note: after merge, both items are equal. We check if conflictingitem
5790               //       has changed, but if yes, we write the incoming item. Conflicting item
5791               //       will get sent to client later
5792               PDEBUGPRINTFX(DBG_DATA,("*** Merged some data from loosing client item into winning server item"));
5793               // set correct status for conflict resultion by merge
5794               aStatusCommand.setStatusCode(207); // merged
5795               // process update in local database
5796               fLocalItemsUpdated++;
5797               aSyncItemP->setSyncOp(sop_replace); // update
5798               remainsvisible=true; // should remain visible
5799               ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // update in local database NOW
5800               break;
5801             }
5802             else {
5803               // - item sent by client has lost and can be deleted now
5804               //   %%% possibly add option here to archive item in some way
5805               //       BUT ONLY IF NOT fReadOnly
5806               delete aSyncItemP;
5807             }
5808           }
5809           else if (crstrategy==cr_client_wins) {
5810             // client item wins and is sent to server
5811             PDEBUGPRINTFX(DBG_PROTO,("Conflict resolved: client item replaces server item"));
5812             aStatusCommand.setStatusCode(208); // client wins
5813             fConflictsClientWins++;
5814             // - attempt to merge data from loosing item (accumulating fields)
5815             if (!deleteconflict) {
5816               aSyncItemP->mergeWith(*conflictingItemP,changedincoming,changedexisting,this);
5817             }
5818             if (changedincoming) {
5819               // we have merged something, so client must be updated even if it has won
5820               // Note: after merge, both items are equal. We check if aSyncItemP
5821               //       has changed, but if yes, we make sure the conflicting item gets
5822               //       sent to the client
5823               PDEBUGPRINTFX(DBG_DATA,("*** Merged some data from loosing server item into winning client item"));
5824               conflictingItemP->setSyncOp(sop_replace); // update
5825               // set correct status for conflict resultion by merge
5826               aStatusCommand.setStatusCode(207); // merged
5827               // will be sent because it is in the list
5828             }
5829             else {
5830               // - make sure conflicting item from server is NOT sent to client
5831               conflictingItemP->setSyncOp(sop_none); // just in case...
5832               dontSendItemAsServer(conflictingItemP);
5833               aStatusCommand.setStatusCode(200); // ok
5834             }
5835             // - replace item in server (or leave it as is, if conflict was with delete)
5836             if (!deleteconflict) {
5837               fLocalItemsUpdated++;
5838               aSyncItemP->setSyncOp(sop_replace);
5839             }
5840             remainsvisible=true; // should remain visible
5841             ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // replace in local database NOW
5842             break;
5843           }
5844         } // replace conflict
5845         else {
5846           // normal replace without any conflict
5847           if (fReadOnly) {
5848             delete aSyncItemP; // we don't need it
5849             aStatusCommand.setStatusCode(200);
5850             PDEBUGPRINTFX(DBG_DATA,("Read-Only: replace command silently discarded"));
5851             ok=true;
5852             break;
5853           }
5854           // no conflict, just let client replace server's item
5855           PDEBUGPRINTFX(DBG_DATA+DBG_CONFLICT,("No Conflict: client item replaces server item"));
5856           // - replace item in server (or add if item does not exist and not fPreventAdd)
5857           aSyncItemP->setSyncOp(sop_replace);
5858           remainsvisible=true; // should remain visible
5859           if (!logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible)) {
5860             // check if this is a 404 or 410 and fPreventAdd
5861             if (fPreventAdd && (aStatusCommand.getStatusCode()==404 || aStatusCommand.getStatusCode()==410))
5862               goto preventadd2; // to-be-replaced item not found and implicit add prevented -> delete from remote
5863             // simply failed
5864             ok=false;
5865             break;
5866           }
5867           // still visible in sync set?
5868           if (!remainsvisible && fSessionP->getSyncMLVersion()>=syncml_vers_1_2) {
5869             // -> cause item to be deleted on remote
5870             PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Item replaced no longer visible in syncset -> returning delete to remote"));
5871             goto removefromremoteandsyncset;
5872           }
5873           // processed ok
5874           if (aStatusCommand.getStatusCode()==201)
5875             fLocalItemsAdded++;
5876           else
5877             fLocalItemsUpdated++;
5878           ok=true;
5879           break;
5880         }
5881       } // normal sync
5882       else {
5883         // slow sync (replaces AND adds are treated the same)
5884         // - first do a strict search for identical item. This is required to
5885         //   prevent that in case of multiple (loosely compared) matches we
5886         //   catch the wrong item and cause a mess at slowsync
5887         //   NOTE: we do compare only relevant fields (eqm_conflict)
5888         TSyncItem *matchingItemP = getMatchingItem(aSyncItemP,eqm_conflict);
5889         if (!matchingItemP) {
5890           // try again with less strict comparison (eqm_slowsync or eqm_always for firsttimesync)
5891           DEBUGPRINTFX(DBG_DATA+DBG_MATCH,("Strict search for matching item failed, try with configured EqMode now"));
5892           matchingItemP = getMatchingItem(aSyncItemP,fFirstTimeSync ? eqm_always : eqm_slowsync);
5893         }
5894         if (matchingItemP) {
5895           // both sides already have this item
5896           PDEBUGPRINTFX(DBG_DATA+DBG_HOT,(
5897             "Slow Sync Match detected - localID='%s' matches incoming item",
5898             matchingItemP->getLocalID()
5899           ));
5900           fSlowSyncMatches++;
5901           aStatusCommand.setStatusCode(syncop==sop_add ? 201 : 200); // default is: simply ok. But if original op was Add, MUST return 201 status (SCTS requires it)
5902           bool matchingok=false;
5903           // - do not update map yet, as we still don't know if client item will
5904           //   possibly be added instead of mapped
5905           // Note: ONLY in case this is a reference-only item, the map is already updated!
5906           bool mapupdated = syncop==sop_reference_only;
5907           // - determine which one is winning
5908           bool needserverupdate=false;
5909           bool needclientupdate=false;
5910           // if updates are ignored, we can short-cut here
5911           // Note: if this is a reference-only item, it was already updated (if needed) before last suspend
5912           //       so skip updating now!
5913           if (syncop!=sop_reference_only && !fIgnoreUpdate) {
5914             // Not a reference-only and also updates not suppressed
5915             // - for a read-only datastore, this defaults to server always winning
5916             TConflictResolution crstrategy =
5917               fReadOnly ?
5918               cr_server_wins : // server always wins for read-only
5919               fItemConflictStrategy; // pre-set strategy for this item
5920             // Determine what data to use
5921             if (crstrategy==cr_newer_wins) {
5922               // use newer
5923               // Note: comparison is clientitem.compareWith(serveritem)
5924               //       so if result>0, client is newer than server
5925               sInt16 cmpRes = aSyncItemP->compareWith(
5926                 *matchingItemP,eqm_nocompare,this
5927                 #ifdef SYDEBUG
5928                 ,PDEBUGTEST(DBG_CONFLICT+DBG_DETAILS) // show age comparisons only if we want to see details
5929                 #endif
5930               );
5931               if (cmpRes==1) crstrategy=cr_client_wins;
5932               else if (cmpRes==-1 || fPreventAdd) crstrategy=cr_server_wins; // server wins if adds prevented
5933               else crstrategy=cr_duplicate; // fall back to duplication if we can't determine newer item
5934               PDEBUGPRINTFX(DBG_DATA,(
5935                 "Newer item %sdetermined: %s",
5936                 crstrategy==cr_duplicate ? "NOT " : "",
5937                 crstrategy==cr_client_wins ? "Client item is newer and wins" :
5938                   (crstrategy==cr_server_wins ? "Server item is newer and wins" : "item is duplicated if different")
5939               ));
5940             }
5941             // if adds prevented, we cannot duplicate, let server win
5942             if (fPreventAdd && crstrategy==cr_duplicate) crstrategy=cr_server_wins;
5943             // now execute chosen strategy
5944             if (crstrategy==cr_client_wins) {
5945               // - merge server's data into client item
5946               PDEBUGPRINTFX(DBG_DATA,("Trying to merge some data from loosing server item into winning client item"));
5947               aSyncItemP->mergeWith(*matchingItemP,changedincoming,changedexisting,this);
5948               // only count if server gets updated
5949               if (changedexisting) fConflictsClientWins++;
5950               // Note: changedexisting will cause needserverupdate to be set below
5951             }
5952             else if (crstrategy==cr_server_wins) {
5953               // - merge client data into server item
5954               PDEBUGPRINTFX(DBG_DATA,("Trying to merge some data from loosing client item into winning server item"));
5955               matchingItemP->mergeWith(*aSyncItemP,changedexisting,changedincoming,this);
5956               // only count if client gets updated
5957               if (changedincoming) fConflictsServerWins++;
5958               // Note: changedincoming will cause needclientupdate to be set below
5959             }
5960             else if (crstrategy==cr_duplicate) {
5961               // test if items are equal enough
5962               // (Note: for first-sync, compare mode for item matching can be looser
5963               // (eqm_always), so re-comparison here makes sense)
5964               if (
5965                 matchingItemP->compareWith(
5966                   *aSyncItemP,eqm_slowsync,this
5967                   #ifdef SYDEBUG
5968                   ,PDEBUGTEST(DBG_CONFLICT+DBG_DETAILS) // show equality re-test only for details enabled
5969                   #endif
5970                 )!=0
5971               ) {
5972                 string guid;
5973                 // items are not really equal in content, so duplicate them on both sides
5974                 PDEBUGPRINTFX(DBG_PROTO,("Matching items are not fully equal, duplicate them on both sides"));
5975                 fConflictsDuplicated++;
5976                 // - duplicates contain merged data
5977                 matchingItemP->mergeWith(*aSyncItemP,changedexisting,changedincoming,this);
5978                 // - add client item (with server data merged) as new item to server
5979                 fLocalItemsAdded++;
5980                 aSyncItemP->setSyncOp(sop_add);
5981                 aStatusCommand.setStatusCode(201); // item added (if no error occurs)
5982                 remainsvisible=true; // should remain visible
5983                 matchingok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible,&guid); // add item in local database NOW
5984                 aSyncItemP=NULL; // is already deleted!
5985                 if (matchingok) { // do it only if server add successful, because otherwise we don't have a GUID
5986                   // - make sure same item is ADDED as new item to client
5987                   matchingItemP->setSyncOp(sop_add); // add it, prevent it from re-match (is sop_wants_add now)
5988                   matchingItemP->setLocalID(guid.c_str()); // this is now a pair with the newly added item (not the original one)
5989                   matchingItemP->setRemoteID(""); // we don't know the remote ID yet
5990                 }
5991                 // adding duplicate to server (add) is already done
5992                 changedexisting=false;
5993                 changedincoming=false;
5994                 mapupdated=true; // no need to update map
5995                 needserverupdate=false; // already done
5996                 // client must received the (updated) server item as an add (set above)
5997                 needclientupdate=true;
5998               }
5999             }
6000           } // if not ignoreUpdate
6001           // Update server map now if required
6002           // - NOTE THAT THIS IS VERY IMPORTANT TO DO BEFORE any possible
6003           //   replaces, because replacing the matchingItem can only be
6004           //   done via its remoteID, which is, at this moment, probably not
6005           //   valid. After Mapping, it is ensured that the mapped remoteID
6006           //   uniquely identifies the matchingItem.
6007           if (!mapupdated) {
6008             // - update map in server
6009             sta = engProcessMap(
6010               aSyncItemP->getRemoteID(), // remote ID (LUID) of item received from client
6011               matchingItemP->getLocalID() // local ID (GUID) of item already stored in server
6012             );
6013             matchingok = sta==LOCERR_OK;
6014             if (!matchingok) {
6015               // failed
6016               ok=false;
6017               aStatusCommand.setStatusCode(sta);
6018               break;
6019             }
6020           }
6021           // Now prepare updates of (already mapped) server and client items if needed
6022           if (changedexisting) {
6023             // matched item in server's sync set was changed and must be update in server DB
6024             // update server only if updates during non-first-time slowsync are enabled
6025             if (fFirstTimeSync || fSessionP->fUpdateServerDuringSlowsync) {
6026               needserverupdate=true; // note: ineffective if fReadOnly
6027               PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Server data was updated by record sent by client, REPLACE it in server DB"));
6028             }
6029             else {
6030               PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Server data was updated by record sent by client, but server updates during non-firsttime slowsyncs are disabled"));
6031             }
6032           }
6033           if (changedincoming) {
6034             // incoming item from remote was changed after receiving and must be reflected
6035             // back to client
6036             // NOTE: this can also happen with sop_reference_only items
6037             // - client will be updated because matchingItemP is still in list
6038             matchingItemP->setSyncOp(sop_replace); // cancel wants_add state, prevent re-match
6039             // - matchingItem was retrieved BEFORE map was done, and has no valid remote ID
6040             //   so remote ID must be added now.
6041             matchingItemP->setRemoteID(aSyncItemP->getRemoteID());
6042             // update client only if updates during non-first-time slowsync are enabled
6043             if (fFirstTimeSync || fSessionP->fUpdateClientDuringSlowsync) {
6044               PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Record sent by client was updated with server data, client will receive a REPLACE"));
6045               needclientupdate=true;
6046             }
6047             else {
6048               PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Record sent by client was updated, but client updates during non-firsttime slowsyncs are disabled"));
6049             }
6050           }
6051           // update
6052           // - check if client must be updated
6053           if (!needclientupdate) {
6054             // prevent updating client
6055             // - make sure matching item from server is NOT sent to client
6056             matchingItemP->setSyncOp(sop_none); // just in case...
6057             dontSendItemAsServer(matchingItemP);
6058           }
6059           else {
6060             // check if replaces may be sent to client during slowsync
6061             if (matchingItemP->getSyncOp()==sop_replace && fSessionP->fNoReplaceInSlowsync) {
6062               PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Update of client item suppressed because of <noreplaceinslowsync>"));
6063               matchingItemP->setSyncOp(sop_none); // just in case...
6064               dontSendItemAsServer(matchingItemP);
6065             }
6066             else {
6067               PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Update of client item enabled (will be sent later)"));
6068             }
6069           }
6070           // - check if server must be updated (NEVER for fReadOnly)
6071           if (!fReadOnly && needserverupdate && aSyncItemP) {
6072             // update server
6073             // - update server side (NOTE: processItemAsServer takes ownership, pointer gets invalid!)
6074             fLocalItemsUpdated++;
6075             aSyncItemP->setSyncOp(sop_replace);
6076             remainsvisible=true; // should remain visible
6077             matchingok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // replace item in local database NOW
6078             PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Updated server item"));
6079           }
6080           else {
6081             // - delete incoming item unprocessed (if not already deleted)
6082             if (aSyncItemP) delete aSyncItemP;
6083           }
6084           // check if we need to actively delete item from the client as it falls out of the filter
6085           if (!remainsvisible && fSessionP->getSyncMLVersion()>=syncml_vers_1_2) {
6086             // -> cause item to be deleted on remote
6087             PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Slow sync matched item no longer visible in syncset -> returning delete to remote"));
6088             goto removefromremoteandsyncset;
6089           }
6090           ok=matchingok;
6091           break;
6092         }
6093         else {
6094           PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("No matching item found - add it to local database"));
6095           // this item is not yet on the server, add it normally
6096           aStatusCommand.setStatusCode(201); // item added (if no error occurs)
6097           if (fReadOnly) {
6098             delete aSyncItemP; // we don't need it
6099             PDEBUGPRINTFX(DBG_DATA,("Read-Only: slow-sync add/replace command silently discarded"));
6100             ok=true;
6101             break;
6102           }
6103           if (fPreventAdd) goto preventadd;
6104           fLocalItemsAdded++;
6105           aSyncItemP->setSyncOp(sop_add); // set correct op
6106           remainsvisible=true; // should remain visible
6107           ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // add to local database NOW
6108           break;
6109         }
6110       } // slow sync
6111       break; // just in case....
6112     preventadd:
6113       // add not allowed, delete remote item instead
6114       // - consume original
6115       delete aSyncItemP;
6116     preventadd2:
6117       aStatusCommand.setStatusCode(201); // pretend adding item was successful
6118       PDEBUGPRINTFX(DBG_DATA,("Prevented explicit add, returning delete to remote"));
6119       ok=true;
6120       goto removefromremote; // as we have PREVENTED adding the item, it is not in the map
6121     removefromremoteandsyncset:
6122       // remove item from local map first
6123       engProcessMap(remoteid.c_str(),NULL);
6124     removefromremote:
6125       // have item removed from remote
6126       delitemP = newItemForRemote(itemtypeid);
6127       delitemP->setRemoteID(remoteid.c_str());
6128       delitemP->setSyncOp(sop_delete);
6129       SendItemAsServer(delitemP); // pass ownership
6130       break;
6131     default :
6132       SYSYNC_THROW(TSyncException("Unknown sync op in TLocalEngineDS::processRemoteItemAsServer"));
6133   } // switch
6134   if (ok) {
6135     DB_PROGRESS_EVENT(this,pev_itemprocessed,fLocalItemsAdded,fLocalItemsUpdated,fLocalItemsDeleted);
6136   }
6137   else {
6138     // if the DB has a error string to show, add it here
6139     aStatusCommand.addItemString(lastDBErrorText().c_str());
6140   }
6141   // done
6142   return ok;
6143 } // TLocalEngineDS::engProcessRemoteItemAsServer
6144
6145
6146 /// @brief called at end of request processing, should be used to save suspend state
6147 /// @note superdatastore does it itself to have correct order of things happening
6148 void TLocalEngineDS::engRequestEnded(void)
6149 {
6150   #ifdef SUPERDATASTORES
6151   if (fAsSubDatastoreOf)
6152     return;
6153   #endif
6154   // If DS 1.2: Make sure everything is ready for a resume in case there's an abort (implicit Suspend)
6155   // before the next request. Note that the we cannot wait for session timeout, as the resume attempt
6156   // from the client probably arrives much earlier.
6157   // Note: It is ESSENTIAL not to save the state until sync set is ready, because saving state will
6158   //   cause DB access, and DB access is not permitted while sync set is possibly still loading
6159   //   (possibly in a separate thread!). So dssta_syncmodestable (as in <=3.0.0.2) is NOT enough here!
6160   if (testState(dssta_syncsetready)) {
6161     // make sure all unsent items are marked for resume
6162     engSaveSuspendState(false); // only if not already aborted
6163   }
6164   // let datastore prepare for end of request
6165   dsRequestEnded();
6166   // and let it prepare for end of this thread as well
6167   dsThreadMayChangeNow();
6168 } // TLocalEngineDS::engRequestEnded
6169
6170 #endif // SYSYNC_SERVER
6171
6172
6173
6174 #ifdef SYSYNC_CLIENT
6175
6176 // Client Case
6177 // ===========
6178
6179
6180 // process sync operation from server with specified sync item
6181 // (according to current sync mode of local datastore)
6182 // - returns true (and unmodified or non-200-successful status) if
6183 //   operation could be processed regularily
6184 // - returns false (but probably still successful status) if
6185 //   operation was processed with internal irregularities, such as
6186 //   trying to delete non-existant item in datastore with
6187 //   incomplete Rollbacks (which returns status 200 in this case!).
6188 bool TLocalEngineDS::engProcessRemoteItemAsClient(
6189   TSyncItem *aSyncItemP,
6190   TStatusCommand &aStatusCommand // status, must be set to correct status code (ok / error)
6191 )
6192 {
6193   string remoteid,localid;
6194   bool ok;
6195   bool remainsvisible;
6196
6197   // send event and check for user abort
6198   #ifdef PROGRESS_EVENTS
6199   if (!DB_PROGRESS_EVENT(this,pev_itemreceived,++fItemsReceived,fRemoteNumberOfChanges,0)) {
6200     fSessionP->AbortSession(500,true,LOCERR_USERABORT); // this also causes datastore to be aborted
6201   }
6202   if (!SESSION_PROGRESS_EVENT(fSessionP,pev_suspendcheck,NULL,0,0,0)) {
6203     fSessionP->SuspendSession(LOCERR_USERSUSPEND);
6204   }
6205   #endif
6206   // check if datastore is aborted
6207   if (CheckAborted(aStatusCommand)) return false;
6208   // init behaviour vars (these are normally used by server only,
6209   // but must be initialized correctly for client as well as descendants might test them
6210   fPreventAdd = false;
6211   fIgnoreUpdate = false;
6212   // get operation out of item
6213   TSyncOperation syncop=aSyncItemP->getSyncOp();
6214   // show
6215   DEBUGPRINTFX(DBG_DATA,("%s item operation received",SyncOpNames[syncop]));
6216   // check if receiving commands is allowed at all
6217   // - must be in correct sync state
6218   if (!testState(dssta_syncgendone)) {
6219     // Modifications from server not allowed before client has done sync gen
6220     // %%% we could possibly relax this one, depending on the DB
6221     aStatusCommand.setStatusCode(403);
6222     PDEBUGPRINTFX(DBG_ERROR,("Server command not allowed before client has sent entire <sync>"));
6223     delete aSyncItemP;
6224     return false;
6225   }
6226   // - must not be one-way
6227   if (fSyncMode==smo_fromclient) {
6228     // Modifications from server not allowed during update from client only
6229     aStatusCommand.setStatusCode(403);
6230     ADDDEBUGITEM(aStatusCommand,"Server command not allowed in one-way/refresh from client");
6231     PDEBUGPRINTFX(DBG_ERROR,("Server command not allowed in one-way/refresh from client"));
6232     delete aSyncItemP;
6233     return false;
6234   }
6235   else {
6236     // silently discard all modifications if readonly
6237     if (fReadOnly) {
6238       PDEBUGPRINTFX(DBG_ERROR,("Read-Only: silently discarding all modifications"));
6239       aStatusCommand.setStatusCode(200); // always ok (but never "added"), so server cannot expect Map
6240       delete aSyncItemP;
6241       return true;
6242     }
6243     // let item check itself to catch special cases
6244     // - init variables which are used/modified by item checking
6245     fItemSizeLimit=-1; // no limit
6246     fCurrentSyncOp = syncop;
6247     fEchoItemOp = sop_none;
6248     fItemConflictStrategy=fSessionConflictStrategy; // get default strategy for this item
6249     fForceConflict = false;
6250     fDeleteWins = fDSConfigP->fDeleteWins; // default to configured value
6251     fRejectStatus = -1; // no rejection
6252     // - now check
6253     //   check reads and possibly modifies:
6254     //   - fEchoItemOp : if not sop_none, the incoming item is echoed back to the remote with the specified syncop
6255     //   - fItemConflictStrategy : might be changed from the pre-set datastore default
6256     //   - fForceConflict : if set, a conflict is forced by adding the corresponding local item (if any) to the list of items to be sent
6257     //   - fDeleteWins : if set, in a replace/delete conflict delete will win (regardless of strategy)
6258     //   - fPreventAdd : if set, attempt to add item from remote (even implicitly trough replace) will cause no add but delete of remote item
6259     //   - fIgnoreUpdate : if set, attempt to update item from remote will be ignored, only adds (also implicit ones) are executed
6260     //   - fRejectStatus : if set>=0, incoming item is irgnored silently(==0) or with error(!=0)
6261     aSyncItemP->checkItem(this);
6262     // - check if incoming item should be processed at all
6263     if (fRejectStatus>=0) {
6264       // now discard the incoming item
6265       delete aSyncItemP;
6266       PDEBUGPRINTFX(fRejectStatus>=400 ? DBG_ERROR : DBG_DATA,("Item rejected with Status=%hd",fRejectStatus));
6267       if (fRejectStatus>0) {
6268         // rejected with status code (not necessarily error)
6269         aStatusCommand.setStatusCode(fRejectStatus);
6270         if (fRejectStatus>=300) {
6271           // non 200-codes are errors
6272           ADDDEBUGITEM(aStatusCommand,"Item rejected");
6273           return false;
6274         }
6275       }
6276       // silently rejected
6277       return true;
6278     }
6279     // now perform requested operation
6280     switch (syncop) {
6281       case sop_soft_delete:
6282       case sop_archive_delete:
6283       case sop_delete:
6284         // delete item
6285         fLocalItemsDeleted++;
6286         remainsvisible=false; // deleted not visible any more
6287         ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // delete in local database NOW
6288         break;
6289       case sop_copy:
6290         // %%% note: this would belong into specific datastore implementation, but is here
6291         //     now for simplicity as copy isn't used heavily het
6292         // retrieve data from local datastore
6293         if (!logicRetrieveItemByID(*aSyncItemP,aStatusCommand)) {
6294           ok=false;
6295           break;
6296         }
6297         // process like add
6298       case sop_add:
6299         // add as new item to client DB
6300         aStatusCommand.setStatusCode(201); // item added (if no error occurs)
6301         fLocalItemsAdded++;
6302         // add to local datastore
6303         // - get remoteid BEFORE processing item (as logicProcessRemoteItem consumes the item!!)
6304         remoteid=aSyncItemP->getRemoteID(); // get remote ID
6305         // check if this remoteid is already known from last session - if so, this means that
6306         // this add was re-sent and must NOT be executed
6307         if (isAddFromLastSession(remoteid.c_str())) {
6308           aStatusCommand.setStatusCode(418); // already exists
6309           PDEBUGPRINTFX(DBG_ERROR,("Warning: Item with same server-side ID (GUID) was already added in previous session - ignore add now"));
6310           delete aSyncItemP; // forget item
6311           ok=false;
6312           break;
6313         }
6314         #ifdef OBJECT_FILTERING
6315         if (!isAcceptable(aSyncItemP,aStatusCommand)) {
6316           delete aSyncItemP; // forget item
6317           ok=false; // cannot be accepted
6318           break;
6319         }
6320         #endif
6321         remainsvisible=true; // should remain visible
6322         ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible,&localid); // add to local database NOW, get back local GUID
6323         if (!ok) break;
6324         // if added (not replaced), we need to send map
6325         if (aStatusCommand.getStatusCode()==201) {
6326           // really added: remember map entry
6327           DEBUGPRINTFX(DBG_ADMIN+DBG_DETAILS,(
6328             "engProcessRemoteItemAsClient: add command: adding new entry to fPendingAddMaps - localid='%s', remoteID='%s'",
6329             localid.c_str(),
6330             remoteid.c_str()
6331           ));
6332           fPendingAddMaps[localid]=remoteid;
6333         }
6334         ok=true; // fine
6335         break;
6336       case sop_replace:
6337         // - replace item in client
6338         fLocalItemsUpdated++;
6339         aSyncItemP->setSyncOp(sop_replace);
6340         #ifdef OBJECT_FILTERING
6341         if (!isAcceptable(aSyncItemP,aStatusCommand)) {
6342           ok=false; // cannot be accepted
6343           break;
6344         }
6345         #endif
6346         // - get remoteid BEFORE processing item (as logicProcessRemoteItem consumes the item!!),
6347         //   in case replace is converted to add and we need to register a map entry.
6348         remoteid=aSyncItemP->getRemoteID(); // get remote ID
6349         remainsvisible=true; // should remain visible
6350         ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible,&localid); // replace in local database NOW
6351         // if added (not replaced), we need to send map
6352         if (aStatusCommand.getStatusCode()==201) {
6353           // Note: logicProcessRemoteItem should NOT do an add if we have no remoteid, but return 404.
6354           //       The following check is just additional security.
6355           // really added: remember map entry if server sent remoteID (normally, it won't for Replace)
6356           if (!remoteid.empty()) {
6357             // we can handle that, as remote sent us the remoteID we need to map it correctly
6358             DEBUGPRINTFX(DBG_ADMIN+DBG_DETAILS,(
6359               "engProcessRemoteItemAsClient: replace command for unknown localID but with remoteID -> adding new entry to fPendingAddMaps - localid='%s', remoteID='%s'",
6360               localid.c_str(),
6361               remoteid.c_str()
6362             ));
6363             fPendingAddMaps[localid]=remoteid;
6364           }
6365           else {
6366             // we cannot handle this (we shouldn't have, in logicProcessRemoteItem!!)
6367             aStatusCommand.setStatusCode(510);
6368             ok=false;
6369           }
6370         }
6371         break; // fine, ok = irregularity status
6372       default:
6373         SYSYNC_THROW(TSyncException("Unknown sync op in TLocalEngineDS::processRemoteItemAsClient"));
6374     } // switch
6375     // processed
6376     if (ok) {
6377       DB_PROGRESS_EVENT(this,pev_itemprocessed,fLocalItemsAdded,fLocalItemsUpdated,fLocalItemsDeleted);
6378     }
6379     else {
6380       // if the DB has a error string to show, add it here
6381       aStatusCommand.addItemString(lastDBErrorText().c_str());
6382     }
6383     return ok;
6384   }
6385 } // TLocalEngineDS::processRemoteItemAsClient
6386
6387
6388
6389 // client case: called whenever outgoing Message of Sync Package starts
6390 void TLocalEngineDS::engClientStartOfSyncMessage(void)
6391 {
6392   // is called for all local datastores, even inactive ones, so check state first
6393   if (testState(dssta_dataaccessstarted) && !isAborted()) {
6394     // only alerted non-sub datastores will start a <sync> command here, ONCE
6395     // if there are pending maps, they will be sent FIRST
6396     if (fRemoteDatastoreP) {
6397       if (!testState(dssta_clientsyncgenstarted)) {
6398         // shift state to syncgen started
6399         //   NOTE: if all sync commands can be sent at once,
6400         //   state will change again by issuing <sync>, so
6401         //   it MUST be changed here (not after issuing!)
6402         changeState(dssta_clientsyncgenstarted,true);
6403         // - make sure remotedatastore has correct full name
6404         fRemoteDatastoreP->setFullName(getRemoteDBPath());
6405         // an interrupted command at this point is a map command - continueIssue() will take care
6406         if (!fSessionP->isInterrupedCmdPending() && numUnsentMaps()>0) {
6407           // Check for pending maps from previous session (even in DS 1.0..1.1 case this is possible)
6408           fUnconfirmedMaps.clear(); // no unconfirmed maps left...
6409           fLastSessionMaps.clear(); // ...and no lastSessionMaps yet: all are in pendingMaps
6410           // if this is a not-resumed slow sync, pending maps must not be sent, but discarded
6411           if (isSlowSync() && !isResuming()) {
6412             // forget the pending maps
6413             PDEBUGPRINTFX(DBG_HOT,("There are %ld cached map entries from last Session, but this is a non-resumed slow sync: discard them",(long)numUnsentMaps()));
6414             fPendingAddMaps.clear();
6415           }
6416           else {
6417             // - now sending pending (cached) map commands from previous session
6418             // Note: if map command was already started, the
6419             //   finished(), continueIssue() mechanism will make sure that
6420             //   more commands are generated. This mechanism will also make
6421             //   sure that outgoing package cannot get <final/> until
6422             //   map is completely sent.
6423             // Note2: subdatastores do not generate their own map commands,
6424             //   but are called by superdatastore for contributing to united map
6425             if (!isSubDatastore()) {
6426               PDEBUGBLOCKFMT(("LastSessionMaps","Now sending cached map entries from last Session","datastore=%s",getName()));
6427               TMapCommand *mapcmdP =
6428                 new TMapCommand(
6429                   fSessionP,
6430                   this,               // local datastore
6431                   fRemoteDatastoreP   // remote datastore
6432                 );
6433               // issue
6434               ISSUE_COMMAND_ROOT(fSessionP,mapcmdP);
6435               PDEBUGENDBLOCK("LastSessionMaps");
6436             }
6437           }
6438         }
6439         // Now, send sync command (unless we are a subdatastore, in this case the superdatastore will take care)
6440         if (!isSubDatastore()) {
6441           // Note: if sync command was already started, the
6442           //   finished(), continueIssue() mechanism will make sure that
6443           //   more commands are generated
6444           // Note2: subdatastores do not generate their own sync commands,
6445           //   but switch to dssta_client/serversyncgenstarted for contributing to united sync command
6446           TSyncCommand *synccmdP =
6447             new TSyncCommand(
6448               fSessionP,
6449               this,               // local datastore
6450               fRemoteDatastoreP   // remote datastore
6451             );
6452           // issue
6453           ISSUE_COMMAND_ROOT(fSessionP,synccmdP);
6454         }
6455       }
6456     }
6457     else {
6458       PDEBUGPRINTFX(DBG_ERROR,("engClientStartOfSyncMessage can't start sync phase - missing remotedatastore"));
6459       changeState(dssta_idle,true); // force to idle, nothing happened yet
6460     }
6461   } // if dsssta_dataaccessstarted and not aborted
6462 } // TLocalEngineDS::engClientStartOfSyncMessage
6463
6464
6465 // Client only: returns number of unsent map items
6466 sInt32 TLocalEngineDS::numUnsentMaps(void)
6467 {
6468   return fPendingAddMaps.size();
6469 } // TLocalEngineDS::numUnsentMaps
6470
6471
6472 // Client only: called whenever outgoing Message starts that may contain <Map> commands
6473 // @param[in] aNotYetInMapPackage set if we are still in sync-from-server package
6474 // @note usually, client starts sending maps while still receiving syncops from server
6475 void TLocalEngineDS::engClientStartOfMapMessage(bool aNotYetInMapPackage)
6476 {
6477   // is called for all local datastores, even inactive ones, so check state first
6478   if (testState(dssta_syncgendone) && !isAborted()) {
6479     // datastores that have finished generating their <sync>
6480     // now can start a <map> command here, once (if not isInterrupedCmdPending(), that is, not a map already started)
6481     if (fRemoteDatastoreP) {
6482       // start a new map command if we don't have any yet and we are not done (dssta_clientmapssent) yet
6483       if (!fSessionP->isInterrupedCmdPending() && !testState(dssta_clientmapssent)) {
6484         // check if we should start one (that is, if the list is not empty)
6485         if (numUnsentMaps()>0) {
6486           // - now sending map command
6487           //   NOTE: if all map commands can be sent at once,
6488           //   fState will be modified again by issuing <map>, so
6489           //   it MUST be set here (not after issuing!)
6490           // - data access done only if we are in Map package now
6491           if (!aNotYetInMapPackage)
6492             changeState(dssta_dataaccessdone); // usually already set in engEndOfSyncFromRemote(), but does not harm here
6493           // Note: if map command was already started, the
6494           //   finished(), continueIssue() mechanism will make sure that
6495           //   more commands are generated. This mechanism will also make
6496           //   sure that outgoing package cannot get <final/> until
6497           //   map is completely sent.
6498           // Note2: subdatastores do not generate their own map commands,
6499           //   but superdatastore calls their generateMapItem for contributing to united map
6500           if (!isSubDatastore()) {
6501             TMapCommand *mapcmdP =
6502               new TMapCommand(
6503                 fSessionP,
6504                 this,               // local datastore
6505                 fRemoteDatastoreP   // remote datastore
6506               );
6507             // issue
6508             ISSUE_COMMAND_ROOT(fSessionP,mapcmdP);
6509           }
6510         }
6511         else {
6512           // we need no map items now, but if this is still sync-from-server-package,
6513           // we are not done yet
6514           if (aNotYetInMapPackage) {
6515             DEBUGPRINTFX(DBG_PROTO,("No map command need to be generated now, but still in <sync> from server package"));
6516           }
6517           else {
6518             // we are done now
6519             DEBUGPRINTFX(DBG_PROTO,("All map commands sending complete"));
6520             changeState(dssta_clientmapssent);
6521           }
6522         }
6523       }
6524     } // if fRemoteDataStoreP
6525   } // if >=dssta_syncgendone
6526 } // TLocalEngineDS::engClientStartOfMapMessage
6527
6528
6529
6530 // called to mark maps confirmed, that is, we have received ok status for them
6531 void TLocalEngineDS::engMarkMapConfirmed(cAppCharP aLocalID, cAppCharP aRemoteID)
6532 {
6533   // As this is client-only, we don't need to check for tempGUIDs here.
6534   // Note: superdatastore has an implementation which dispatches by prefix
6535   TStringToStringMap::iterator pos=fUnconfirmedMaps.find(aLocalID);
6536   if (pos!=fUnconfirmedMaps.end()) {
6537     DEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,(
6538       "engMarkMapConfirmed: deleting confirmed entry localID='%s', remoteID='%s' from fUnconfirmedMaps",
6539       aLocalID,
6540       aRemoteID
6541     ));
6542     // move it to lastsessionmap
6543     fLastSessionMaps[(*pos).first]=(*pos).second;
6544     // remove it from unconfirmed map
6545     fUnconfirmedMaps.erase(pos);
6546   }
6547 } // TLocalEngineDS::engMarkMapConfirmed
6548
6549
6550
6551 // Check if the remoteid was used by an add command not
6552 // fully mapped&confirmed in the previous session
6553 bool TLocalEngineDS::isAddFromLastSession(cAppCharP aRemoteID)
6554 {
6555   TStringToStringMap::iterator pos;
6556   TStringToStringMap *mapListP;
6557
6558   for (int i=0; i<3; i++) {
6559     // determine next list to search
6560     mapListP = i==0 ? &fPendingAddMaps : (i==1 ? &fUnconfirmedMaps : &fLastSessionMaps);
6561     // search it
6562     for (pos=mapListP->begin(); pos!=mapListP->end(); ++pos) {
6563       if (strcmp((*pos).second.c_str(),aRemoteID)==0)
6564         return true; // remoteID known -> is add from last session
6565     }
6566   }
6567   // not found in any of the lists
6568   return false;
6569 } // TLocalEngineDS::isAddFromLastSession
6570
6571
6572
6573 // - called to generate Map items
6574 //   Returns true if now finished for this datastore
6575 //   also sets fState to dss_done when finished
6576 bool TLocalEngineDS::engGenerateMapItems(TMapCommand *aMapCommandP, cAppCharP aLocalIDPrefix)
6577 {
6578   #ifdef USE_SML_EVALUATION
6579   sInt32 leavefree = fSessionP->getNotUsableBufferBytes();
6580   #else
6581   sInt32 freeroom = fSessionP->getFreeCommandSize();
6582   #endif
6583
6584   TStringToStringMap::iterator pos=fPendingAddMaps.begin();
6585   PDEBUGBLOCKFMT(("MapGenerate","Generating Map items...","datastore=%s",getName()));
6586   do {
6587     // check if already done
6588     if (pos==fPendingAddMaps.end()) break; // done
6589     // get ID
6590     string locID = (*pos).first;
6591     dsFinalizeLocalID(locID); // make sure we have the permanent version in case datastore implementation did deliver temp IDs
6592     // create prefixed version of ID
6593     string prefixedLocID;
6594     AssignString(prefixedLocID, aLocalIDPrefix); // init with prefix (if any)
6595     prefixedLocID += locID; // append local ID
6596     // add it to map command
6597     aMapCommandP->addMapItem(prefixedLocID.c_str(),(*pos).second.c_str());
6598     // check if we could send this command
6599     #ifdef USE_SML_EVALUATION
6600     if (
6601       (aMapCommandP->evalIssue(
6602         fSessionP->peekNextOutgoingCmdID(),
6603         fSessionP->getOutgoingMsgID()
6604       ) // what is left in buffer after sending Map so far
6605       >=leavefree) // all this must still be more than what we MUST leave free
6606     )
6607     #else
6608     if (freeroom>aMapCommandP->messageSize())
6609     #endif
6610     {
6611       // yes, it should work
6612       PDEBUGPRINTFX(DBG_PROTO,(
6613         "Mapitem generated: localID='%s', remoteID='%s'",
6614         prefixedLocID.c_str(),
6615         (*pos).second.c_str()
6616       ));
6617       // move sent ones to unconfirmed list (Note: use real locID, without prefix!)
6618       fUnconfirmedMaps[locID]=(*pos).second;
6619       // remove item from to-be-sent list
6620       TStringToStringMap::iterator temp_pos = pos++; // make copy and set iterator to next
6621       fPendingAddMaps.erase(temp_pos); // now entry can be deleted (N.M. Josuttis, pg204)
6622     }
6623     else {
6624       // no room for this item in this message, interrupt now
6625       // - delete already added item again
6626       aMapCommandP->deleteLastMapItem();
6627       // - interrupt here
6628       PDEBUGPRINTFX(DBG_PROTO,("Interrupted generating Map items because max message size reached"))
6629       PDEBUGENDBLOCK("MapGenerate");
6630       return false;
6631     }
6632   } while(true);
6633   // done
6634   // if we are dataaccessdone or more -> end of map phase for this datastore
6635   if (testState(dssta_dataaccessdone)) {
6636     changeState(dssta_clientmapssent,true);
6637     PDEBUGPRINTFX(DBG_PROTO,("Finished generating Map items, server has finished <sync>, we are done now"))
6638   }
6639   #ifdef SYDEBUG
6640   // else if we are not yet dssta_syncgendone -> this is the end of a early pending map send
6641   else if (!dbgTestState(dssta_syncgendone)) {
6642     PDEBUGPRINTFX(DBG_PROTO,("Finished sending cached Map items from last session"))
6643   }
6644   // otherwise, we are not really finished with the maps yet (but with the current map command)
6645   else {
6646     PDEBUGPRINTFX(DBG_PROTO,("Finished generating Map items for now, but server still sending <Sync>"))
6647   }
6648   #endif
6649   PDEBUGENDBLOCK("MapGenerate");
6650   return true;
6651 } // TLocalEngineDS::engGenerateMapItems
6652
6653
6654 #endif // SYSYNC_SERVER
6655
6656
6657
6658 // - called to mark an already generated (but probably not sent or not yet statused) item
6659 //   as "to-be-resumed", by localID or remoteID (latter only in server case).
6660 //   NOTE: This must be repeatable without side effects, as server must mark/save suspend state
6661 //         after every request (and not just at end of session)
6662 void TLocalEngineDS::engMarkItemForResume(cAppCharP aLocalID, cAppCharP aRemoteID, bool aUnSent)
6663 {
6664   #ifdef SUPERDATASTORES
6665   // if we are acting as a subdatastore, aLocalID might be prefixed
6666   if (fAsSubDatastoreOf && aLocalID) {
6667     // remove prefix
6668     aLocalID = fAsSubDatastoreOf->removeSubDSPrefix(aLocalID, this);
6669   }
6670   #endif
6671   #ifdef SYSYNC_SERVER
6672   // Now mark for resume
6673   if (IS_SERVER && aLocalID && *aLocalID) {
6674     // localID can be a translated localid at this point (for adds), so check for it
6675     string localid=aLocalID;
6676     obtainRealLocalID(localid);
6677     logicMarkItemForResume(localid.c_str(), aRemoteID, aUnSent);
6678   }
6679   else
6680   #endif
6681   {
6682     // localID not used or client (which never has tempGUIDs)
6683     logicMarkItemForResume(aLocalID, aRemoteID, aUnSent);
6684   }
6685 } // TLocalEngineDS::engMarkItemForResume
6686
6687
6688 // - called to mark an already generated (but probably not sent or not yet statused) item
6689 //   as "to-be-resent", by localID or remoteID (latter only in server case).
6690 void TLocalEngineDS::engMarkItemForResend(cAppCharP aLocalID, cAppCharP aRemoteID)
6691 {
6692   #ifdef SUPERDATASTORES
6693   // if we are acting as a subdatastore, aLocalID might be prefixed
6694   if (fAsSubDatastoreOf && aLocalID) {
6695     // remove prefix
6696     aLocalID = fAsSubDatastoreOf->removeSubDSPrefix(aLocalID, this);
6697   }
6698   #endif
6699   // a need to resend is always a problem with the remote (either explicit non-ok status
6700   // received or implicit like data size too big to be sent at all due to maxmsgsize or
6701   // maxobjsize restrictions.
6702   fRemoteItemsError++;
6703   // now mark for resend
6704   #ifdef SYSYNC_SERVER
6705   if (IS_SERVER && aLocalID && *aLocalID) {
6706     // localID can be a translated localid at this point (for adds), so check for it
6707     string localid=aLocalID;
6708     obtainRealLocalID(localid);
6709     logicMarkItemForResend(localid.c_str(), aRemoteID);
6710   }
6711   else
6712   #endif
6713   {
6714     // localID not used or client (which never has tempGUIDs)
6715     logicMarkItemForResend(aLocalID, aRemoteID);
6716   }
6717 } // TLocalEngineDS::engMarkItemForResend
6718
6719
6720
6721
6722
6723 // @brief save everything needed to resume later, in case we get suspended
6724 /// - Might be called multiple times during a session, must make sure every time
6725 ///   that the status is correct, that is, previous suspend state is erased
6726 localstatus TLocalEngineDS::engSaveSuspendState(bool aAnyway)
6727 {
6728   // only save here if not aborted already (aborting saves the state immediately)
6729   // or explicitly requested
6730   if (aAnyway || !isAborted()) {
6731     // only save if DS 1.2 and supported by DB
6732     if ((fSessionP->getSyncMLVersion()>=syncml_vers_1_2) && dsResumeSupportedInDB()) {
6733       PDEBUGBLOCKFMT(("SaveSuspendState","Saving state for suspend/resume","datastore=%s",getName()));
6734       // save alert state (if not explicitly prevented)
6735       fResumeAlertCode=fPreventResuming ? 0 : fAlertCode;
6736       if (fResumeAlertCode) {
6737         if (fPartialItemState!=pi_state_save_outgoing) {
6738           // ONLY if we have no request for saving an outgoing item state already,
6739           // we possibly need to save a pending incoming item
6740           // if there is an incompletely received item, let it update Partial Item (fPIxxx) state
6741           // (if it is an item of this datastore, that is).
6742           if (fSessionP->fIncompleteDataCommandP)
6743             fSessionP->fIncompleteDataCommandP->updatePartialItemState(this);
6744         }
6745         // this makes sure that only ungenerated (but to-be generated) items will be
6746         // marked for resume. Items that have been generated in this session (but might
6747         // have been marked for resume in a previous session must no longer be marked
6748         // after this call.
6749         // This also includes saving state for a partially sent item so we could resume it (fPIxxx)
6750         logicMarkOnlyUngeneratedForResume();
6751         // then, we need to additionally mark those items for resume which have been
6752         // generated, but not yet sent or sent but not received status so far.
6753         fSessionP->markPendingForResume(this);
6754       }
6755       // let datastore make all this persistent
6756       // NOTE: this must happen even if we have no suspend state here,
6757       //   as marked-for-resends need to be saved here as well.
6758       localstatus sta=logicSaveResumeMarks();
6759       if (sta!=LOCERR_OK) {
6760         PDEBUGPRINTFX(DBG_ERROR,("Error saving suspend state with logicSaveResumeMarks(), status=%hd",sta));
6761       }
6762       PDEBUGENDBLOCK("SaveSuspendState");
6763       return sta;
6764     }
6765     // resume not supported due to datastore or SyncML version<1.2 -> ok anyway
6766     PDEBUGPRINTFX(DBG_PROTO,("SaveSuspendState not possible (SyncML<1.2 or not supported by DB)"));
6767   }
6768   return LOCERR_OK;
6769 } // TLocalEngineDS::engSaveSuspendState
6770
6771
6772
6773 } // namespace sysync
6774
6775 /* end of TLocalEngineDS implementation */
6776
6777 // eof