2 * @File localengineds.cpp
4 * @Author Lukas Zeller (luz@plan44.ch)
6 * @brief TLocalEngineDS
7 * Abstraction of the local datastore - interface class to the
10 * Copyright (c) 2001-2011 by Synthesis AG + plan44.ch
12 * @Date 2005-09-15 : luz : created from localdatastore
16 #include "prefix_file.h"
18 #include "localengineds.h"
19 #include "syncappbase.h"
20 #include "scriptcontext.h"
21 #include "superdatastore.h"
22 #include "syncagent.h"
25 using namespace sysync;
31 cAppCharP const LocalDSStateNames[numDSStates] = {
37 "server_answered_alert",
38 "client_alert_statused",
41 "data_access_started",
43 "client_sync_gen_started",
44 "server_seen_client_mods",
45 "server_sync_gen_started",
55 #ifdef OBJECT_FILTERING
57 // add a new expression to an existing filter
58 static void addToFilter(const char *aNewFilter, string &aFilter, bool aORChain=false)
60 if (aNewFilter && *aNewFilter) {
61 // just assign if current filter expression is empty
65 // construct new filter
67 StringObjPrintf(newFilter,"(%s)%c(%s)",aFilter.c_str(),aORChain ? '|' : '&',aNewFilter);
83 #ifdef OBJECT_FILTERING
84 #ifdef SYNCML_TAF_SUPPORT
86 // string GETCGITARGETFILTER()
87 // returns current CGI-specified target address filter expression
88 static void func_GetCGITargetFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
90 TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
91 aTermP->setAsString(dsP->fTargetAddressFilter.c_str());
92 }; // func_GetCGITargetFilter
95 // string GETTARGETFILTER()
96 // returns current internal target address filter expression
97 static void func_GetTargetFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
99 TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
100 aTermP->setAsString(dsP->fIntTargetAddressFilter.c_str());
101 }; // func_GetTargetFilter
104 // SETTARGETFILTER(string filter)
105 // sets (overwrites) the internal target address filter
106 static void func_SetTargetFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
108 TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
109 aFuncContextP->getLocalVar(0)->getAsString(dsP->fIntTargetAddressFilter);
110 }; // func_SetTargetFilter
113 // ADDTARGETFILTER(string filter)
114 // adds a filter expression to the existing internal targetfilter (automatically paranthesizing and adding AND)
115 static void func_AddTargetFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
118 TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
119 aFuncContextP->getLocalVar(0)->getAsString(f);
120 addToFilter(f.c_str(),dsP->fIntTargetAddressFilter,false); // AND-chaining
121 }; // func_AddTargetFilter
123 #endif // SYNCML_TAF_SUPPORT
126 // string GETFILTER()
127 // returns current sync set filter expression
128 static void func_GetFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
130 TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
131 aTermP->setAsString(dsP->fSyncSetFilter.c_str());
135 // SETFILTER(string filter)
136 // sets (overwrites) the current sync set filter
137 static void func_SetFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
139 TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
140 aFuncContextP->getLocalVar(0)->getAsString(dsP->fSyncSetFilter);
141 dsP->engFilteredFetchesFromDB(true); // update filter dependencies
145 // ADDFILTER(string filter)
146 // adds a filter expression to the existing (dynamic) targetfilter (automatically paranthesizing and adding AND)
147 static void func_AddFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
150 TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
151 aFuncContextP->getLocalVar(0)->getAsString(f);
152 addToFilter(f.c_str(),dsP->fSyncSetFilter,false); // AND-chaining
153 dsP->engFilteredFetchesFromDB(true); // update filter dependencies
157 // ADDSTATICFILTER(string filter)
158 // adds a filter expression to the existing (static) localdbfilter (automatically paranthesizing and adding AND)
159 static void func_AddStaticFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
162 TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
163 aFuncContextP->getLocalVar(0)->getAsString(f);
164 addToFilter(f.c_str(),dsP->fLocalDBFilter,false); // AND-chaining
165 dsP->engFilteredFetchesFromDB(true); // update filter dependencies
166 }; // func_AddStaticFilter
168 #endif // OBJECT_FILTERING
170 #ifdef SYSYNC_TARGET_OPTIONS
172 // string DBOPTIONS()
173 // returns current DB options
174 static void func_DBOptions(TItemField *&aTermP, TScriptContext *aFuncContextP)
176 TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
177 aTermP->setAsString(dsP->fDBOptions.c_str());
181 // integer DBHANDLESOPTS()
182 // returns true if database can completely handle options like /dr() and /li during fetching
183 static void func_DBHandlesOpts(TItemField *&aTermP, TScriptContext *aFuncContextP)
185 aTermP->setAsBoolean(
186 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->dsOptionFilterFetchesFromDB()
188 }; // func_DBHandlesOpts
191 // timestamp STARTDATE()
192 // returns startdate if one is set in datastore
193 static void func_StartDate(TItemField *&aTermP, TScriptContext *aFuncContextP)
195 lineartime_t d = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fDateRangeStart;
196 TTimestampField *resP = static_cast<TTimestampField *>(aTermP);
200 resP->setTimestampAndContext(d,TCTX_UTC);
204 // SETSTARTDATE(timestamp startdate)
205 // sets startdate for datastore
206 static void func_SetStartDate(TItemField *&aTermP, TScriptContext *aFuncContextP)
208 TTimestampField *tsP = static_cast<TTimestampField *>(aFuncContextP->getLocalVar(0));
211 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fDateRangeStart =
212 tsP->getTimestampAs(TCTX_UTC,&tctx); // floating will also be treated as UTC
213 }; // func_SetStartDate
216 // timestamp ENDDATE()
217 // returns enddate if one is set in datastore
218 static void func_EndDate(TItemField *&aTermP, TScriptContext *aFuncContextP)
220 lineartime_t d = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fDateRangeEnd;
221 TTimestampField *resP = static_cast<TTimestampField *>(aTermP);
225 resP->setTimestampAndContext(d,TCTX_UTC);
229 // SETENDDATE(timestamp startdate)
230 // sets enddate for datastore
231 static void func_SetEndDate(TItemField *&aTermP, TScriptContext *aFuncContextP)
233 TTimestampField *tsP = static_cast<TTimestampField *>(aFuncContextP->getLocalVar(0));
236 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fDateRangeEnd =
237 tsP->getTimestampAs(TCTX_UTC,&tctx); // floating will also be treated as UTC
238 }; // func_SetEndDate
241 // integer DEFAULTSIZELIMIT()
242 // returns limit set for all items in this datastore (the /li(xxx) CGI option value)
243 static void func_DefaultLimit(TItemField *&aTermP, TScriptContext *aFuncContextP)
245 fieldinteger_t i = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fSizeLimit;
247 aTermP->unAssign(); // no limit
249 aTermP->setAsInteger(i);
250 }; // func_DefaultLimit
253 // SETDEFAULTSIZELIMIT(integer limit)
254 // sets limit for all items in this datastore (the /li(xxx) CGI option value)
255 static void func_SetDefaultLimit(TItemField *&aTermP, TScriptContext *aFuncContextP)
257 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fSizeLimit =
258 aFuncContextP->getLocalVar(0)->getAsInteger();
259 }; // func_SetDefaultLimit
262 // integer NOATTACHMENTS()
263 // returns true if attachments should be suppressed (/na CGI option)
264 static void func_NoAttachments(TItemField *&aTermP, TScriptContext *aFuncContextP)
266 aTermP->setAsBoolean(
267 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fNoAttachments
269 }; // func_NoAttachments
272 // SETNOATTACHMENTS(integer flag)
273 // if true, attachments will be suppressed (/na CGI option)
274 static void func_SetNoAttachments(TItemField *&aTermP, TScriptContext *aFuncContextP)
276 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fNoAttachments =
277 aFuncContextP->getLocalVar(0)->getAsBoolean();
278 }; // func_SetNoAttachments
281 // integer MAXITEMCOUNT()
282 // returns item count limit (0=none) as set by /max(n) CGI option
283 static void func_MaxItemCount(TItemField *&aTermP, TScriptContext *aFuncContextP)
285 aTermP->setAsInteger(
286 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fMaxItemCount
288 }; // func_MaxItemCount
291 // SETMAXITEMCOUNT(integer maxcount)
292 // set item count limit (0=none) as set by /max(n) CGI option
293 static void func_SetMaxItemCount(TItemField *&aTermP, TScriptContext *aFuncContextP)
295 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fMaxItemCount =
296 aFuncContextP->getLocalVar(0)->getAsInteger();
297 }; // func_SetMaxItemCount
299 #endif // SYSYNC_TARGET_OPTIONS
302 // integer SLOWSYNC()
303 // returns true if we are in slow sync
304 static void func_SlowSync(TItemField *&aTermP, TScriptContext *aFuncContextP)
306 aTermP->setAsBoolean(
307 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->isSlowSync()
313 // force a slow sync (like with /na CGI option)
314 static void func_ForceSlowSync(TItemField *&aTermP, TScriptContext *aFuncContextP)
316 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->engForceSlowSync();
317 }; // func_ForceSlowSync
321 // integer ALERTCODE()
322 // returns the alert code as currently know by datastore (might change from normal to slow while processing)
323 static void func_AlertCode(TItemField *&aTermP, TScriptContext *aFuncContextP)
325 aTermP->setAsInteger(
326 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fAlertCode
331 // SETALERTCODE(integer maxcount)
332 // set the alert code (makes sense in alertscript to modify the incoming code to something different)
333 static void func_SetAlertCode(TItemField *&aTermP, TScriptContext *aFuncContextP)
335 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fAlertCode =
336 aFuncContextP->getLocalVar(0)->getAsInteger();
337 }; // func_SetAlertCode
341 // integer REFRESHONLY()
342 // returns true if sync is only refreshing local (note that alert code might be different, as local
343 // refresh can take place without telling the remote so, for compatibility with clients that do not support the mode)
344 static void func_RefreshOnly(TItemField *&aTermP, TScriptContext *aFuncContextP)
346 aTermP->setAsBoolean(
347 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->isRefreshOnly()
349 }; // func_RefreshOnly
352 // SETREFRESHONLY(integer flag)
353 // modifies the refresh only flag (one way sync from remote to local only)
354 // Note that clearing this flag when a client has alerted one-way will probably lead to an error
355 static void func_SetRefreshOnly(TItemField *&aTermP, TScriptContext *aFuncContextP)
357 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->engSetRefreshOnly(
358 aFuncContextP->getLocalVar(0)->getAsBoolean()
360 }; // func_SetRefreshOnly
363 // integer READONLY()
364 // returns true if sync is read-only (only reading from local datastore)
365 static void func_ReadOnly(TItemField *&aTermP, TScriptContext *aFuncContextP)
367 aTermP->setAsBoolean(
368 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->isReadOnly()
373 // SETREADONLY(integer flag)
374 // modifies the read only flag (only reading from local datastore)
375 static void func_SetReadOnly(TItemField *&aTermP, TScriptContext *aFuncContextP)
377 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->engSetReadOnly(
378 aFuncContextP->getLocalVar(0)->getAsBoolean()
380 }; // func_SetReadOnly
384 // integer FIRSTTIMESYNC()
385 // returns true if we are in first time slow sync
386 static void func_FirstTimeSync(TItemField *&aTermP, TScriptContext *aFuncContextP)
388 aTermP->setAsBoolean(
389 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->isFirstTimeSync()
391 }; // func_FirstTimeSync
394 // void SETCONFLICTSTRATEGY(string strategy)
395 // sets conflict strategy for this session
396 static void func_SetConflictStrategy(TItemField *&aTermP, TScriptContext *aFuncContextP)
400 aFuncContextP->getLocalVar(0)->getAsString(s);
402 StrToEnum(conflictStrategyNames,numConflictStrategies, strategy, s.c_str());
403 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->fSessionConflictStrategy =
404 (TConflictResolution) strategy;
405 }; // func_SetConflictStrategy
409 // returns name of DB
410 static void func_DBName(TItemField *&aTermP, TScriptContext *aFuncContextP)
413 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->getName()
417 // void ABORTDATASTORE(integer statuscode)
418 static void func_AbortDatastore(TItemField *&aTermP, TScriptContext *aFuncContextP)
420 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->engAbortDataStoreSync(aFuncContextP->getLocalVar(0)->getAsInteger(),true); // we cause the abort locally
421 } // func_AbortDatastore
423 // string LOCALDBNAME()
424 // returns name of local DB with which it was identified for the sync
425 static void func_LocalDBName(TItemField *&aTermP, TScriptContext *aFuncContextP)
428 static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext())->getIdentifyingName()
430 }; // func_LocalDBName
433 // string REMOTEDBNAME()
434 // returns remote datastore's full name (as used by the remote in <sync> command, may contain subpath and CGI)
435 static void func_RemoteDBName(TItemField *&aTermP, TScriptContext *aFuncContextP)
437 TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
438 aTermP->setAsString(dsP->getRemoteDatastore()->getFullName());
439 }; // func_RemoteDBName
444 // ADDTARGETCGI(string cgi)
445 // adds CGI to the target URI. If target URI already contains a ?, string will be just
446 // appended, otherwise a ? is added, then the new CGI.
447 // Note: if string to be added is already contained, it will not be added again
448 static void func_AddTargetCGI(TItemField *&aTermP, TScriptContext *aFuncContextP)
450 TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
451 // Add extra CGI specified
453 aFuncContextP->getLocalVar(0)->getAsString(cgi);
454 addCGItoString(dsP->fRemoteDBPath,cgi.c_str(),true);
455 }; // func_AddTargetCGI
458 // SETRECORDFILTER(string filter, boolean inclusive)
459 // Sets record level filter expression for remote
460 static void func_SetRecordFilter(TItemField *&aTermP, TScriptContext *aFuncContextP)
462 TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
463 // put into record level filter
464 if (dsP->getSession()->getSyncMLVersion()>=syncml_vers_1_2) {
465 // DS 1.2: use <filter>
466 aFuncContextP->getLocalVar(0)->getAsString(dsP->fRemoteRecordFilterQuery);
467 dsP->fRemoteFilterInclusive = aFuncContextP->getLocalVar(1)->getAsBoolean();
469 else if (!aFuncContextP->getLocalVar(0)->isEmpty()) {
470 // DS 1.1 and below and not empty filter: add as cgi
472 if (aFuncContextP->getLocalVar(1)->getAsBoolean())
473 filtercgi = "/tf("; // exclusive, use TAF
475 filtercgi = "/fi("; // inclusive, use sync set filter
476 aFuncContextP->getLocalVar(0)->appendToString(filtercgi);
478 addCGItoString(dsP->fRemoteDBPath,filtercgi.c_str(),true);
480 }; // func_SetRecordFilter
483 // SETDAYSRANGE(integer daysbefore, integer daysafter)
484 // Sets type of record filter
485 static void func_SetDaysRange(TItemField *&aTermP, TScriptContext *aFuncContextP)
487 TLocalEngineDS *dsP = static_cast<TLocalEngineDS *>(aFuncContextP->getCallerContext());
489 int daysbefore = aFuncContextP->getLocalVar(0)->getAsInteger();
490 int daysafter = aFuncContextP->getLocalVar(1)->getAsInteger();
491 // depending on SyncML version, create a SINCE/BEFORE filter or use the /dr(x,y) syntax
492 if (dsP->getSession()->getSyncMLVersion()>=syncml_vers_1_2 && static_cast<TSyncAgent *>(dsP->getSession())->fServerHasSINCEBEFORE) {
493 // use the SINCE/BEFORE syntax
494 // BEFORE&EQ;20070808T000000Z&AND;SINCE&EQ;20070807T000000Z
495 lineartime_t now = getSystemNowAs(TCTX_UTC,aFuncContextP->getSessionZones());
497 // AND-chain with possibly existing filter
499 if (!dsP->fRemoteRecordFilterQuery.empty())
502 dsP->fRemoteRecordFilterQuery += sep;
503 dsP->fRemoteRecordFilterQuery += "SINCE&EQ;";
504 TimestampToISO8601Str(ts,now-daysbefore*linearDateToTimeFactor,TCTX_UTC,false,false);
505 dsP->fRemoteRecordFilterQuery += ts;
509 dsP->fRemoteRecordFilterQuery += sep;
510 dsP->fRemoteRecordFilterQuery += "BEFORE&EQ;";
511 TimestampToISO8601Str(ts,now+daysafter*linearDateToTimeFactor,TCTX_UTC,false,false);
512 dsP->fRemoteRecordFilterQuery += ts;
516 // use the /dr(-x,y) syntax
518 StringObjPrintf(rangecgi,"/dr(%ld,%ld)",(long int)(-daysbefore),(long int)(daysafter));
519 addCGItoString(dsP->fRemoteDBPath,rangecgi.c_str(),true);
521 }; // func_SetDaysRange
524 #endif // SYSYNC_CLIENT
528 const uInt8 param_FilterArg[] = { VAL(fty_string) };
529 const uInt8 param_DateArg[] = { VAL(fty_timestamp) };
530 const uInt8 param_IntArg[] = { VAL(fty_integer) };
531 const uInt8 param_StrArg[] = { VAL(fty_string) };
532 const uInt8 param_OneInteger[] = { VAL(fty_integer) };
534 const TBuiltInFuncDef DBFuncDefs[] = {
535 #ifdef OBJECT_FILTERING
536 #ifdef SYNCML_TAF_SUPPORT
537 { "GETCGITARGETFILTER", TLDSfuncs::func_GetCGITargetFilter, fty_string, 0, NULL },
538 { "GETTARGETFILTER", TLDSfuncs::func_GetTargetFilter, fty_string, 0, NULL },
539 { "SETTARGETFILTER", TLDSfuncs::func_SetTargetFilter, fty_none, 1, param_FilterArg },
540 { "ADDTARGETFILTER", TLDSfuncs::func_AddTargetFilter, fty_none, 1, param_FilterArg },
542 { "GETFILTER", TLDSfuncs::func_GetFilter, fty_string, 0, NULL },
543 { "SETFILTER", TLDSfuncs::func_SetFilter, fty_none, 1, param_FilterArg },
544 { "ADDFILTER", TLDSfuncs::func_AddFilter, fty_none, 1, param_FilterArg },
545 { "ADDSTATICFILTER", TLDSfuncs::func_AddStaticFilter, fty_none, 1, param_FilterArg },
547 #ifdef SYSYNC_TARGET_OPTIONS
548 { "DBOPTIONS", TLDSfuncs::func_DBOptions, fty_string, 0, NULL },
549 { "STARTDATE", TLDSfuncs::func_StartDate, fty_timestamp, 0, NULL },
550 { "ENDDATE", TLDSfuncs::func_EndDate, fty_timestamp, 0, NULL },
551 { "SETSTARTDATE", TLDSfuncs::func_SetStartDate, fty_none, 1, param_DateArg },
552 { "SETENDDATE", TLDSfuncs::func_SetEndDate, fty_none, 1, param_DateArg },
553 { "MAXITEMCOUNT", TLDSfuncs::func_MaxItemCount, fty_integer, 0, NULL },
554 { "SETMAXITEMCOUNT", TLDSfuncs::func_SetMaxItemCount, fty_none, 1, param_IntArg },
555 { "NOATTACHMENTS", TLDSfuncs::func_NoAttachments, fty_integer, 0, NULL },
556 { "SETNOATTACHMENTS", TLDSfuncs::func_SetNoAttachments, fty_none, 1, param_IntArg },
557 { "DEFAULTSIZELIMIT", TLDSfuncs::func_DefaultLimit, fty_integer, 0, NULL },
558 { "SETDEFAULTSIZELIMIT", TLDSfuncs::func_SetDefaultLimit, fty_none, 1, param_IntArg },
559 { "DBHANDLESOPTS", TLDSfuncs::func_DBHandlesOpts, fty_integer, 0, NULL },
561 { "ALERTCODE", TLDSfuncs::func_AlertCode, fty_integer, 0, NULL },
562 { "SETALERTCODE", TLDSfuncs::func_SetAlertCode, fty_none, 1, param_IntArg },
563 { "SLOWSYNC", TLDSfuncs::func_SlowSync, fty_integer, 0, NULL },
564 { "FORCESLOWSYNC", TLDSfuncs::func_ForceSlowSync, fty_none, 0, NULL },
565 { "REFRESHONLY", TLDSfuncs::func_RefreshOnly, fty_integer, 0, NULL },
566 { "SETREFRESHONLY", TLDSfuncs::func_SetRefreshOnly, fty_none, 1, param_IntArg },
567 { "READONLY", TLDSfuncs::func_ReadOnly, fty_integer, 0, NULL },
568 { "SETREADONLY", TLDSfuncs::func_SetReadOnly, fty_none, 1, param_IntArg },
569 { "FIRSTTIMESYNC", TLDSfuncs::func_FirstTimeSync, fty_integer, 0, NULL },
570 { "SETCONFLICTSTRATEGY", TLDSfuncs::func_SetConflictStrategy, fty_none, 1, param_StrArg },
571 { "DBNAME", TLDSfuncs::func_DBName, fty_string, 0, NULL },
572 { "LOCALDBNAME", TLDSfuncs::func_LocalDBName, fty_string, 0, NULL },
573 { "REMOTEDBNAME", TLDSfuncs::func_RemoteDBName, fty_string, 0, NULL },
574 { "ABORTDATASTORE", TLDSfuncs::func_AbortDatastore, fty_none, 1, param_OneInteger },
577 // functions for all datastores
578 const TFuncTable DBFuncTable = {
579 sizeof(DBFuncDefs) / sizeof(TBuiltInFuncDef), // size of table
580 DBFuncDefs, // table pointer
581 NULL // no chain func
587 const uInt8 param_OneStr[] = { VAL(fty_string) };
588 const uInt8 param_OneInt[] = { VAL(fty_integer) };
589 const uInt8 param_TwoInt[] = { VAL(fty_integer), VAL(fty_integer) };
590 const uInt8 param_SetRecordFilter[] = { VAL(fty_string), VAL(fty_integer) };
592 const TBuiltInFuncDef ClientDBFuncDefs[] = {
593 { "ADDTARGETCGI", TLDSfuncs::func_AddTargetCGI, fty_none, 1, param_OneStr },
594 { "SETRECORDFILTER", TLDSfuncs::func_SetRecordFilter, fty_none, 2, param_SetRecordFilter },
595 { "SETDAYSRANGE", TLDSfuncs::func_SetDaysRange, fty_none, 2, param_TwoInt },
599 // chain to general DB functions
600 static void *ClientDBChainFunc(void *&aCtx)
602 // caller context remains unchanged
603 // -> no change needed
604 // next table is general DS func table
605 return (void *)&DBFuncTable;
606 } // ClientDBChainFunc
609 // function table for client-only script functions
610 const TFuncTable ClientDBFuncTable = {
611 sizeof(ClientDBFuncDefs) / sizeof(TBuiltInFuncDef), // size of table
612 ClientDBFuncDefs, // table pointer
613 ClientDBChainFunc // chain to general agent funcs.
617 #endif // SYSYNC_CLIENT
619 #endif // SCRIPT_SUPPORT
627 // conflict strategy names
628 // bfo: Problems with XCode (expicit qualification), already within namespace ?
629 //const char * const sysync::conflictStrategyNames[numConflictStrategies] = {
630 const char * const conflictStrategyNames[numConflictStrategies] = {
631 "duplicate", // add conflicting counterpart to both databases
632 "newer-wins", // newer version wins (if date/version comparison is possible, like sst_duplicate otherwise)
633 "server-wins", // server version wins (and is written to client)
634 "client-wins" // client version wins (and is written to server)
638 // type support config
639 TTypeSupportConfig::TTypeSupportConfig(const char* aName, TConfigElement *aParentElement) :
640 TConfigElement(aName,aParentElement)
643 } // TTypeSupportConfig::TTypeSupportConfig
646 TTypeSupportConfig::~TTypeSupportConfig()
649 } // TTypeSupportConfig::~TTypeSupportConfig
653 void TTypeSupportConfig::clear(void)
658 fPreferredLegacy = NULL;
659 fAdditionalTypes.clear();
660 #ifndef NO_REMOTE_RULES
661 fRuleMatchTypes.clear();
665 } // TTypeSupportConfig::clear
668 #ifdef HARDCODED_CONFIG
671 bool TTypeSupportConfig::addTypeSupport(
680 TDataTypeConfig *typecfgP =
681 static_cast<TRootConfig *>(getRootElement())->fDatatypesConfigP->getDataType(aTypeName);
682 if (!typecfgP) return false;
684 TTypeVariantDescriptor variantDescP = NULL;
685 if (aVariant && *aVariant)
686 variantDescP = typecfgP->getVariantDescriptor(aVariant);
692 fPreferredRx=typecfgP; // set it
693 fPrefRxVariantDescP=variantDescP;
698 fPreferredTx=typecfgP; // set it
699 fPrefTxVariantDescP=variantDescP;
705 TAdditionalDataType adt;
706 adt.datatypeconfig=typecfgP;
707 adt.forRead=aForRead;
708 adt.forWrite=aForWrite;
709 adt.variantDescP=variantDescP; // variant of that type
710 #ifndef NO_REMOTE_RULES
712 // this is a rulematch type (which overrides normal type selection mechanism)
713 AssignString(atd.remoteRuleMatch,aRuleMatch); // remote rule match string
714 fRuleMatchTypes.push_back(adt); // save it in the list
720 fAdditionalTypes.push_back(adt); // save it in the list
724 } // TTypeSupportConfig::addTypeSupport
728 // config element parsing
729 bool TTypeSupportConfig::localStartElement(const char *aElementName, const char **aAttributes, sInt32 aLine)
731 // checking the elements
732 if (strucmp(aElementName,"use")==0) {
733 expectEmpty(); // datatype usage specs may not have
735 const char* nam = getAttr(aAttributes,"datatype");
737 return fail("use must have 'datatype' attribute");
739 TDataTypeConfig *typecfgP =
740 static_cast<TRootConfig *>(getRootElement())->fDatatypesConfigP->getDataType(nam);
742 return fail("unknown datatype '%s' specified",nam);
743 #ifndef NO_REMOTE_RULES
744 // get rulematch string, if any
745 cAppCharP ruleMatch = getAttr(aAttributes,"rulematch");
748 TTypeVariantDescriptor variantDescP=NULL; // no variant descriptor by default
749 cAppCharP variant = getAttr(aAttributes,"variant");
751 // get a type-specific descriptor which describes the variant of a type to be used with this datastore
752 variantDescP = typecfgP->getVariantDescriptor(variant);
754 return fail("unknown variant '%s' specified",variant);
757 bool rd=true,wr=true;
758 const char* mode = getAttr(aAttributes,"mode");
763 if (tolower(*mode)=='r') rd=true;
764 else if (tolower(*mode)=='w') wr=true;
766 ReportError(true,"invalid mode '%c'",*mode);
773 return fail("mode must specify 'r', 'w' or 'rw' at least");
776 bool preferred=false;
777 const char* pref = getAttr(aAttributes,"preferred");
779 if (!StrToBool(pref, preferred)) {
780 if (strucmp(pref,"legacy")==0) {
781 // this is the preferred type for blind and legacy mode sync attempts
782 fPreferredLegacy=typecfgP; // remember (note that there is only ONE preferred type, mode is ignored)
783 preferred=false; // not officially preferred
786 return fail("bad value for 'preferred'");
794 return fail("preferred read type already defined");
796 fPreferredRx=typecfgP; // set it
797 fPrefRxVariantDescP=variantDescP;
802 return fail("preferred write type already defined");
804 fPreferredTx=typecfgP; // set it
805 fPrefTxVariantDescP=variantDescP;
811 TAdditionalDataType adt;
812 adt.datatypeconfig=typecfgP;
815 adt.variantDescP=variantDescP;
816 #ifndef NO_REMOTE_RULES
818 // this is a rulematch type (which overrides normal type selection mechanism)
819 AssignString(adt.remoteRuleMatch,ruleMatch); // remote rule match string
820 fRuleMatchTypes.push_back(adt); // save it in the list
826 fAdditionalTypes.push_back(adt); // save it in the list
832 return inherited::localStartElement(aElementName,aAttributes,aLine);
835 } // TTypeSupportConfig::localStartElement
841 void TTypeSupportConfig::localResolve(bool aLastPass)
843 #ifndef HARDCODED_CONFIG
845 // check for required settings
846 if (!fPreferredTx || !fPreferredRx)
847 SYSYNC_THROW(TConfigParseException("'typesupport' must contain at least one preferred type for read and write"));
851 inherited::localResolve(aLastPass);
852 } // TTypeSupportConfig::localResolve
858 TLocalDSConfig::TLocalDSConfig(const char* aName, TConfigElement *aParentElement) :
859 TConfigElement(aName,aParentElement),
860 fTypeSupport("typesupport",this)
863 } // TLocalDSConfig::TLocalDSConfig
866 TLocalDSConfig::~TLocalDSConfig()
869 } // TLocalDSConfig::~TLocalDSConfig
873 void TLocalDSConfig::clear(void)
876 // - conflict resolution strategy
877 fConflictStrategy=cr_newer_wins;
878 fSlowSyncStrategy=cr_newer_wins;
879 fFirstTimeStrategy=cr_newer_wins;
885 fDeleteWins=false; // replace wins over delete by default
886 fResendFailing=true; // resend failing items in next session by default
888 fTryUpdateDeleted=false; // no attempt to update already deleted items (assuming they are invisible only)
889 fAlwaysSendLocalID=false; // off as it used to be not SCTS conformant (but would give clients chances to remap IDs)
891 fMaxItemsPerMessage=0; // no limit
892 #ifdef OBJECT_FILTERING
894 fRemoteAcceptFilter.erase();
895 fSilentlyDiscardUnaccepted=false;
896 fLocalDBFilterConf.erase();
897 fMakePassFilter.erase();
898 fInvisibleFilter.erase();
899 fMakeVisibleFilter.erase();
900 // - DS 1.2 Filter support (<filter> allowed in Alert, <filter-rx>/<filterCap> shown in devInf)
901 fDS12FilterSupport=false; // off by default, as clients usually don't have it
902 // - Set if date range support is available in this datastore
903 fDateRangeSupported=false;
905 #ifdef SCRIPT_SUPPORT
906 fDBInitScript.erase();
907 fSentItemStatusScript.erase();
908 fReceivedItemStatusScript.erase();
909 fAlertScript.erase();
911 fAlertPrepScript.erase();
913 fDBFinishScript.erase();
916 fTypeSupport.clear();
919 } // TLocalDSConfig::clear
922 #ifndef HARDCODED_CONFIG
924 // config element parsing
925 bool TLocalDSConfig::localStartElement(const char *aElementName, const char **aAttributes, sInt32 aLine)
927 // checking the elements
928 if (strucmp(aElementName,"dbtypeid")==0)
929 expectUInt32(fLocalDBTypeID);
930 else if (strucmp(aElementName,"typesupport")==0)
931 expectChildParsing(fTypeSupport);
932 else if (strucmp(aElementName,"conflictstrategy")==0)
933 expectEnum(sizeof(fConflictStrategy),&fConflictStrategy,conflictStrategyNames,numConflictStrategies);
934 else if (strucmp(aElementName,"slowsyncstrategy")==0)
935 expectEnum(sizeof(fSlowSyncStrategy),&fSlowSyncStrategy,conflictStrategyNames,numConflictStrategies);
936 else if (strucmp(aElementName,"firsttimestrategy")==0)
937 expectEnum(sizeof(fFirstTimeStrategy),&fFirstTimeStrategy,conflictStrategyNames,numConflictStrategies);
938 else if (strucmp(aElementName,"readonly")==0)
939 expectBool(fReadOnly);
940 else if (strucmp(aElementName,"canrestart")==0)
941 expectBool(fCanRestart);
942 else if (strucmp(aElementName,"syncmode")==0) {
943 if (!fSyncModeBuffer.empty()) {
944 fSyncModes.insert(fSyncModeBuffer);
945 fSyncModeBuffer.clear();
947 expectString(fSyncModeBuffer);
948 } else if (strucmp(aElementName,"reportupdates")==0)
949 expectBool(fReportUpdates);
950 else if (strucmp(aElementName,"deletewins")==0)
951 expectBool(fDeleteWins);
952 else if (strucmp(aElementName,"resendfailing")==0)
953 expectBool(fResendFailing);
955 else if (strucmp(aElementName,"tryupdatedeleted")==0)
956 expectBool(fTryUpdateDeleted);
957 else if (strucmp(aElementName,"alwayssendlocalid")==0)
958 expectBool(fAlwaysSendLocalID);
959 else if (strucmp(aElementName,"alias")==0) {
962 if (!getAttrExpanded(aAttributes, "name", name, true))
963 return fail("Missing 'name' attribute in 'alias'");
964 fAliasNames.push_back(name);
968 else if (strucmp(aElementName,"maxitemspermessage")==0)
969 expectUInt32(fMaxItemsPerMessage);
970 #ifdef OBJECT_FILTERING
972 else if (strucmp(aElementName,"acceptfilter")==0)
973 expectString(fRemoteAcceptFilter);
974 else if (strucmp(aElementName,"silentdiscard")==0)
975 expectBool(fSilentlyDiscardUnaccepted);
976 else if (strucmp(aElementName,"localdbfilter")==0)
977 expectString(fLocalDBFilterConf);
978 else if (strucmp(aElementName,"makepassfilter")==0)
979 expectString(fMakePassFilter);
980 else if (strucmp(aElementName,"invisiblefilter")==0)
981 expectString(fInvisibleFilter);
982 else if (strucmp(aElementName,"makevisiblefilter")==0)
983 expectString(fMakeVisibleFilter);
984 else if (strucmp(aElementName,"ds12filters")==0)
985 expectBool(fDS12FilterSupport);
986 else if (strucmp(aElementName,"daterangesupport")==0)
987 expectBool(fDateRangeSupported);
989 #ifdef SCRIPT_SUPPORT
990 else if (strucmp(aElementName,"datastoreinitscript")==0)
991 expectScript(fDBInitScript,aLine,&DBFuncTable);
992 else if (strucmp(aElementName,"sentitemstatusscript")==0)
993 expectScript(fSentItemStatusScript,aLine,&ErrorFuncTable);
994 else if (strucmp(aElementName,"receiveditemstatusscript")==0)
995 expectScript(fReceivedItemStatusScript,aLine,&ErrorFuncTable);
996 else if (strucmp(aElementName,"alertscript")==0)
997 expectScript(fAlertScript,aLine,&DBFuncTable);
999 else if (strucmp(aElementName,"alertprepscript")==0)
1000 expectScript(fAlertPrepScript,aLine,getClientDBFuncTable());
1002 else if (strucmp(aElementName,"datastorefinishscript")==0)
1003 expectScript(fDBFinishScript,aLine,&DBFuncTable);
1005 #ifndef MINIMAL_CODE
1006 else if (strucmp(aElementName,"displayname")==0)
1007 expectString(fDisplayName);
1009 // - none known here
1011 return inherited::localStartElement(aElementName,aAttributes,aLine);
1014 } // TLocalDSConfig::localStartElement
1019 void TLocalDSConfig::localResolve(bool aLastPass)
1021 if (!fSyncModeBuffer.empty()) {
1022 fSyncModes.insert(fSyncModeBuffer);
1023 fSyncModeBuffer.clear();
1027 #ifdef SCRIPT_SUPPORT
1028 TScriptContext *sccP = NULL;
1030 // resolve all scripts in same context
1031 // - first script needed (when alert is created)
1032 #ifdef SYSYNC_CLIENT
1033 TScriptContext::resolveScript(getSyncAppBase(),fAlertPrepScript,sccP,NULL);
1035 // - scripts needed when database is made ready
1036 TScriptContext::resolveScript(getSyncAppBase(),fDBInitScript,sccP,NULL);
1037 TScriptContext::resolveScript(getSyncAppBase(),fSentItemStatusScript,sccP,NULL);
1038 TScriptContext::resolveScript(getSyncAppBase(),fReceivedItemStatusScript,sccP,NULL);
1039 TScriptContext::resolveScript(getSyncAppBase(),fAlertScript,sccP,NULL);
1040 TScriptContext::resolveScript(getSyncAppBase(),fDBFinishScript,sccP,NULL);
1041 // - forget this context
1042 if (sccP) delete sccP;
1045 if (sccP) delete sccP;
1051 fTypeSupport.Resolve(aLastPass);
1052 // resolve inherited
1053 inherited::localResolve(aLastPass);
1054 } // TLocalDSConfig::localResolve
1057 // - add type support to datatstore from config
1058 void TLocalDSConfig::addTypes(TLocalEngineDS *aDatastore, TSyncSession *aSessionP)
1060 TSyncItemType *typeP;
1061 TSyncItemType *writetypeP;
1063 // preferred types, create instances only if not already existing
1064 // - preferred receive
1065 typeP = aSessionP->findLocalType(fTypeSupport.fPreferredRx);
1067 typeP = fTypeSupport.fPreferredRx->newSyncItemType(aSessionP,NULL); // local types are never exclusively related to a datastore
1068 aSessionP->addLocalItemType(typeP); // add to session
1071 writetypeP = aSessionP->findLocalType(fTypeSupport.fPreferredTx);
1073 writetypeP = fTypeSupport.fPreferredTx->newSyncItemType(aSessionP,NULL); // local types are never exclusively related to a datastore
1074 aSessionP->addLocalItemType(writetypeP);
1076 // - set preferred types
1077 aDatastore->setPreferredTypes(typeP,writetypeP);
1079 TAdditionalTypesList::iterator pos;
1080 for (pos=fTypeSupport.fAdditionalTypes.begin(); pos!=fTypeSupport.fAdditionalTypes.end(); pos++) {
1081 // - search for type already created from same config item
1082 typeP = aSessionP->findLocalType((*pos).datatypeconfig);
1084 // - does not exist yet, create the type
1085 typeP = (*pos).datatypeconfig->newSyncItemType(aSessionP,NULL); // local types are never exclusively related to a datastore
1086 // - add type to session types
1087 aSessionP->addLocalItemType(typeP);
1089 // - add type to datastore's supported types
1090 aDatastore->addTypeSupport(typeP,(*pos).forRead,(*pos).forWrite);
1092 #ifndef NO_REMOTE_RULES
1094 for (pos=fTypeSupport.fRuleMatchTypes.begin(); pos!=fTypeSupport.fRuleMatchTypes.end(); pos++) {
1095 // - search for type already created from same config item
1096 typeP = aSessionP->findLocalType((*pos).datatypeconfig);
1098 // - does not exist yet, create the type
1099 typeP = (*pos).datatypeconfig->newSyncItemType(aSessionP,NULL); // local types are never exclusively related to a datastore
1100 // - add type to session types
1101 aSessionP->addLocalItemType(typeP);
1103 // - add type to datastore's rulematch types
1104 aDatastore->addRuleMatchTypeSupport(typeP,(*pos).remoteRuleMatch.c_str());
1107 // now apply type limits
1108 // Note: this is usually derived, as limits are often defined within the datastore,
1109 // not the type itself (however, for hardcoded template-based fieldlists,
1110 // the limits are taken from the template, see TLocalDSConfig::addTypeLimits()
1111 addTypeLimits(aDatastore, aSessionP);
1112 } // TLocalDSConfig::addTypes
1115 // Add (probably datastore-specific) limits such as MaxSize and NoTruncate to types
1116 void TLocalDSConfig::addTypeLimits(TLocalEngineDS *aLocalDatastoreP, TSyncSession *aSessionP)
1118 // add field size limitations from map to all types
1119 TSyncItemTypePContainer::iterator pos;
1120 TSyncItemTypePContainer *typesP = &(aLocalDatastoreP->fRxItemTypes);
1121 for (uInt8 i=0; i<2; i++) {
1122 for (pos=typesP->begin(); pos!=typesP->end(); pos++) {
1123 // apply default limits to type (e.g. from hard-coded template in config)
1124 (*pos)->addDefaultTypeLimits();
1126 typesP = &(aLocalDatastoreP->fTxItemTypes);
1128 } // TLocalDSConfig::addTypeLimits
1131 // Check for alias names
1132 uInt16 TLocalDSConfig::isDatastoreAlias(cAppCharP aDatastoreURI)
1134 // only servers have (and may need) aliases
1135 #ifdef SYSYNC_SERVER
1136 for (TStringList::iterator pos = fAliasNames.begin(); pos!=fAliasNames.end(); pos++) {
1137 if (*pos == aDatastoreURI)
1138 return (*pos).size(); // return size of match
1142 } // TLocalDSConfig::isDatastoreAlias
1148 * Implementation of TLocalEngineDS
1152 /// @Note InternalResetDataStore() must also be callable from destructor
1153 /// (care not to call other objects which will refer to the already
1154 /// half-destructed datastore!)
1155 void TLocalEngineDS::InternalResetDataStore(void)
1157 // possibly complete, if not already done (should be, by engFinishDataStoreSync() !)
1158 if (fLocalDSState>dssta_idle)
1159 changeState(dssta_completed); // complete NOW, opportunity to show stats, etc.
1160 // switch down to idle
1161 changeState(dssta_idle);
1162 /// @todo obsolete: fState=dss_idle;
1163 fAbortStatusCode=LOCERR_OK; // not aborted yet
1164 fLocalAbortCause=true; // assume local cause
1165 fRemoteAddingStopped=false;
1166 fAlertCode=0; // not yet alerted
1168 /// Init Sync mode @ref dsSyncMode
1169 fSyncMode=smo_twoway; // default to twoway
1170 fForceSlowSync=false;
1174 fReportUpdates=fDSConfigP->fReportUpdates; // update reporting according to what is configured
1175 fCanRestart=fDSConfigP->fCanRestart;
1176 fSyncModes=fDSConfigP->fSyncModes;
1177 fServerAlerted=false;
1179 #ifdef SUPERDATASTORES
1180 fAsSubDatastoreOf=NULL; // is not a subdatastore
1184 /// Init administrative data to defaults @ref dsAdminData
1186 fLastRemoteAnchor.erase();
1187 fLastLocalAnchor.erase();
1189 fNextRemoteAnchor.erase();
1190 fNextLocalAnchor.erase();
1192 fResumeAlertCode=0; // none
1193 fPreventResuming=false;
1194 // suspend of chunked items
1195 fPartialItemState=pi_state_none;
1196 fLastSourceURI.erase();
1197 fLastTargetURI.erase();
1201 fPIUnconfirmedSize=0;
1202 if (fPIStoredDataAllocated) {
1203 smlLibFree(fPIStoredDataP);
1204 fPIStoredDataAllocated=false;
1206 fPIStoredDataP=NULL;
1209 fFirstTimeSync=false; // not first sync by default
1211 #ifdef SYSYNC_CLIENT
1212 // - maps for add commands
1213 fPendingAddMaps.clear();
1214 fUnconfirmedMaps.clear();
1215 fLastSessionMaps.clear();
1217 #ifdef SYSYNC_SERVER
1218 PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,(
1219 "fTempGUIDMap: removing %ld items", (long)fTempGUIDMap.size()
1221 fTempGUIDMap.clear();
1224 /// Init type negotiation
1225 /// - for sending data
1226 fLocalSendToRemoteTypeP = NULL;
1227 fRemoteReceiveFromLocalTypeP = NULL;
1228 /// - for receiving data
1229 fLocalReceiveFromRemoteTypeP = NULL;
1230 fRemoteSendToLocalTypeP = NULL;
1232 /// Init Filtering @ref dsFiltering
1235 /// Init item processing @ref dsItemProcessing
1236 fSessionConflictStrategy=cr_duplicate; // will be updated later when sync mode is known
1237 fItemSizeLimit=-1; // no limit yet
1238 fCurrentSyncOp = sop_none; // will be set at engProcessItem()
1239 fEchoItemOp = sop_none; // will be set at engProcessItem()
1240 fItemConflictStrategy=fSessionConflictStrategy; // will be set at engProcessItem()
1241 fForceConflict = false; // will be set at engProcessItem()
1242 fDeleteWins = false; // will be set at engProcessItem()
1243 fRejectStatus = -1; // will be set at engProcessItem()
1244 #ifdef SCRIPT_SUPPORT
1245 // - delete the script context if any
1246 if (fSendingTypeScriptContextP) {
1247 delete fSendingTypeScriptContextP;
1248 fSendingTypeScriptContextP=NULL;
1250 if (fReceivingTypeScriptContextP) {
1251 delete fReceivingTypeScriptContextP;
1252 fReceivingTypeScriptContextP=NULL;
1254 if (fDataStoreScriptContextP) {
1255 delete fDataStoreScriptContextP;
1256 fDataStoreScriptContextP=NULL;
1260 /// Init other vars @ref dsOther
1263 /// Init Counters and statistics @ref dsCountStats
1264 // - NOC from remote
1265 fRemoteNumberOfChanges=-1; // none known yet
1266 // - data transferred
1267 fIncomingDataBytes=0;
1268 fOutgoingDataBytes=0;
1269 // - locally performed ops
1271 fLocalItemsUpdated=0;
1272 fLocalItemsDeleted=0;
1274 // - remotely performed ops
1275 fRemoteItemsAdded=0;
1276 fRemoteItemsUpdated=0;
1277 fRemoteItemsDeleted=0;
1278 fRemoteItemsError=0;
1279 #ifdef SYSYNC_SERVER
1281 fConflictsServerWins=0;
1282 fConflictsClientWins=0;
1283 fConflictsDuplicated=0;
1284 // - slow sync matches
1288 // Override defaults from ancestor
1289 // - generally, limit GUID size to reasonable size (even if we can
1290 // theoretically handle unlimited GUIDs in client, except for
1291 // some DEBUGPRINTF statements that will crash for example with
1292 // the brain-damaged GUIDs that Exchange server uses.
1294 } // TLocalEngineDS::InternalResetDataStore
1298 TLocalEngineDS::TLocalEngineDS(TLocalDSConfig *aDSConfigP, TSyncSession *aSessionP, const char *aName, uInt32 aCommonSyncCapMask) :
1299 TSyncDataStore(aSessionP, aName, aCommonSyncCapMask)
1300 ,fPIStoredDataP(NULL)
1301 ,fPIStoredDataAllocated(false)
1302 #ifdef SCRIPT_SUPPORT
1303 ,fSendingTypeScriptContextP(NULL) // no associated script context
1304 ,fReceivingTypeScriptContextP(NULL) // no associated script context
1305 ,fDataStoreScriptContextP(NULL) // no datastore level context
1307 ,fRemoteDatastoreP(NULL) // no associated remote
1310 fDSConfigP = aDSConfigP;
1312 SYSYNC_THROW(TSyncException(DEBUGTEXT("TLocalEngineDS::TLocalEngineDS called with NULL config","lds1")));
1313 /// Init Sync state @ref dsSyncState
1314 fLocalDSState=dssta_idle; // idle to begin with
1316 InternalResetDataStore();
1317 } // TLocalEngineDS::TLocalEngineDS
1322 TLocalEngineDS::~TLocalEngineDS()
1325 InternalResetDataStore();
1326 } // TLocalEngineDS::~TLocalEngineDS
1331 // return datastore state name
1332 cAppCharP TLocalEngineDS::getDSStateName(void)
1334 return LocalDSStateNames[fLocalDSState];
1335 } // TLocalEngineDS::getDSStateName
1338 // return datastore state name
1339 cAppCharP TLocalEngineDS::getDSStateName(TLocalEngineDSState aState)
1341 return LocalDSStateNames[aState];
1342 } // TLocalEngineDS::getDSStateName
1346 // reset datastore (after use)
1347 void TLocalEngineDS::engResetDataStore(void)
1350 // - logic layer and above
1353 InternalResetDataStore();
1355 inherited::engResetDataStore();
1356 } // TLocalEngineDS::engResetDataStore
1360 // check if this datastore is accessible with given URI
1361 // NOTE: By default, local datastore type is addressed with
1362 // first path element of URI, rest of path might be used
1363 // by derivates to subselect data folders etc.
1364 uInt16 TLocalEngineDS::isDatastore(const char *aDatastoreURI)
1366 // extract base name
1368 analyzeName(aDatastoreURI,&basename);
1369 // compare only base name
1370 // - compare with main name
1371 int res = inherited::isDatastore(basename.c_str());
1373 // Not main name: compare with aliases
1374 res = fDSConfigP->isDatastoreAlias(basename.c_str());
1377 } // TLocalEngineDS::isDatastore
1381 /// get DB specific error message text for dbg log, or empty string if none
1382 /// @return platform specific DB error text
1383 string TLocalEngineDS::lastDBErrorText(void)
1387 uInt32 err = lastDBError();
1388 if (isDBError(err)) {
1389 StringObjPrintf(s," (DB specific error code = %ld)",(long)lastDBError());
1392 } // TLocalEngineDS::lastDBErrorText
1395 #ifdef SYSYNC_CLIENT
1397 // - init Sync Parameters (client case)
1398 // Derivates might override this to pre-process and modify parameters
1399 // (such as adding client settings as CGI to remoteDBPath)
1400 bool TLocalEngineDS::dsSetClientSyncParams(
1401 TSyncModes aSyncMode,
1403 const char *aRemoteDBPath,
1404 const char *aDBUser,
1405 const char *aDBPassword,
1406 const char *aLocalPathExtension,
1407 const char *aRecordFilterQuery,
1408 bool aFilterInclusive
1411 // - set remote params
1412 fRemoteDBPath=aRemoteDBPath;
1413 AssignString(fDBUser,aDBUser);
1414 AssignString(fDBPassword,aDBPassword);
1415 // check for running under control of a superdatastore
1416 // - aRemoteDBPath might contain a special prefix: "<super>remote", with "super" specifying the
1417 // name of a local superdatastore to run the sync with
1419 if (!fRemoteDBPath.empty() && fRemoteDBPath.at(0)=='<') {
1420 // we have an option prefix
1421 size_t pfxe = fRemoteDBPath.find('>', 1);
1422 if (pfxe!=string::npos) {
1424 opts.assign(fRemoteDBPath, 1, pfxe-1);
1425 // store remote path cleaned from options
1426 fRemoteDBPath.erase(0,pfxe+1);
1429 if (!opts.empty()) {
1430 #ifdef SUPERDATASTORES
1431 // For now, the only option withing angle brackets is the name of the superdatastore, so opts==superdatastorename
1432 // - look for superdatastore having the specified name
1433 TSuperDSConfig *superdscfgP = static_cast<TSuperDSConfig *>(getSession()->getSessionConfig()->getLocalDS(opts.c_str()));
1434 if (superdscfgP && superdscfgP->isAbstractDatastore()) {
1435 // see if we have an instance of this already
1436 fAsSubDatastoreOf = static_cast<TSuperDataStore *>(getSession()->findLocalDataStore(superdscfgP));
1437 if (fAsSubDatastoreOf) {
1438 // that superdatastore already exists, just override client sync params with those already set
1439 aSyncMode = fAsSubDatastoreOf->fSyncMode;
1440 aSlowSync = fAsSubDatastoreOf->fSlowSync;
1441 aRecordFilterQuery = fAsSubDatastoreOf->fRemoteRecordFilterQuery.c_str();
1444 // instantiate new superdatastore
1445 fAsSubDatastoreOf = static_cast<TSuperDataStore *>(superdscfgP->newLocalDataStore(getSession()));
1446 if (fAsSubDatastoreOf) {
1447 fSessionP->fLocalDataStores.push_back(fAsSubDatastoreOf);
1448 // configure it with the same parameters as the subdatastore
1449 if (!fAsSubDatastoreOf->dsSetClientSyncParams(
1452 fRemoteDBPath.c_str(), // already cleaned from <xxx> prefix
1455 aLocalPathExtension,
1459 return false; // failed
1462 if (fAsSubDatastoreOf) {
1463 // find link config for this superdatastore
1464 TSubDSLinkConfig *lcfgP = NULL;
1465 TSubDSConfigList::iterator pos;
1466 for(pos=superdscfgP->fSubDatastores.begin();pos!=superdscfgP->fSubDatastores.end();pos++) {
1467 if ((*pos)->fLinkedDSConfigP==fDSConfigP) {
1474 // now link into superdatastore
1475 fAsSubDatastoreOf->addSubDatastoreLink(lcfgP,this);
1478 PDEBUGPRINTFX(DBG_ERROR,("Warning: '%s' is not a subdatastore of '%s'", getName(), opts.c_str()));
1479 return false; // failed
1484 PDEBUGPRINTFX(DBG_ERROR,("Warning: No superdatastore name '%s' exists -> can't run '%s' under superdatastore control", opts.c_str(), getName()));
1485 return false; // failed
1487 #endif // SUPERDATASTORES
1490 fSyncMode=aSyncMode;
1491 fSlowSync=aSlowSync;
1492 fRefreshOnly=fSyncMode==smo_fromserver; // here to make sure, should be set by setSyncMode(FromAlertCode) later anyway
1494 AssignString(fRemoteRecordFilterQuery,aRecordFilterQuery);
1495 fRemoteFilterInclusive=aFilterInclusive;
1497 // - build local path
1498 fLocalDBPath=URI_RELPREFIX;
1499 fLocalDBPath+=getName();
1500 if (aLocalPathExtension && *aLocalPathExtension) {
1502 fLocalDBPath+=aLocalPathExtension;
1504 // - we have the params for syncing now
1505 return changeState(dssta_clientparamset)==LOCERR_OK;
1506 } // TLocalEngineDS::dsSetClientSyncParams
1508 #endif // SYSYNC_CLIENT
1512 // add support for more data types
1513 // (for programatically creating local datastores from specialized TSyncSession derivates)
1514 void TLocalEngineDS::addTypeSupport(TSyncItemType *aItemTypeP,bool aForRx, bool aForTx)
1516 if (aForRx) fRxItemTypes.push_back(aItemTypeP);
1517 if (aForTx) fTxItemTypes.push_back(aItemTypeP);
1518 } // TLocalEngineDS::addTypeSupport
1521 #ifndef NO_REMOTE_RULES
1522 // add data type that overrides normal type selection if string matches active remote rule
1523 void TLocalEngineDS::addRuleMatchTypeSupport(TSyncItemType *aItemTypeP,cAppCharP aRuleMatchString)
1525 TRuleMatchTypeEntry rme;
1526 rme.itemTypeP = aItemTypeP;
1527 rme.ruleMatchString = aRuleMatchString;
1528 fRuleMatchItemTypes.push_back(rme);
1529 } // TLocalEngineDS::addRuleMatchTypeSupport
1533 TTypeVariantDescriptor TLocalEngineDS::getVariantDescForType(TSyncItemType *aItemTypeP)
1535 // search in config for specific variant descriptor
1536 // - first check preferred rx
1537 if (fDSConfigP->fTypeSupport.fPreferredRx == aItemTypeP->getTypeConfig())
1538 return fDSConfigP->fTypeSupport.fPrefRxVariantDescP;
1539 // - then check preferred tx
1540 if (fDSConfigP->fTypeSupport.fPreferredTx == aItemTypeP->getTypeConfig())
1541 return fDSConfigP->fTypeSupport.fPrefTxVariantDescP;
1542 // - then check additional types
1543 TAdditionalTypesList::iterator pos;
1544 for (pos=fDSConfigP->fTypeSupport.fAdditionalTypes.begin(); pos!=fDSConfigP->fTypeSupport.fAdditionalTypes.end(); pos++) {
1545 if ((*pos).datatypeconfig == aItemTypeP->getTypeConfig())
1546 return (*pos).variantDescP;
1550 } // TLocalEngineDS::getVariantDescForType
1555 // - called when a item in the sync set changes its localID (due to local DB internals)
1556 void TLocalEngineDS::dsLocalIdHasChanged(const char *aOldID, const char *aNewID)
1558 #ifdef SYSYNC_SERVER
1560 // make sure remapped localIDs get updated as well
1561 TStringToStringMap::iterator pos;
1562 for (pos=fTempGUIDMap.begin(); pos!=fTempGUIDMap.end(); pos++) {
1563 if (pos->second == aOldID) {
1565 PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,(
1566 "fTempGUIDMap: updating mapping of %s from %s to %s",
1571 pos->second = aNewID;
1576 #endif // SYSYNC_SERVER
1577 } // TLocalEngineDS::dsLocalIdHasChanged
1580 #ifdef SYSYNC_SERVER
1582 // for received GUIDs (Map command), obtain real GUID (might be temp GUID due to maxguidsize restrictions)
1583 void TLocalEngineDS::obtainRealLocalID(string &aLocalID)
1585 if (aLocalID.size()>0 && aLocalID[0]=='#') {
1586 // seems to be a temp GUID
1587 TStringToStringMap::iterator pos =
1588 fTempGUIDMap.find(aLocalID);
1589 if (pos!=fTempGUIDMap.end()) {
1590 // found temp GUID mapping, replace it
1591 PDEBUGPRINTFX(DBG_DATA,(
1592 "translated tempLocalID='%s' back to real localID='%s'",
1594 (*pos).second.c_str()
1596 aLocalID = (*pos).second;
1599 PDEBUGPRINTFX(DBG_ERROR,("No realLocalID found for tempLocalID='%s'",aLocalID.c_str()));
1602 } // TLocalEngineDS::obtainRealLocalID
1605 // for sending GUIDs (Add command), generate temp GUID which conforms to maxguidsize of remote datastore if needed
1606 void TLocalEngineDS::adjustLocalIDforSize(string &aLocalID, sInt32 maxguidsize, sInt32 prefixsize)
1608 if (maxguidsize>0) {
1609 if (aLocalID.length()+prefixsize>(uInt32)maxguidsize) { //BCPPB needed unsigned cast
1610 // real GUID is too long, we need to create a temp
1612 // first check if there is already a mapping for it,
1613 // because on-disk storage can only hold one; also
1615 // TODO: implement this more efficiently than this O(N) search
1616 for (TStringToStringMap::const_iterator it = fTempGUIDMap.begin();
1617 it != fTempGUIDMap.end();
1619 if (it->second == aLocalID) {
1620 // found an existing mapping!
1621 PDEBUGPRINTFX(DBG_ERROR,(
1622 "fTempGUIDMap: translated realLocalID='%s' to tempLocalID='%s' (reused?!)",
1626 aLocalID = it->first;
1631 long counter = fTempGUIDMap.size(); // as list only grows, we have unique tempuids for sure
1634 StringObjPrintf(tempguid,"#%ld",counter);
1635 if (fTempGUIDMap.find(tempguid) != fTempGUIDMap.end()) {
1636 PDEBUGPRINTFX(DBG_ERROR,(
1637 "fTempGUIDMap: '%s' not new?!",
1645 // rely on tempguid list only growing (which still holds true)
1647 StringObjPrintf(tempguid,"#%ld",(long)fTempGUIDMap.size()+1); // as list only grows, we have unique tempuids for sure
1649 fTempGUIDMap[tempguid]=aLocalID;
1650 PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,(
1651 "fTempGUIDMap: translated realLocalID='%s' to tempLocalID='%s'",
1658 } // TLocalEngineDS::adjustLocalIDforSize
1660 #endif // SYSYNC_SERVER
1663 // set Sync types needed for sending local data to remote DB
1664 void TLocalEngineDS::setSendTypeInfo(
1665 TSyncItemType *aLocalSendToRemoteTypeP,
1666 TSyncItemType *aRemoteReceiveFromLocalTypeP
1669 fLocalSendToRemoteTypeP=aLocalSendToRemoteTypeP;
1670 fRemoteReceiveFromLocalTypeP=aRemoteReceiveFromLocalTypeP;
1671 } // TLocalEngineDS::setSendTypeInfo
1674 // set Sync types needed for receiving remote data in local DB
1675 void TLocalEngineDS::setReceiveTypeInfo(
1676 TSyncItemType *aLocalReceiveFromRemoteTypeP,
1677 TSyncItemType *aRemoteSendToLocalTypeP
1680 fLocalReceiveFromRemoteTypeP=aLocalReceiveFromRemoteTypeP;
1681 fRemoteSendToLocalTypeP=aRemoteSendToLocalTypeP;
1682 } // TLocalEngineDS::setReceiveTypeInfo
1685 // - init usage of datatypes set with setSendTypeInfo/setReceiveTypeInfo
1686 localstatus TLocalEngineDS::initDataTypeUse(void)
1688 localstatus sta = LOCERR_OK;
1690 // check compatibility
1692 !fLocalSendToRemoteTypeP || !fLocalReceiveFromRemoteTypeP ||
1693 !fLocalSendToRemoteTypeP->isCompatibleWith(fLocalReceiveFromRemoteTypeP)
1695 // send and receive types not compatible
1697 PDEBUGPRINTFX(DBG_ERROR,("Incompatible send and receive types -> cannot sync (415)"));
1698 engAbortDataStoreSync(sta,true,false); // do not proceed with sync of this datastore, local problem, not resumable
1701 #ifdef SCRIPT_SUPPORT
1702 // let types initialize themselves for being used by this datastore
1703 // - optimization: if both types are same, initialize only once
1704 if (fLocalSendToRemoteTypeP == fLocalReceiveFromRemoteTypeP)
1705 fLocalReceiveFromRemoteTypeP->initDataTypeUse(this,true,true); // send and receive
1707 fLocalSendToRemoteTypeP->initDataTypeUse(this,true,false); // for sending, not receiving
1708 fLocalReceiveFromRemoteTypeP->initDataTypeUse(this,false,true); // not sending, for receiving
1713 } // TLocalEngineDS::initDataTypeUse
1717 // conflict resolution strategy. Conservative here
1718 TConflictResolution TLocalEngineDS::getConflictStrategy(bool aForSlowSync, bool aForFirstTime)
1720 return aForSlowSync ?
1721 (aForFirstTime ? fDSConfigP->fFirstTimeStrategy : fDSConfigP->fSlowSyncStrategy) :
1722 fDSConfigP->fConflictStrategy;
1723 } // TLocalEngineDS::getConflictStrategy
1727 // add filter keywords and property names to filterCap
1728 void TLocalEngineDS::addFilterCapPropsAndKeywords(SmlPcdataListPtr_t &aFilterKeywords, SmlPcdataListPtr_t &aFilterProps)
1730 #ifdef OBJECT_FILTERING
1731 // add my own properties
1732 if (fDSConfigP->fDateRangeSupported) {
1733 addPCDataStringToList("BEFORE", &aFilterKeywords);
1734 addPCDataStringToList("SINCE", &aFilterKeywords);
1736 // get default send type
1737 TSyncItemType *itemTypeP = fSessionP->findLocalType(fDSConfigP->fTypeSupport.fPreferredTx);
1738 TTypeVariantDescriptor variantDesc = NULL;
1739 doesUseType(itemTypeP, &variantDesc);
1740 // have it add it's keywords and properties
1741 itemTypeP->addFilterCapPropsAndKeywords(aFilterKeywords,aFilterProps,variantDesc);
1743 } // TLocalEngineDS::addFilterCapPropsAndKeywords
1749 // reset all filter settings
1750 void TLocalEngineDS::resetFiltering(void)
1752 #ifdef OBJECT_FILTERING
1753 // - dynamic sync set filter
1754 fSyncSetFilter.erase();
1756 fLocalDBFilter=fDSConfigP->fLocalDBFilterConf; // use configured localDB filter
1758 #ifdef SYNCML_TAF_SUPPORT
1759 fTargetAddressFilter.erase(); // none from <sync> yet
1760 fIntTargetAddressFilter.erase(); // none from internal source (script)
1762 #ifdef SYSYNC_TARGET_OPTIONS
1763 // - Other filtering options
1764 fDateRangeStart=0; // no date range
1766 fSizeLimit=-1; // no size limit
1767 fMaxItemCount=0; // no item count limit
1768 fNoAttachments=false; // attachments not suppressed
1769 fDBOptions.erase(); // no options
1771 // - Filtering requirements
1772 fTypeFilteringNeeded=false;
1773 fFilteringNeededForAll=false;
1774 fFilteringNeeded=false;
1775 #endif // OBJECT_FILTERING
1776 } // TLocalEngineDS::resetFiltering
1780 #ifdef OBJECT_FILTERING
1782 /// @brief check single filter term for DS 1.2 filterkeywords.
1783 /// @return true if term still needs to be added to normal filter expression, false if term will be handled otherwise
1784 bool TLocalEngineDS::checkFilterkeywordTerm(
1785 cAppCharP aIdent, bool aAssignToMakeTrue,
1786 cAppCharP aOp, bool aCaseInsensitive,
1787 cAppCharP aVal, bool aSpecialValue,
1788 TSyncItemType *aItemTypeP
1791 // show it to the datatype (if any)
1793 if (!aItemTypeP->checkFilterkeywordTerm(aIdent, aAssignToMakeTrue, aOp, aCaseInsensitive, aVal, aSpecialValue))
1794 return false; // type fully handles it, no need to check it further or add it to the filter expression
1796 // we generally implement BEFORE and SINCE on the datastore level
1797 // This might not make sense depending on the actual datatype, but does not harm either
1798 #ifdef SYSYNC_TARGET_OPTIONS
1801 if (strucmp(aIdent,"BEFORE")==0) {
1802 if (ISO8601StrToTimestamp(aVal,fDateRangeEnd,tctx)) {
1803 TzConvertTimestamp(fDateRangeEnd,tctx,TCTX_UTC,getSessionZones(),fSessionP->fUserTimeContext);
1806 PDEBUGPRINTFX(DBG_ERROR,("invalid ISO datetime for BEFORE: '%s'",aVal));
1807 return true; // add it to filter, possibly this is not meant to be a filterkeyword
1810 else if (strucmp(aIdent,"SINCE")==0) {
1811 if (ISO8601StrToTimestamp(aVal,fDateRangeStart,tctx)) {
1812 TzConvertTimestamp(fDateRangeStart,tctx,TCTX_UTC,getSessionZones(),fSessionP->fUserTimeContext);
1815 PDEBUGPRINTFX(DBG_ERROR,("invalid ISO datetime for SINCE: '%s'",aVal));
1816 return true; // add it to filter, possibly this is not meant to be a filterkeyword
1819 else if (strucmp(aIdent,"MAXSIZE")==0) {
1820 if (StrToFieldinteger(aVal,fSizeLimit)==0) {
1821 PDEBUGPRINTFX(DBG_ERROR,("invalid integer for MAXSIZE: '%s'",aVal));
1822 return true; // add it to filter, possibly this is not meant to be a filterkeyword
1825 else if (strucmp(aIdent,"MAXCOUNT")==0) {
1826 if (StrToULong(aVal,fMaxItemCount)==0) {
1827 PDEBUGPRINTFX(DBG_ERROR,("invalid integer for MAXSIZE: '%s'",aVal));
1828 return true; // add it to filter, possibly this is not meant to be a filterkeyword
1831 else if (strucmp(aIdent,"NOATT")==0) {
1832 if (!StrToBool(aVal,fNoAttachments)==0) {
1833 PDEBUGPRINTFX(DBG_ERROR,("invalid boolean for NOATT: '%s'",aVal));
1834 return true; // add it to filter, possibly this is not meant to be a filterkeyword
1837 else if (strucmp(aIdent,"DBOPTIONS")==0) {
1838 fDBOptions = aVal; // just get DB options
1842 // unknown identifier, add to filter expression
1845 // this term will be processed by special mechanism like fDateRangeStart/fDateRangeEnd
1846 // or fSizeLimit, so there is no need for normal filtering
1847 return false; // do not include into filter
1848 } // TLocalEngineDS::checkFilterkeywordTerm
1851 /// @brief parse "syncml:filtertype-cgi" filter, convert into internal filter syntax
1852 /// and possibly sets some special filter options (fDateRangeStart, fDateRangeEnd)
1853 /// based on "filterkeywords" available for the type passed (DS 1.2).
1854 /// For parsing DS 1.1/1.0 TAF-style filters, aItemType can be NULL, no type-specific
1855 /// filterkeywords can be parsed then.
1856 /// @return pointer to next character after processing (usually points to terminator)
1857 /// @param[in] aCGI the NUL-terminated filter string
1858 /// @param[in] aItemTypeP if not NULL, this is the item type the filter applies to
1859 const char *TLocalEngineDS::parseFilterCGI(cAppCharP aCGI, TSyncItemType *aItemTypeP, string &aFilter)
1861 const char *p=aCGI, *q;
1862 sInt16 paraNest=0; // nested paranthesis
1868 bool assigntomaketrue;
1870 bool caseinsensitive;
1872 PDEBUGPRINTFX(DBG_FILTER+DBG_EXOTIC,("Parsing syncml:filtertype-cgi filter: %s",aCGI));
1876 if (aFilter.empty()) logop=0; // ignore logical operation that would be at beginning of an expression
1878 while (isspace(*p)) p++;
1879 // now we need an ident or paranthesis
1881 if (logop) aFilter+=logop; // expression continues, we need the logop now
1882 logop=0; // now consumed
1888 // must be term: ident op val
1889 // - check special case pseudo-identifiers
1890 if (strucmp(p,"&LUID;",6)==0) {
1895 // normal identifier
1898 while (isalnum(*q) || *q=='[' || *q==']' || *q=='.' || *q=='_') q++;
1901 PDEBUGPRINTFX(DBG_ERROR,("Expected identifier but found '%s'",p));
1904 ident.assign(p,q-p);
1908 while (isspace(*p)) p++;
1909 // next must be comparison operator, possibly preceeded by modifiers
1910 op[0]=0; op[1]=0; op[2]=0;
1911 assigntomaketrue=false;
1913 caseinsensitive=false;
1914 // - check modifiers first
1915 if (*p==':') { assigntomaketrue=true; p++; }
1916 if (*p=='*') { specialvalue=true; p++; }
1917 if (*p=='^') { caseinsensitive=true; p++; }
1918 // - now OP either in internal form or as pseudo-entity
1919 if (*p=='>' || *p=='<') {
1920 // possible two-char ops (>=, <=, <>)
1922 if (*p=='>' || *p=='=') {
1926 else if (*p=='=' || *p=='%' || *p=='$') {
1927 // single char ops, just use them as is
1932 if (tolower(*p)=='i') { caseinsensitive=true; p++; }
1933 if (strucmp(p,"eq;",3)==0) { op[0]='='; p+=3; }
1934 else if (strucmp(p,"gt;",3)==0) { op[0]='>'; p+=3; }
1935 else if (strucmp(p,"ge;",3)==0) { op[0]='>'; op[1]='='; p+=3; }
1936 else if (strucmp(p,"lt;",3)==0) { op[0]='<'; p+=3; }
1937 else if (strucmp(p,"le;",3)==0) { op[0]='<'; op[1]='='; p+=3; }
1938 else if (strucmp(p,"ne;",3)==0) { op[0]='<'; op[1]='>'; p+=3; }
1939 else if (strucmp(p,"con;",4)==0) { op[0]='%'; p+=4; }
1940 else if (strucmp(p,"ncon;",5)==0) { op[0]='$'; p+=5; }
1942 PDEBUGPRINTFX(DBG_ERROR,("Expected comparison operator pseudo-entity but found '%s'",p-1));
1947 PDEBUGPRINTFX(DBG_ERROR,("Expected comparison operator but found '%s'",p));
1950 // next must be value
1951 // - check for special value cases
1952 if (strucmp(p,"&NULL;",6)==0) {
1958 else if (strucmp(p,"&UNASSIGNED;",12)==0) {
1959 // Synthesis extension
1967 // - get value chars
1968 while (*p && *p!='&' && *p!='|' && *p!=')') {
1969 // value char, possibly hex escaped
1973 if (HexStrToUShort(p+1,c,2)==2) {
1984 // now we have identifier, op and value
1985 // - check and possibly sort out filterkeyword terms
1986 termtofilter = checkFilterkeywordTerm(ident.c_str(),assigntomaketrue,op,caseinsensitive,val.c_str(),specialvalue,aItemTypeP);
1987 // - add to filter if not handled already by other mechanism
1989 if (logop) aFilter+=logop; // if this is a continuation, add logical operator now
1991 if (assigntomaketrue) aFilter+=':';
1992 if (specialvalue) aFilter+='*';
1993 if (caseinsensitive) aFilter+='^';
1998 PDEBUGPRINTFX(DBG_FILTER+DBG_EXOTIC,(
1999 "checkFilterkeywordTerm(%s,%hd,%s,%hd,%s,%hd) prevents adding to filter",
2000 ident.c_str(),(uInt16)assigntomaketrue,op,(uInt16)caseinsensitive,val.c_str(),(uInt16)specialvalue
2003 PDEBUGPRINTFX(DBG_FILTER,("Ignored logical operation '%c' due to always-ANDed filterkeyword",logop));
2006 // now check for continuation: optional closing paranthesis plus logical op
2007 // - closing paranthesis
2010 while (isspace(*p)) p++;
2013 // as we might parse filters as part of /fi() or /tf() options,
2014 // this is not an error but only means end of filter expression
2025 logop=*p++; // if no entity matches, & by itself is treated as AND
2026 if (strucmp(p,"amp;",4)==0) { logop='&'; p+=4; }
2027 else if (strucmp(p,"and;",4)==0) { logop='&'; p+=4; }
2028 else if (strucmp(p,"or;",3)==0) { logop='|'; p+=3; }
2034 PDEBUGPRINTFX(DBG_ERROR,("Expected logical operator or end of filter but found '%s'",p));
2037 } // not opening paranthesis
2038 } // while not end of filter
2040 PDEBUGPRINTFX(DBG_FILTER+DBG_EXOTIC,("Resulting internal filter: %s",aFilter.c_str()));
2041 // return pointer to terminating character
2043 } // TLocalEngineDS::parseFilterCGI
2049 // analyze database name
2050 void TLocalEngineDS::analyzeName(
2051 const char *aDatastoreURI,
2053 string *aTableNameP,
2057 const char *p,*q=NULL, *r;
2058 r=strchr(aDatastoreURI,'?');
2059 p=strchr(aDatastoreURI,'/');
2060 if (r && p>r) p=NULL; // if slash is in CGI, ignore it
2061 else q=p+1; // slash exclusive
2063 // we have more than just the first element
2064 if (aBaseNameP) aBaseNameP->assign(aDatastoreURI,p-aDatastoreURI);
2065 // rest is table name and probably CGI
2067 if (r) aTableNameP->assign(q,r-q); // we have CGI
2068 else *aTableNameP=q; // entire rest is tablename
2072 // no second path element, but possibly CGI
2073 // - assign base name
2076 (*aBaseNameP).assign(aDatastoreURI,r-aDatastoreURI); // only up to CGI
2078 (*aBaseNameP)=aDatastoreURI; // complete name
2080 // - there is no table name
2081 if (aTableNameP) aTableNameP->erase();
2083 // return CGI (w/o question mark) if any
2085 if (r) *aCGIP=r+1; else aCGIP->erase();
2087 } // TLocalEngineDS::analyzeName
2090 #ifdef SYSYNC_TARGET_OPTIONS
2092 // parses single option, returns pointer to terminating char of argument string
2094 // Note: if aArguments is passed NULL, this is an option without arguments,
2095 // and an arbitrary non-NULL will be returned if parsing is ok
2096 const char *TLocalEngineDS::parseOption(
2097 const char *aOptName,
2098 const char *aArguments,
2099 bool aFromSyncCommand
2102 #ifdef OBJECT_FILTERING
2103 if (strucmp(aOptName,"fi")==0) {
2104 if (!aArguments) return NULL;
2105 // make sync set filter expression
2107 aArguments=parseFilterCGI(aArguments,fLocalSendToRemoteTypeP,f); // if type being used for sending to remote is known here, use it
2108 if (!aFromSyncCommand) {
2109 addToFilter(f.c_str(),fSyncSetFilter,false); // AND chaining
2110 // call this once to give derivate a chance to see if it can filter the now set fSyncSetFilter
2111 engFilteredFetchesFromDB(true);
2113 return aArguments; // end of filter pattern
2115 #ifdef SYNCML_TAF_SUPPORT
2116 else if (strucmp(aOptName,"tf")==0) {
2117 if (!aArguments) return NULL;
2118 // make temporary filter (or TAF) expression
2119 aArguments=parseFilterCGI(aArguments,fLocalSendToRemoteTypeP,fTargetAddressFilter); // if type being used for sending to remote is known here, use it
2120 // Note: TAF filters are always evaluated internally as we need all SyncSet records
2121 // regardless of possible TAF suppression (for slowsync matching etc.)
2122 return aArguments; // end of filter pattern
2125 else if (aArguments && strucmp(aOptName,"dr")==0) {
2128 if (sscanf(aArguments,"%hd,%hd",&dstart,&dend)==2) {
2129 // - find end of arguments
2130 aArguments=strchr(aArguments,')');
2131 // - calculate start and end
2132 fDateRangeStart=getSystemNowAs(TCTX_UTC,getSessionZones());
2133 fDateRangeEnd=fDateRangeStart;
2134 // - now use offsets
2135 fDateRangeStart+=dstart*linearDateToTimeFactor;
2136 fDateRangeEnd+=dend*linearDateToTimeFactor;
2141 else if (aArguments && strucmp(aOptName,"li")==0) {
2143 sInt16 n=StrToFieldinteger(aArguments,fSizeLimit);
2145 // - find end of arguments
2152 if (!aArguments && strucmp(aOptName,"na")==0) {
2154 fNoAttachments=true;
2155 return (const char *)1; // non-zero
2158 if (aArguments && strucmp(aOptName,"max")==0) {
2159 // maximum number of items (for email for example)
2160 sInt16 n=StrToULong(aArguments,fMaxItemCount);
2162 // - find end of arguments
2170 #ifdef SYSYNC_SERVER
2171 if (IS_SERVER && !aArguments && strucmp(aOptName,"slow")==0) {
2172 // force a slow sync
2173 PDEBUGPRINTFX(DBG_HOT,("Slowsync forced by CGI-option in db path"));
2174 fForceSlowSync=true;
2175 return (const char *)1; // non-zero
2178 #endif // SYSYNC_SERVER
2179 if (aArguments && strucmp(aOptName,"o")==0) {
2180 // datastore options
2181 // - find end of arguments
2182 const char *p=strchr(aArguments,')');
2183 if (p) fDBOptions.assign(aArguments,p-aArguments);
2187 return NULL; // not parsed
2188 } // TLocalEngineDS::parseOption
2193 localstatus TLocalEngineDS::engParseOptions(
2194 const char *aTargetURIOptions, // option string contained in target URI
2195 bool aFromSyncCommand // must be set when parsing options from <sync> target URI
2198 localstatus sta=LOCERR_OK;
2199 if (aTargetURIOptions) {
2200 const char *p = aTargetURIOptions;
2201 #ifdef SYSYNC_TARGET_OPTIONS
2205 string taf; // official TAF
2207 #ifdef SYSYNC_TARGET_OPTIONS
2209 // proprietary option lead-in
2210 // - get option name
2213 while(isalnum(c=*(++p)))
2219 p=parseOption(optname.c_str(),p,aFromSyncCommand);
2221 // unrecognized or badly formatted option, just add it to TAF
2224 p=q; // restart after option name
2229 PDEBUGPRINTFX(DBG_ERROR,("Syntax error in target options"));
2234 // option without arguments
2235 if (!parseOption(optname.c_str(),NULL,aFromSyncCommand)) {
2237 PDEBUGPRINTFX(DBG_ERROR,("Unknown target option"));
2239 } // error, not parsed
2240 p--; // will be incremented once again below
2246 // char not part of an option
2252 // check if we have TAF
2254 #if defined(TAF_AS_SYNCSETFILTER) && defined(SYSYNC_TARGET_OPTIONS)
2255 // treat as "fi(<filterexpression>)" option like before 1.0.8.10
2256 if (!parseOption("fi",taf.c_str(),aFromSyncCommand)) { sta=406; } // error, not parsed
2258 #ifdef SYNCML_TAF_SUPPORT
2259 // treat as "tf(<filterexpression>)" = real TAF
2260 if (!parseOption("tf",taf.c_str(),aFromSyncCommand)) { sta=406; } // error, not parsed
2263 PDEBUGPRINTFX(DBG_ERROR,("TAF not supported"));
2270 } // TLocalEngineDS::engParseOptions
2273 // process SyncML 1.2 style filter
2274 localstatus TLocalEngineDS::engProcessDS12Filter(SmlFilterPtr_t aTargetFilter)
2276 localstatus sta=LOCERR_OK;
2278 if (aTargetFilter) {
2279 // check general availability
2280 #ifdef OBJECT_FILTERING
2281 if (!fDSConfigP->fDS12FilterSupport)
2284 PDEBUGPRINTFX(DBG_ERROR,("DS 1.2 style filtering is not available or disabled in config (<ds12filters>)"));
2289 TSyncItemType *itemTypeP=NULL; // no associated type so far
2290 bool inclusiveFilter=false; // default is EXCLUSIVE
2292 if (aTargetFilter->meta) {
2293 SmlMetInfMetInfPtr_t metaP = smlPCDataToMetInfP(aTargetFilter->meta);
2294 const char *typestr = smlMetaTypeToCharP(metaP);
2295 // get sync item type for it
2296 // - filter mostly applies to items SENT, so we search these first
2297 itemTypeP = getSendType(typestr,NULL);
2299 itemTypeP = getReceiveType(typestr,NULL);
2300 PDEBUGPRINTFX(DBG_FILTER,("DS12 <Filter> <Type> is '%s' -> %sfound",typestr,itemTypeP ? "" : "NOT "));
2307 if (aTargetFilter->filtertype) {
2308 const char *ftystr = smlPCDataToCharP(aTargetFilter->filtertype);
2309 if (strucmp(ftystr,SYNCML_FILTERTYPE_INCLUSIVE)==0) {
2310 inclusiveFilter=true;
2312 else if (strucmp(ftystr,SYNCML_FILTERTYPE_EXCLUSIVE)==0) {
2313 inclusiveFilter=false;
2316 PDEBUGPRINTFX(DBG_ERROR,("Invalid <FilterType> '%s'",ftystr));
2321 // - field level filter
2322 if (aTargetFilter->field) {
2323 /// @todo %%% to be implemented
2324 PDEBUGPRINTFX(DBG_ERROR,("Field-level filtering not supported"));
2328 // - record level filter
2329 if (aTargetFilter->record) {
2330 #ifdef OBJECT_FILTERING
2331 SmlItemPtr_t recordItemP = aTargetFilter->record->item;
2334 const char *grammarstr = smlMetaTypeToCharP(smlPCDataToMetInfP(recordItemP->meta));
2335 if (strucmp(grammarstr,SYNCML_FILTERTYPE_CGI)!=0) {
2336 PDEBUGPRINTFX(DBG_ERROR,("Invalid filter grammar '%s'",grammarstr));
2340 // now get the actual filter string
2341 const char *filterstring = smlPCDataToCharP(recordItemP->data);
2342 PDEBUGPRINTFX(DBG_HOT,(
2343 "Remote specified %sCLUSIVE filter query: '%s'",
2344 inclusiveFilter ? "IN" : "EX",
2347 if (*filterstring) {
2350 filterstring = parseFilterCGI(filterstring,itemTypeP,f);
2351 if (*filterstring) {
2353 PDEBUGPRINTFX(DBG_ERROR,("filter query syntax error at: '%s'",filterstring));
2357 /// @todo: %%% check if this is correct interpretation
2358 // - exclusive is what we used to call "sync set" filtering
2359 // - inclusive seems to be former TAF
2360 if (inclusiveFilter) {
2362 #ifdef SYNCML_TAF_SUPPORT
2363 fTargetAddressFilter=f;
2365 PDEBUGPRINTFX(DBG_ERROR,("This SyncML engine version has no INCLUSIVE filter support"));
2370 addToFilter(f.c_str(),fSyncSetFilter,false); // AND chaining
2371 PDEBUGPRINTFX(DBG_FILTER,("complete sync set filter is now: '%s'",fSyncSetFilter.c_str()));
2372 // call this once to give derivate a chance to see if it can filter the now set fSyncSetFilter
2373 engFilteredFetchesFromDB(true);
2378 // no object filtering
2379 PDEBUGPRINTFX(DBG_ERROR,("This SyncML engine version has no filter support (only PRO has)"));
2387 } // TLocalEngineDS::engProcessDS12Filter
2390 // process Sync alert from remote party: check if alert code is supported,
2391 // check if slow sync is needed due to anchor mismatch
2392 // - server case: also generate appropriate Alert acknowledge command
2393 TAlertCommand *TLocalEngineDS::engProcessSyncAlert(
2394 TSuperDataStore *aAsSubDatastoreOf, // if acting as subdatastore
2395 uInt16 aAlertCode, // the alert code
2396 const char *aLastRemoteAnchor, // last anchor of remote
2397 const char *aNextRemoteAnchor, // next anchor of remote
2398 const char *aTargetURI, // target URI as sent by remote, no processing at all
2399 const char *aIdentifyingTargetURI, // target URI that was used to identify datastore
2400 const char *aTargetURIOptions, // option string contained in target URI
2401 SmlFilterPtr_t aTargetFilter, // DS 1.2 filter, NULL if none
2402 const char *aSourceURI, // source URI
2403 TStatusCommand &aStatusCommand // status that might be modified
2406 TAlertCommand *alertcmdP=NULL;
2407 localstatus sta=LOCERR_OK;
2411 // save the identifying URI
2412 fIdentifyingDBName = aIdentifyingTargetURI;
2414 // determine status of read-only option
2416 fSessionP->getReadOnly() || // session level read-only flag (probably set by login)
2417 fDSConfigP->fReadOnly; // or datastore config
2418 #ifdef SUPERDATASTORES
2419 // if running as subdatastore of a superdatastore already, this call mus be from a superdatastore as well (aAsSubDatastoreOf!=NULL)
2420 // Note: On a client, fAsSubDatastoreOf is set earlier in dsSetClientSyncParams()
2421 // On a server, fAsSubDatastoreOf will be set now to avoid alerting as sub- and normal datastore at the same time.
2422 if (fAsSubDatastoreOf && !aAsSubDatastoreOf) {
2423 // bad, cannot be alerted directly AND as subdatastore
2424 aStatusCommand.setStatusCode(400);
2425 ADDDEBUGITEM(aStatusCommand,"trying to alert already alerted subdatastore");
2426 PDEBUGPRINTFX(DBG_ERROR,("Already alerted as subdatastore of '%s'",fAsSubDatastoreOf->getName()));
2429 // set subdatastore mode
2430 fAsSubDatastoreOf = aAsSubDatastoreOf;
2433 fLocalSendToRemoteTypeP = NULL;
2434 fLocalReceiveFromRemoteTypeP = NULL;
2435 fRemoteReceiveFromLocalTypeP=NULL;
2436 fRemoteSendToLocalTypeP=NULL;
2437 // prepare database-level scripts
2438 // NOTE: in client case, alertprepscript is already rebuilt here!
2439 #ifdef SCRIPT_SUPPORT
2440 TScriptContext::rebuildContext(fSessionP->getSyncAppBase(),fDSConfigP->fDBInitScript,fDataStoreScriptContextP,fSessionP);
2441 TScriptContext::rebuildContext(fSessionP->getSyncAppBase(),fDSConfigP->fSentItemStatusScript,fDataStoreScriptContextP,fSessionP);
2442 TScriptContext::rebuildContext(fSessionP->getSyncAppBase(),fDSConfigP->fReceivedItemStatusScript,fDataStoreScriptContextP,fSessionP);
2443 TScriptContext::rebuildContext(fSessionP->getSyncAppBase(),fDSConfigP->fAlertScript,fDataStoreScriptContextP,fSessionP);
2444 TScriptContext::rebuildContext(fSessionP->getSyncAppBase(),fDSConfigP->fDBFinishScript,fDataStoreScriptContextP,fSessionP,true); // now instantiate vars
2446 // NOTE for client case:
2447 // ALL instantiated datastores have already sent an Alert to the server by now here
2449 // check DS 1.2 <filter>
2450 sta = engProcessDS12Filter(aTargetFilter);
2451 if (sta != LOCERR_OK) {
2452 aStatusCommand.setStatusCode(sta);
2453 ADDDEBUGITEM(aStatusCommand,"Invalid <Filter> in target options");
2454 return NULL; // error in options
2457 // Filter CGI is now a combination of TAF and Synthesis-Style
2458 // extras (options).
2459 if (aTargetURIOptions && *aTargetURIOptions) {
2460 // there are target address options (such as filter CGI and TAF)
2461 sta = engParseOptions(aTargetURIOptions,false);
2462 if (sta != LOCERR_OK) {
2463 aStatusCommand.setStatusCode(sta);
2464 ADDDEBUGITEM(aStatusCommand,"Invalid CGI target URI options");
2465 return NULL; // error in options
2469 // server case: initially we are not in refresh only mode. Alert code or alert script could change this
2473 // save it for suspend and reference in scripts
2474 fAlertCode=aAlertCode;
2475 #ifdef SCRIPT_SUPPORT
2476 // call the alert script, which might want to force a slow sync and/or a server sync set zap
2477 TScriptContext::execute(
2478 fDataStoreScriptContextP,
2479 fDSConfigP->fAlertScript,
2481 this // caller context
2483 aAlertCode=fAlertCode; // get possibly modified version back (SETALERTCODE)
2485 // if we process a sync alert now, we haven't started sync or map generation
2486 #ifdef SYSYNC_SERVER
2488 // server case: forget Temp GUID mapping
2489 // make sure we are not carrying forward any left-overs. Last sessions's tempGUID mappings that are
2490 // needed for "early map" resolution might be loaded by the call to engInitSyncAnchors below.
2491 // IMPORTANT NOTE: the tempGUIDs that might get loaded will become invalid as soon as <Sync>
2492 // starts - so fTempGUIDMap needs to be cleared again as soon as the first <Sync> command arrives from the client.
2493 fTempGUIDMap.clear();
2496 // save remote's next anchor for saving at end of session
2497 fNextRemoteAnchor = aNextRemoteAnchor;
2498 // get target info in case we are server
2499 #ifdef SYSYNC_SERVER
2501 // now get anchor info out of database
2502 // - make sure other anchor variables are set
2503 sta = engInitSyncAnchors(
2504 aIdentifyingTargetURI, // use processed form, not as sent by remote
2507 if (sta!=LOCERR_OK) {
2508 // error getting anchors
2509 aStatusCommand.setStatusCode(syncmlError(sta));
2510 PDEBUGPRINTFX(DBG_ERROR,("Could not get Sync Anchor info, status=%hd",sta));
2511 return NULL; // no alert to send back
2513 // Server ok until here
2514 PDEBUGPRINTFX(DBG_PROTO,(
2515 "Saved Last Remote Client Anchor='%s', received <last> Remote Client Anchor='%s' (must match for normal sync)",
2516 fLastRemoteAnchor.c_str(),
2519 PDEBUGPRINTFX(DBG_PROTO,(
2520 "Received <next> Remote Client Anchor='%s' (to be compared with <last> in NEXT session)",
2521 fNextRemoteAnchor.c_str()
2523 PDEBUGPRINTFX(DBG_PROTO,(
2524 "(Saved) Last Local Server Anchor='%s', (generated) Next Local Server Anchor='%s' (sent to client as <last>/<next> in <alert>)",
2525 fLastLocalAnchor.c_str(),
2526 fNextLocalAnchor.c_str()
2530 #ifdef SYSYNC_CLIENT
2532 // Client ok until here
2533 PDEBUGPRINTFX(DBG_PROTO,(
2534 "Saved Last Remote Server Anchor='%s', received <last> Remote Server Anchor='%s' (must match for normal sync)",
2535 fLastRemoteAnchor.c_str(),
2538 PDEBUGPRINTFX(DBG_PROTO,(
2539 "Received <next> Remote Server Anchor='%s' (to be compared with <last> in NEXT session)",
2540 fNextRemoteAnchor.c_str()
2544 PDEBUGPRINTFX(DBG_PROTO,(
2545 "(Saved) fResumeAlertCode = %hd (valid for >DS 1.2 only)",
2548 // Now check for resume
2549 // - default to what was actually alerted
2550 uInt16 effectiveAlertCode=aAlertCode;
2551 #ifdef SYSYNC_SERVER
2553 // - check if resuming server session
2555 if (aAlertCode==225) {
2556 if (fSessionP->getSyncMLVersion()<syncml_vers_1_2) {
2557 aStatusCommand.setStatusCode(406);
2558 ADDDEBUGITEM(aStatusCommand,"Resume not supported in SyncML prior to 1.2");
2559 PDEBUGPRINTFX(DBG_ERROR,("Resume not supported in SyncML prior to 1.2"));
2563 if (fResumeAlertCode==0 || !dsResumeSupportedInDB()) {
2564 // cannot resume, suggest a normal sync (in case anchors do not match, this will become a 508 below)
2565 aStatusCommand.setStatusCode(509); // cannot resume, override
2566 effectiveAlertCode=200; // suggest normal sync
2567 ADDDEBUGITEM(aStatusCommand,"Cannot resume, suggesting a normal sync");
2568 PDEBUGPRINTFX(DBG_ERROR,("Cannot resume, suggesting a normal sync"));
2571 // we can resume, use the saved alert code
2572 effectiveAlertCode=fResumeAlertCode;
2573 PDEBUGPRINTFX(DBG_HOT,("Alerted to resume previous session, Switching to alert Code = %hd",fResumeAlertCode));
2579 // now do the actual alert internally
2580 if (sta==LOCERR_OK) {
2581 // check if we can process the alert
2582 // NOTE: for client this might cause a change in Sync mode, if server
2583 // alerts something different than client alerted before.
2584 // Note that a client will keep fromserver mode even if server
2585 // changes to two-way, as it might be that we have sent the server
2586 // a two-way alert even if we want fromserver due to compatibility with
2587 // servers that cannot do fromserver.
2589 // - Server always obeys what client requests (that is, if alertscript does not modify it)
2590 sta = setSyncModeFromAlertCode(effectiveAlertCode,false); // as server
2593 // - for client, check that server can't switch to a client writing mode
2594 // (we had a case when mobical did that for a user and erased all his data)
2595 TSyncModes prevMode = fSyncMode; // remember previous mode
2596 sta = setSyncModeFromAlertCode(effectiveAlertCode,true); // as client
2597 if (prevMode==smo_fromclient && fSyncMode!=smo_fromclient) {
2598 // server tries to switch to a mode that could be writing data to the client
2601 aStatusCommand.setStatusCode(syncmlError(sta));
2602 ADDDEBUGITEM(aStatusCommand,"Server may not write to client");
2603 PDEBUGPRINTFX(DBG_ERROR,("From-Client only: Server may not alert mode that writes client data (%hd) - blocked",effectiveAlertCode));
2604 // - also abort sync of this datastore without chance to resume, as this problem might
2605 // originate from a resume attempt, which the server
2606 // tried to convert to a normal or slow sync. With cancelling the resume here, we make sure
2607 // next sync will start over and sending the desired (one-way) sync mode again.
2608 engAbortDataStoreSync(sta, false, false); // remote problem, not resumable
2612 if (sta!=LOCERR_OK) {
2613 // Sync type not supported
2614 // - go back to idle
2615 changeState(dssta_idle,true); // force to idle
2616 aStatusCommand.setStatusCode(syncmlError(sta));
2617 ADDDEBUGITEM(aStatusCommand,"Sync type not supported");
2618 PDEBUGPRINTFX(DBG_ERROR,("Sync type (Alert code %hd) not supported, err=%hd",effectiveAlertCode,sta));
2622 if (sta==LOCERR_OK) {
2623 // Sync type supported
2624 // - set new state to alerted
2626 changeState(dssta_clientalerted,true); // force it
2629 changeState(dssta_serveralerted,true); // force it
2631 // - datastore state is now dss_alerted
2632 PDEBUGPRINTFX(DBG_HOT,(
2633 "Alerted (code=%hd) for %s%s %s%s%s ",
2635 fResuming ? "Resumed " : "",
2636 SyncModeDescriptions[fSyncMode],
2637 fSlowSync ? (fSyncMode==smo_twoway ? "Slow Sync" : "Refresh") : "Normal Sync",
2638 fReadOnly ? " (Readonly)" : "",
2639 fRefreshOnly ? " (Refreshonly)" : ""
2642 "- Remote alerts (%hd): %s%s %s%s%s for '%s' (source='%s')",
2644 fResuming ? "Resumed " : "",
2645 SyncModeDescriptions[fSyncMode],
2646 fSlowSync ? (fSyncMode==smo_twoway ? "Slow Sync" : "Refresh") : "Normal Sync",
2647 fReadOnly ? " (Readonly)" : "",
2648 fRefreshOnly ? " (Refreshonly)" : "",
2652 // - now test if the sync state is same in client & server
2653 // (if saved anchor is empty, slow sync is needed anyway)
2657 (!fLastRemoteAnchor.empty() &&
2658 ( (fLastRemoteAnchor==aLastRemoteAnchor)
2659 #ifdef SYSYNC_CLIENT
2660 || (fSessionP->fLenientMode && IS_CLIENT)
2663 ) || // either anchors must match (or lenient mode for client)...
2664 (fResuming && *aLastRemoteAnchor==0) // ...or in case of resume, remote not sending anchor is ok as well
2666 && !fForceSlowSync // ...but no force for slowsync may be set internally
2668 || fSlowSync // if slow sync is requested by the remote anyway, we don't need to be in sync anyway, so just go on
2670 if (!(fLastRemoteAnchor==aLastRemoteAnchor) && fSessionP->fLenientMode) {
2671 PDEBUGPRINTFX(DBG_ERROR,("Warning - remote anchor mismatch but tolerated in lenient mode"));
2673 // sync state ok or Slow sync requested anyway:
2674 #ifdef SYSYNC_SERVER
2676 // we can generate Alert with same code as sent
2677 // %%% Note: this is not entirely clear, as SCTS sends
2678 // corresponding SERVER ALERTED code back.
2679 // Specs suggest that we send the code back unmodified
2680 uInt16 alertCode = getSyncStateAlertCode(fServerAlerted);
2681 alertcmdP = new TAlertCommand(fSessionP,this,alertCode);
2682 fAlertCode=alertCode; // save it for reference in scripts and for suspend/resume
2687 // switch to slow sync
2689 PDEBUGPRINTFX(DBG_HOT,("Switched to SlowSync because of Anchor mismatch or server-side user option"));
2690 CONSOLEPRINTF(("- switched to SlowSync because of Sync Anchor mismatch"));
2691 // sync state not ok, we need slow sync
2692 aStatusCommand.setStatusCode(508); // Refresh required
2693 // update effective alert code
2694 uInt16 alertCode = getSyncStateAlertCode(false);
2695 fAlertCode=alertCode; // save it for reference in scripts and for suspend
2696 // NOTE: if client detected slow-sync not before here, status 508 alone
2697 // (without another Alert 201 sent to the server) is sufficient for
2698 // server to switch to slow sync.
2700 // generate Alert for Slow sync
2701 alertcmdP = new TAlertCommand(fSessionP,this,alertCode);
2704 // Now we are alerted for a sync
2705 // - reset item counters
2708 #ifdef SYSYNC_SERVER
2712 PDEBUGPRINTFX(DBG_HOT,(
2713 "ALERTED from client for %s%s%s Sync",
2714 fResuming ? "resumed " : "",
2715 fSlowSync ? "slow" : "normal",
2716 fFirstTimeSync ? " first time" : ""
2718 // server: add Item with Anchors and URIs
2719 SmlItemPtr_t itemP = newItem();
2721 itemP->meta=newMetaAnchor(fNextLocalAnchor.c_str(),fLastLocalAnchor.c_str());
2722 // - MaxObjSize here again to make SCTS happy
2724 (fSessionP->getRootConfig()->fLocalMaxObjSize>0) &&
2725 (fSessionP->getSyncMLVersion()>=syncml_vers_1_1)
2727 // SyncML 1.1 has object size and we need to put it here for SCTS
2728 smlPCDataToMetInfP(itemP->meta)->maxobjsize=newPCDataLong(
2729 fSessionP->getRootConfig()->fLocalMaxObjSize
2732 // - URIs (reversed from what was received in Alert)
2733 itemP->source=newLocation(aTargetURI); // use unprocessed form as sent by remote
2734 itemP->target=newLocation(aSourceURI);
2735 // - add to alert command
2736 alertcmdP->addItem(itemP);
2737 // - set new state, alert now answered
2738 changeState(dssta_serveransweredalert,true); // force it
2740 #endif // SYSYNC_SERVER
2741 #ifdef SYSYNC_CLIENT
2744 // - now sync mode is stable (late switch to slowsync has now occurred if any)
2745 changeState(dssta_syncmodestable,true);
2747 PDEBUGPRINTFX(DBG_HOT,(
2748 "ALERTED from server for %s%s%s Sync",
2749 fResuming ? "resumed " : "",
2750 fSlowSync ? "slow" : "normal",
2751 fFirstTimeSync ? " first time" : ""
2754 #endif // SYSYNC_CLIENT
2756 // clear partial item if we definitely know we are not resuming
2758 // not resuming - prevent that partial item is used in TSyncOpCommand
2759 fPartialItemState=pi_state_none;
2760 // free this space early (would be freed at session end anyway, but we don't need it any more now)
2761 if (fPIStoredDataAllocated) {
2762 smlLibFree(fPIStoredDataP);
2763 fPIStoredDataAllocated=false;
2765 fPIStoredDataP=NULL;
2767 // save name how remote adresses local database
2768 // (for sending same URI back in own Sync)
2769 fRemoteViewOfLocalURI = aTargetURI; // save it
2771 fRemoteDBPath = aSourceURI;
2773 if (sta!=LOCERR_OK) {
2775 if (alertcmdP) delete alertcmdP;
2777 aStatusCommand.setStatusCode(syncmlError(sta));
2778 PDEBUGPRINTFX(DBG_HOT,("engProcessSyncAlert failed with status=%hd",sta));
2782 // clean up locally owned objects
2783 if (alertcmdP) delete alertcmdP;
2786 // return alert command, if any
2788 } // TLocalEngineDS::engProcessSyncAlert
2791 // process status received for sync alert
2792 bool TLocalEngineDS::engHandleAlertStatus(TSyError aStatusCode)
2796 // for client, make sure we have just sent the alert
2797 if (!testState(dssta_clientsentalert,true)) return false; // cannot switch if server not alerted
2798 // anyway, we have seen the status
2799 changeState(dssta_clientalertstatused,true); // force it
2802 // for server, check if client did combined init&sync
2803 if (fLocalDSState>=dssta_syncmodestable) {
2804 // must be combined init&sync
2805 if (aStatusCode!=200) {
2806 // everything except ok is not allowed here
2807 PDEBUGPRINTFX(DBG_ERROR,("In combined init&sync, Alert status must be ok (but is %hd)",aStatusCode));
2808 dsAbortDatastoreSync(400,false); // remote problem
2810 // aborted or not, status is handled
2813 // normal case with separate init: we need to have answered the alert here
2814 if (!testState(dssta_serveransweredalert,true)) return false; // cannot switch if server not alerted
2816 // now check status code
2817 if (aStatusCode==508) {
2818 // remote party needs slow sync
2819 PDEBUGPRINTFX(DBG_HOT,("engHandleAlertStatus: Remote party needs SlowSync, switching to slowsync (AFTER alert, cancelling possible Resume)"));
2820 // Note: in server and client cases, this mode change may happen AFTER alert command exchange
2821 // - switch to slow sync
2823 // - if we are late-forced to slow sync, this means that this cannot be a resume
2825 // - update effective alert code that will be saved when this session gets suspended
2826 fAlertCode=getSyncStateAlertCode(fServerAlerted);
2829 else if (aStatusCode==200) {
2833 // check for resume override by server
2834 if (!handled && fResuming) {
2835 // we have requested resume
2836 if (aStatusCode==509) {
2837 // resume not accepted by server, but overridden by another sync type
2839 PDEBUGPRINTFX(DBG_ERROR,("engHandleAlertStatus: Server rejected Resume"));
2845 // if we have handled it here, sync mode is now stable
2847 // if we get that far, sync mode for server is now stable AND we can receive cached maps
2848 changeState(dssta_syncmodestable,true); // force it, sync mode is now stable, no further changes are possible
2851 // no other status codes are supported at the datastore level
2852 if (!handled && aStatusCode>=400) {
2853 engAbortDataStoreSync(aStatusCode, false); // remote problem
2856 return handled; // status handled
2857 } // TLocalEngineDS::engHandleAlertStatus
2860 // initialize reception of syncop commands for datastore
2861 // Note: previously, this was implemented as initLocalDatastoreSync in syncsession
2862 localstatus TLocalEngineDS::engInitForSyncOps(
2863 const char *aRemoteDatastoreURI // URI of remote datastore
2866 localstatus sta = LOCERR_OK;
2869 TSyncItemType *LocalSendToRemoteTypeP=NULL; // used by local to send to remote
2870 TSyncItemType *RemoteReceiveFromLocalTypeP=NULL; // used by remote to receive from local
2871 TSyncItemType *LocalReceiveFromRemoteTypeP=NULL; // used by local to receive from remote
2872 TSyncItemType *RemoteSendToLocalTypeP=NULL; // used by remote to send to local
2874 // Now determine remote datastore
2875 // Note: It might be that this was called already earlier in the session, so
2876 // the link between local and remote datastore might already exist
2877 if (fRemoteDatastoreP==NULL) {
2878 // try to locate it by name and set it - in case of superdatastore, it will be set in all subdatastores
2879 engSetRemoteDatastore(fSessionP->findRemoteDataStore(aRemoteDatastoreURI));
2882 // There is a remote datastore already associated
2884 // - make a sanity check to see if sepcified remote URI matches
2885 if(fRemoteDatastoreP!=fSessionP->findRemoteDataStore(aRemoteDatastoreURI)) {
2886 PDEBUGPRINTFX(DBG_ERROR,(
2887 "Warning: Received remote DS LocURI '%s' does not match already associated DS '%s'. We use the associated DS.",
2888 aRemoteDatastoreURI,
2889 fRemoteDatastoreP->getName()
2894 // Now create a dummy remote data store for a blind sync attempt
2895 if (!fRemoteDatastoreP) {
2896 // no such remote datastore for this local datastore known, create one (or fail)
2897 #ifdef REMOTE_DS_MUST_BE_IN_DEVINF
2898 if (fSessionP->fRemoteDataStoresKnown) {
2899 // we have received devinf, but still can't find remote data store: error
2900 // Note: we had to disable this because of bugs in smartner server
2901 PDEBUGPRINTFX(DBG_ERROR,("Remote datastore name '%s' not found in received DevInf",aRemoteDatastoreURI));
2906 if (fSessionP->fRemoteDataStoresKnown) {
2907 // we have received devinf, but still can't find remote data store:
2908 // just show in log, but continue as if there was no devInf received at all
2909 PDEBUGPRINTFX(DBG_ERROR,("Warning: Remote datastore name '%s' not found in received DevInf.",aRemoteDatastoreURI));
2913 // We couldn't retrieve DevInf (or !REMOTE_DS_MUST_BE_IN_DEVINF), so we have to try blind
2914 // - check remote specifics here if we had no devinf (there might be default remote
2915 // rules to apply or checking license restrictions
2916 // - this is executed only once per session, after that, we'll be fRemoteDevInfLock-ed
2917 if (!fSessionP->fRemoteDevInfKnown && !fSessionP->fRemoteDevInfLock) {
2918 // detect client specific server behaviour if needed
2919 sta = fSessionP->checkRemoteSpecifics(NULL, NULL);
2920 fSessionP->remoteAnalyzed(); // analyzed now (accepted or not does not matter)
2922 return sta; // not ok, device rejected
2924 // default data types are those preferred by local datastore (or explicitly marked for blind sync attempts)
2925 if (getDSConfig()->fTypeSupport.fPreferredLegacy) {
2926 // we have a preferred type for blind sync attempts
2927 LocalSendToRemoteTypeP = getSession()->findLocalType(getDSConfig()->fTypeSupport.fPreferredLegacy);
2928 LocalReceiveFromRemoteTypeP = LocalSendToRemoteTypeP;
2931 // no specific "blind" preference, use my own normally preferred types
2932 LocalSendToRemoteTypeP = getPreferredTxItemType(); // send in preferred tx type of local datastore
2933 LocalReceiveFromRemoteTypeP = getPreferredRxItemType(); // receive in preferred rx type of local datastore
2935 // same type on both end (as only local type exists)
2936 RemoteReceiveFromLocalTypeP = LocalSendToRemoteTypeP; // same on both end
2937 RemoteSendToLocalTypeP = LocalReceiveFromRemoteTypeP; // same on both end (as only local type exists)
2938 // create "remote" datastore with matching properties to local one
2939 PDEBUGPRINTFX(DBG_ERROR,("Warning: No DevInf for remote datastore, running blind sync attempt"));
2940 TRemoteDataStore *remDsP;
2941 MP_NEW(remDsP,DBG_OBJINST,"TRemoteDataStore",TRemoteDataStore(
2943 aRemoteDatastoreURI, // remote name of datastore
2944 0 // standard Sync caps
2946 // - set it (in case of superdatastore in all subdatastores as well)
2947 engSetRemoteDatastore(remDsP);
2949 fRemoteDatastoreP->setPreferredTypes(
2950 RemoteReceiveFromLocalTypeP, // remote receives in preferred tx type of local datastore
2951 RemoteSendToLocalTypeP // remote sends in preferred rx type of local datastore
2953 // add it to the remote datastore list
2954 fSessionP->fRemoteDataStores.push_back(fRemoteDatastoreP);
2955 // make sure late devInf arriving won't supersede our artificially created remote datastore any more
2956 fSessionP->fRemoteDevInfLock=true;
2960 // found remote DB, determine default data exchange types
2961 // - common types for sending data to remote
2962 LocalSendToRemoteTypeP=getTypesForTxTo(fRemoteDatastoreP,&RemoteReceiveFromLocalTypeP);
2963 // - common types for receiving data from remote
2964 LocalReceiveFromRemoteTypeP=getTypesForRxFrom(fRemoteDatastoreP,&RemoteSendToLocalTypeP);
2966 #ifndef NO_REMOTE_RULES
2967 // check if rule match type will override what we found so far
2968 if (!fSessionP->fActiveRemoteRules.empty()) {
2969 // have a look at our rulematch types
2970 TRuleMatchTypesContainer::iterator pos;
2971 TSyncItemType *ruleMatchTypeP = NULL;
2972 for (pos=fRuleMatchItemTypes.begin();pos!=fRuleMatchItemTypes.end();++pos) {
2973 // there is a rule applied
2974 // - parse match string in format "rule[,rule]..." with * and ? wildcards allowed in "rule"
2975 cAppCharP p=(*pos).ruleMatchString;
2978 cAppCharP e=strchr(p,',');
2988 // see if that matches with any of the active rules
2989 TRemoteRulesList::iterator apos;
2990 for(apos=fSessionP->fActiveRemoteRules.begin();apos!=fSessionP->fActiveRemoteRules.end();apos++) {
2991 if (strwildcmp((*apos)->getName(), p, 0, n)==0) {
2992 ruleMatchTypeP=(*pos).itemTypeP; // get the matching type
2996 if (ruleMatchTypeP) break; // found a rule match type
2997 // test next match target
3000 // apply if found one already
3001 if (ruleMatchTypeP) {
3002 // use this instead of normal types
3004 LocalSendToRemoteTypeP=ruleMatchTypeP; // used by local to send to remote
3005 LocalReceiveFromRemoteTypeP=ruleMatchTypeP; // used by local to receive from remote
3006 // Find matching remote types
3007 // - first look for existing remote type with same config as local one
3008 TSyncItemType *remCorrTypeP = fSessionP->findRemoteType(ruleMatchTypeP->getTypeConfig(),fRemoteDatastoreP);
3009 // - if none found, create one and have it inherit the CTCap options of the generic version that is already there
3010 if (!remCorrTypeP) {
3011 // none found: need to create one
3012 remCorrTypeP = ruleMatchTypeP->newCopyForSameType(fSessionP,fRemoteDatastoreP);
3014 // - get generic remote type (the one that might have received CTCap already)
3015 TSyncItemType *remGenericTypeP = fRemoteDatastoreP->getSendType(ruleMatchTypeP);
3017 if (remGenericTypeP) remCorrTypeP->copyCTCapInfoFrom(*remGenericTypeP);
3021 RemoteReceiveFromLocalTypeP=remCorrTypeP;
3022 RemoteSendToLocalTypeP=remCorrTypeP;
3023 // Show that we are using ruleMatch type
3024 PDEBUGPRINTFX(DBG_DATA+DBG_HOT,(
3025 "An active remote rule overrides default type usage - forcing type '%s' for send and receive",
3026 ruleMatchTypeP->getTypeConfig()->getName()
3034 // check if we are sync compatible (common type for both directions)
3035 if (LocalSendToRemoteTypeP && LocalReceiveFromRemoteTypeP && RemoteReceiveFromLocalTypeP && RemoteSendToLocalTypeP) {
3036 // avoid further changes in remote devInf (e.g. by late result of GET, sent *after* first <sync>)
3037 fSessionP->fRemoteDevInfLock=true;
3038 // there is a common data type for each of both directions
3039 // - show local types
3040 PDEBUGPRINTFX(DBG_DATA,(
3041 "Local Datastore '%s' - Types: tx to remote: '%s': %s (%s), rx from remote: '%s': %s (%s)",
3043 LocalSendToRemoteTypeP->getTypeConfig()->getName(),LocalSendToRemoteTypeP->getTypeName(), LocalSendToRemoteTypeP->getTypeVers(),
3044 LocalReceiveFromRemoteTypeP->getTypeConfig()->getName(),LocalReceiveFromRemoteTypeP->getTypeName(), LocalReceiveFromRemoteTypeP->getTypeVers()
3046 // - show remote types
3047 PDEBUGPRINTFX(DBG_DATA+DBG_DETAILS,(
3048 "Remote Datastore '%s' - Types: tx to local: '%s': %s (%s), rx from local: '%s': %s (%s)",
3049 fRemoteDatastoreP->getName(),
3050 RemoteSendToLocalTypeP->getTypeConfig()->getName(),RemoteSendToLocalTypeP->getTypeName(), RemoteSendToLocalTypeP->getTypeVers(),
3051 RemoteReceiveFromLocalTypeP->getTypeConfig()->getName(),RemoteReceiveFromLocalTypeP->getTypeName(), RemoteReceiveFromLocalTypeP->getTypeVers()
3055 // datastores are not sync compatible
3057 PDEBUGPRINTFX(DBG_ERROR,("No common datastore formats -> cannot sync (415)"));
3058 PDEBUGPRINTFX(DBG_EXOTIC,("- LocalSendToRemoteTypeP = '%s'", LocalSendToRemoteTypeP ? LocalSendToRemoteTypeP->getTypeName() : "<missing>"));
3059 PDEBUGPRINTFX(DBG_EXOTIC,("- LocalReceiveFromRemoteTypeP = '%s'", LocalReceiveFromRemoteTypeP ? LocalReceiveFromRemoteTypeP->getTypeName() : "<missing>"));
3060 PDEBUGPRINTFX(DBG_EXOTIC,("- RemoteSendToLocalTypeP = '%s'", RemoteSendToLocalTypeP ? RemoteSendToLocalTypeP->getTypeName() : "<missing>"));
3061 PDEBUGPRINTFX(DBG_EXOTIC,("- RemoteReceiveFromLocalTypeP = '%s'", RemoteReceiveFromLocalTypeP ? RemoteReceiveFromLocalTypeP->getTypeName() : "<missing>"));
3062 engAbortDataStoreSync(sta,true,false); // do not proceed with sync of this datastore, local problem, not resumable
3065 // set type info in local datastore
3066 setSendTypeInfo(LocalSendToRemoteTypeP,RemoteReceiveFromLocalTypeP);
3067 setReceiveTypeInfo(LocalReceiveFromRemoteTypeP,RemoteSendToLocalTypeP);
3068 // - initialize usage of types (checks compatibility as well)
3069 return initDataTypeUse();
3070 } // TLocalEngineDS::engInitForSyncOps
3073 // called from <sync> command to generate sync sub-commands to be sent to remote
3074 // Returns true if now finished for this datastore
3075 // also changes state to dssta_syncgendone when all sync commands have been generated
3076 bool TLocalEngineDS::engGenerateSyncCommands(
3077 TSmlCommandPContainer &aNextMessageCommands,
3078 TSmlCommand * &aInterruptedCommandP,
3079 const char *aLocalIDPrefix
3082 PDEBUGBLOCKFMT(("SyncGen","Now generating sync commands","datastore=%s",getName()));
3083 bool finished=false;
3084 #ifdef SYSYNC_CLIENT
3086 finished = logicGenerateSyncCommandsAsClient(aNextMessageCommands, aInterruptedCommandP, aLocalIDPrefix);
3088 #ifdef SYSYNC_SERVER
3090 finished = logicGenerateSyncCommandsAsServer(aNextMessageCommands, aInterruptedCommandP, aLocalIDPrefix);
3092 // change state when finished
3094 changeState(dssta_syncgendone,true);
3096 // from client only skips to clientmapssent without any server communication
3097 // (except if we are in old synthesis-compatible mode which runs from-client-only
3098 // with empty sync-from-server and map phases.
3099 if (getSyncMode()==smo_fromclient && !fSessionP->fCompleteFromClientOnly) {
3100 // data access ends with all sync commands generated in from-client-only
3101 PDEBUGPRINTFX(DBG_PROTO,("From-Client-Only sync: skipping directly to end of map phase now"));
3102 changeState(dssta_dataaccessdone,true);
3103 changeState(dssta_clientmapssent,true);
3107 PDEBUGPRINTFX(DBG_DATA,(
3108 "engGenerateSyncCommands ended, state='%s', sync generation %sdone",
3110 fLocalDSState>=dssta_syncgendone ? "" : "NOT "
3112 PDEBUGENDBLOCK("SyncGen");
3114 } // TLocalEngineDS::engGenerateSyncCommands
3117 // called to confirm a sync operation's completion (status from remote received)
3118 // @note aSyncOp passed not necessarily reflects what was sent to remote, but what actually happened
3119 void TLocalEngineDS::dsConfirmItemOp(TSyncOperation aSyncOp, cAppCharP aLocalID, cAppCharP aRemoteID, bool aSuccess, localstatus aErrorStatus)
3121 // commands failed with "cancelled" should be re-sent for resume
3122 if (!aSuccess && aErrorStatus==514 && dsResumeSupportedInDB() && fSessionP->isSuspending()) {
3123 // cancelled syncop as result of explicit suspend: mark for resume as it was never really processed at the other end
3124 PDEBUGPRINTFX(DBG_DATA+DBG_EXOTIC,("Cancelled SyncOp during suspend -> mark for resume"));
3125 engMarkItemForResume(aLocalID,aRemoteID,true);
3127 PDEBUGPRINTFX(DBG_DATA+DBG_EXOTIC,(
3128 "dsConfirmItemOp completed, syncop=%s, localID='%s', remoteID='%s', %s, errorstatus=%hd",
3129 SyncOpNames[aSyncOp],
3130 aLocalID ? aLocalID : "<none>",
3131 aRemoteID ? aRemoteID : "<none>",
3132 aSuccess ? "SUCCESS" : "FAILURE",
3135 } // TLocalEngineDS::dsConfirmItemOp
3139 // handle status of sync operation
3140 // Note: in case of superdatastore, status is always directed to the originating subdatastore, as
3141 // the fDataStoreP of the SyncOpCommand is set to subdatastore when generating the SyncOps.
3142 bool TLocalEngineDS::engHandleSyncOpStatus(TStatusCommand *aStatusCmdP,TSyncOpCommand *aSyncOpCmdP)
3144 TSyError statuscode = aStatusCmdP->getStatusCode();
3145 // we can make it simple here because we KNOW that we do not send multiple items per SyncOp, so we
3146 // just need to look at the first item's target and source
3147 const char *localID = aSyncOpCmdP->getSourceLocalID();
3148 const char *remoteID = aSyncOpCmdP->getTargetRemoteID();
3149 #ifdef SYSYNC_SERVER
3153 #ifdef SUPERDATASTORES
3154 // remove possible prefix if this item was sent in the <sync> command context of a superdatastore
3155 if (fAsSubDatastoreOf) {
3156 // let superdatastore remove the prefix for me
3157 localID = fAsSubDatastoreOf->removeSubDSPrefix(localID,this);
3160 #ifdef SYSYNC_SERVER
3162 // for server only: convert to internal representation
3164 obtainRealLocalID(realLocID);
3165 localID=realLocID.c_str();
3169 // handle special cases for Add/Replace/Delete
3170 TSyncOperation sop = aSyncOpCmdP->getSyncOp();
3174 if (statuscode<300 || statuscode==419) {
3175 // All ok status 2xx as well as special "merged" 419 is ok for an add:
3176 // Whatever remote said, I know this is an add and so I counts this as such
3177 // (even if the remote somehow merged it with existing data,
3178 // it is obviously a new item in my sync set with this remote)
3179 fRemoteItemsAdded++;
3180 dsConfirmItemOp(sop_add,localID,remoteID,true); // ok added
3185 || (isSlowSync() && IS_CLIENT)
3188 // "Already exists"/418 is acceptable...
3189 // ... in slow sync as client, as some servers use it instead of 200/419 for slow sync match
3190 // ... during resumed sync as server with clients like Symbian which
3191 // can detect duplicate adds themselves. Should not generally
3192 // occur, as we shouldn't re-send them as long as we haven't seen
3193 // a map. But symbian cannot send early maps - it instead does
3194 // it's own duplicate checking.
3195 // ... during resumed sync as client (as servers might issue 418 for
3196 // items sent a second time after an implicit suspend)
3197 PDEBUGPRINTFX(DBG_ERROR,("Warning: received 418 status for add in resumed/slowsync session -> treat it as ok (200)"));
3198 dsConfirmItemOp(sop_replace,localID,remoteID,true); // kind of ok
3199 statuscode=200; // convert to ok (but no count incremented, as nothing changed)
3202 dsConfirmItemOp(sop_add,localID,remoteID,false,statuscode); // failed add
3204 // adding with 420 error: device full
3205 if (statuscode==420) {
3206 // special case: device indicates that it is full, so stop adding in this session
3207 PDEBUGPRINTFX(DBG_ERROR,("Warning: Status %hd: Remote device full -> preventing further adds in this session",statuscode));
3208 engStopAddingToRemote();
3209 fRemoteItemsError++; // this is considered a remote item error
3212 // case sop_copy: break;
3213 case sop_wants_replace:
3215 #ifdef SYSYNC_SERVER
3216 if (IS_SERVER && (statuscode==404 || statuscode==410)) {
3217 // obviously, remote item that we wanted to change does not exist any more.
3218 // Instead of aborting the session we'll just remove the map item for that
3219 // server item, such that it will be re-added in the next sync session
3220 PDEBUGPRINTFX(DBG_DATA,("Status %hd: Replace target not found on client -> silently ignore but remove map in server (item will be added in next session), ",statuscode));
3221 // remove map for remote item(s), targetRef contain remoteIDs
3222 SmlTargetRefListPtr_t targetrefP = aStatusCmdP->getStatusElement()->targetRefList;
3223 while (targetrefP) {
3224 // target ref available
3225 engProcessMap(smlPCDataToCharP(targetrefP->targetRef),NULL);
3227 targetrefP=targetrefP->next;
3229 statuscode=410; // always use "gone" status (even if we might have received a 404)
3230 dsConfirmItemOp(sop_replace,localID,remoteID,false,statuscode);
3235 if (statuscode==201) {
3236 fRemoteItemsAdded++;
3237 dsConfirmItemOp(sop_add,localID,remoteID,true); // ok as add
3239 else if (statuscode<300 || statuscode==419) { // conflict resolved counts as ok as well
3240 fRemoteItemsUpdated++;
3241 dsConfirmItemOp(sop_replace,localID,remoteID,true); // ok as replace
3243 #ifdef SYSYNC_CLIENT
3244 else if (IS_CLIENT && (isSlowSync() && statuscode==418)) {
3245 // "Already exists"/418 is acceptable as client in slow sync because some
3246 // servers use it instead of 200/419 for slow sync match
3247 PDEBUGPRINTFX(DBG_ERROR,("Warning: received 418 for for replace during slow sync - treat it as ok (200), but don't count as update"));
3248 dsConfirmItemOp(sop_replace,localID,remoteID,true); // 418 is acceptable in slow sync (not really conformant, but e.g. happening with Scheduleworld)
3249 statuscode=200; // always use "gone" status (even if we might have received a 404)
3251 #endif // SYSYNC_CLIENT
3253 dsConfirmItemOp(sop_replace,localID,remoteID,false,statuscode); // failed replace
3256 case sop_archive_delete:
3257 case sop_soft_delete:
3259 if (statuscode<211) fRemoteItemsDeleted++;
3260 // allow 211 and 404 for delete - after all, the record is not there
3261 // any more on the remote
3262 if (statuscode==404 || statuscode==211) {
3263 PDEBUGPRINTFX(DBG_DATA,("Status: %hd: To-be-deleted item not found, but accepted this (changed status to 200)",statuscode));
3266 // if ok (explicit or implicit), we can confirm the delete
3267 dsConfirmItemOp(sop_delete,localID,remoteID,statuscode<300,statuscode); // counts as ok delete
3271 // check if we want to mark failed items for resend in the next session or abort
3272 bool resend = fDSConfigP->fResendFailing; // get default from config
3273 #ifdef SCRIPT_SUPPORT
3274 // let script check status code
3275 TErrorFuncContext errctx;
3276 errctx.statuscode = statuscode;
3277 errctx.resend = resend;
3278 errctx.newstatuscode = statuscode;
3279 errctx.syncop = sop;
3280 errctx.datastoreP = this;
3281 // - first check datastore level
3283 TScriptContext::executeTest(
3284 false, // assume script does NOT handle status entirely
3285 fDataStoreScriptContextP,
3286 fDSConfigP->fSentItemStatusScript,
3288 &errctx // caller context
3291 // completely handled
3292 PDEBUGPRINTFX(DBG_ERROR,("Status: %hd: Handled by datastore script (original op was %s)",statuscode,SyncOpNames[sop]));
3295 errctx.statuscode = errctx.newstatuscode;
3296 // - then check session level
3298 TScriptContext::executeTest(
3299 false, // assume script does NOT handle status entirely
3300 fSessionP->fSessionScriptContextP,
3301 fSessionP->getSessionConfig()->fSentItemStatusScript,
3303 &errctx // caller context
3306 // completely handled
3307 PDEBUGPRINTFX(DBG_ERROR,("Status: %hd: Handled by session script (original op was %s)",statuscode,SyncOpNames[sop]));
3310 // not completely handled, use possibly modified status code
3312 if (statuscode != errctx.newstatuscode) {
3313 PDEBUGPRINTFX(DBG_ERROR,("Status: Script changed original status=%hd to %hd (original op was %s)",statuscode,errctx.newstatuscode,SyncOpNames[errctx.syncop]));
3316 statuscode = errctx.newstatuscode;
3317 resend = errctx.resend;
3319 // now perform default action according to status code
3320 switch (statuscode) {
3324 PDEBUGPRINTFX(DBG_PROTO,("Status: %hd: Item added (original op was %s)",statuscode,SyncOpNames[sop]));
3327 PDEBUGPRINTFX(DBG_PROTO,("Status: %hd: No content (original op was %s)",statuscode,SyncOpNames[sop]));
3333 PDEBUGPRINTFX(DBG_HOT,("Status: %hd: Conflict resolved (original op was %s)",statuscode,SyncOpNames[sop]));
3336 PDEBUGPRINTFX(DBG_HOT,("Status: %hd: Delete without archive (original op was %s)",statuscode,SyncOpNames[sop]));
3339 PDEBUGPRINTFX(DBG_ERROR,("Status: %hd: nothing deleted, item not found (original op was %s)",statuscode,SyncOpNames[sop]));
3342 case 420: // device full
3343 // these have been handled above and are considered ok now
3345 case 514: // cancelled
3346 // ignore cancelling while suspending, as these are CAUSED by the suspend
3347 if (fSessionP->isSuspending() && dsResumeSupportedInDB()) {
3348 // don't do anything here - we'll be suspended later (but process commands until then)
3349 // dsConfirmItemOp() has already caused the item to be marked for resume
3352 // for non-DS-1.2 sessions, we treat 514 like the other errors below (that is - retry might help)
3353 case 424: // size mismatch (e.g. due to faild partial item resume attempt -> retry will help)
3354 case 417: // retry later (remote says that retry will probably work)
3355 case 506: // processing error, retry later (remote says that retry will probably work)
3356 case 404: // not found (retry is not likely to help, but does not harm too much, either)
3357 case 408: // timeout (if that happens on a single item, retry probably helps)
3358 case 415: // bad type (retry is not likely to help, but does not harm too much, either)
3359 case 510: // datastore failure (too unspecific to know if retry might help, but why not?)
3360 case 500: // general failure (too unspecific to know if retry might help, but why not?)
3361 // these errors cause either a resend in a later session
3362 // or only abort the datastore, but not the session
3363 if (resend && dsResumeSupportedInDB()) {
3364 PDEBUGPRINTFX(DBG_ERROR,("Status: General error %hd (original op was %s) -> marking item for resend in next session",statuscode,SyncOpNames[sop]));
3365 engMarkItemForResend(localID,remoteID); // Note: includes incrementing fRemoteItemsError
3368 PDEBUGPRINTFX(DBG_ERROR,("Status: General error %hd (original op was %s) -> aborting sync with this datastore",statuscode,SyncOpNames[sop]));
3369 engAbortDataStoreSync(statuscode,false); // remote problem
3373 // let command handle it
3378 return true; // handled status
3379 } // TLocalEngineDS::engHandleSyncOpStatus
3382 /// Internal events during sync for derived classes
3383 /// @Note local DB authorisation must be established already before calling these
3384 /// - cause loading of all session anchoring info and other admin data (logicMakeAdminReady())
3385 /// fLastRemoteAnchor,fLastLocalAnchor,fNextLocalAnchor; isFirstTimeSync() will be valid after the call
3386 /// - in case of superdatastore, consolidates the anchor information from the subdatastores
3387 localstatus TLocalEngineDS::engInitSyncAnchors(
3388 cAppCharP aDatastoreURI, ///< local datastore URI
3389 cAppCharP aRemoteDBID ///< ID of remote datastore (to find session information in local DB)
3392 // nothing more to do than making admin data ready
3393 // - this will fill all dsSavedAdminData members here and in all derived classes
3394 localstatus sta=logicMakeAdminReady(aDatastoreURI, aRemoteDBID);
3395 if (sta==LOCERR_OK) {
3396 changeState(dssta_adminready); // admin data is now ready
3400 } // TLocalEngineDS::engInitSyncAnchors
3403 #ifdef SYSYNC_CLIENT
3405 // initialize Sync alert for datastore according to Parameters set with dsSetClientSyncParams()
3406 localstatus TLocalEngineDS::engPrepareClientSyncAlert(void)
3408 #ifdef SUPERDATASTORES
3409 // no operation here if running under control of a superdatastore.
3410 // superdatastore's engPrepareClientSyncAlert() will call engPrepareClientRealDSSyncAlert of all subdatastores at the right time
3411 if (fAsSubDatastoreOf)
3414 // this is a real datastore
3415 return engPrepareClientDSForAlert();
3416 } // TLocalEngineDS::engPrepareClientSyncAlert
3420 // initialize Sync alert for datastore according to Parameters set with dsSetClientSyncParams()
3421 localstatus TLocalEngineDS::engPrepareClientDSForAlert(void)
3425 // reset the filters that might be added to in alertprepscript
3426 // (as they might have been half set-up in a previous failed alert, they must be cleared and re-constructed here)
3429 #ifdef SCRIPT_SUPPORT
3430 // AlertPrepareScript to add filters and CGI
3431 // - rebuild early (before all of the other DS scripts in makeAdminReady caused by engInitSyncAnchors below!)
3432 TScriptContext::rebuildContext(fSessionP->getSyncAppBase(),fDSConfigP->fAlertPrepScript,fDataStoreScriptContextP,fSessionP);
3433 // - add custom DS 1.2 filters and/or custom CGI to fRemoteDBPath
3434 TScriptContext::execute(
3435 fDataStoreScriptContextP,
3436 fDSConfigP->fAlertPrepScript,
3437 fDSConfigP->getClientDBFuncTable(), // function table with extra
3438 this // datastore pointer needed for context
3441 // - save the identifying name of the DB
3442 fIdentifyingDBName = fLocalDBPath;
3443 // - get information about last session out of database
3444 sta = engInitSyncAnchors(
3445 relativeURI(fLocalDBPath.c_str()),
3446 fRemoteDBPath.c_str()
3448 if (sta!=LOCERR_OK) {
3449 // error getting anchors
3450 PDEBUGPRINTFX(DBG_ERROR,("Could not get Sync Anchor info"));
3451 return localError(sta);
3453 // check if we are forced to slowsync (otherwise, fSlowSync is pre-set from dsSetClientSyncParams()
3454 fSlowSync = fSlowSync || fLastLocalAnchor.empty() || fFirstTimeSync;
3456 if (fResumeAlertCode!=0 && fSessionP->getSyncMLVersion()>=syncml_vers_1_2) {
3457 // we have a suspended session, try to resume
3458 PDEBUGPRINTFX(DBG_PROTO,("Found suspended session with Alert Code = %hd",fResumeAlertCode));
3461 return LOCERR_OK; // ok
3462 } // TLocalEngineDS::engPrepareClientDSForAlert
3465 // generate Sync alert for datastore after initialisation with engPrepareClientSyncAlert()
3466 // Note: this could be repeatedly called due to auth failures at beginning of session
3467 // Note: this is a NOP for subDatastores (should not be called in this case, anyway)
3468 localstatus TLocalEngineDS::engGenerateClientSyncAlert(
3469 TAlertCommand *&aAlertCommandP
3472 aAlertCommandP=NULL;
3473 #ifdef SUPERDATASTORES
3474 if (fAsSubDatastoreOf) return LOCERR_OK; // NOP, ok, only superdatastore creates an alert!
3477 PDEBUGPRINTFX(DBG_PROTO,(
3478 "(Saved) Last Local Client Anchor='%s', (generated) Next Local Client Anchor='%s' (sent to server as <last>/<next> in <alert>)",
3479 fLastLocalAnchor.c_str(),
3480 fNextLocalAnchor.c_str()
3482 // create appropriate initial alert command
3483 uInt16 alertCode = getSyncStateAlertCode(fServerAlerted,true);
3486 // check if what we resume is same as what we wanted to do
3487 if (alertCode != fResumeAlertCode) {
3488 // this is ok for client, just show in log
3489 PDEBUGPRINTFX(DBG_PROTO,(
3490 "Sync mode seems to have changed (alert code = %hd) since last Suspend (alert code = %hd)",
3496 alertCode=225; // resume
3497 PDEBUGPRINTFX(DBG_PROTO,(
3498 "Alerting resume of last sync session (original alert code = %hd)",
3502 aAlertCommandP = new TAlertCommand(fSessionP,this,alertCode);
3503 PDEBUGPRINTFX(DBG_HOT,(
3504 "%s: ALERTING server for %s%s%s Sync",
3506 fResuming ? "RESUMED " : "",
3507 fSlowSync ? "slow" : "normal",
3508 fFirstTimeSync ? " first time" : ""
3510 // add Item with Anchors and URIs
3511 SmlItemPtr_t itemP = newItem();
3513 itemP->meta=newMetaAnchor(fNextLocalAnchor.c_str(),fLastLocalAnchor.c_str());
3514 // - MaxObjSize here again to make SCTS happy
3516 (fSessionP->getSyncMLVersion()>=syncml_vers_1_1) &&
3517 (fSessionP->getRootConfig()->fLocalMaxObjSize>0)
3519 // SyncML 1.1 has object size and we need to put it here for SCTS
3520 smlPCDataToMetInfP(itemP->meta)->maxobjsize=newPCDataLong(
3521 fSessionP->getRootConfig()->fLocalMaxObjSize
3525 itemP->source=newLocation(fLocalDBPath.c_str()); // local DB ID
3526 itemP->target=newLocation(fRemoteDBPath.c_str()); // use remote path as configured in client settings
3527 // - add DS 1.2 filters
3528 if (!fRemoteRecordFilterQuery.empty() || false /* %%% field level filter */) {
3529 if (fSessionP->getSyncMLVersion()<syncml_vers_1_2) {
3530 PDEBUGPRINTFX(DBG_ERROR,("Filter specified, but SyncML version is < 1.2"));
3531 engAbortDataStoreSync(406, true, false); // can't continue sync
3532 return 406; // feature not supported
3534 SmlFilterPtr_t filterP = SML_NEW(SmlFilter_t);
3535 memset(filterP,0,sizeof(SmlFilter_t));
3537 // Must have a meta with the content type
3538 // - get the preferred receive type
3539 TSyncItemType *itemTypeP = fSessionP->findLocalType(fDSConfigP->fTypeSupport.fPreferredRx);
3542 filterP->meta = newMetaType(itemTypeP->getTypeName());
3544 // add filtertype if needed (=not EXCLUSIVE)
3545 if (fRemoteFilterInclusive) {
3546 filterP->filtertype=newPCDataString(SYNCML_FILTERTYPE_INCLUSIVE);
3549 if (!fRemoteRecordFilterQuery.empty()) {
3551 filterP->record = SML_NEW(SmlRecordOrFieldFilter_t);
3552 // - add item with data=filterquery
3553 filterP->record->item = newStringDataItem(fRemoteRecordFilterQuery.c_str());
3554 // - add meta type with grammar
3555 filterP->record->item->meta = newMetaType(SYNCML_FILTERTYPE_CGI);
3556 PDEBUGPRINTFX(DBG_HOT,(
3557 "Alerting with %sCLUSIVE Record Level Filter Query = '%s'",
3558 fRemoteFilterInclusive ? "IN" : "EX",
3559 fRemoteRecordFilterQuery.c_str()
3563 /// @todo %%% to be implemented
3565 // !!! remember to add real check (now: false) in outer if-statement as well!!!!!
3570 itemP->target->filter = filterP;
3572 // - add to alert command
3573 aAlertCommandP->addItem(itemP);
3574 // we have now produced the client alert command, change state
3575 return changeState(dssta_clientsentalert);
3576 } // TLocalEngineDS::engGenerateClientSyncAlert
3579 // Init engine for client sync
3580 // - determine types to exchange
3581 // - make sync set ready
3582 localstatus TLocalEngineDS::engInitForClientSync(void)
3584 #ifdef SUPERDATASTORES
3585 // no init in case we are under control of a superdatastore -> the superdatastore will do that
3586 if (fAsSubDatastoreOf)
3589 return engInitDSForClientSync();
3590 } // TLocalEngineDS::engInitForClientSync
3594 // Init engine for client sync
3595 // - determine types to exchange
3596 // - make sync set ready
3597 localstatus TLocalEngineDS::engInitDSForClientSync(void)
3599 // make ready for syncops
3600 localstatus sta = engInitForSyncOps(getRemoteDBPath());
3601 if (sta==LOCERR_OK) {
3602 // - let local datastore (derived DB-specific class) prepare for sync
3603 sta = changeState(dssta_dataaccessstarted);
3604 if (sta==LOCERR_OK && isStarted(false)) {
3605 // already started now, change state
3606 sta = changeState(dssta_syncsetready);
3610 } // TLocalEngineDS::engInitDSForClientSync
3616 // get Alert code for current Sync State
3617 uInt16 TLocalEngineDS::getSyncStateAlertCode(bool aServerAlerted, bool aClientMinimal)
3621 switch (fSyncMode) {
3623 alertcode = aServerAlerted ? 206 : 200;
3625 case smo_fromclient :
3626 alertcode = aServerAlerted ? 207 : 202; // fully specified
3628 case smo_fromserver :
3629 if (aClientMinimal) {
3630 // refresh from server is just client not sending any data, so we can alert like two-way
3631 alertcode = aServerAlerted ? 206 : 200;
3634 // correctly alert it
3635 alertcode = aServerAlerted ? 209 : 204;
3642 // slowsync/refresh variants are always plus one, except 206 --> 201 (same as client initiated slow sync)
3643 if (fSlowSync) alertcode = (alertcode!=206 ? alertcode+1 : 201);
3645 } // TLocalEngineDS::getSyncStateAlertCode
3648 /// initializes Sync state variables and returns false if alert is not supported
3649 localstatus TLocalEngineDS::setSyncModeFromAlertCode(uInt16 aAlertCode, bool aAsClient)
3652 TSyncModes syncMode;
3653 bool slowSync, serverAlerted;
3654 // - translate into mode and flags
3655 sta=getSyncModeFromAlertCode(aAlertCode,syncMode,slowSync,serverAlerted);
3656 if (sta==LOCERR_OK) {
3658 sta=setSyncMode(aAsClient,syncMode,slowSync,serverAlerted);
3661 } // TLocalEngineDS::setSyncModeFromAlertCode
3664 /// initializes Sync mode variables
3665 localstatus TLocalEngineDS::setSyncMode(bool aAsClient, TSyncModes aSyncMode, bool aIsSlowSync, bool aIsServerAlerted)
3667 // get sync caps of this datastore
3668 uInt32 synccapmask=getSyncCapMask();
3669 // check if mode supported
3670 if (aIsServerAlerted) {
3671 // check if we support server alerted modes, SyncCap/Bit=7
3672 if (~synccapmask & (1<<7)) return 406; // not supported
3676 // Two-way Sync, SyncCap/Bit=1
3677 // or Two-way slow Sync, SyncCap/Bit=2
3678 if (~synccapmask & (1<< (aIsSlowSync ? 2 : 1))) return 406; // not supported
3679 if (fSyncMode==smo_fromserver && aAsClient)
3680 aSyncMode=smo_fromserver; // for client, if already fromserver mode set, keep it
3682 case smo_fromclient:
3683 // One-way from client, SyncCap/Bit=3
3684 // or Refresh (=slow one-way) from client, SyncCap/Bit=4
3685 if (~synccapmask & (1<< (aIsSlowSync ? 4 : 3))) return 406; // not supported
3686 if (!aAsClient) fRefreshOnly=true; // as server, we are in refresh-only-mode if we get one-way from client
3688 case smo_fromserver:
3689 // One-way from server, SyncCap/Bit=5
3690 // or Refresh (=slow one-way) from server, SyncCap/Bit=6
3691 if (~synccapmask & (1<< (aIsSlowSync ? 6 : 5))) return 406; // not supported
3692 if (aAsClient) fRefreshOnly=true; // as client, we are in refresh-only-mode if we get one-way fromm server
3695 return 400; // bad request
3697 // now set mode and flags (possibly adjusted above)
3698 fSyncMode=aSyncMode;
3699 fSlowSync=aIsSlowSync;
3700 fServerAlerted=aIsServerAlerted;
3703 } // TLocalEngineDS::setSyncMode
3706 /// get Sync mode variables from given alert code
3707 localstatus TLocalEngineDS::getSyncModeFromAlertCode(uInt16 aAlertCode, TSyncModes &aSyncMode, bool &aIsSlowSync, bool &aIsServerAlerted)
3709 // these might be pre-defined)
3710 /// @deprecated state change does not belong here
3712 aIsServerAlerted=false;
3713 aSyncMode=smo_twoway; // to make sure it is valid
3714 // first test if server-alerted
3715 if (aAlertCode>=206 && aAlertCode<210) {
3716 // Server alerted modes
3717 aIsServerAlerted=true;
3719 // test for compatibility with alert code
3720 switch(aAlertCode) {
3724 aSyncMode=smo_twoway;
3728 // Two-way slow Sync
3729 aSyncMode=smo_twoway;
3734 // One-way from client
3735 aSyncMode=smo_fromclient;
3740 // refresh (=slow one-way) from client
3741 aSyncMode=smo_fromclient;
3746 // One-way from server
3747 aSyncMode=smo_fromserver;
3752 // refresh (=slow one-way) from server
3753 aSyncMode=smo_fromserver;
3761 } // TLocalEngineDS::getSyncModeFromAlertCode
3764 // create new Sync capabilities info from capabilities mask
3765 // Bit0=reserved, Bit1..Bitn = SyncType 1..n available
3766 // Note: derived classes might add special sync codes and/or mask standard ones
3767 SmlDevInfSyncCapPtr_t TLocalEngineDS::newDevInfSyncCap(uInt32 aSyncCapMask)
3769 SmlDevInfSyncCapPtr_t synccapP;
3770 SmlPcdataPtr_t synctypeP;
3772 // new synccap structure
3773 synccapP = SML_NEW(SmlDevInfSyncCap_t);
3774 // synccap list is empty
3775 synccapP->synctype=NULL;
3776 // now add standard synccaps
3777 for (sInt16 k=0; k<32; k++) {
3778 if (aSyncCapMask & (1<<k)) {
3779 // capability available
3780 synctypeP=newPCDataLong(k);
3781 addPCDataToList(synctypeP,&(synccapP->synctype));
3784 // Now add non-standard synccaps.
3785 // From the spec: "Other values can also be specified."
3786 // Values are PCDATA, so we can use plain strings.
3788 // But the Funambol server expects integer numbers and
3789 // throws a parser error when sent a string. So better
3790 // stick to a semi-random number (hopefully no-one else
3793 // Worse, Nokia phones cancel direct sync sessions with an
3794 // OBEX error ("Forbidden") when non-standard sync modes
3795 // are included in the SyncCap. As a workaround for that
3796 // we use the following logic:
3797 // - libsynthesis in a SyncML client will always send
3798 // all the extended sync modes; with the Funambol
3799 // workaround in place that works
3800 // - libsynthesis in a SyncML server will only send the
3801 // extended sync modes if the client has sent any
3802 // extended sync modes itself; the 390002 mode is
3803 // sent unconditionally for that purpose
3805 // Corresponding code in TRemoteDataStore::setDatastoreDevInf().
3808 fSessionP->receivedSyncModeExtensions()) {
3810 synctypeP=newPCDataString("390001");
3811 addPCDataToList(synctypeP,&(synccapP->synctype));
3813 synctypeP=newPCDataString("390002");
3814 addPCDataToList(synctypeP,&(synccapP->synctype));
3816 // Finally add non-standard synccaps that are outside of the
3817 // engine's control.
3819 getSyncModes(modes);
3820 for (set<string>::const_iterator it = modes.begin();
3823 synctypeP=newPCDataString(*it);
3824 addPCDataToList(synctypeP,&(synccapP->synctype));
3830 } // TLocalEngineDS::newDevInfSyncCap
3833 // obtain new datastore info, returns NULL if none available
3834 SmlDevInfDatastorePtr_t TLocalEngineDS::newDevInfDatastore(bool aAsServer, bool aWithoutCTCapProps)
3836 SmlDevInfDatastorePtr_t datastoreP;
3838 // set only basic info, details must be added in derived class
3839 // - sourceref is the name of the datastore,
3840 // or for server, if already alerted, the name used in the alert
3841 // (this is to allow /dsname/foldername with clients that expect the
3842 // devInf to contain exactly the same full path as name, like newer Nokias)
3844 #ifdef SYSYNC_SERVER
3845 if (IS_SERVER && testState(dssta_serveralerted,false) && fSessionP->fDSPathInDevInf) {
3846 // server and already alerted
3847 // - don't include sub-datastores
3848 if (fAsSubDatastoreOf) {
3852 // - use datastore spec as sent from remote, minus CGI, as relative spec
3853 dotname = URI_RELPREFIX;
3854 dotname += fSessionP->SessionRelativeURI(fRemoteViewOfLocalURI.c_str());
3855 if (!fSessionP->fDSCgiInDevInf) {
3857 string::size_type n=dotname.find("?");
3858 if (n!=string::npos)
3859 dotname.resize(n); // remove CGI
3865 // client or not yet alerted - just use datastore base name
3866 StringObjPrintf(dotname,URI_RELPREFIX "%s",fName.c_str());
3870 datastoreP=SML_NEW(SmlDevInfDatastore_t);
3871 datastoreP->sourceref=newPCDataString(dotname);
3872 #ifndef MINIMAL_CODE
3873 // - Optional display name
3874 datastoreP->displayname=newPCDataOptString(getDisplayName());
3876 datastoreP->displayname=NULL;
3878 // - MaxGUIDsize (for client only)
3880 datastoreP->maxguidsize=NULL;
3882 datastoreP->maxguidsize=newPCDataLong(fMaxGUIDSize);
3883 // - check for legacy mode type (that is to be used as "preferred" instead of normal preferred)
3884 TSyncItemType *legacyTypeP = NULL;
3885 if (getSession()->fLegacyMode && getDSConfig()->fTypeSupport.fPreferredLegacy) {
3886 // get the type marked as blind
3887 legacyTypeP = getSession()->findLocalType(getDSConfig()->fTypeSupport.fPreferredLegacy);
3890 if (!fRxPrefItemTypeP) SYSYNC_THROW(TStructException("Datastore has no RxPref ItemType"));
3891 datastoreP->rxpref = (legacyTypeP ? legacyTypeP : fRxPrefItemTypeP)->newXMitDevInf();
3892 // - Rx (excluding the type we report as preferred)
3893 datastoreP->rx=TSyncItemType::newXMitListDevInf(fRxItemTypes,legacyTypeP ? legacyTypeP : fRxPrefItemTypeP);
3895 if (!fTxPrefItemTypeP) SYSYNC_THROW(TStructException("Datastore has no TxPref ItemType"));
3896 datastoreP->txpref = (legacyTypeP ? legacyTypeP : fTxPrefItemTypeP)->newXMitDevInf();
3897 // - Tx (excluding the type we report as preferred)
3898 datastoreP->tx=TSyncItemType::newXMitListDevInf(fTxItemTypes,legacyTypeP ? legacyTypeP : fTxPrefItemTypeP);
3900 /// @todo %%% tbd: add dsmem
3901 datastoreP->dsmem=NULL;
3902 // - SyncML DS 1.2 datastore-local CTCap
3903 if (fSessionP->getSyncMLVersion()>=syncml_vers_1_2) {
3904 // CTCap is local to datastore, get only CTCaps relevant for this datastore, but independently from alerted state
3905 datastoreP->ctcap = fSessionP->newLocalCTCapList(false, this, aWithoutCTCapProps);
3908 datastoreP->ctcap=NULL; // before SyncML 1.2, there was no datastore-local CTCap
3909 // - SyncML DS 1.2 flags (SmlDevInfHierarchical_f)
3910 /// @todo %%% tbd: add SmlDevInfHierarchical_f
3911 datastoreP->flags=0;
3912 // - SyncML DS 1.2 filters
3913 datastoreP->filterrx=NULL;
3914 datastoreP->filtercap=NULL;
3915 #ifdef OBJECT_FILTERING
3916 if (IS_SERVER && fDSConfigP->fDS12FilterSupport && fSessionP->getSyncMLVersion()>=syncml_vers_1_2) {
3917 // Show Filter info in 1.2 devInf if this is not a client
3918 // - FilterRx constant
3919 datastoreP->filterrx = SML_NEW(SmlDevInfXmitList_t);
3920 datastoreP->filterrx->next = NULL;
3921 datastoreP->filterrx->data = SML_NEW(SmlDevInfXmit_t);
3922 datastoreP->filterrx->data->cttype=newPCDataString(SYNCML_FILTERTYPE_CGI);
3923 datastoreP->filterrx->data->verct=newPCDataString(SYNCML_FILTERTYPE_CGI_VERS);
3925 SmlPcdataListPtr_t filterkeywords = NULL;
3926 SmlPcdataListPtr_t filterprops = NULL;
3927 // - fill the lists from types
3928 addFilterCapPropsAndKeywords(filterkeywords,filterprops);
3929 // - if we have something, actually build a filtercap
3930 if (filterkeywords || filterprops) {
3931 // we have filtercaps, add them
3933 datastoreP->filtercap = SML_NEW(SmlDevInfFilterCapList_t);
3934 datastoreP->filtercap->next = NULL;
3935 datastoreP->filtercap->data = SML_NEW(SmlDevInfFilterCap_t);
3936 datastoreP->filtercap->data->cttype=newPCDataString(SYNCML_FILTERTYPE_CGI);
3937 datastoreP->filtercap->data->verct=newPCDataString(SYNCML_FILTERTYPE_CGI_VERS);
3938 // - add list we've got
3939 datastoreP->filtercap->data->filterkeyword=filterkeywords;
3940 datastoreP->filtercap->data->propname=filterprops;
3943 #endif // OBJECT_FILTERING
3944 // - Sync capabilities of this datastore
3945 datastoreP->synccap=newDevInfSyncCap(getSyncCapMask());
3948 } // TLocalEngineDS::newDevInfDatastore
3951 // Set remote datastore for local
3952 void TLocalEngineDS::engSetRemoteDatastore(
3953 TRemoteDataStore *aRemoteDatastoreP // the remote datastore involved
3956 // save link to remote datastore
3957 if (fRemoteDatastoreP) {
3958 if (fRemoteDatastoreP!=aRemoteDatastoreP)
3959 SYSYNC_THROW(TSyncException("Sync continues with different datastore"));
3961 fRemoteDatastoreP=aRemoteDatastoreP;
3962 } // TLocalEngineDS::engSetRemoteDatastore
3965 // SYNC command bracket start (check credentials if needed)
3966 bool TLocalEngineDS::engProcessSyncCmd(
3967 SmlSyncPtr_t aSyncP, // the Sync element
3968 TStatusCommand &aStatusCommand, // status that might be modified
3969 bool &aQueueForLater // will be set if command must be queued for later (re-)execution
3972 // get number of changes info if available
3974 StrToLong(smlPCDataToCharP(aSyncP->noc),fRemoteNumberOfChanges);
3976 // check if datastore is aborted
3977 if (CheckAborted(aStatusCommand)) return false;
3979 if (!fRemoteDatastoreP)
3980 SYSYNC_THROW(TSyncException("No remote datastore linked"));
3981 // let remote datastore process it first
3982 if (!fRemoteDatastoreP->remoteProcessSyncCmd(aSyncP,aStatusCommand,aQueueForLater)) {
3983 PDEBUGPRINTFX(DBG_ERROR,("TLocalEngineDS::engProcessSyncCmd: remote datastore failed processing <sync>"));
3984 changeState(dssta_idle,true); // force it
3987 // check for combined init&sync
3988 if (!testState(dssta_syncmodestable,false)) {
3989 // <sync> encountered before sync mode stable: could be combined init&sync session
3990 if (fLocalDSState>=dssta_serveransweredalert) {
3991 // ok for switching to combined init&sync
3992 PDEBUGPRINTFX(DBG_HOT,("TLocalEngineDS::engProcessSyncCmd: detected combined init&sync, freeze sync mode already now"));
3993 // - freeze sync mode as it is now
3994 changeState(dssta_syncmodestable,true); // force it, sync mode is now stable, no further changes are possible
3997 // now init if this is the first <sync> command
3998 bool startingNow = false; // assume start already initiated
3999 if (testState(dssta_syncmodestable,true)) {
4000 // all alert and alert status must be done by now, sync mode must be stable
4001 CONSOLEPRINTF(("- Starting Sync with Datastore '%s', %s sync",fRemoteViewOfLocalURI.c_str(),fSlowSync ? "slow" : "normal"));
4002 startingNow = true; // initiating start now
4003 #ifdef SYSYNC_SERVER
4005 // at this point, all temporary GUIDs become invalid (no "early map" possible any more which might refer to last session's tempGUIDs)
4006 fTempGUIDMap.clear(); // forget all previous session's temp GUID mappings
4007 // let local datastore (derived DB-specific class) prepare for sync
4008 localstatus sta = changeState(dssta_dataaccessstarted);
4009 if (sta!=LOCERR_OK) {
4010 // abort session (old comment: %%% aborting datastore only does not work, will loop, why? %%%)
4011 aStatusCommand.setStatusCode(syncmlError(sta));
4012 PDEBUGPRINTFX(DBG_ERROR,("TLocalEngineDS::engProcessSyncCmd: could not change state to dsssta_dataaccessstarted -> abort"));
4013 engAbortDataStoreSync(sta,true); // local problem
4019 // if data access started (finished or not), check start status
4020 // for every execution and re-execution of the sync command
4021 if (testState(dssta_dataaccessstarted)) {
4022 // queue <sync> command if datastore is not yet started already
4023 if (engIsStarted(!startingNow)) { // wait only if start was already initiated
4024 // - is now initialized
4026 // - for server, make the sync set ready now (as engine is now started)
4027 changeState(dssta_syncsetready,true); // force it
4030 // - for client, we need at least dssta_syncgendone (sync set has been ready long ago, we've already sent changes to server!)
4031 if (!testState(dssta_syncgendone)) {
4032 // bad sequence of commands (<sync> from server too early!)
4033 aStatusCommand.setStatusCode(400);
4034 PDEBUGPRINTFX(DBG_ERROR,("TLocalEngineDS::engProcessSyncCmd: client received SYNC before sending SYNC complete"));
4035 engAbortDataStoreSync(400,false,false); // remote problem, not resumable
4039 PDEBUGPRINTFX(DBG_HOT,(
4040 "- Started %s Sync (first <sync> command)",
4041 fSlowSync ? "slow" : "normal"
4043 if (fRemoteNumberOfChanges>=0) PDEBUGPRINTFX(DBG_HOT,("- NumberOfChanges announced by remote = %ld",(long)fRemoteNumberOfChanges));
4044 DB_PROGRESS_EVENT(this,pev_syncstart,0,0,0);
4047 // - not yet started
4048 PDEBUGPRINTFX(DBG_HOT,(
4049 "- Starting sync not yet complete - re-execute <sync> command again in next message"
4051 aQueueForLater=true;
4052 return true; // ok so far
4055 // must be syncready here (otherwise we return before we reach this)
4056 if (!testState(dssta_syncsetready)) {
4057 aStatusCommand.setStatusCode(403); // forbidden
4058 ADDDEBUGITEM(aStatusCommand,"SYNC received too early");
4059 PDEBUGPRINTFX(DBG_ERROR,("TLocalEngineDS::engProcessSyncCmd: SYNC received too early"));
4060 engAbortDataStoreSync(403,false,false); // remote problem, not resumable
4063 // state is now syncing
4064 /// @deprecated - dssta_syncsetready is enough
4065 //fState=dss_syncing;
4067 } // TLocalEngineDS::engProcessSyncCmd
4070 // SYNC command bracket end (but another might follow in next message)
4071 bool TLocalEngineDS::engProcessSyncCmdEnd(bool &aQueueForLater)
4074 // queue it for later as long as datastore is not ready now
4075 if (!engIsStarted(false)) { // not waiting if not started
4076 // no state change, just postpone execution
4077 aQueueForLater=true;
4079 // also inform remote
4080 if (fRemoteDatastoreP) ok=fRemoteDatastoreP->remoteProcessSyncCmdEnd();
4082 } // TLocalEngineDS::engProcessSyncCmdEnd
4085 #ifdef SYSYNC_SERVER
4087 // server case: called whenever outgoing Message of Sync Package starts
4088 void TLocalEngineDS::engServerStartOfSyncMessage(void)
4090 // this is where we might start our own <Sync> command (all
4091 // received commands are now processed)
4092 // - Note that this might be a subdatastore -> if so, do NOT
4093 // start a sync (superdatastore will handle this)
4094 // - Note that this will be called even if current message is
4095 // already full, so it could well be that this sync command
4097 if (!fSessionP->fCompleteFromClientOnly && testState(dssta_serverseenclientmods) && getSyncMode()==smo_fromclient) {
4098 // from-client only does not send back a <sync> command, simply end data access here
4099 PDEBUGPRINTFX(DBG_PROTO,("from-client-only:do not send <sync> command back to client, data access ends here"));
4100 changeState(dssta_syncgendone,true);
4101 changeState(dssta_dataaccessdone,true);
4103 // ### SyncFest #5, found with Tactel Jazz Client:
4104 // - do not send anything when remote datastore is not known
4105 else if (fRemoteDatastoreP) {
4106 if (!testState(dssta_serversyncgenstarted) && testState(dssta_serverseenclientmods)) {
4107 changeState(dssta_serversyncgenstarted,true);
4108 if (!isSubDatastore()) {
4109 // - Note: if sync command was already started, the
4110 // finished(), continueIssue() mechanism will make sure that
4111 // more commands are generated
4112 // - Note2: if all sync commands can be sent at once,
4113 // fState will be modified by issuing <sync>, so
4114 // it must be ok for issuing syncops here already!
4115 TSyncCommand *synccmdP =
4118 this, // local datastore
4119 fRemoteDatastoreP // remote datastore
4122 ISSUE_COMMAND_ROOT(fSessionP,synccmdP);
4127 changeState(dssta_idle,true); // force it
4129 } // TLocalEngineDS::engServerStartOfSyncMessage
4132 #endif // server only
4135 // called whenever Message of Sync Package ends or after last queued Sync command is executed
4136 // - aEndOfAllSyncCommands is true when at end of Sync-data-from-remote packet
4137 // AND all possibly queued sync/syncop commands have been processed.
4138 void TLocalEngineDS::engEndOfSyncFromRemote(bool aEndOfAllSyncCommands)
4140 // is called for all local datastores, including superdatastore, even inactive ones, so check state first
4141 if (testState(dssta_syncsetready)) {
4142 if (aEndOfAllSyncCommands) {
4143 // we are at end of sync-data-from-remote for THIS datastore
4145 // - we are done with <Sync> from server, that is, data access is done now
4146 changeState(dssta_dataaccessdone,true); // force it
4149 // - server has seen all client modifications now
4150 // Note: in case of the simulated-empty-sync-hack in action, we
4151 // allow that we are already in server_sync_gen_started and
4152 // won't try to force down to dssta_serverseenclientmods
4153 if (!fSessionP->fFakeFinalFlag || getDSState()<dssta_serverseenclientmods) {
4154 // under normal circumstances, wee need dssta_serverseenclientmods here
4155 changeState(dssta_serverseenclientmods,true); // force it
4159 PDEBUGPRINTFX(DBG_ERROR,("Warning: simulated </final> active - allowing state>server_seen_client_mods"));
4163 #ifdef SYSYNC_SERVER
4165 engServerStartOfSyncMessage();
4168 // now do final things
4169 if (testState(dssta_dataaccessdone,true)) {
4170 // @todo: I think, as long as we need to send maps, we're not done yet!!!
4171 // sync packets in both directions done, forget remote datastore
4172 fRemoteDatastoreP=NULL;
4174 } // dssta_syncsetready
4175 } // TLocalEngineDS::engEndOfSyncFromRemote
4178 // - must return true if this datastore is finished with <sync>
4179 // (if all datastores return true,
4180 // session is allowed to finish sync packet with outgoing message
4181 bool TLocalEngineDS::isSyncDone(void)
4183 // is called for all local datastores, even inactive ones, which must signal sync done, too
4184 // - only datastores currently receiving or sending <sync> commands are not done with sync
4185 // - aborted datastores are also done with sync, no matter what status they have
4187 fAbortStatusCode!=0 ||
4188 //(fState!=dss_syncsend && fState!=dss_syncing && fState!=dss_syncready && fState!=dss_syncfinish)
4189 !testState(dssta_clientsentalert) || // nothing really happened yet...
4190 testState(dssta_syncgendone) // ...or already completed generating <sync>
4192 } // TLocalEngineDS::isSyncDone
4195 // test datastore state for minimal or exact state
4196 bool TLocalEngineDS::testState(TLocalEngineDSState aMinState, bool aNeedExactMatch)
4199 (!aNeedExactMatch || (fLocalDSState==aMinState)) &&
4200 (fLocalDSState>=aMinState);
4201 DEBUGPRINTFX(DBG_EXOTIC,(
4202 "%s: testState=%s - expected state%c='%s', found state=='%s'",
4204 result ? "TRUE" : "FALSE",
4205 aNeedExactMatch ? '=' : '>',
4206 getDSStateName(aMinState),
4210 } // TLocalEngineDS::testState
4213 // change datastore state, calls logic layer before and after change
4214 localstatus TLocalEngineDS::changeState(TLocalEngineDSState aNewState, bool aForceOnError)
4216 localstatus err1,err2;
4217 TLocalEngineDSState oldState = fLocalDSState;
4219 // nop if no change in state
4220 if (aNewState==oldState) return LOCERR_OK;
4221 // state cannot be decremented except down to adminready and below
4222 if ((aNewState<oldState) && (aNewState>dssta_adminready)) {
4223 PDEBUGPRINTFX(DBG_ERROR,(
4224 "%s: Internal error: attempt to reduce state from '%s' to '%s' - aborting",
4226 getDSStateName(oldState),
4227 getDSStateName(aNewState)
4230 dsAbortDatastoreSync(err1,true);
4233 // give logic opportunity to react before state changes
4236 "Datastore changes state",
4237 "datastore=%s|oldstate=%s|newstate=%s",
4239 getDSStateName(oldState),
4240 getDSStateName(aNewState)
4243 err1 = dsBeforeStateChange(oldState,aNewState);
4244 if (!aForceOnError && err1) goto endchange;
4246 fLocalDSState = aNewState;
4248 if (aNewState == dssta_syncmodestable) {
4249 // There are multiple places where the sync mode is frozen. Ensure
4250 // that this change is reported in all of them by putting the code
4252 PDEBUGPRINTFX(DBG_HOT,(
4253 "executing %s%s%s Sync%s",
4254 fResuming ? "resumed " : "",
4255 fSlowSync ? "slow" : "normal",
4256 fFirstTimeSync ? " first time" : "",
4257 fSyncMode == smo_twoway ? ", two-way" :
4258 fSyncMode == smo_fromclient ? " from client" :
4259 fSyncMode == smo_fromserver ? " from server" :
4260 " in unknown direction?!"
4262 #ifdef PROGRESS_EVENTS
4264 DB_PROGRESS_EVENT(this,
4266 fSlowSync ? (fFirstTimeSync ? 2 : 1) : 0,
4270 #endif // PROGRESS_EVENTS
4273 // now give logic opportunity to react again
4274 err2 = dsAfterStateChange(oldState,aNewState);
4276 PDEBUGENDBLOCK("DSStateChange");
4277 // return most recent error
4278 return err2 ? err2 : err1;
4279 } // TLocalEngineDS::changeState
4283 // test datastore abort status
4284 // datastore is aborted when
4285 // - it was explicitly aborted (engAbortDataStoreSync() called, fAbortStatusCode set)
4286 // - session is suspending and the datastore has not yet completed sync up to sending
4287 // maps (client) or admin already saved (server+client).
4288 // If client has sent maps, all that MIGHT be missing would be map status, and
4289 // if that hasn't arrived, the pendingMaps mechanism will make sure these get
4290 // sent in the next session.
4291 bool TLocalEngineDS::isAborted(void)
4293 return fAbortStatusCode!=0 || (fSessionP->isSuspending() && !testState(dssta_clientmapssent));
4294 } // TLocalEngineDS::isAborted
4297 // abort sync with this datastore
4298 void TLocalEngineDS::engAbortDataStoreSync(TSyError aStatusCode, bool aLocalProblem, bool aResumable)
4300 if (fLocalDSState!=dssta_idle && !fAbortStatusCode) {
4302 fAbortStatusCode = aStatusCode ? aStatusCode : 514; // make sure we have a non-zero fAbortStatusCode
4303 fLocalAbortCause = aLocalProblem;
4304 if (!aResumable) preventResuming(); // prevent resuming
4306 "DSAbort","Aborting datastore sync","abortStatusCode=%hd|localProblem=%s|resumable=%s",
4308 aLocalProblem ? "yes" : "no",
4309 aResumable ? "yes" : "no"
4311 // tell that to the session
4312 fSessionP->DatastoreFailed(aStatusCode,aLocalProblem);
4313 // as soon as sync set is ready, we have potentially started the sync and resume makes sense
4314 // NOTE: before we have made the sync set ready, WE MUST NOT resume, because making the sync
4315 // set ready includes zapping it on slow refreshes, and this is only done when not resuming
4316 // (so saving a suspend state before dssta_syncsetready would cause that the zapping is
4317 // possibly skipped)
4318 if (!testState(dssta_syncsetready)) preventResuming(); // prevent resuming before sync set is ready
4319 // save resume (or non-resumable!) status only if this is NOT A TIMEOUT, because if it is a
4320 // (server) timeout, suspend state was saved at end of last request, and writing again here would destroy
4322 if (aStatusCode!=408) {
4323 engSaveSuspendState(true); // save even if already aborted
4325 // let derivates know
4326 dsAbortDatastoreSync(aStatusCode, aLocalProblem);
4328 PDEBUGPRINTFX(DBG_ERROR,(
4329 "*************** Warning: Datastore flagged aborted (after %ld sec. request processing, %ld sec. total) with %s Status %hd",
4330 (long)((getSession()->getSystemNowAs(TCTX_UTC)-fSessionP->getLastRequestStarted()) / secondToLinearTimeFactor),
4331 (long)((getSession()->getSystemNowAs(TCTX_UTC)-fSessionP->getSessionStarted()) / secondToLinearTimeFactor),
4332 aLocalProblem ? "LOCAL" : "REMOTE",
4338 getAbortStatusCode(),
4339 fSlowSync ? (fFirstTimeSync ? 2 : 1) : 0,
4342 PDEBUGENDBLOCK("DSAbort");
4344 } // TLocalEngineDS::engAbortDataStoreSync
4347 // check if aborted, set status to abort reason code if yes
4348 bool TLocalEngineDS::CheckAborted(TStatusCommand &aStatusCommand)
4350 if (fAbortStatusCode!=0) {
4351 aStatusCommand.setStatusCode(
4352 fSessionP->getSyncMLVersion()>=syncml_vers_1_1 ? 514 : // cancelled
4353 (fAbortStatusCode<LOCAL_STATUS_CODE ? fAbortStatusCode : 512) // sync failed
4355 PDEBUGPRINTFX(DBG_DATA,("This datastore is in aborted state, rejects all commands with %hd",aStatusCommand.getStatusCode()));
4356 return true; // aborted, status set
4358 return false; // not aborted
4359 } // TLocalEngineDS::CheckAborted
4363 // Do common logfile substitutions
4364 void TLocalEngineDS::DoLogSubstitutions(string &aLog,bool aPlaintext)
4366 #ifndef MINIMAL_CODE
4370 StringObjTimestamp(s,fEndOfSyncTime);
4371 // %T Time of sync (in derived datastores, this is the point of reference for newer/older comparisons) as plain text
4372 StringSubst(aLog,"%T",s,2);
4373 // %seT Time of session end (with this datastore) as plain text
4374 StringSubst(aLog,"%seT",s,4);
4375 // %ssT Time of session start as plain text
4376 StringObjTimestamp(s,fSessionP->getSessionStarted());
4377 StringSubst(aLog,"%ssT",s,4);
4379 // %sdT sync duration (in seconds) for this datastore (start of session until datastore finished)
4380 StringSubst(aLog,"%sdT",((sInt32)(fEndOfSyncTime-fSessionP->getSessionStarted())/secondToLinearTimeFactor),4);
4381 // %nD Datastore name
4382 StringSubst(aLog,"%nD",getName(),3);
4383 // %rD Datastore remote path
4384 StringSubst(aLog,"%rD",fRemoteDBPath,3);
4385 // %lD Datastore local path (complete with all CGI)
4386 StringSubst(aLog,"%lD",fRemoteViewOfLocalURI,3);
4387 // %iR Remote Device ID (URI)
4388 StringSubst(aLog,"%iR",fSessionP->getRemoteURI(),3);
4389 // %nR Remote name: [Manufacturer ]Model")
4390 StringSubst(aLog,"%nR",fSessionP->getRemoteDescName(),3);
4391 // %vR Remote Device Version Info ("Type (HWV, FWV, SWV) Oem")
4392 StringSubst(aLog,"%vR",fSessionP->getRemoteInfoString(),3);
4394 StringSubst(aLog,"%U",fSessionP->getSyncUserName(),2);
4395 // %iS local Session ID
4396 StringSubst(aLog,"%iS",fSessionP->getLocalSessionID(),3);
4397 // %sS Status code (0 if successful)
4398 StringSubst(aLog,"%sS",fAbortStatusCode,3);
4399 // %ssS Session Status code (0 if successful)
4400 StringSubst(aLog,"%ssS",fSessionP->getAbortReasonStatus(),4);
4401 // %syV SyncML version (as text) of session
4402 StringSubst(aLog,"%syV",SyncMLVerDTDNames[fSessionP->getSyncMLVersion()],4);
4403 // %syV SyncML version numeric (0=unknown, 1=1.0, 2=1.1, 3=1.2) of session
4404 StringSubst(aLog,"%syVn",(long)fSessionP->getSyncMLVersion(),5);
4405 // %mS Syncmode (0=twoway, 1=fromclient 2=fromserver)
4406 StringSubst(aLog,"%mS",(sInt32)fSyncMode,3);
4407 // %tS Synctype (0=normal,1=slow,2=firsttime slow, +10 if resumed session)
4408 StringSubst(aLog,"%tS",(fSlowSync ? (fFirstTimeSync ? 2 : 1) : 0) + (isResuming() ? 10 : 0),3);
4409 // %laI locally added Items
4410 StringSubst(aLog,"%laI",fLocalItemsAdded,4);
4411 // %raI remotely added Items
4412 StringSubst(aLog,"%raI",fRemoteItemsAdded,4);
4413 // %ldI locally deleted Items
4414 StringSubst(aLog,"%ldI",fLocalItemsDeleted,4);
4415 // %rdI remotely deleted Items
4416 StringSubst(aLog,"%rdI",fRemoteItemsDeleted,4);
4417 // %luI locally updated Items
4418 StringSubst(aLog,"%luI",fLocalItemsUpdated,4);
4419 // %ruI remotely updated Items
4420 StringSubst(aLog,"%ruI",fRemoteItemsUpdated,4);
4421 // %reI locally not accepted Items (sent error to remote, remote MAY resend them or abort the session)
4422 StringSubst(aLog,"%leI",fLocalItemsError,4);
4423 // %leI remotely not accepted Items (got error from remote, local will resend them later)
4424 StringSubst(aLog,"%reI",fRemoteItemsError,4);
4425 #ifdef SYSYNC_SERVER
4427 // %smI Slowsync matched Items
4428 StringSubst(aLog,"%smI",fSlowSyncMatches,4);
4429 // %scI Server won Conflicts
4430 StringSubst(aLog,"%scI",fConflictsServerWins,4);
4431 // %ccI Client won Conflicts
4432 StringSubst(aLog,"%ccI",fConflictsClientWins,4);
4433 // %dcI Conflicts with duplications
4434 StringSubst(aLog,"%dcI",fConflictsDuplicated,4);
4435 // %tiB total incoming bytes
4436 StringSubst(aLog,"%tiB",fSessionP->getIncomingBytes(),4);
4437 // %toB total outgoing bytes
4438 StringSubst(aLog,"%toB",fSessionP->getOutgoingBytes(),4);
4441 // %niB net incoming data bytes for this datastore
4442 StringSubst(aLog,"%diB",fIncomingDataBytes,4);
4443 // %noB net incoming data bytes for this datastore
4444 StringSubst(aLog,"%doB",fOutgoingDataBytes,4);
4446 } // TLocalEngineDS::DoLogSubstitutions
4449 // log datastore sync result
4450 // - Called at end of sync with this datastore
4451 void TLocalEngineDS::dsLogSyncResult(void)
4453 #ifndef MINIMAL_CODE
4454 if (fSessionP->logEnabled()) {
4456 logtext=fSessionP->getSessionConfig()->fLogFileFormat;
4457 if (!logtext.empty()) {
4459 DoLogSubstitutions(logtext,true); // plaintext
4461 fSessionP->WriteLogLine(logtext.c_str());
4465 } // TLocalEngineDS::dsLogSyncResult
4469 // Terminate all activity with this datastore
4470 // Note: may be called repeatedly, must only execute relevant shutdown code once
4471 void TLocalEngineDS::engTerminateDatastore(localstatus aAbortStatusCode)
4473 // now abort (if not already aborted), then finish activities
4474 engFinishDataStoreSync(aAbortStatusCode);
4475 // and finally reset completely
4476 engResetDataStore();
4477 } // TLocalEngineDS::TerminateDatastore
4480 // called at very end of sync session, when everything is done
4481 // Note: is also called before deleting a datastore (so aborted sessions
4482 // can do cleanup and/or statistics display as well)
4483 void TLocalEngineDS::engFinishDataStoreSync(localstatus aErrorStatus)
4485 // set end of sync time
4486 fEndOfSyncTime = getSession()->getSystemNowAs(TCTX_UTC);
4487 // check if we have something to do at all
4488 if (fLocalDSState!=dssta_idle && fLocalDSState!=dssta_completed) {
4489 if (aErrorStatus==LOCERR_OK) {
4490 // Check if we need to abort now due to failed items only
4491 if (fRemoteItemsError>0) {
4492 // remote reported errors
4493 if (fSlowSync && fRemoteItemsAdded==0 && fRemoteItemsDeleted==0 && fRemoteItemsUpdated==0 && fSessionP->getSessionConfig()->fAbortOnAllItemsFailed) {
4494 PDEBUGPRINTFX(DBG_ERROR+DBG_DETAILS,("All remote item operations failed -> abort sync"));
4495 engAbortDataStoreSync(512,false,false); // remote problems (only failed items in a slow sync) caused sync to fail, not resumable
4498 fSessionP->DatastoreHadErrors(); // at least SOME items were successful, so it's not a completely unsuccessful sync
4501 // abort, if requested from caller or only-failed-items
4502 if (aErrorStatus!=LOCERR_OK)
4503 engAbortDataStoreSync(aErrorStatus,true); // if we have an error here, this is considered a local problem
4509 fSlowSync ? (fFirstTimeSync ? 2 : 1) : 0,
4513 #ifdef SUPERDATASTORES
4514 // if this is part of a superdatastore, include its statistics into mine, as
4515 // superdatastore can not save any statistics.
4516 // This ensures that the result sum over all subdatastores is correct,
4517 // however the assignment of error and byte counts is not (all non-related
4518 // counts go to first subdatastores with the following code)
4519 if (fAsSubDatastoreOf) {
4520 fOutgoingDataBytes += fAsSubDatastoreOf->fOutgoingDataBytes;
4521 fIncomingDataBytes += fAsSubDatastoreOf->fIncomingDataBytes;
4522 fRemoteItemsError += fAsSubDatastoreOf->fRemoteItemsError;
4523 fLocalItemsError += fAsSubDatastoreOf->fLocalItemsError;
4524 // consumed now, clear in superdatastore
4525 fAsSubDatastoreOf->fOutgoingDataBytes=0;
4526 fAsSubDatastoreOf->fIncomingDataBytes=0;
4527 fAsSubDatastoreOf->fRemoteItemsError=0;
4528 fAsSubDatastoreOf->fLocalItemsError=0;
4533 // update my session state vars for successful sessions
4534 if (aErrorStatus==LOCERR_OK) {
4536 fLastRemoteAnchor=fNextRemoteAnchor;
4537 fLastLocalAnchor=fNextLocalAnchor; // note: when using TStdLogicDS, this is not saved, but re-generated at next sync from timestamp
4540 // no resume item (just to make sure we don't get strange effects later)
4541 fLastItemStatus = 0;
4542 fLastSourceURI.erase();
4543 fLastTargetURI.erase();
4544 fPartialItemState = pi_state_none;
4547 // now shift state to complete, let logic and implementation save the state
4548 changeState(dssta_completed,true);
4549 #ifdef SCRIPT_SUPPORT
4550 // - call DB finish script
4551 TScriptContext::execute(
4552 fDataStoreScriptContextP,
4553 fDSConfigP->fDBFinishScript,
4554 &DBFuncTable, // context's function table
4555 this // datastore pointer needed for context
4559 // in any case: idle now again (note: could be shift from dssta_completed to dssta_idle)
4560 changeState(dssta_idle,true);
4561 } // TLocalEngineDS::engFinishDataStoreSync
4564 /// inform everyone of coming state change
4565 localstatus TLocalEngineDS::dsBeforeStateChange(TLocalEngineDSState aOldState,TLocalEngineDSState aNewState)
4567 localstatus sta = LOCERR_OK;
4569 } // TLocalEngineDS::dsBeforeStateChange
4572 /// inform everyone of happened state change
4573 localstatus TLocalEngineDS::dsAfterStateChange(TLocalEngineDSState aOldState,TLocalEngineDSState aNewState)
4575 localstatus sta = LOCERR_OK;
4576 if (aOldState>dssta_idle && aNewState==dssta_completed) {
4577 // we are going from a non-idle state to completed
4578 // - show statistics
4582 } // TLocalEngineDS::dsAfterStateChange
4585 // show statistics or error of current sync
4586 void TLocalEngineDS::showStatistics(void)
4589 CONSOLEPRINTF((""));
4590 CONSOLEPRINTF(("- Sync Statistics for '%s' (%s), %s sync",
4592 fRemoteViewOfLocalURI.c_str(),
4593 fSlowSync ? "slow" : "normal"
4598 CONSOLEPRINTF((" ************ Failed with status code=%hd",fAbortStatusCode));
4601 // successful: show statistics on console
4602 CONSOLEPRINTF((" =================================================="));
4604 CONSOLEPRINTF((" on Server on Client"));
4607 CONSOLEPRINTF((" on Client on Server"));
4609 CONSOLEPRINTF((" Added: %9ld %9ld",(long)fLocalItemsAdded,(long)fRemoteItemsAdded));
4610 CONSOLEPRINTF((" Deleted: %9ld %9ld",(long)fLocalItemsDeleted,(long)fRemoteItemsDeleted));
4611 CONSOLEPRINTF((" Updated: %9ld %9ld",(long)fLocalItemsUpdated,(long)fRemoteItemsUpdated));
4612 CONSOLEPRINTF((" Rejected with error: %9ld %9ld",(long)fLocalItemsError,(long)fRemoteItemsError));
4613 #ifdef SYSYNC_SERVER
4615 CONSOLEPRINTF((" SlowSync Matches: %9ld",(long)fSlowSyncMatches));
4616 CONSOLEPRINTF((" Server won Conflicts: %9ld",(long)fConflictsServerWins));
4617 CONSOLEPRINTF((" Client won Conflicts: %9ld",(long)fConflictsClientWins));
4618 CONSOLEPRINTF((" Conflicts with Duplication: %9ld",(long)fConflictsDuplicated));
4622 CONSOLEPRINTF((""));
4623 // Always provide statistics as events
4624 DB_PROGRESS_EVENT(this,pev_dsstats_l,fLocalItemsAdded,fLocalItemsUpdated,fLocalItemsDeleted);
4625 DB_PROGRESS_EVENT(this,pev_dsstats_r,fRemoteItemsAdded,fRemoteItemsUpdated,fRemoteItemsDeleted);
4626 DB_PROGRESS_EVENT(this,pev_dsstats_e,fLocalItemsError,fRemoteItemsError,0);
4627 #ifdef SYSYNC_SERVER
4629 DB_PROGRESS_EVENT(this,pev_dsstats_s,fSlowSyncMatches,0,0);
4630 DB_PROGRESS_EVENT(this,pev_dsstats_c,fConflictsServerWins,fConflictsClientWins,fConflictsDuplicated);
4633 // NOTE: pev_dsstats_d should remain the last log data event sent (as it terminates collecting data in some GUIs)
4634 DB_PROGRESS_EVENT(this,pev_dsstats_d,fOutgoingDataBytes,fIncomingDataBytes,fRemoteItemsError);
4635 // Always show statistics in debug log
4637 PDEBUGPRINTFX(DBG_HOT,("Sync Statistics for '%s' (%s), %s sync",
4639 fRemoteViewOfLocalURI.c_str(),
4640 fSlowSync ? "slow" : "normal"
4642 if (PDEBUGTEST(DBG_HOT)) {
4644 "==================================================\n";
4646 stats += " on Server on Client\n";
4649 stats += " on Client on Server\n";
4651 StringObjAppendPrintf(stats,"Added: %9ld %9ld\n",(long)fLocalItemsAdded,(long)fRemoteItemsAdded);
4652 StringObjAppendPrintf(stats,"Deleted: %9ld %9ld\n",(long)fLocalItemsDeleted,(long)fRemoteItemsDeleted);
4653 StringObjAppendPrintf(stats,"Updated: %9ld %9ld\n",(long)fLocalItemsUpdated,(long)fRemoteItemsUpdated);
4654 StringObjAppendPrintf(stats,"Rejected with error: %9ld %9ld\n\n",(long)fLocalItemsError,(long)fRemoteItemsError);
4655 #ifdef SYSYNC_SERVER
4657 StringObjAppendPrintf(stats,"SlowSync Matches: %9ld\n",(long)fSlowSyncMatches);
4658 StringObjAppendPrintf(stats,"Server won Conflicts: %9ld\n",(long)fConflictsServerWins);
4659 StringObjAppendPrintf(stats,"Client won Conflicts: %9ld\n",(long)fConflictsClientWins);
4660 StringObjAppendPrintf(stats,"Conflicts with Duplication: %9ld\n\n",(long)fConflictsDuplicated);
4663 StringObjAppendPrintf(stats,"Content Data Bytes sent: %9ld\n",(long)fOutgoingDataBytes);
4664 StringObjAppendPrintf(stats,"Content Data Bytes received: %9ld\n\n",(long)fIncomingDataBytes);
4665 StringObjAppendPrintf(stats,"Duration of sync [seconds]: %9ld\n",(long)((fEndOfSyncTime-fSessionP->getSessionStarted())/secondToLinearTimeFactor));
4666 PDEBUGPUTSXX(DBG_HOT,stats.c_str(),0,true);
4670 PDEBUGPRINTFX(DBG_ERROR,("Warning: Failed with status code=%hd, statistics are incomplete!!",fAbortStatusCode));
4673 } // TLocalEngineDS::showStatistics
4677 // create a new syncop command for sending to remote
4678 TSyncOpCommand *TLocalEngineDS::newSyncOpCommand(
4679 TSyncItem *aSyncItemP, // the sync item
4680 TSyncItemType *aSyncItemTypeP, // the sync item type
4681 cAppCharP aLocalIDPrefix
4685 TSyncOperation syncop=aSyncItemP->getSyncOp();
4687 SmlPcdataPtr_t metaP = newMetaType(aSyncItemTypeP->getTypeName());
4689 TSyncOpCommand *syncopcmdP = new TSyncOpCommand(fSessionP,this,syncop,metaP);
4690 // make sure item does not have stuff it is not allowed to have
4691 // %%% SCTS does not like SourceURI in Replace and Delete commands sent to Client
4692 // there are the only ones allowed to carry a GUID
4694 #ifdef SYSYNC_SERVER
4695 // Server: commands only have remote IDs, except add which only has target ID
4696 if (syncop==sop_add || syncop==sop_wants_add)
4697 aSyncItemP->clearRemoteID(); // no remote ID
4699 if (!fDSConfigP->fAlwaysSendLocalID &&
4700 aSyncItemP->hasRemoteID()) {
4701 // only if localID may not be included in all syncops,
4702 // and not if the item has no remote ID yet
4704 // The second case had to be added to solve an issue
4705 // during suspended syncs:
4706 // - server tries to add a new item and uses the Replace op for it
4707 // - pending Replace is added to map
4708 // - next sync resends the Replace, but with empty IDs and thus
4709 // cannot be processed by client
4711 // Log from such a failed sync:
4712 // Item localID='328' already has map entry: remoteid='', mapflags=0x1, changed=0, deleted=0, added=0, markforresume=0, savedmark=1
4713 // Resuming and found marked-for-resume -> send replace
4715 // Command 'Replace': is 1-th counted cmd, cmdsize(+tags needed to end msg)=371, available=130664 (maxfree=299132, freeaftersend=298761, notUsableBufferBytes()=168468)
4716 // Item remoteID='', localID='', datasize=334
4717 // Replace: issued as (outgoing MsgID=2, CmdID=4), now queueing for status
4719 // Status 404: Replace target not found on client -> silently ignore but remove map in server (item will be added in next session),
4720 aSyncItemP->clearLocalID(); // no local ID
4726 // Client: all commands only have local IDs
4727 aSyncItemP->clearRemoteID(); // no remote ID
4729 // add the localID prefix if we do have a localID to send
4730 if (aSyncItemP->hasLocalID()) {
4732 #ifdef SYSYNC_SERVER
4733 // make sure GUID (plus prefixes) is not exceeding allowed size
4734 adjustLocalIDforSize(aSyncItemP->fLocalID,getRemoteDatastore()->getMaxGUIDSize(),aLocalIDPrefix ? strlen(aLocalIDPrefix) : 0);
4737 // add local ID prefix, if any
4738 if (aLocalIDPrefix && *aLocalIDPrefix)
4739 aSyncItemP->fLocalID.insert(0,aLocalIDPrefix);
4741 #ifdef SYSYNC_TARGET_OPTIONS
4742 // init item generation variables
4743 fItemSizeLimit=fSizeLimit;
4745 fItemSizeLimit=-1; // no limit
4748 SmlItemPtr_t itemP = aSyncItemTypeP->newSmlItem(aSyncItemP,this);
4749 // check if data size is ok
4750 if (itemP && fSessionP->fMaxOutgoingObjSize) {
4751 if (itemP->data && itemP->data->content && itemP->data->length) {
4752 // there is data, check if size is ok
4753 if (itemP->data->length > fSessionP->fMaxOutgoingObjSize) {
4754 // too large, suppress it
4755 PDEBUGPRINTFX(DBG_ERROR,(
4756 "WARNING: outgoing item is larger (%ld) than MaxObjSize (%ld) of remote -> suppress now/mark for resend",
4757 (long)itemP->data->length,
4758 (long)fSessionP->fMaxOutgoingObjSize
4760 smlFreeItemPtr(itemP);
4762 // mark item for resend
4763 // For datastores without resume support, this will just have no effect at all
4764 engMarkItemForResend(aSyncItemP->getLocalID(),aSyncItemP->getRemoteID());
4769 // add it to the command
4770 syncopcmdP->addItem(itemP);
4773 // no item - command is invalid, delete it
4779 } // TLocalEngineDS::newSyncOpCommand
4782 // create SyncItem suitable for being sent from local to remote
4783 TSyncItem *TLocalEngineDS::newItemForRemote(
4784 uInt16 aExpectedTypeID // typeid of expected type
4788 if (!canCreateItemForRemote())
4789 SYSYNC_THROW(TSyncException("newItemForRemote called without sufficient type information ready"));
4791 TSyncItem *itemP = fLocalSendToRemoteTypeP->newSyncItem(fRemoteReceiveFromLocalTypeP,this);
4793 SYSYNC_THROW(TSyncException("newItemForRemote could not create item"));
4795 if (!itemP->isBasedOn(aExpectedTypeID)) {
4796 PDEBUGPRINTFX(DBG_ERROR,(
4797 "newItemForRemote created item of typeID %hd, caller expects %hd",
4801 SYSYNC_THROW(TSyncException("newItemForRemote created wrong item type"));
4804 } // TLocalEngineDS::newItemForRemote
4807 // return pure relative (item) URI (removes absolute part or ./ prefix)
4808 const char *TLocalEngineDS::DatastoreRelativeURI(const char *aURI)
4810 return relativeURI(relativeURI(aURI,fSessionP->getLocalURI()),getName());
4811 } // TLocalEngineDS::DatastoreRelativeURI
4815 // - init filtering and check if needed (sets fTypeFilteringNeeded, fFilteringNeeded and fFilteringNeededForAll)
4816 void TLocalEngineDS::initPostFetchFiltering(void)
4818 #ifdef OBJECT_FILTERING
4819 if (!fLocalSendToRemoteTypeP) {
4820 fTypeFilteringNeeded=false;
4821 fFilteringNeeded=false;
4822 fFilteringNeededForAll=false;
4825 // get basic settings from type
4826 fLocalSendToRemoteTypeP->initPostFetchFiltering(fTypeFilteringNeeded,fFilteringNeededForAll,this);
4827 fFilteringNeeded=fTypeFilteringNeeded;
4828 // NOTE: if type filtering is needed, it's the responsibility of initPostFetchFiltering() of
4829 // the type to check (using the DBHANDLESOPTS() script func) if DB does already handle
4830 // the range filters and such and possibly avoid type filtering then.
4831 // then check for standard filter requirements
4833 #ifdef SYNCML_TAF_SUPPORT
4834 if (!fTargetAddressFilter.empty()) PDEBUGPRINTFX(DBG_DATA,("using (dynamic, temporary) TAF expression from CGI : %s",fTargetAddressFilter.c_str()));
4835 if (!fIntTargetAddressFilter.empty()) PDEBUGPRINTFX(DBG_DATA,("using (dynamic, temporary) internally set TAF expression : %s",fIntTargetAddressFilter.c_str()));
4836 #endif // SYNCML_TAF_SUPPORT
4837 if (!fSyncSetFilter.empty()) PDEBUGPRINTFX(DBG_DATA,("using (dynamic) sync set filter expression : %s",fSyncSetFilter.c_str()));
4838 if (!fLocalDBFilter.empty()) PDEBUGPRINTFX(DBG_DATA,("using (static) local db filter expression : %s",fLocalDBFilter.c_str()));
4840 // - if DB does the standard filters, we don't need to check them here again
4841 if (!engFilteredFetchesFromDB(true)) {
4842 // If DB does NOT do the standard filters, we have to do them here
4843 // - this is the case if we have an (old-style) sync set filter, but not filtered by DB
4844 // we need to filter all because sync set filter can be dynamic
4845 if (!fSyncSetFilter.empty())
4846 fFilteringNeededForAll=true;
4847 // always return true if there is something to filter at all
4849 !fLocalDBFilter.empty() ||
4850 !fDSConfigP->fInvisibleFilter.empty() ||
4851 !fSyncSetFilter.empty() ||
4852 !fDSConfigP->fRemoteAcceptFilter.empty()
4854 fFilteringNeeded=true;
4857 PDEBUGPRINTFX(DBG_FILTER+DBG_HOT,(
4858 "Datastore-level postfetch filtering %sneeded%s",
4859 fFilteringNeeded ? "" : "NOT ",
4860 fFilteringNeeded ? (fFilteringNeededForAll ? " and to be applied to all records" : " only for changed records") : ""
4863 } // TLocalEngineDS::initPostFetchFiltering
4866 // filter fetched record
4867 bool TLocalEngineDS::postFetchFiltering(TSyncItem *aSyncItemP)
4869 #ifndef OBJECT_FILTERING
4870 return true; // no filters, always pass
4872 if (!aSyncItemP) return false; // null item does not pass
4873 // first do standard filters
4874 // - if DB has filtered the
4876 if (fFilteringNeeded) {
4877 // - first make sure outgoing object has all properties set
4878 // such that it would pass the acceptance filter (for example KIND for calendar...)
4879 if (!aSyncItemP->makePassFilter(fDSConfigP->fRemoteAcceptFilter.c_str())) {
4880 // we could not make item pass acceptance filters
4881 PDEBUGPRINTFX(DBG_ERROR,("- item localid='%s' cannot be made passing <acceptfilter> -> ignored",aSyncItemP->getLocalID()));
4884 // now check for field-level filters
4885 if (passes && !engFilteredFetchesFromDB()) {
4886 // DB has not already filtered these, so we need to do it here
4887 // - "moving target" first
4888 passes=fSyncSetFilter.empty() || aSyncItemP->testFilter(fSyncSetFilter.c_str());
4892 aSyncItemP->testFilter(fLocalDBFilter.c_str()) && // local filter
4894 fDSConfigP->fInvisibleFilter.empty() || // and either no invisibility defined...
4895 !aSyncItemP->testFilter(fDSConfigP->fInvisibleFilter.c_str()) // ...or NOT passed
4899 if (passes && fTypeFilteringNeeded) {
4900 // finally, apply type's filter
4901 passes=aSyncItemP->postFetchFiltering(this);
4905 // no filtering needed, DB has already filtered out those that would not pass
4906 // BUT: make sure outgoing items WILL pass the acceptance filter. If this
4907 // cannot be done, item will be filtered out.
4908 if (!aSyncItemP->makePassFilter(fDSConfigP->fRemoteAcceptFilter.c_str())) {
4909 // we could not make item pass acceptance filters
4910 PDEBUGPRINTFX(DBG_ERROR,("- item localid='%s' cannot be made passing <acceptfilter> -> ignored",aSyncItemP->getLocalID()));
4916 PDEBUGPRINTFX(DBG_DATA,("- item localid='%s' does not pass filters -> ignored",aSyncItemP->getLocalID()));
4922 } // TLocalEngineDS::postFetchFiltering
4925 #ifdef OBJECT_FILTERING
4927 // - called to check if incoming item passes acception filters
4928 bool TLocalEngineDS::isAcceptable(TSyncItem *aSyncItemP, TStatusCommand &aStatusCommand)
4931 if (aSyncItemP->testFilter(fDSConfigP->fRemoteAcceptFilter.c_str())) return true; // ok
4932 // not accepted, set 415 error
4933 if (!fDSConfigP->fSilentlyDiscardUnaccepted)
4934 aStatusCommand.setStatusCode(415);
4935 ADDDEBUGITEM(aStatusCommand,"Received item does not pass acceptance filter");
4936 PDEBUGPRINTFX(DBG_ERROR,(
4937 "Received item does not pass acceptance filter: %s",
4938 fDSConfigP->fRemoteAcceptFilter.c_str()
4941 } // TLocalEngineDS::isAcceptable
4944 /// @brief called to make incoming item visible
4945 /// @return true if now visible
4946 bool TLocalEngineDS::makeVisible(TSyncItem *aSyncItemP)
4948 bool invisible=false;
4949 if (!fDSConfigP->fInvisibleFilter.empty()) {
4950 invisible=aSyncItemP->testFilter(fDSConfigP->fInvisibleFilter.c_str());
4953 return aSyncItemP->makePassFilter(fDSConfigP->fMakeVisibleFilter.c_str());
4955 return true; // is already visible
4956 } // TLocalEngineDS::makeVisible
4959 /// @brief called to make incoming item INvisible
4960 /// @return true if now INvisible
4961 bool TLocalEngineDS::makeInvisible(TSyncItem *aSyncItemP)
4963 // return true if could make invisible or already was invisible
4964 if (fDSConfigP->fInvisibleFilter.empty())
4965 return false; // no invisible filter, cannot make invisible
4966 // make pass invisible filter - if successful, we're now invisible
4967 return aSyncItemP->makePassFilter(fDSConfigP->fInvisibleFilter.c_str()); // try to make invisible (and return result)
4968 } // TLocalEngineDS::makeInvisible
4972 // - called to make incoming item pass sync set filtering
4973 bool TLocalEngineDS::makePassSyncSetFilter(TSyncItem *aSyncItemP)
4977 // make sure we pass sync set filtering and stay visible
4978 if (!fSyncSetFilter.empty()) {
4979 // try to make pass sync set filter (modifies item only if it would not pass otherwise)
4980 pass=aSyncItemP->makePassFilter(fSyncSetFilter.c_str());
4982 if (!pass || fSyncSetFilter.empty()) {
4983 // specified sync set filter cannot make item pass, or no sync set filter at all:
4984 // - apply makePassFilter default expression
4985 if (!fDSConfigP->fMakePassFilter.empty()) {
4986 pass=aSyncItemP->makePassFilter(fDSConfigP->fMakePassFilter.c_str());
4988 // check again to check if item would pass the syncset filter now
4989 pass=aSyncItemP->testFilter(fSyncSetFilter.c_str());
4994 } // TLocalEngineDS::makePassSyncSetFilter
4999 // process remote item
5000 bool TLocalEngineDS::engProcessRemoteItem(
5001 TSyncItem *syncitemP,
5002 TStatusCommand &aStatusCommand
5005 #ifdef SYSYNC_CLIENT
5007 return engProcessRemoteItemAsClient(syncitemP,aStatusCommand); // status, must be set to correct status code (ok / error)
5009 #ifdef SYSYNC_SERVER
5011 return engProcessRemoteItemAsServer(syncitemP,aStatusCommand); // status, must be set to correct status code (ok / error)
5015 } // TLocalEngineDS::engProcessRemoteItem
5018 // process SyncML SyncOp command for this datastore
5019 bool TLocalEngineDS::engProcessSyncOpItem(
5020 TSyncOperation aSyncOp, // the operation
5021 SmlItemPtr_t aItemP, // the item to be processed
5022 SmlMetInfMetInfPtr_t aMetaP, // command-wide meta, if any
5023 TStatusCommand &aStatusCommand // pre-set 200 status, can be modified in case of errors
5026 bool regular = false;
5027 // determine SyncItemType that can handle this item data
5028 if (fRemoteDatastoreP==NULL) {
5029 PDEBUGPRINTFX(DBG_ERROR,("engProcessSyncOpItem: Remote Datastore not known"));
5030 aStatusCommand.setStatusCode(500);
5032 // - start with default
5033 TSyncItemType *remoteTypeP=getRemoteSendType();
5034 TSyncItemType *localTypeP=getLocalReceiveType();
5035 // - see if command-wide meta plus item contents specify another type
5036 // (item meta, if present, overrides command wide meta)
5037 // see if item itself or command meta specify a type name or format
5038 SmlMetInfMetInfPtr_t itemmetaP = smlPCDataToMetInfP(aItemP->meta);
5040 TFmtTypes fmt=fmt_chr;
5041 if (itemmetaP && itemmetaP->format)
5042 smlPCDataToFormat(itemmetaP->format,fmt); // use type name from item's meta
5043 else if (aMetaP && aMetaP->format)
5044 smlPCDataToFormat(aMetaP->format,fmt); // use type name from command-wide meta
5047 const char *typestr = NULL;
5048 if (itemmetaP && itemmetaP->type)
5049 typestr = smlPCDataToCharP(itemmetaP->type); // use type name from item's meta
5050 else if (aMetaP && aMetaP->type)
5051 typestr = smlPCDataToCharP(aMetaP->type); // use type name from command-wide meta
5052 // check if there is a type specified
5054 PDEBUGPRINTFX(DBG_DATA,("Explicit type '%s' specified in command or item meta",typestr));
5055 if (strcmp(remoteTypeP->getTypeName(),typestr)!=0) {
5056 // specified type is NOT default type: search appropriate remote type
5057 remoteTypeP=fRemoteDatastoreP->getSendType(typestr,NULL); // no version known so far
5059 // specified type is not a remote type listed in remote's devInf.
5060 // But as remote is actually using it, we can assume it does support it, so use local type of same name instead
5061 PDEBUGPRINTFX(DBG_ERROR,("According to remote devInf, '%s' is not supported, but obviously it is used here so we try to handle it",typestr));
5062 // look it up in local datastore's list
5063 remoteTypeP=getReceiveType(typestr,NULL);
5067 #ifdef APP_CAN_EXPIRE
5068 // get modified date of item
5069 lineardate_t moddat=0; // IMPORTANT, must be initialized in case expiryFromData returns nothing!
5070 bool ok = remoteTypeP->expiryFromData(aItemP,moddat)<=MAX_EXPIRY_DIFF+5;
5071 // ok==true: we are within hard expiry
5072 // ok==false: we are out of hard expiry
5073 #ifdef SYSER_REGISTRATION
5074 if (getSession()->getSyncAppBase()->fRegOK) {
5075 // we have a license (permanent or timed) --> hard expiry is irrelevant
5076 // (so override ok according to validity of current license)
5077 ok=true; // assume ok
5078 // check if license is timed, and if so, check if mod date is within timed range
5079 // (if not, set ok to false)
5080 uInt8 rd = getSession()->getSyncAppBase()->fRegDuration;
5082 lineardate_t ending = date2lineardate(rd/12+2000,rd%12+1,1);
5083 ok = ending>=moddat; // ok if not modified after end of license period
5087 // when we have no license (neither permanent nor timed), hard expiry decides as is
5088 // (so just use ok as is)
5090 aStatusCommand.setStatusCode(403); // forbidden to hack this expiry stuff!
5091 fSessionP->AbortSession(403,true); // local problem
5094 #endif // APP_CAN_EXPIRE
5095 // we have a type, which should be able to determine version from data
5096 if (remoteTypeP->versionFromData(aItemP,versstr)) {
5097 // version found, Make sure version matches as well
5098 PDEBUGPRINTFX(DBG_DATA,("Version '%s' obtained from item data",versstr.c_str()));
5099 // check if current remotetype already has correct version (and type, but we know this already)
5100 if (!remoteTypeP->supportsType(remoteTypeP->getTypeName(),versstr.c_str(),true)) {
5101 // no, type/vers do not match, search again
5102 remoteTypeP=fRemoteDatastoreP->getSendType(typestr,versstr.c_str());
5104 // specified type is not a remote type listed in remote's devInf.
5105 // But as remote is actually using it, we can assume it does support it, so use local type of same name instead
5106 PDEBUGPRINTFX(DBG_ERROR,("According to remote devInf, '%s' version '%s' is not supported, but obviously it is used here so we try to handle it",typestr,versstr.c_str()));
5107 // look it up in local datastore's list
5108 remoteTypeP=getReceiveType(typestr,versstr.c_str());
5113 PDEBUGPRINTFX(DBG_HOT,("Version could not be obtained from item data"));
5117 // no matching remote type: fail
5118 aStatusCommand.setStatusCode(415);
5119 ADDDEBUGITEM(aStatusCommand,"Incompatible content type specified in command or item meta");
5120 PDEBUGPRINTFX(DBG_ERROR,(
5121 "Incompatible content type '%s' version '%s' specified in command or item meta",
5123 versstr.empty() ? "[none]" : versstr.c_str()
5125 return false; // irregular
5128 // we have the remote type, now determine matching local type
5129 // - first check if this is compatible with the existing localTypeP (which
5130 // was possibly selected by remote rule match
5131 if (!localTypeP->supportsType(remoteTypeP->getTypeName(),remoteTypeP->getTypeVers(),false)) {
5132 // current default local type does not support specified remote type
5133 // - find a matching local type
5134 localTypeP=getReceiveType(remoteTypeP);
5137 PDEBUGPRINTFX(DBG_DATA+DBG_HOT,(
5138 "Explicit type '%s' does not match default type -> switching to local type '%s' for processing item",
5140 localTypeP->getTypeConfig()->getName()
5149 if (localTypeP && remoteTypeP) {
5150 TSyncItem *syncitemP = NULL;
5151 // create the item (might have empty data in case of delete)
5152 syncitemP=remoteTypeP->newSyncItem(aItemP,aSyncOp,fmt,localTypeP,this,aStatusCommand);
5154 // failed to create item
5155 return false; // irregular
5157 // Now start the real processing
5158 PDEBUGBLOCKFMT(("Process_Item","processing remote item",
5159 "SyncOp=%s|LocalID=%s|RemoteID=%s",
5160 SyncOpNames[syncitemP->getSyncOp()],
5161 syncitemP->getLocalID(),
5162 syncitemP->getRemoteID()
5164 #ifdef SCRIPT_SUPPORT
5165 TErrorFuncContext errctx;
5166 errctx.syncop = syncitemP->getSyncOp();
5169 // this call frees the item
5171 engProcessRemoteItem(syncitemP,aStatusCommand);
5173 PDEBUGENDBLOCK("Process_Item");
5176 // Hmm, was the item freed? Not sure, so assume that it was freed.
5177 PDEBUGENDBLOCK("Process_Item");
5180 // Check for datastore level scripts that might change the status code and/or regular status
5181 #ifdef SCRIPT_SUPPORT
5182 errctx.statuscode = aStatusCommand.getStatusCode();
5183 errctx.newstatuscode = errctx.statuscode;
5184 errctx.datastoreP = this;
5187 TScriptContext::executeTest(
5188 regular, // pass through regular status
5189 fDataStoreScriptContextP,
5190 fDSConfigP->fReceivedItemStatusScript,
5192 &errctx // caller context
5194 // use possibly modified status code
5196 if (aStatusCommand.getStatusCode() != errctx.newstatuscode) {
5197 PDEBUGPRINTFX(DBG_ERROR,("Status: Datastore script changed original status=%hd to %hd (original op was %s)",aStatusCommand.getStatusCode(),errctx.newstatuscode,SyncOpNames[errctx.syncop]));
5200 aStatusCommand.setStatusCode(errctx.newstatuscode);
5203 // item 100% successfully processed
5204 // - set new defaults to same type as current item
5205 setReceiveTypeInfo(localTypeP,remoteTypeP);
5209 // missing remote or local type: fail
5210 aStatusCommand.setStatusCode(415);
5211 ADDDEBUGITEM(aStatusCommand,"Unknown content type");
5212 PDEBUGPRINTFX(DBG_ERROR,(
5213 "Missing remote or local SyncItemType"
5215 regular=false; // irregular
5218 } // TLocalEngineDS::engProcessSyncOpItem
5221 #ifdef SYSYNC_SERVER
5227 // helper to cause database version of an item (as identified by aSyncItemP's ID) to be sent to client
5228 // (aka "force a conflict")
5229 TSyncItem *TLocalEngineDS::SendDBVersionOfItemAsServer(TSyncItem *aSyncItemP)
5231 TStatusCommand dummy(fSessionP);
5232 // - create new item
5233 TSyncItem *conflictingItemP =
5234 newItemForRemote(aSyncItemP->getTypeID());
5235 if (!conflictingItemP) return NULL;
5237 conflictingItemP->setLocalID(aSyncItemP->getLocalID());
5238 conflictingItemP->setRemoteID(aSyncItemP->getRemoteID());
5239 // - this is always a replace conflict (item exists in DB)
5240 conflictingItemP->setSyncOp(sop_wants_replace);
5241 // - try to get from DB
5242 bool ok=logicRetrieveItemByID(*conflictingItemP,dummy);
5243 if (ok && dummy.getStatusCode()!=404) {
5244 // item found in DB, add it to the sync set so it can be sent to remote
5245 // if not cancelled by dontSendItemAsServer()
5246 SendItemAsServer(conflictingItemP);
5247 PDEBUGPRINTFX(DBG_DATA,("Forced conflict with corresponding item from server DB"));
5250 // no item found, we cannot force a conflict
5251 delete conflictingItemP;
5252 conflictingItemP=NULL;
5254 return conflictingItemP;
5255 } // TLocalEngineDS::SendDBVersionOfItemAsServer
5260 localstatus TLocalEngineDS::engProcessMap(cAppCharP aRemoteID, cAppCharP aLocalID)
5262 if (!testState(dssta_syncmodestable)) {
5263 // Map received when not appropriate
5264 PDEBUGPRINTFX(DBG_ERROR,("Map not allowed in this stage of sync"));
5267 // pre-process localID
5269 if (aLocalID && *aLocalID) {
5270 // Note: Map must be ready to have either empty local or remote ID to delete an entry
5271 // perform reverse lookup of received GUID to real GUID
5272 realLocalID = aLocalID;
5273 obtainRealLocalID(realLocalID);
5274 aLocalID=realLocalID.c_str();
5279 // pre-process remoteID
5280 if (!aRemoteID || *aRemoteID==0)
5282 // let implementation process the map command
5283 return logicProcessMap(aRemoteID, aLocalID);
5284 } // TLocalEngineDS::engProcessMap
5288 // process sync operation from client with specified sync item
5289 // (according to current sync mode of local datastore)
5290 // - returns true (and unmodified or non-200-successful status) if
5291 // operation could be processed regularily
5292 // - returns false (but probably still successful status) if
5293 // operation was processed with internal irregularities, such as
5294 // trying to delete non-existant item in datastore with
5295 // incomplete Rollbacks (which returns status 200 in this case!).
5296 bool TLocalEngineDS::engProcessRemoteItemAsServer(
5297 TSyncItem *aSyncItemP,
5298 TStatusCommand &aStatusCommand // status, must be set to correct status code (ok / error)
5301 TSyncItem *conflictingItemP=NULL;
5302 TSyncItem *echoItemP=NULL;
5303 TSyncItem *delitemP=NULL;
5304 bool changedincoming=false;
5305 bool changedexisting=false;
5306 bool remainsvisible=true; // usually, we want the item to remain visible in the sync set
5307 TStatusCommand dummy(fSessionP);
5309 // get some info out of item (we might need it after item is already consumed)
5310 TSyncOperation syncop=aSyncItemP->getSyncOp();
5311 uInt16 itemtypeid=aSyncItemP->getTypeID();
5312 string remoteid=aSyncItemP->getRemoteID();
5313 // check if datastore is aborted
5314 if(CheckAborted(aStatusCommand))
5316 // send event (but no abort checking)
5317 DB_PROGRESS_EVENT(this,pev_itemreceived,++fItemsReceived,fRemoteNumberOfChanges,0);
5318 fPreventAdd = false;
5319 fIgnoreUpdate = false;
5321 PDEBUGPRINTFX(DBG_DATA,("%s item operation received",SyncOpNames[syncop]));
5322 // check if receiving commands is allowed at all
5323 if (fSyncMode==smo_fromserver) {
5324 // Modifications from client not allowed during update from server only
5325 aStatusCommand.setStatusCode(403);
5326 ADDDEBUGITEM(aStatusCommand,"Client command not allowed in one-way/refresh from server");
5327 PDEBUGPRINTFX(DBG_ERROR,("Client command not allowed in one-way/refresh from server"));
5331 // let item check itself to catch special cases
5332 // - init variables which are used/modified by item checking
5333 #ifdef SYSYNC_TARGET_OPTIONS
5334 // init item generation variables
5335 fItemSizeLimit=fSizeLimit;
5337 fItemSizeLimit=-1; // no limit
5339 fCurrentSyncOp = syncop;
5340 fEchoItemOp = sop_none;
5341 fItemConflictStrategy=fSessionConflictStrategy; // get default strategy for this item
5342 fForceConflict = false;
5343 fDeleteWins = fDSConfigP->fDeleteWins; // default to configured value
5344 fRejectStatus = -1; // no rejection
5346 // check reads and possibly modifies:
5347 // - fEchoItemOp : if not sop_none, the incoming item is echoed back to the remote with the specified syncop
5348 // - fItemConflictStrategy : might be changed from the pre-set datastore default
5349 // - fForceConflict : if set, a conflict is forced by adding the corresponding local item (if any) to the list of items to be sent
5350 // - fDeleteWins : if set, in a replace/delete conflict delete will win (regardless of strategy)
5351 // - fPreventAdd : if set, attempt to add item from remote (even implicitly trough replace) will cause no add but delete of remote item
5352 // - fIgnoreUpdate : if set, attempt to update item from remote will be ignored, only adds (also implicit ones) are executed
5353 // - fRejectStatus : if set>=0, incoming item is irgnored silently(==0) or with error(!=0)
5354 aSyncItemP->checkItem(this);
5355 // - create echo item if we need one
5356 if (fEchoItemOp!=sop_none) {
5357 // Note: sop_add makes no sense at all.
5358 // Note: If echo is enabled, conflicts are not checked, as echo makes only sense in
5359 // cases where we know that a conflict cannot occur or is irrelevant
5360 // - artifically create a "conflicting" item, that is, one to be sent back to remote
5361 echoItemP=newItemForRemote(aSyncItemP->getTypeID());
5362 // - assign data from incoming item if echo is not a delete
5363 if (fEchoItemOp!=sop_delete && fEchoItemOp!=sop_archive_delete && fEchoItemOp!=sop_soft_delete)
5364 echoItemP->replaceDataFrom(*aSyncItemP);
5365 // - set remote ID (note again: sop_add makes no sense here)
5366 echoItemP->setRemoteID(aSyncItemP->getRemoteID());
5368 echoItemP->setSyncOp(fEchoItemOp);
5369 // - now check for possible conflict
5371 conflictingItemP = getConflictingItemByRemoteID(aSyncItemP);
5372 // remove item if there is one that would conflict with the echo
5373 if (conflictingItemP) dontSendItemAsServer(conflictingItemP);
5374 conflictingItemP = NULL;
5376 // - add echo to the list of items to be sent (DB takes ownership)
5377 SendItemAsServer(echoItemP);
5378 PDEBUGPRINTFX(DBG_DATA,("Echoed item back to remote with sop=%s",SyncOpNames[fEchoItemOp]));
5379 // process item normally (except that we don't check for LUID conflicts)
5381 // - check if incoming item should be processed at all
5382 if (fRejectStatus>=0) {
5383 // Note: a forced conflict can still occur even if item is rejected
5384 // (this has the effect of unconditionally letting the server item win)
5385 if (fForceConflict && syncop!=sop_add) {
5386 conflictingItemP = SendDBVersionOfItemAsServer(aSyncItemP);
5387 // Note: conflictingitem is always a replace
5388 if (conflictingItemP) {
5389 if (syncop==sop_delete) {
5390 // original was delete, forced conflict means re-adding to remote
5391 conflictingItemP->setSyncOp(sop_wants_add);
5394 // merge here because we'll not process the item further
5395 conflictingItemP->mergeWith(*aSyncItemP,changedexisting,changedincoming,this);
5399 // now discard the incoming item
5401 PDEBUGPRINTFX(fRejectStatus>=400 ? DBG_ERROR : DBG_DATA,("Item rejected with Status=%hd",fRejectStatus));
5402 if (fRejectStatus>0) {
5403 // rejected with status code (not necessarily error)
5404 aStatusCommand.setStatusCode(fRejectStatus);
5405 if (fRejectStatus>=300) {
5406 // non 200-codes are errors
5407 ADDDEBUGITEM(aStatusCommand,"Item rejected");
5411 // silently rejected
5414 // now perform requested operation
5419 // read-only handling of delete is like soft delete: remove map entry, but nothing else
5420 PDEBUGPRINTFX(DBG_DATA,("Read-Only Datastore: Prevented actual deletion, just removing map entry"));
5421 case sop_soft_delete:
5422 // Readonly: allowed, as only map is touched
5423 // soft delete from client is treated as an indication that the item was
5424 // removed from the client's datastore, but is still in the set
5425 // of sync data for that client.
5426 // This means that the map item must be removed.
5427 // - when the item is hard-deleted on the server, nothing will happen at next sync
5428 // - when the item is modified on the server, it will be re-added to the client at next sync
5429 // - when slow sync is performed, the item will be re-added, too.
5430 // %%%%% Note that this does NOT work as it is now, as adds also occur for non-modified
5431 // items that have no map AND are visible under current targetFilter.
5432 // probably we should use a map entry with no remoteID for soft-deleted items later....
5433 // Delete Map entry by remote ID
5434 aSyncItemP->clearLocalID(); // none
5435 sta=engProcessMap(aSyncItemP->getRemoteID(),NULL);
5437 aStatusCommand.setStatusCode(ok ? 200 : sta);
5439 case sop_archive_delete:
5440 if (fReadOnly) goto readonly_delete; // register removal of item in map, but do nothing to data itself
5441 #ifdef OBJECT_FILTERING
5442 if (!fDSConfigP->fInvisibleFilter.empty()) {
5443 // turn into replace with all fields unavailable but made to pass invisible filter
5444 // - make sure that no data field is assigned
5445 aSyncItemP->cleardata();
5446 // - make item pass "invisible" filter
5447 if (aSyncItemP->makePassFilter(fDSConfigP->fInvisibleFilter.c_str())) {
5448 // item now passes invisible rule, that is, it is invisible -> replace in DB
5449 goto archive_delete;
5452 // fall trough, no archive delete supported
5454 // No archive delete support if there is no filter to detect/generate invisibles
5455 // before SyncML 1.1 : we could return 210 here and still process the delete op.
5456 // SyncML 1.1 : we must return 501 (not implemented) here
5457 aStatusCommand.setStatusCode(501);
5458 PDEBUGPRINTFX(DBG_ERROR,("Datastore does not support Archive-Delete, error status = 501"));
5463 // delete item by LUID
5465 aStatusCommand.setStatusCode(403);
5466 ADDDEBUGITEM(aStatusCommand,"Delete during slow sync not allowed");
5467 PDEBUGPRINTFX(DBG_ERROR,("Delete during slow sync not allowed"));
5472 // check for conflict with replace from server
5473 // Note: conflict cases do not change local DB, so they are allowed before checking fReadOnly
5474 if (!echoItemP) conflictingItemP = getConflictingItemByRemoteID(aSyncItemP); // do not check conflicts if we have already created an echo
5475 // - check if we must force the conflict
5476 if (!conflictingItemP && fForceConflict) {
5477 conflictingItemP=SendDBVersionOfItemAsServer(aSyncItemP);
5479 if (conflictingItemP) {
5480 // conflict only if other party has replace
5481 if (conflictingItemP->getSyncOp()==sop_replace || conflictingItemP->getSyncOp()==sop_wants_replace) {
5483 // act as if successfully deleted and cause re-adding of still existing server item
5484 // - discard deletion
5486 // - remove map entry for this item (it no longer exists on the client)
5487 sta = engProcessMap(NULL,conflictingItemP->getLocalID());
5488 aStatusCommand.setStatusCode(sta==LOCERR_OK ? 200 : sta);
5489 // - change replace to add (as to-be-replaced item is already deleted on remote)
5490 conflictingItemP->setSyncOp(sop_add);
5491 // - remove remote ID (will be assigned a new ID because the item is now re-added)
5492 conflictingItemP->setRemoteID("");
5493 // - no server operation needed
5494 PDEBUGPRINTFX(DBG_DATA,("Conflict of Client Delete with Server replace -> discarded delete, re-added server item to client"));
5499 // delete preceedes replace
5500 // - avoid sending item from server
5501 dontSendItemAsServer(conflictingItemP);
5502 // - let delete happen
5505 // if both have deleted the item, we should remove the map
5506 // and avoid sending a delete to the client
5507 else if (conflictingItemP->getSyncOp()==sop_delete) {
5508 // - discard deletion
5510 // - remove map entry for this item (it no longer exists)
5511 sta = engProcessMap(NULL,conflictingItemP->getLocalID());
5512 aStatusCommand.setStatusCode(sta==LOCERR_OK ? 200 : sta);
5513 // - make sure delete from server is not sent
5514 dontSendItemAsServer(conflictingItemP);
5515 PDEBUGPRINTFX(DBG_DATA,("Client and Server have deleted same item -> just removed map entry"));
5520 // real delete is discarded silently when fReadOnly is set
5521 if (fReadOnly) goto readonly_delete; // register removal of item in map, but do nothing to data itself
5523 fLocalItemsDeleted++;
5524 remainsvisible=false; // deleted not visible any more
5525 ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // delete in local database NOW
5529 delete aSyncItemP; // we don't need it
5530 aStatusCommand.setStatusCode(200);
5531 PDEBUGPRINTFX(DBG_DATA,("Read-Only: copy command silently discarded"));
5535 // %%% note: this would belong into specific datastore implementation, but is here
5536 // now for simplicity as copy isn't used heavily het
5537 // retrieve data from local datastore
5538 if (!logicRetrieveItemByID(*aSyncItemP,aStatusCommand)) { ok=false; break; }
5540 /// @todo %%%%%%%%%%%%%%%% NOTE: MISSING SENDING BACK MAP COMMAND for new GUID created
5543 // test for slow sync
5544 if (fSlowSync) goto sop_slow_add; // add in slow sync is like replace
5546 // add as new item to server DB
5547 aStatusCommand.setStatusCode(201); // item added (if no error occurs)
5549 delete aSyncItemP; // we don't need it
5550 PDEBUGPRINTFX(DBG_DATA,("Read-Only: add command silently discarded"));
5554 // check if adds are prevented
5558 #ifdef OBJECT_FILTERING
5559 // test if acceptable
5560 if (!isAcceptable(aSyncItemP,aStatusCommand)) { ok=false; break; } // cannot be accepted
5561 // Note: making item to pass sync set filter is implemented in derived DB implementation
5562 // as criteria for passing might be in data that must first be read from the DB
5564 remainsvisible=true; // should remain visible
5565 ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // add to local database NOW
5566 if (!remainsvisible && fSessionP->getSyncMLVersion()>=syncml_vers_1_2) {
5567 PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Added item is not visible under current filters -> remove it on client"));
5568 goto removefromremoteandsyncset;
5575 aSyncItemP->setSyncOp(sop_replace); // set correct op
5576 // ...and process like replace
5577 case sop_reference_only:
5579 #ifdef OBJECT_FILTERING
5580 // test if acceptable
5581 if (!isAcceptable(aSyncItemP,aStatusCommand)) { ok=false; break; } // cannot be accepted
5582 // Note: making item to pass sync set filter is implemented in derived DB implementation
5583 // as criteria for passing might be in data that must first be read from the DB
5585 // check for conflict with server side modifications
5587 if (!echoItemP) conflictingItemP = getConflictingItemByRemoteID(aSyncItemP);
5588 // - check if we must force the conflict
5589 if (!conflictingItemP && fForceConflict) {
5590 conflictingItemP=SendDBVersionOfItemAsServer(aSyncItemP);
5592 bool deleteconflict=false;
5593 if (conflictingItemP) {
5594 // Note: if there is a conflict, this replace cannot be an
5595 // implicit add, so we don't need to check for fPreventAdd
5597 // Note: if we are in ignoreUpdate mode, the only conflict resolution
5598 // possible is unconditional server win
5599 sInt16 cmpRes = SYSYNC_NOT_COMPARABLE;
5600 // assume we can resolve the conflict
5601 aStatusCommand.setStatusCode(419); // default to server win
5602 ADDDEBUGITEM(aStatusCommand,"Conflict resolved by server");
5603 PDEBUGPRINTFX(DBG_HOT,(
5604 "Conflict: Remote <%s> with remoteID=%s <--> Local <%s> with localID=%s, remoteID=%s",
5605 SyncOpNames[aSyncItemP->getSyncOp()],
5606 aSyncItemP->getRemoteID(),
5607 SyncOpNames[conflictingItemP->getSyncOp()],
5608 conflictingItemP->getLocalID(),
5609 conflictingItemP->getRemoteID()
5611 // we have a conflict, decide what to do
5612 TConflictResolution crstrategy;
5613 if (fReadOnly || fIgnoreUpdate) {
5614 // server always wins and overwrites modified client version
5615 PDEBUGPRINTFX(DBG_DATA,("Read-Only or IgnoreUpdate: server always wins"));
5616 crstrategy=cr_server_wins;
5620 crstrategy = fItemConflictStrategy; // get conflict strategy pre-set for this item
5621 if (conflictingItemP->getSyncOp()==sop_delete) {
5622 // server wants to delete item, client wants to replace
5623 if (fDSConfigP->fTryUpdateDeleted) {
5624 // if items are not really deleted, but only made invisible,
5625 // we can assume we can update the "deleted" item
5626 // BUT ONLY if the conflict strategy is not "server always wins"
5627 if (crstrategy==cr_server_wins) {
5628 PDEBUGPRINTFX(DBG_PROTO+DBG_HOT,("Conflict of Client Replace with Server delete and strategy is server-wins -> delete from client"));
5629 aStatusCommand.setStatusCode(419); // server wins, client command ignored
5633 PDEBUGPRINTFX(DBG_PROTO+DBG_HOT,("Conflict of Client Replace with Server delete -> try to update already deleted item (as it might still exist in syncset)"));
5634 // apply replace (and in case of !fDeleteWins, possible implicit add)
5635 fPreventAdd=fDeleteWins; // we want implicit add only if delete cannot win
5636 remainsvisible=!fDeleteWins; // we want to see the item in the sync set if delete does not win!
5637 ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible);
5641 // could not update already deleted item
5642 PDEBUGPRINTFX(DBG_PROTO,("Could not update already deleted server item (seems to be really deleted, not just invisible)"));
5643 aStatusCommand.setStatusCode(419); // server wins, client command ignored
5646 // update of invisible item successful, but it will still be deleted from client
5647 // Note: possibly, the update was apparently successful, but only because an UPDATE with no
5648 // target does not report an error. So effectively, no update might have happened.
5649 PDEBUGPRINTFX(DBG_PROTO,("Updated already deleted server item, but delete still wins -> client item will be deleted"));
5650 fLocalItemsUpdated++;
5651 aStatusCommand.setStatusCode(200); // client command successful (but same item will still be deleted)
5653 // nothing more to do, let delete happen on the client (conflictingItemP delete will be sent)
5657 // not fDeleteWins - item failed, updated or implicitly added
5659 // update (or implicit add) successful
5660 if (aStatusCommand.getStatusCode()==201) {
5661 PDEBUGPRINTFX(DBG_PROTO,("Client Update wins and has re-added already deleted server item -> prevent delete on client"));
5665 PDEBUGPRINTFX(DBG_PROTO,("Client Update wins and has updated still existing server item -> prevent delete on client"));
5666 fLocalItemsUpdated++;
5668 // and client item wins - prevent sending delete to client
5669 // - don't send delete to client
5670 conflictingItemP->setSyncOp(sop_none); // just in case...
5671 dontSendItemAsServer(conflictingItemP);
5678 // Normal delete conflict processing (assuming deleted items REALLY deleted)
5680 // - client always wins (replace over delete)
5681 crstrategy=cr_client_wins;
5682 deleteconflict=true; // flag condition for processing below
5683 // - change from replace to add, because item is already deleted in server and must be re-added
5685 aSyncItemP->setSyncOp(sop_add);
5686 PDEBUGPRINTFX(DBG_PROTO,("Conflict of Client Replace with Server delete -> client wins, client item is re-added to server"));
5689 // delete wins, just discard incoming item
5691 PDEBUGPRINTFX(DBG_PROTO,("Conflict of Client Replace with Server delete -> DELETEWINS() set -> ignore client replace"));
5698 // replace from client conflicts with replace from server
5699 // - compare items for further conflict resolution
5700 // NOTE: it is serveritem.compareWith(clientitem)
5701 cmpRes = conflictingItemP->compareWith(
5702 *aSyncItemP,eqm_conflict,this
5704 ,PDEBUGTEST(DBG_CONFLICT) // show conflict comparisons in normal sync if conflict details are enabled
5707 PDEBUGPRINTFX(DBG_DATA,(
5708 "Compared conflicting items with eqm_conflict: remoteItem %s localItem",
5709 cmpRes==0 ? "==" : (cmpRes>0 ? "<" : ">")
5711 // see if we can determine newer item
5712 if (crstrategy==cr_newer_wins) {
5713 if (cmpRes!=0 && conflictingItemP->sortable(*aSyncItemP)) {
5715 // (comparison was: serveritem.compareWith(clientitem), so
5716 // cmpRes<0 means that client is newer
5717 PDEBUGPRINTFX(DBG_PROTO,("Conflict resolved by identifying newer item"));
5719 crstrategy=cr_server_wins; // server has newer item
5721 crstrategy=cr_client_wins; // client has newer item
5724 // newer item cannot be determined, duplicate items
5725 crstrategy=cr_duplicate;
5727 PDEBUGPRINTFX(DBG_DATA,(
5728 "Newer item %sdetermined: %s",
5729 crstrategy==cr_duplicate ? "NOT " : "",
5730 crstrategy==cr_client_wins ? "Client item is newer and wins" :
5731 (crstrategy==cr_server_wins ? "Server item is newer ans wins" : "item is duplicated if different")
5735 // modify strategy based on compare
5736 if (cmpRes==0 && crstrategy==cr_duplicate) {
5737 // items are equal by definition of item comparison,
5738 // but obviously both changed, this means that changes should be
5740 // So, by deciding arbitrarily that server has won, we will not loose any data
5741 crstrategy=cr_server_wins; // does not matter, because merge will be attempted
5742 PDEBUGPRINTFX(DBG_DATA,("Duplication avoided because items are equal by their own definition, just merge"));
5744 // if adds prevented, we cannot duplicate, let server win
5745 if (fPreventAdd && crstrategy==cr_duplicate) crstrategy=cr_server_wins;
5747 // now apply strategy
5748 if (crstrategy==cr_duplicate) {
5749 // add items vice versa
5750 PDEBUGPRINTFX(DBG_PROTO,("Conflict resolved by duplicating items in both databases"));
5751 aStatusCommand.setStatusCode(209);
5752 fConflictsDuplicated++;
5753 // - set server item such that it will be added as new item to client DB
5754 conflictingItemP->setSyncOp(sop_add);
5755 // - break up mapping between client and server item BEFORE adding to server
5756 // because else adding of item with already existing remoteID can fail.
5757 // In addition, item now being sent to client may not have a map before
5758 // it receives a map command from the client!
5759 sta = engProcessMap(NULL,conflictingItemP->getLocalID());
5760 if(sta!=LOCERR_OK) {
5761 PDEBUGPRINTFX(DBG_ERROR,(
5762 "Problem (status=%hd) removing map entry for LocalID='%s'",
5764 conflictingItemP->getLocalID()
5767 // - add client item as new item to server DB
5769 aSyncItemP->setSyncOp(sop_add); // set correct op
5770 remainsvisible=true; // should remain visible
5771 ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // add to local database NOW
5774 else if (crstrategy==cr_server_wins) {
5775 // Note: for fReadOnly, this is always the case!
5776 // server item wins and is sent to client
5777 PDEBUGPRINTFX(DBG_PROTO,("Conflict resolved: server item replaces client item"));
5778 aStatusCommand.setStatusCode(419); // server wins, client command ignored
5779 fConflictsServerWins++;
5780 // - make sure item is set to replace data in client
5781 conflictingItemP->setSyncOp(sop_replace);
5782 // - attempt to merge data from loosing item (accumulating fields)
5784 conflictingItemP->mergeWith(*aSyncItemP,changedexisting,changedincoming,this);
5786 if (fIgnoreUpdate) changedexisting=false; // never try to update existing item
5787 if (changedexisting) {
5788 // we have merged something, so server must be updated, too
5789 // Note: after merge, both items are equal. We check if conflictingitem
5790 // has changed, but if yes, we write the incoming item. Conflicting item
5791 // will get sent to client later
5792 PDEBUGPRINTFX(DBG_DATA,("*** Merged some data from loosing client item into winning server item"));
5793 // set correct status for conflict resultion by merge
5794 aStatusCommand.setStatusCode(207); // merged
5795 // process update in local database
5796 fLocalItemsUpdated++;
5797 aSyncItemP->setSyncOp(sop_replace); // update
5798 remainsvisible=true; // should remain visible
5799 ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // update in local database NOW
5803 // - item sent by client has lost and can be deleted now
5804 // %%% possibly add option here to archive item in some way
5805 // BUT ONLY IF NOT fReadOnly
5809 else if (crstrategy==cr_client_wins) {
5810 // client item wins and is sent to server
5811 PDEBUGPRINTFX(DBG_PROTO,("Conflict resolved: client item replaces server item"));
5812 aStatusCommand.setStatusCode(208); // client wins
5813 fConflictsClientWins++;
5814 // - attempt to merge data from loosing item (accumulating fields)
5815 if (!deleteconflict) {
5816 aSyncItemP->mergeWith(*conflictingItemP,changedincoming,changedexisting,this);
5818 if (changedincoming) {
5819 // we have merged something, so client must be updated even if it has won
5820 // Note: after merge, both items are equal. We check if aSyncItemP
5821 // has changed, but if yes, we make sure the conflicting item gets
5822 // sent to the client
5823 PDEBUGPRINTFX(DBG_DATA,("*** Merged some data from loosing server item into winning client item"));
5824 conflictingItemP->setSyncOp(sop_replace); // update
5825 // set correct status for conflict resultion by merge
5826 aStatusCommand.setStatusCode(207); // merged
5827 // will be sent because it is in the list
5830 // - make sure conflicting item from server is NOT sent to client
5831 conflictingItemP->setSyncOp(sop_none); // just in case...
5832 dontSendItemAsServer(conflictingItemP);
5833 aStatusCommand.setStatusCode(200); // ok
5835 // - replace item in server (or leave it as is, if conflict was with delete)
5836 if (!deleteconflict) {
5837 fLocalItemsUpdated++;
5838 aSyncItemP->setSyncOp(sop_replace);
5840 remainsvisible=true; // should remain visible
5841 ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // replace in local database NOW
5844 } // replace conflict
5846 // normal replace without any conflict
5848 delete aSyncItemP; // we don't need it
5849 aStatusCommand.setStatusCode(200);
5850 PDEBUGPRINTFX(DBG_DATA,("Read-Only: replace command silently discarded"));
5854 // no conflict, just let client replace server's item
5855 PDEBUGPRINTFX(DBG_DATA+DBG_CONFLICT,("No Conflict: client item replaces server item"));
5856 // - replace item in server (or add if item does not exist and not fPreventAdd)
5857 aSyncItemP->setSyncOp(sop_replace);
5858 remainsvisible=true; // should remain visible
5859 if (!logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible)) {
5860 // check if this is a 404 or 410 and fPreventAdd
5861 if (fPreventAdd && (aStatusCommand.getStatusCode()==404 || aStatusCommand.getStatusCode()==410))
5862 goto preventadd2; // to-be-replaced item not found and implicit add prevented -> delete from remote
5867 // still visible in sync set?
5868 if (!remainsvisible && fSessionP->getSyncMLVersion()>=syncml_vers_1_2) {
5869 // -> cause item to be deleted on remote
5870 PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Item replaced no longer visible in syncset -> returning delete to remote"));
5871 goto removefromremoteandsyncset;
5874 if (aStatusCommand.getStatusCode()==201)
5877 fLocalItemsUpdated++;
5883 // slow sync (replaces AND adds are treated the same)
5884 // - first do a strict search for identical item. This is required to
5885 // prevent that in case of multiple (loosely compared) matches we
5886 // catch the wrong item and cause a mess at slowsync
5887 // NOTE: we do compare only relevant fields (eqm_conflict)
5888 TSyncItem *matchingItemP = getMatchingItem(aSyncItemP,eqm_conflict);
5889 if (!matchingItemP) {
5890 // try again with less strict comparison (eqm_slowsync or eqm_always for firsttimesync)
5891 DEBUGPRINTFX(DBG_DATA+DBG_MATCH,("Strict search for matching item failed, try with configured EqMode now"));
5892 matchingItemP = getMatchingItem(aSyncItemP,fFirstTimeSync ? eqm_always : eqm_slowsync);
5894 if (matchingItemP) {
5895 // both sides already have this item
5896 PDEBUGPRINTFX(DBG_DATA+DBG_HOT,(
5897 "Slow Sync Match detected - localID='%s' matches incoming item",
5898 matchingItemP->getLocalID()
5901 aStatusCommand.setStatusCode(syncop==sop_add ? 201 : 200); // default is: simply ok. But if original op was Add, MUST return 201 status (SCTS requires it)
5902 bool matchingok=false;
5903 // - do not update map yet, as we still don't know if client item will
5904 // possibly be added instead of mapped
5905 // Note: ONLY in case this is a reference-only item, the map is already updated!
5906 bool mapupdated = syncop==sop_reference_only;
5907 // - determine which one is winning
5908 bool needserverupdate=false;
5909 bool needclientupdate=false;
5910 // if updates are ignored, we can short-cut here
5911 // Note: if this is a reference-only item, it was already updated (if needed) before last suspend
5912 // so skip updating now!
5913 if (syncop!=sop_reference_only && !fIgnoreUpdate) {
5914 // Not a reference-only and also updates not suppressed
5915 // - for a read-only datastore, this defaults to server always winning
5916 TConflictResolution crstrategy =
5918 cr_server_wins : // server always wins for read-only
5919 fItemConflictStrategy; // pre-set strategy for this item
5920 // Determine what data to use
5921 if (crstrategy==cr_newer_wins) {
5923 // Note: comparison is clientitem.compareWith(serveritem)
5924 // so if result>0, client is newer than server
5925 sInt16 cmpRes = aSyncItemP->compareWith(
5926 *matchingItemP,eqm_nocompare,this
5928 ,PDEBUGTEST(DBG_CONFLICT+DBG_DETAILS) // show age comparisons only if we want to see details
5931 if (cmpRes==1) crstrategy=cr_client_wins;
5932 else if (cmpRes==-1 || fPreventAdd) crstrategy=cr_server_wins; // server wins if adds prevented
5933 else crstrategy=cr_duplicate; // fall back to duplication if we can't determine newer item
5934 PDEBUGPRINTFX(DBG_DATA,(
5935 "Newer item %sdetermined: %s",
5936 crstrategy==cr_duplicate ? "NOT " : "",
5937 crstrategy==cr_client_wins ? "Client item is newer and wins" :
5938 (crstrategy==cr_server_wins ? "Server item is newer and wins" : "item is duplicated if different")
5941 // if adds prevented, we cannot duplicate, let server win
5942 if (fPreventAdd && crstrategy==cr_duplicate) crstrategy=cr_server_wins;
5943 // now execute chosen strategy
5944 if (crstrategy==cr_client_wins) {
5945 // - merge server's data into client item
5946 PDEBUGPRINTFX(DBG_DATA,("Trying to merge some data from loosing server item into winning client item"));
5947 aSyncItemP->mergeWith(*matchingItemP,changedincoming,changedexisting,this);
5948 // only count if server gets updated
5949 if (changedexisting) fConflictsClientWins++;
5950 // Note: changedexisting will cause needserverupdate to be set below
5952 else if (crstrategy==cr_server_wins) {
5953 // - merge client data into server item
5954 PDEBUGPRINTFX(DBG_DATA,("Trying to merge some data from loosing client item into winning server item"));
5955 matchingItemP->mergeWith(*aSyncItemP,changedexisting,changedincoming,this);
5956 // only count if client gets updated
5957 if (changedincoming) fConflictsServerWins++;
5958 // Note: changedincoming will cause needclientupdate to be set below
5960 else if (crstrategy==cr_duplicate) {
5961 // test if items are equal enough
5962 // (Note: for first-sync, compare mode for item matching can be looser
5963 // (eqm_always), so re-comparison here makes sense)
5965 matchingItemP->compareWith(
5966 *aSyncItemP,eqm_slowsync,this
5968 ,PDEBUGTEST(DBG_CONFLICT+DBG_DETAILS) // show equality re-test only for details enabled
5973 // items are not really equal in content, so duplicate them on both sides
5974 PDEBUGPRINTFX(DBG_PROTO,("Matching items are not fully equal, duplicate them on both sides"));
5975 fConflictsDuplicated++;
5976 // - duplicates contain merged data
5977 matchingItemP->mergeWith(*aSyncItemP,changedexisting,changedincoming,this);
5978 // - add client item (with server data merged) as new item to server
5980 aSyncItemP->setSyncOp(sop_add);
5981 aStatusCommand.setStatusCode(201); // item added (if no error occurs)
5982 remainsvisible=true; // should remain visible
5983 matchingok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible,&guid); // add item in local database NOW
5984 aSyncItemP=NULL; // is already deleted!
5985 if (matchingok) { // do it only if server add successful, because otherwise we don't have a GUID
5986 // - make sure same item is ADDED as new item to client
5987 matchingItemP->setSyncOp(sop_add); // add it, prevent it from re-match (is sop_wants_add now)
5988 matchingItemP->setLocalID(guid.c_str()); // this is now a pair with the newly added item (not the original one)
5989 matchingItemP->setRemoteID(""); // we don't know the remote ID yet
5991 // adding duplicate to server (add) is already done
5992 changedexisting=false;
5993 changedincoming=false;
5994 mapupdated=true; // no need to update map
5995 needserverupdate=false; // already done
5996 // client must received the (updated) server item as an add (set above)
5997 needclientupdate=true;
6000 } // if not ignoreUpdate
6001 // Update server map now if required
6002 // - NOTE THAT THIS IS VERY IMPORTANT TO DO BEFORE any possible
6003 // replaces, because replacing the matchingItem can only be
6004 // done via its remoteID, which is, at this moment, probably not
6005 // valid. After Mapping, it is ensured that the mapped remoteID
6006 // uniquely identifies the matchingItem.
6008 // - update map in server
6009 sta = engProcessMap(
6010 aSyncItemP->getRemoteID(), // remote ID (LUID) of item received from client
6011 matchingItemP->getLocalID() // local ID (GUID) of item already stored in server
6013 matchingok = sta==LOCERR_OK;
6017 aStatusCommand.setStatusCode(sta);
6021 // Now prepare updates of (already mapped) server and client items if needed
6022 if (changedexisting) {
6023 // matched item in server's sync set was changed and must be update in server DB
6024 // update server only if updates during non-first-time slowsync are enabled
6025 if (fFirstTimeSync || fSessionP->fUpdateServerDuringSlowsync) {
6026 needserverupdate=true; // note: ineffective if fReadOnly
6027 PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Server data was updated by record sent by client, REPLACE it in server DB"));
6030 PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Server data was updated by record sent by client, but server updates during non-firsttime slowsyncs are disabled"));
6033 if (changedincoming) {
6034 // incoming item from remote was changed after receiving and must be reflected
6036 // NOTE: this can also happen with sop_reference_only items
6037 // - client will be updated because matchingItemP is still in list
6038 matchingItemP->setSyncOp(sop_replace); // cancel wants_add state, prevent re-match
6039 // - matchingItem was retrieved BEFORE map was done, and has no valid remote ID
6040 // so remote ID must be added now.
6041 matchingItemP->setRemoteID(aSyncItemP->getRemoteID());
6042 // update client only if updates during non-first-time slowsync are enabled
6043 if (fFirstTimeSync || fSessionP->fUpdateClientDuringSlowsync) {
6044 PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Record sent by client was updated with server data, client will receive a REPLACE"));
6045 needclientupdate=true;
6048 PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Record sent by client was updated, but client updates during non-firsttime slowsyncs are disabled"));
6052 // - check if client must be updated
6053 if (!needclientupdate) {
6054 // prevent updating client
6055 // - make sure matching item from server is NOT sent to client
6056 matchingItemP->setSyncOp(sop_none); // just in case...
6057 dontSendItemAsServer(matchingItemP);
6060 // check if replaces may be sent to client during slowsync
6061 if (matchingItemP->getSyncOp()==sop_replace && fSessionP->fNoReplaceInSlowsync) {
6062 PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Update of client item suppressed because of <noreplaceinslowsync>"));
6063 matchingItemP->setSyncOp(sop_none); // just in case...
6064 dontSendItemAsServer(matchingItemP);
6067 PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Update of client item enabled (will be sent later)"));
6070 // - check if server must be updated (NEVER for fReadOnly)
6071 if (!fReadOnly && needserverupdate && aSyncItemP) {
6073 // - update server side (NOTE: processItemAsServer takes ownership, pointer gets invalid!)
6074 fLocalItemsUpdated++;
6075 aSyncItemP->setSyncOp(sop_replace);
6076 remainsvisible=true; // should remain visible
6077 matchingok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // replace item in local database NOW
6078 PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Updated server item"));
6081 // - delete incoming item unprocessed (if not already deleted)
6082 if (aSyncItemP) delete aSyncItemP;
6084 // check if we need to actively delete item from the client as it falls out of the filter
6085 if (!remainsvisible && fSessionP->getSyncMLVersion()>=syncml_vers_1_2) {
6086 // -> cause item to be deleted on remote
6087 PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("Slow sync matched item no longer visible in syncset -> returning delete to remote"));
6088 goto removefromremoteandsyncset;
6094 PDEBUGPRINTFX(DBG_DATA+DBG_HOT,("No matching item found - add it to local database"));
6095 // this item is not yet on the server, add it normally
6096 aStatusCommand.setStatusCode(201); // item added (if no error occurs)
6098 delete aSyncItemP; // we don't need it
6099 PDEBUGPRINTFX(DBG_DATA,("Read-Only: slow-sync add/replace command silently discarded"));
6103 if (fPreventAdd) goto preventadd;
6105 aSyncItemP->setSyncOp(sop_add); // set correct op
6106 remainsvisible=true; // should remain visible
6107 ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // add to local database NOW
6111 break; // just in case....
6113 // add not allowed, delete remote item instead
6114 // - consume original
6117 aStatusCommand.setStatusCode(201); // pretend adding item was successful
6118 PDEBUGPRINTFX(DBG_DATA,("Prevented explicit add, returning delete to remote"));
6120 goto removefromremote; // as we have PREVENTED adding the item, it is not in the map
6121 removefromremoteandsyncset:
6122 // remove item from local map first
6123 engProcessMap(remoteid.c_str(),NULL);
6125 // have item removed from remote
6126 delitemP = newItemForRemote(itemtypeid);
6127 delitemP->setRemoteID(remoteid.c_str());
6128 delitemP->setSyncOp(sop_delete);
6129 SendItemAsServer(delitemP); // pass ownership
6132 SYSYNC_THROW(TSyncException("Unknown sync op in TLocalEngineDS::processRemoteItemAsServer"));
6135 DB_PROGRESS_EVENT(this,pev_itemprocessed,fLocalItemsAdded,fLocalItemsUpdated,fLocalItemsDeleted);
6138 // if the DB has a error string to show, add it here
6139 aStatusCommand.addItemString(lastDBErrorText().c_str());
6143 } // TLocalEngineDS::engProcessRemoteItemAsServer
6146 /// @brief called at end of request processing, should be used to save suspend state
6147 /// @note superdatastore does it itself to have correct order of things happening
6148 void TLocalEngineDS::engRequestEnded(void)
6150 #ifdef SUPERDATASTORES
6151 if (fAsSubDatastoreOf)
6154 // If DS 1.2: Make sure everything is ready for a resume in case there's an abort (implicit Suspend)
6155 // before the next request. Note that the we cannot wait for session timeout, as the resume attempt
6156 // from the client probably arrives much earlier.
6157 // Note: It is ESSENTIAL not to save the state until sync set is ready, because saving state will
6158 // cause DB access, and DB access is not permitted while sync set is possibly still loading
6159 // (possibly in a separate thread!). So dssta_syncmodestable (as in <=3.0.0.2) is NOT enough here!
6160 if (testState(dssta_syncsetready)) {
6161 // make sure all unsent items are marked for resume
6162 engSaveSuspendState(false); // only if not already aborted
6164 // let datastore prepare for end of request
6166 // and let it prepare for end of this thread as well
6167 dsThreadMayChangeNow();
6168 } // TLocalEngineDS::engRequestEnded
6170 #endif // SYSYNC_SERVER
6174 #ifdef SYSYNC_CLIENT
6180 // process sync operation from server with specified sync item
6181 // (according to current sync mode of local datastore)
6182 // - returns true (and unmodified or non-200-successful status) if
6183 // operation could be processed regularily
6184 // - returns false (but probably still successful status) if
6185 // operation was processed with internal irregularities, such as
6186 // trying to delete non-existant item in datastore with
6187 // incomplete Rollbacks (which returns status 200 in this case!).
6188 bool TLocalEngineDS::engProcessRemoteItemAsClient(
6189 TSyncItem *aSyncItemP,
6190 TStatusCommand &aStatusCommand // status, must be set to correct status code (ok / error)
6193 string remoteid,localid;
6195 bool remainsvisible;
6197 // send event and check for user abort
6198 #ifdef PROGRESS_EVENTS
6199 if (!DB_PROGRESS_EVENT(this,pev_itemreceived,++fItemsReceived,fRemoteNumberOfChanges,0)) {
6200 fSessionP->AbortSession(500,true,LOCERR_USERABORT); // this also causes datastore to be aborted
6202 if (!SESSION_PROGRESS_EVENT(fSessionP,pev_suspendcheck,NULL,0,0,0)) {
6203 fSessionP->SuspendSession(LOCERR_USERSUSPEND);
6206 // check if datastore is aborted
6207 if (CheckAborted(aStatusCommand)) return false;
6208 // init behaviour vars (these are normally used by server only,
6209 // but must be initialized correctly for client as well as descendants might test them
6210 fPreventAdd = false;
6211 fIgnoreUpdate = false;
6212 // get operation out of item
6213 TSyncOperation syncop=aSyncItemP->getSyncOp();
6215 DEBUGPRINTFX(DBG_DATA,("%s item operation received",SyncOpNames[syncop]));
6216 // check if receiving commands is allowed at all
6217 // - must be in correct sync state
6218 if (!testState(dssta_syncgendone)) {
6219 // Modifications from server not allowed before client has done sync gen
6220 // %%% we could possibly relax this one, depending on the DB
6221 aStatusCommand.setStatusCode(403);
6222 PDEBUGPRINTFX(DBG_ERROR,("Server command not allowed before client has sent entire <sync>"));
6226 // - must not be one-way
6227 if (fSyncMode==smo_fromclient) {
6228 // Modifications from server not allowed during update from client only
6229 aStatusCommand.setStatusCode(403);
6230 ADDDEBUGITEM(aStatusCommand,"Server command not allowed in one-way/refresh from client");
6231 PDEBUGPRINTFX(DBG_ERROR,("Server command not allowed in one-way/refresh from client"));
6236 // silently discard all modifications if readonly
6238 PDEBUGPRINTFX(DBG_ERROR,("Read-Only: silently discarding all modifications"));
6239 aStatusCommand.setStatusCode(200); // always ok (but never "added"), so server cannot expect Map
6243 // let item check itself to catch special cases
6244 // - init variables which are used/modified by item checking
6245 fItemSizeLimit=-1; // no limit
6246 fCurrentSyncOp = syncop;
6247 fEchoItemOp = sop_none;
6248 fItemConflictStrategy=fSessionConflictStrategy; // get default strategy for this item
6249 fForceConflict = false;
6250 fDeleteWins = fDSConfigP->fDeleteWins; // default to configured value
6251 fRejectStatus = -1; // no rejection
6253 // check reads and possibly modifies:
6254 // - fEchoItemOp : if not sop_none, the incoming item is echoed back to the remote with the specified syncop
6255 // - fItemConflictStrategy : might be changed from the pre-set datastore default
6256 // - fForceConflict : if set, a conflict is forced by adding the corresponding local item (if any) to the list of items to be sent
6257 // - fDeleteWins : if set, in a replace/delete conflict delete will win (regardless of strategy)
6258 // - fPreventAdd : if set, attempt to add item from remote (even implicitly trough replace) will cause no add but delete of remote item
6259 // - fIgnoreUpdate : if set, attempt to update item from remote will be ignored, only adds (also implicit ones) are executed
6260 // - fRejectStatus : if set>=0, incoming item is irgnored silently(==0) or with error(!=0)
6261 aSyncItemP->checkItem(this);
6262 // - check if incoming item should be processed at all
6263 if (fRejectStatus>=0) {
6264 // now discard the incoming item
6266 PDEBUGPRINTFX(fRejectStatus>=400 ? DBG_ERROR : DBG_DATA,("Item rejected with Status=%hd",fRejectStatus));
6267 if (fRejectStatus>0) {
6268 // rejected with status code (not necessarily error)
6269 aStatusCommand.setStatusCode(fRejectStatus);
6270 if (fRejectStatus>=300) {
6271 // non 200-codes are errors
6272 ADDDEBUGITEM(aStatusCommand,"Item rejected");
6276 // silently rejected
6279 // now perform requested operation
6281 case sop_soft_delete:
6282 case sop_archive_delete:
6285 fLocalItemsDeleted++;
6286 remainsvisible=false; // deleted not visible any more
6287 ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible); // delete in local database NOW
6290 // %%% note: this would belong into specific datastore implementation, but is here
6291 // now for simplicity as copy isn't used heavily het
6292 // retrieve data from local datastore
6293 if (!logicRetrieveItemByID(*aSyncItemP,aStatusCommand)) {
6299 // add as new item to client DB
6300 aStatusCommand.setStatusCode(201); // item added (if no error occurs)
6302 // add to local datastore
6303 // - get remoteid BEFORE processing item (as logicProcessRemoteItem consumes the item!!)
6304 remoteid=aSyncItemP->getRemoteID(); // get remote ID
6305 // check if this remoteid is already known from last session - if so, this means that
6306 // this add was re-sent and must NOT be executed
6307 if (isAddFromLastSession(remoteid.c_str())) {
6308 aStatusCommand.setStatusCode(418); // already exists
6309 PDEBUGPRINTFX(DBG_ERROR,("Warning: Item with same server-side ID (GUID) was already added in previous session - ignore add now"));
6310 delete aSyncItemP; // forget item
6314 #ifdef OBJECT_FILTERING
6315 if (!isAcceptable(aSyncItemP,aStatusCommand)) {
6316 delete aSyncItemP; // forget item
6317 ok=false; // cannot be accepted
6321 remainsvisible=true; // should remain visible
6322 ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible,&localid); // add to local database NOW, get back local GUID
6324 // if added (not replaced), we need to send map
6325 if (aStatusCommand.getStatusCode()==201) {
6326 // really added: remember map entry
6327 DEBUGPRINTFX(DBG_ADMIN+DBG_DETAILS,(
6328 "engProcessRemoteItemAsClient: add command: adding new entry to fPendingAddMaps - localid='%s', remoteID='%s'",
6332 fPendingAddMaps[localid]=remoteid;
6337 // - replace item in client
6338 fLocalItemsUpdated++;
6339 aSyncItemP->setSyncOp(sop_replace);
6340 #ifdef OBJECT_FILTERING
6341 if (!isAcceptable(aSyncItemP,aStatusCommand)) {
6342 ok=false; // cannot be accepted
6346 // - get remoteid BEFORE processing item (as logicProcessRemoteItem consumes the item!!),
6347 // in case replace is converted to add and we need to register a map entry.
6348 remoteid=aSyncItemP->getRemoteID(); // get remote ID
6349 remainsvisible=true; // should remain visible
6350 ok=logicProcessRemoteItem(aSyncItemP,aStatusCommand,remainsvisible,&localid); // replace in local database NOW
6351 // if added (not replaced), we need to send map
6352 if (aStatusCommand.getStatusCode()==201) {
6353 // Note: logicProcessRemoteItem should NOT do an add if we have no remoteid, but return 404.
6354 // The following check is just additional security.
6355 // really added: remember map entry if server sent remoteID (normally, it won't for Replace)
6356 if (!remoteid.empty()) {
6357 // we can handle that, as remote sent us the remoteID we need to map it correctly
6358 DEBUGPRINTFX(DBG_ADMIN+DBG_DETAILS,(
6359 "engProcessRemoteItemAsClient: replace command for unknown localID but with remoteID -> adding new entry to fPendingAddMaps - localid='%s', remoteID='%s'",
6363 fPendingAddMaps[localid]=remoteid;
6366 // we cannot handle this (we shouldn't have, in logicProcessRemoteItem!!)
6367 aStatusCommand.setStatusCode(510);
6371 break; // fine, ok = irregularity status
6373 SYSYNC_THROW(TSyncException("Unknown sync op in TLocalEngineDS::processRemoteItemAsClient"));
6377 DB_PROGRESS_EVENT(this,pev_itemprocessed,fLocalItemsAdded,fLocalItemsUpdated,fLocalItemsDeleted);
6380 // if the DB has a error string to show, add it here
6381 aStatusCommand.addItemString(lastDBErrorText().c_str());
6385 } // TLocalEngineDS::processRemoteItemAsClient
6389 // client case: called whenever outgoing Message of Sync Package starts
6390 void TLocalEngineDS::engClientStartOfSyncMessage(void)
6392 // is called for all local datastores, even inactive ones, so check state first
6393 if (testState(dssta_dataaccessstarted) && !isAborted()) {
6394 // only alerted non-sub datastores will start a <sync> command here, ONCE
6395 // if there are pending maps, they will be sent FIRST
6396 if (fRemoteDatastoreP) {
6397 if (!testState(dssta_clientsyncgenstarted)) {
6398 // shift state to syncgen started
6399 // NOTE: if all sync commands can be sent at once,
6400 // state will change again by issuing <sync>, so
6401 // it MUST be changed here (not after issuing!)
6402 changeState(dssta_clientsyncgenstarted,true);
6403 // - make sure remotedatastore has correct full name
6404 fRemoteDatastoreP->setFullName(getRemoteDBPath());
6405 // an interrupted command at this point is a map command - continueIssue() will take care
6406 if (!fSessionP->isInterrupedCmdPending() && numUnsentMaps()>0) {
6407 // Check for pending maps from previous session (even in DS 1.0..1.1 case this is possible)
6408 fUnconfirmedMaps.clear(); // no unconfirmed maps left...
6409 fLastSessionMaps.clear(); // ...and no lastSessionMaps yet: all are in pendingMaps
6410 // if this is a not-resumed slow sync, pending maps must not be sent, but discarded
6411 if (isSlowSync() && !isResuming()) {
6412 // forget the pending maps
6413 PDEBUGPRINTFX(DBG_HOT,("There are %ld cached map entries from last Session, but this is a non-resumed slow sync: discard them",(long)numUnsentMaps()));
6414 fPendingAddMaps.clear();
6417 // - now sending pending (cached) map commands from previous session
6418 // Note: if map command was already started, the
6419 // finished(), continueIssue() mechanism will make sure that
6420 // more commands are generated. This mechanism will also make
6421 // sure that outgoing package cannot get <final/> until
6422 // map is completely sent.
6423 // Note2: subdatastores do not generate their own map commands,
6424 // but are called by superdatastore for contributing to united map
6425 if (!isSubDatastore()) {
6426 PDEBUGBLOCKFMT(("LastSessionMaps","Now sending cached map entries from last Session","datastore=%s",getName()));
6427 TMapCommand *mapcmdP =
6430 this, // local datastore
6431 fRemoteDatastoreP // remote datastore
6434 ISSUE_COMMAND_ROOT(fSessionP,mapcmdP);
6435 PDEBUGENDBLOCK("LastSessionMaps");
6439 // Now, send sync command (unless we are a subdatastore, in this case the superdatastore will take care)
6440 if (!isSubDatastore()) {
6441 // Note: if sync command was already started, the
6442 // finished(), continueIssue() mechanism will make sure that
6443 // more commands are generated
6444 // Note2: subdatastores do not generate their own sync commands,
6445 // but switch to dssta_client/serversyncgenstarted for contributing to united sync command
6446 TSyncCommand *synccmdP =
6449 this, // local datastore
6450 fRemoteDatastoreP // remote datastore
6453 ISSUE_COMMAND_ROOT(fSessionP,synccmdP);
6458 PDEBUGPRINTFX(DBG_ERROR,("engClientStartOfSyncMessage can't start sync phase - missing remotedatastore"));
6459 changeState(dssta_idle,true); // force to idle, nothing happened yet
6461 } // if dsssta_dataaccessstarted and not aborted
6462 } // TLocalEngineDS::engClientStartOfSyncMessage
6465 // Client only: returns number of unsent map items
6466 sInt32 TLocalEngineDS::numUnsentMaps(void)
6468 return fPendingAddMaps.size();
6469 } // TLocalEngineDS::numUnsentMaps
6472 // Client only: called whenever outgoing Message starts that may contain <Map> commands
6473 // @param[in] aNotYetInMapPackage set if we are still in sync-from-server package
6474 // @note usually, client starts sending maps while still receiving syncops from server
6475 void TLocalEngineDS::engClientStartOfMapMessage(bool aNotYetInMapPackage)
6477 // is called for all local datastores, even inactive ones, so check state first
6478 if (testState(dssta_syncgendone) && !isAborted()) {
6479 // datastores that have finished generating their <sync>
6480 // now can start a <map> command here, once (if not isInterrupedCmdPending(), that is, not a map already started)
6481 if (fRemoteDatastoreP) {
6482 // start a new map command if we don't have any yet and we are not done (dssta_clientmapssent) yet
6483 if (!fSessionP->isInterrupedCmdPending() && !testState(dssta_clientmapssent)) {
6484 // check if we should start one (that is, if the list is not empty)
6485 if (numUnsentMaps()>0) {
6486 // - now sending map command
6487 // NOTE: if all map commands can be sent at once,
6488 // fState will be modified again by issuing <map>, so
6489 // it MUST be set here (not after issuing!)
6490 // - data access done only if we are in Map package now
6491 if (!aNotYetInMapPackage)
6492 changeState(dssta_dataaccessdone); // usually already set in engEndOfSyncFromRemote(), but does not harm here
6493 // Note: if map command was already started, the
6494 // finished(), continueIssue() mechanism will make sure that
6495 // more commands are generated. This mechanism will also make
6496 // sure that outgoing package cannot get <final/> until
6497 // map is completely sent.
6498 // Note2: subdatastores do not generate their own map commands,
6499 // but superdatastore calls their generateMapItem for contributing to united map
6500 if (!isSubDatastore()) {
6501 TMapCommand *mapcmdP =
6504 this, // local datastore
6505 fRemoteDatastoreP // remote datastore
6508 ISSUE_COMMAND_ROOT(fSessionP,mapcmdP);
6512 // we need no map items now, but if this is still sync-from-server-package,
6513 // we are not done yet
6514 if (aNotYetInMapPackage) {
6515 DEBUGPRINTFX(DBG_PROTO,("No map command need to be generated now, but still in <sync> from server package"));
6519 DEBUGPRINTFX(DBG_PROTO,("All map commands sending complete"));
6520 changeState(dssta_clientmapssent);
6524 } // if fRemoteDataStoreP
6525 } // if >=dssta_syncgendone
6526 } // TLocalEngineDS::engClientStartOfMapMessage
6530 // called to mark maps confirmed, that is, we have received ok status for them
6531 void TLocalEngineDS::engMarkMapConfirmed(cAppCharP aLocalID, cAppCharP aRemoteID)
6533 // As this is client-only, we don't need to check for tempGUIDs here.
6534 // Note: superdatastore has an implementation which dispatches by prefix
6535 TStringToStringMap::iterator pos=fUnconfirmedMaps.find(aLocalID);
6536 if (pos!=fUnconfirmedMaps.end()) {
6537 DEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,(
6538 "engMarkMapConfirmed: deleting confirmed entry localID='%s', remoteID='%s' from fUnconfirmedMaps",
6542 // move it to lastsessionmap
6543 fLastSessionMaps[(*pos).first]=(*pos).second;
6544 // remove it from unconfirmed map
6545 fUnconfirmedMaps.erase(pos);
6547 } // TLocalEngineDS::engMarkMapConfirmed
6551 // Check if the remoteid was used by an add command not
6552 // fully mapped&confirmed in the previous session
6553 bool TLocalEngineDS::isAddFromLastSession(cAppCharP aRemoteID)
6555 TStringToStringMap::iterator pos;
6556 TStringToStringMap *mapListP;
6558 for (int i=0; i<3; i++) {
6559 // determine next list to search
6560 mapListP = i==0 ? &fPendingAddMaps : (i==1 ? &fUnconfirmedMaps : &fLastSessionMaps);
6562 for (pos=mapListP->begin(); pos!=mapListP->end(); ++pos) {
6563 if (strcmp((*pos).second.c_str(),aRemoteID)==0)
6564 return true; // remoteID known -> is add from last session
6567 // not found in any of the lists
6569 } // TLocalEngineDS::isAddFromLastSession
6573 // - called to generate Map items
6574 // Returns true if now finished for this datastore
6575 // also sets fState to dss_done when finished
6576 bool TLocalEngineDS::engGenerateMapItems(TMapCommand *aMapCommandP, cAppCharP aLocalIDPrefix)
6578 #ifdef USE_SML_EVALUATION
6579 sInt32 leavefree = fSessionP->getNotUsableBufferBytes();
6581 sInt32 freeroom = fSessionP->getFreeCommandSize();
6584 TStringToStringMap::iterator pos=fPendingAddMaps.begin();
6585 PDEBUGBLOCKFMT(("MapGenerate","Generating Map items...","datastore=%s",getName()));
6587 // check if already done
6588 if (pos==fPendingAddMaps.end()) break; // done
6590 string locID = (*pos).first;
6591 dsFinalizeLocalID(locID); // make sure we have the permanent version in case datastore implementation did deliver temp IDs
6592 // create prefixed version of ID
6593 string prefixedLocID;
6594 AssignString(prefixedLocID, aLocalIDPrefix); // init with prefix (if any)
6595 prefixedLocID += locID; // append local ID
6596 // add it to map command
6597 aMapCommandP->addMapItem(prefixedLocID.c_str(),(*pos).second.c_str());
6598 // check if we could send this command
6599 #ifdef USE_SML_EVALUATION
6601 (aMapCommandP->evalIssue(
6602 fSessionP->peekNextOutgoingCmdID(),
6603 fSessionP->getOutgoingMsgID()
6604 ) // what is left in buffer after sending Map so far
6605 >=leavefree) // all this must still be more than what we MUST leave free
6608 if (freeroom>aMapCommandP->messageSize())
6611 // yes, it should work
6612 PDEBUGPRINTFX(DBG_PROTO,(
6613 "Mapitem generated: localID='%s', remoteID='%s'",
6614 prefixedLocID.c_str(),
6615 (*pos).second.c_str()
6617 // move sent ones to unconfirmed list (Note: use real locID, without prefix!)
6618 fUnconfirmedMaps[locID]=(*pos).second;
6619 // remove item from to-be-sent list
6620 TStringToStringMap::iterator temp_pos = pos++; // make copy and set iterator to next
6621 fPendingAddMaps.erase(temp_pos); // now entry can be deleted (N.M. Josuttis, pg204)
6624 // no room for this item in this message, interrupt now
6625 // - delete already added item again
6626 aMapCommandP->deleteLastMapItem();
6628 PDEBUGPRINTFX(DBG_PROTO,("Interrupted generating Map items because max message size reached"))
6629 PDEBUGENDBLOCK("MapGenerate");
6634 // if we are dataaccessdone or more -> end of map phase for this datastore
6635 if (testState(dssta_dataaccessdone)) {
6636 changeState(dssta_clientmapssent,true);
6637 PDEBUGPRINTFX(DBG_PROTO,("Finished generating Map items, server has finished <sync>, we are done now"))
6640 // else if we are not yet dssta_syncgendone -> this is the end of a early pending map send
6641 else if (!dbgTestState(dssta_syncgendone)) {
6642 PDEBUGPRINTFX(DBG_PROTO,("Finished sending cached Map items from last session"))
6644 // otherwise, we are not really finished with the maps yet (but with the current map command)
6646 PDEBUGPRINTFX(DBG_PROTO,("Finished generating Map items for now, but server still sending <Sync>"))
6649 PDEBUGENDBLOCK("MapGenerate");
6651 } // TLocalEngineDS::engGenerateMapItems
6654 #endif // SYSYNC_SERVER
6658 // - called to mark an already generated (but probably not sent or not yet statused) item
6659 // as "to-be-resumed", by localID or remoteID (latter only in server case).
6660 // NOTE: This must be repeatable without side effects, as server must mark/save suspend state
6661 // after every request (and not just at end of session)
6662 void TLocalEngineDS::engMarkItemForResume(cAppCharP aLocalID, cAppCharP aRemoteID, bool aUnSent)
6664 #ifdef SUPERDATASTORES
6665 // if we are acting as a subdatastore, aLocalID might be prefixed
6666 if (fAsSubDatastoreOf && aLocalID) {
6668 aLocalID = fAsSubDatastoreOf->removeSubDSPrefix(aLocalID, this);
6671 #ifdef SYSYNC_SERVER
6672 // Now mark for resume
6673 if (IS_SERVER && aLocalID && *aLocalID) {
6674 // localID can be a translated localid at this point (for adds), so check for it
6675 string localid=aLocalID;
6676 obtainRealLocalID(localid);
6677 logicMarkItemForResume(localid.c_str(), aRemoteID, aUnSent);
6682 // localID not used or client (which never has tempGUIDs)
6683 logicMarkItemForResume(aLocalID, aRemoteID, aUnSent);
6685 } // TLocalEngineDS::engMarkItemForResume
6688 // - called to mark an already generated (but probably not sent or not yet statused) item
6689 // as "to-be-resent", by localID or remoteID (latter only in server case).
6690 void TLocalEngineDS::engMarkItemForResend(cAppCharP aLocalID, cAppCharP aRemoteID)
6692 #ifdef SUPERDATASTORES
6693 // if we are acting as a subdatastore, aLocalID might be prefixed
6694 if (fAsSubDatastoreOf && aLocalID) {
6696 aLocalID = fAsSubDatastoreOf->removeSubDSPrefix(aLocalID, this);
6699 // a need to resend is always a problem with the remote (either explicit non-ok status
6700 // received or implicit like data size too big to be sent at all due to maxmsgsize or
6701 // maxobjsize restrictions.
6702 fRemoteItemsError++;
6703 // now mark for resend
6704 #ifdef SYSYNC_SERVER
6705 if (IS_SERVER && aLocalID && *aLocalID) {
6706 // localID can be a translated localid at this point (for adds), so check for it
6707 string localid=aLocalID;
6708 obtainRealLocalID(localid);
6709 logicMarkItemForResend(localid.c_str(), aRemoteID);
6714 // localID not used or client (which never has tempGUIDs)
6715 logicMarkItemForResend(aLocalID, aRemoteID);
6717 } // TLocalEngineDS::engMarkItemForResend
6723 // @brief save everything needed to resume later, in case we get suspended
6724 /// - Might be called multiple times during a session, must make sure every time
6725 /// that the status is correct, that is, previous suspend state is erased
6726 localstatus TLocalEngineDS::engSaveSuspendState(bool aAnyway)
6728 // only save here if not aborted already (aborting saves the state immediately)
6729 // or explicitly requested
6730 if (aAnyway || !isAborted()) {
6731 // only save if DS 1.2 and supported by DB
6732 if ((fSessionP->getSyncMLVersion()>=syncml_vers_1_2) && dsResumeSupportedInDB()) {
6733 PDEBUGBLOCKFMT(("SaveSuspendState","Saving state for suspend/resume","datastore=%s",getName()));
6734 // save alert state (if not explicitly prevented)
6735 fResumeAlertCode=fPreventResuming ? 0 : fAlertCode;
6736 if (fResumeAlertCode) {
6737 if (fPartialItemState!=pi_state_save_outgoing) {
6738 // ONLY if we have no request for saving an outgoing item state already,
6739 // we possibly need to save a pending incoming item
6740 // if there is an incompletely received item, let it update Partial Item (fPIxxx) state
6741 // (if it is an item of this datastore, that is).
6742 if (fSessionP->fIncompleteDataCommandP)
6743 fSessionP->fIncompleteDataCommandP->updatePartialItemState(this);
6745 // this makes sure that only ungenerated (but to-be generated) items will be
6746 // marked for resume. Items that have been generated in this session (but might
6747 // have been marked for resume in a previous session must no longer be marked
6749 // This also includes saving state for a partially sent item so we could resume it (fPIxxx)
6750 logicMarkOnlyUngeneratedForResume();
6751 // then, we need to additionally mark those items for resume which have been
6752 // generated, but not yet sent or sent but not received status so far.
6753 fSessionP->markPendingForResume(this);
6755 // let datastore make all this persistent
6756 // NOTE: this must happen even if we have no suspend state here,
6757 // as marked-for-resends need to be saved here as well.
6758 localstatus sta=logicSaveResumeMarks();
6759 if (sta!=LOCERR_OK) {
6760 PDEBUGPRINTFX(DBG_ERROR,("Error saving suspend state with logicSaveResumeMarks(), status=%hd",sta));
6762 PDEBUGENDBLOCK("SaveSuspendState");
6765 // resume not supported due to datastore or SyncML version<1.2 -> ok anyway
6766 PDEBUGPRINTFX(DBG_PROTO,("SaveSuspendState not possible (SyncML<1.2 or not supported by DB)"));
6769 } // TLocalEngineDS::engSaveSuspendState
6773 } // namespace sysync
6775 /* end of TLocalEngineDS implementation */