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