Imported Upstream version 1.3.99.4
[platform/upstream/syncevolution.git] / src / synthesis / src / DB_interfaces / api_db / pluginapids.cpp
1 /**
2  *  @File     pluginapids.cpp
3  *
4  *  @Author   Lukas Zeller (luz@plan44.ch)
5  *
6  *  @brief TPluginApiDS
7  *    Plugin based datastore API implementation
8  *
9  *    Copyright (c) 2001-2011 by Synthesis AG + plan44.ch
10  *
11  *  @Date 2005-10-06 : luz : created from apidbdatastore
12  */
13
14 // includes
15 #include "sysync.h"
16 #include "pluginapids.h"
17 #include "pluginapiagent.h"
18
19 #ifdef SYSER_REGISTRATION
20 #include "syserial.h"
21 #endif
22
23 #ifdef ENGINEINTERFACE_SUPPORT
24 #include "engineentry.h"
25 #endif
26
27 #include "SDK_support.h"
28
29
30 namespace sysync {
31
32 // Config
33 // ======
34
35 // Helpers
36 // =======
37
38
39 // Field Map item
40 // ==============
41
42 TApiFieldMapItem::TApiFieldMapItem(const char *aElementName, TConfigElement *aParentElement) :
43   inherited(aElementName,aParentElement)
44 {
45   /* nop for now */
46 } // TApiFieldMapItem::TApiFieldMapItem
47
48
49 void TApiFieldMapItem::checkAttrs(const char **aAttributes)
50 {
51   /* nop for now */
52   inherited::checkAttrs(aAttributes);
53 } // TApiFieldMapItem::checkAttrs
54
55
56 #ifdef ARRAYDBTABLES_SUPPORT
57
58 // Array Map item
59 // ===============
60
61 TApiFieldMapArrayItem::TApiFieldMapArrayItem(TCustomDSConfig *aCustomDSConfigP, TConfigElement *aParentElement) :
62   inherited(aCustomDSConfigP,aParentElement)
63 {
64   /* nop for now */
65 } // TApiFieldMapArrayItem::TApiFieldMapArrayItem
66
67
68 void TApiFieldMapArrayItem::checkAttrs(const char **aAttributes)
69 {
70   /* nop for now */
71   inherited::checkAttrs(aAttributes);
72 } // TApiFieldMapArrayItem::checkAttrs
73
74 #endif
75
76
77
78 // TPluginDSConfig
79 // ============
80
81 TPluginDSConfig::TPluginDSConfig(const char* aName, TConfigElement *aParentElement) :
82   inherited(aName,aParentElement),
83   fPluginParams_Admin(this),
84   fPluginParams_Data(this)
85 {
86   // nop so far
87   clear();
88 } // TPluginDSConfig::TPluginDSConfig
89
90
91 TPluginDSConfig::~TPluginDSConfig()
92 {
93   clear();
94   // disconnect from the API module
95   fDBApiConfig_Data.Disconnect();
96   fDBApiConfig_Admin.Disconnect();
97 } // TPluginDSConfig::~TPluginDSConfig
98
99
100 // init defaults
101 void TPluginDSConfig::clear(void)
102 {
103   // init defaults
104   fDBAPIModule_Admin.erase();
105   fDBAPIModule_Data.erase();
106   fDataModuleAlsoHandlesAdmin=false;
107   fEarlyStartDataRead = false;
108   // - default to use all debug flags set (if debug for plugin is enabled at all)
109   fPluginDbgMask_Admin=0xFFFF;
110   fPluginDbgMask_Data=0xFFFF;
111   // - clear plugin params
112   fPluginParams_Admin.clear();
113   fPluginParams_Data.clear();
114   // - clear capabilities
115   fItemAsKey = false;
116   fResumeSupported = true;
117   fHasDeleteSyncSet = false;
118   // clear inherited
119   inherited::clear();
120 } // TPluginDSConfig::clear
121
122
123 // config element parsing
124 bool TPluginDSConfig::localStartElement(const char *aElementName, const char **aAttributes, sInt32 aLine)
125 {
126   // checking the elements
127   if (strucmp(aElementName,"plugin_module")==0)
128     expectMacroString(fDBAPIModule_Data);
129   else if (strucmp(aElementName,"plugin_datastoreadmin")==0)
130     expectBool(fDataModuleAlsoHandlesAdmin);
131   else if (strucmp(aElementName,"plugin_earlystartdataread")==0)
132     expectBool(fEarlyStartDataRead);
133   else if (strucmp(aElementName,"plugin_params")==0)
134     expectChildParsing(fPluginParams_Data);
135   else if (strucmp(aElementName,"plugin_debugflags")==0)
136     expectUInt16(fPluginDbgMask_Data);
137   else if (strucmp(aElementName,"plugin_module_admin")==0)
138     expectMacroString(fDBAPIModule_Admin);
139   else if (strucmp(aElementName,"plugin_params_admin")==0)
140     expectChildParsing(fPluginParams_Admin);
141   else if (strucmp(aElementName,"plugin_debugflags_admin")==0)
142     expectUInt16(fPluginDbgMask_Admin);
143   else
144     return inherited::localStartElement(aElementName,aAttributes,aLine);
145   // ok
146   return true;
147 } // TPluginDSConfig::localStartElement
148
149
150 // resolve
151 void TPluginDSConfig::localResolve(bool aLastPass)
152 {
153   // resolve plugin specific config leaf
154   fPluginParams_Admin.Resolve(aLastPass);
155   fPluginParams_Data.Resolve(aLastPass);
156   // try to resolve configured API-module name into set of function pointers
157   if (aLastPass) {
158     // Determine if we may use non-built-in plugins
159     bool allowDLL= true; // by default, it is allowed, but if PLUGIN_DLL is not set, it will be disabled anyway.
160     #if defined(SYSER_REGISTRATION) && !defined(DLL_PLUGINS_ALWAYS_ALLOWED)
161     // license flags present and DLL plugins not generally allowed:
162     // -> license decides if DLL is allowed
163     allowDLL= (getSyncAppBase()->fRegProductFlags & SYSER_PRODFLAG_SERVER_SDKAPI)!=0;
164     // warn about DLL not allowed ONLY if this build actually supports DLL plugins
165     #if defined(PLUGIN_DLL)
166     if (!allowDLL) {
167       SYSYNC_THROW(TConfigParseException("License does not allow using <datastore type=\"plugin\">"));
168     }
169     #endif // DLL support available in the code at all
170     #endif // DLL plugins not generally allowed (or DLL support not compiled in)
171     // Connect module for handling data access
172     if (!fDBAPIModule_Data.empty()) {
173       // we have a module specified for data access
174       DB_Callback cb= &fDBApiConfig_Data.fCB.Callback;
175       cb->callbackRef       = getSyncAppBase();
176       #ifdef ENGINEINTERFACE_SUPPORT
177       cb->thisBase          = getSyncAppBase()->fEngineInterfaceP;
178       #endif
179       #ifdef SYDEBUG
180       // direct Module level debug to global log
181       cb->debugFlags= (getSyncAppBase()->getRootConfig()->fDebugConfig.fGlobalDebugLogs) &&
182                       PDEBUGTEST(DBG_DATA+DBG_DBAPI+DBG_PLUGIN) ? fPluginDbgMask_Data : 0;
183       cb->DB_DebugPuts      = AppBaseLogDebugPuts;
184       cb->DB_DebugBlock     = AppBaseLogDebugBlock;
185       cb->DB_DebugEndBlock  = AppBaseLogDebugEndBlock;
186       cb->DB_DebugEndThread = AppBaseLogDebugEndThread;
187       cb->DB_DebugExotic    = AppBaseLogDebugExotic;
188       #endif
189       if (fDBApiConfig_Data.Connect(fDBAPIModule_Data.c_str(), getSyncAppBase()->fApiInterModuleContext, getName(), false, allowDLL)!=LOCERR_OK)
190         SYSYNC_THROW(TConfigParseException("Cannot connect to datastore implementation module specified in <plugin_module>"));
191       // now pass plugin-specific config
192       if (fDBApiConfig_Data.PluginParams(fPluginParams_Data.fConfigString.c_str())!=LOCERR_OK)
193         SYSYNC_THROW(TConfigParseException("Module does not understand params passed in <plugin_params>"));
194       // Check module capabilities
195       TDB_Api_Str capa;
196       fDBApiConfig_Data.Capabilities(capa);
197       string capaStr = capa.c_str();
198       // - check existence of DeleteSyncSet()
199       fHasDeleteSyncSet = FlagOK(capaStr,CA_DeleteSyncSet,true);
200       // - Check for new method for data access (as keys instead of as text items)
201       fItemAsKey = FlagOK(capaStr,CA_ItemAsKey,true);
202       // - Allow module to choose whether it wants to support suspend/resume.
203       fResumeSupported = FlagOK(capaStr,CA_ResumeSupported,true);
204       // Check if engine is compatible
205       #ifndef DBAPI_TEXTITEMS
206       if (!fItemAsKey) SYSYNC_THROW(TConfigParseException("This engine does not support data items in text format"));
207       #endif
208       #if !defined(DBAPI_ASKEYITEMS) || !defined(ENGINEINTERFACE_SUPPORT)
209       if (fItemAsKey) SYSYNC_THROW(TConfigParseException("This engine does not support data items passed as key handles"));
210       #endif
211     }
212     // connect module for handling admin access
213     // - use same module and params as data if no separate module specified and plugin_datastoreadmin is set
214     if (fDBAPIModule_Admin.empty() && fDataModuleAlsoHandlesAdmin) {
215       fDBAPIModule_Admin=fDBAPIModule_Data;
216       fPluginParams_Admin=fPluginParams_Data;
217       fPluginDbgMask_Admin=fPluginDbgMask_Data;
218     }
219     // - now connect the admin module
220     if (!fDBAPIModule_Admin.empty()) {
221       // we have a module specified for data access
222       DB_Callback cb= &fDBApiConfig_Admin.fCB.Callback;
223       cb->callbackRef      = getSyncAppBase();
224       #ifdef ENGINEINTERFACE_SUPPORT
225       cb->thisBase         = getSyncAppBase()->fEngineInterfaceP;
226       #endif
227       #ifdef SYDEBUG
228       // direct Module level debug to global log
229       cb->debugFlags= (getSyncAppBase()->getRootConfig()->fDebugConfig.fGlobalDebugLogs) &&
230       PDEBUGTEST(DBG_ADMIN+DBG_DBAPI+DBG_PLUGIN) ? fPluginDbgMask_Admin : 0;
231       cb->DB_DebugPuts     = AppBaseLogDebugPuts;
232       cb->DB_DebugBlock    = AppBaseLogDebugBlock;
233       cb->DB_DebugEndBlock = AppBaseLogDebugEndBlock;
234       cb->DB_DebugEndThread= AppBaseLogDebugEndThread;
235       cb->DB_DebugExotic   = AppBaseLogDebugExotic;
236       #endif
237       if (fDBApiConfig_Admin.Connect(fDBAPIModule_Admin.c_str(),getSyncAppBase()->fApiInterModuleContext,getName())!=LOCERR_OK)
238         SYSYNC_THROW(TConfigParseException("Cannot connect to datastore implementation module specified in <plugin_module_admin>"));
239       // now pass plugin-specific config
240       if (fDBApiConfig_Admin.PluginParams(fPluginParams_Admin.fConfigString.c_str())!=LOCERR_OK)
241         SYSYNC_THROW(TConfigParseException("Module does not understand params passed in <plugin_params_admin>"));
242     }
243   }
244   // resolve inherited
245   inherited::localResolve(aLastPass);
246 } // TPluginDSConfig::localResolve
247
248
249 // - create appropriate datastore from config, calls addTypeSupport as well
250 TLocalEngineDS *TPluginDSConfig::newLocalDataStore(TSyncSession *aSessionP)
251 {
252   // Synccap defaults to normal set supported by the engine by default
253   TLocalEngineDS *ldsP;
254   if (IS_CLIENT) {
255     ldsP = new TPluginApiDS(this,aSessionP,getName(),aSessionP->getSyncCapMask() & ~(isOneWayFromRemoteSupported() ? 0 : SCAP_MASK_ONEWAY_SERVER));
256   }
257   else {
258     ldsP = new TPluginApiDS(this,aSessionP,getName(),aSessionP->getSyncCapMask() & ~(isOneWayFromRemoteSupported() ? 0 : SCAP_MASK_ONEWAY_CLIENT));
259   }
260   // do common stuff
261   addTypes(ldsP,aSessionP);
262   // return
263   return ldsP;
264 } // TPluginDSConfig::newLocalDataStore
265
266
267 /*
268  * Implementation of TPluginApiDS
269  */
270
271 // constructor
272 TPluginApiDS::TPluginApiDS(
273   TPluginDSConfig *aConfigP,
274   sysync::TSyncSession *aSessionP,
275   const char *aName,
276   uInt32 aCommonSyncCapMask
277 ) :
278   #ifdef SDK_ONLY_SUPPORT
279   TCustomImplDS(aConfigP,aSessionP, aName, aCommonSyncCapMask)
280   #else
281   TODBCApiDS(aConfigP,aSessionP, aName, aCommonSyncCapMask)
282   #endif
283 {
284   // save a type casted pointer to the agent
285   fPluginAgentP=static_cast<TPluginApiAgent *>(aSessionP);
286   // save pointer to config record
287   fPluginDSConfigP=aConfigP;
288   // make a local copy of the typed agent pointer (note that the agent itself does
289   // NOT YET have its constructor completely run so we can't just copy the agents pointer)
290   fPluginAgentConfigP = DYN_CAST<TPluginAgentConfig *>(
291     aSessionP->getRootConfig()->fAgentConfigP
292   );
293   if (!fPluginAgentConfigP) SYSYNC_THROW(TSyncException(DEBUGTEXT("TPluginApiDS finds no AgentConfig","api1")));
294   // Note: do not create context here because Agent is not yet initialized.
295   // clear rest
296   InternalResetDataStore();
297 } // TPluginApiDS::TPluginApiDS
298
299
300 TPluginApiDS::~TPluginApiDS()
301 {
302   InternalResetDataStore();
303 } // TPluginApiDS::~TPluginApiDS
304
305
306 /// @brief called while agent is still fully ok, so we must clean up such that later call of destructor does NOT access agent any more
307 void TPluginApiDS::announceAgentDestruction(void)
308 {
309   // reset myself
310   InternalResetDataStore();
311   // make sure we don't access the agent any more
312   engTerminateDatastore();
313   fPluginAgentP = NULL;
314   // destroy API context
315   fDBApi_Data.DeleteContext();
316   fDBApi_Admin.DeleteContext();
317   // call inherited
318   inherited::announceAgentDestruction();
319 } // TPluginApiDS::announceAgentDestruction
320
321
322 /// @brief called to reset datastore
323 /// @note must be safe to be called multiple times and even after announceAgentDestruction()
324 void TPluginApiDS::InternalResetDataStore(void)
325 {
326   // filtering capabilities need to be evaluated first
327   fAPICanFilter = false;
328   fAPIFiltersTested = false;
329 } // TPluginApiDS::InternalResetDataStore
330
331
332
333
334 #ifdef DBAPI_TEXTITEMS
335
336 // store API key/value pair field in mapped field, if one is defined
337 bool TPluginApiDS::storeField(
338   cAppCharP aName,
339   cAppCharP aParams,
340   cAppCharP aValue,
341   TMultiFieldItem &aItem,
342   uInt16 aSetNo,
343   sInt16 aArrayIndex
344 )
345 {
346   TFieldMapList *fmlP = &(fPluginDSConfigP->fFieldMappings.fFieldMapList);
347   TFieldMapList::iterator pos;
348   TApiFieldMapItem *fmiP;
349   string s;
350   bool stored=false;
351   // search field map list for matching map entry
352   for (pos=fmlP->begin(); pos!=fmlP->end(); pos++) {
353     fmiP = static_cast<TApiFieldMapItem *>(*pos);
354     // check name
355     if (
356       fmiP->readable &&
357       fmiP->setNo==aSetNo &&
358       strucmp(fmiP->getName(),aName)==0
359     ) {
360       // DB-readable field with matching name
361       TDBFieldType dbfty = fmiP->dbfieldtype;
362       TItemField *fieldP;
363       sInt16 fid = fmiP->fid;
364       // determine leaf field
365       fieldP = getMappedFieldOrVar(aItem,fid,aArrayIndex);
366       // continue only if we have a field
367       if (!fieldP) continue;
368       // check if the field is proxyable and input defines a BLOB id
369       #ifdef STREAMFIELD_SUPPORT
370       if (fieldP->isBasedOn(fty_string)) {
371         // - check if params contain a BLOBID
372         string blobid;
373         if (paramScan(aParams,"BLOBID",blobid)) {
374           // this field is a blob, create a proxy for it
375           TApiBlobProxy *apiProxyP = new TApiBlobProxy(this,!fieldP->isBasedOn(fty_blob),blobid.c_str(),aItem.getLocalID());
376           // attach it to the string or blob field
377           static_cast<TStringField *>(fieldP)->setBlobProxy(apiProxyP);
378           // check if we must read it right now
379           if (paramScan(aParams,"READNOW",blobid))
380             static_cast<TStringField *>(fieldP)->pullFromProxy();
381           // check next mapping
382           continue;
383         }
384       }
385       #endif
386       // store according to database field type
387       switch (dbfty) {
388         case dbft_string:
389           // for explicit strings, perform character set and line feed conversion
390           s.erase();
391           // - convert from database charset to UTF-8 and to C-string linefeeds
392           appendStringAsUTF8(aValue, s, fPluginDSConfigP->fDataCharSet, lem_cstr);
393           fieldP->setAsString(s.c_str());
394           break;
395         case dbft_blob:
396           // blob is treated as 1:1 string if there's no proxy for it
397         default:
398           // for all other DB types, string w/o charset conversion is enough (these are by definition all single-line, ASCII-only)
399           if (fieldP->isBasedOn(fty_timestamp)) {
400             // interpret timestamps in dataTimeZone context (or as floating if this field is mapped in "f" mode)
401             TTimestampField *tsfP = static_cast<TTimestampField *>(fieldP);
402             tsfP->setAsISO8601(aValue, fmiP->floating_ts ? TCTX_UNKNOWN : fPluginDSConfigP->fDataTimeZone, false);
403             // modify time zone if params contain a TZNAME
404             if (paramScan(aParams,"TZNAME",s)) {
405               // convert to time zone context
406               timecontext_t tctx;
407               TimeZoneNameToContext(s.c_str(), tctx, tsfP->getGZones(), true);
408               tsfP->moveToContext(tctx, true); // move to new context, bind floating (and float fixed, if TZNAME=FLOATING)
409             }
410           }
411           else {
412             // just set as string
413             fieldP->setAsString(aValue);
414           }
415           break;
416       } // switch
417       // field successfully stored, do NOT exit loop
418       // because there could be a second map for the same attribute!
419       stored=true;
420     } // if map found for attribute
421   } // for all field mappings
422   return stored;
423 } // TPluginApiDS::storeField
424
425
426
427 // - parse text data into item
428 //   Note: generic implementation, using virtual storeField() method
429 //         to differentiate between use with mapped fields in DBApi and
430 //         direct (unmapped) TMultiFieldItem access in Tunnel API.
431 bool TPluginApiDS::parseDBItemData(
432   TMultiFieldItem &aItem,
433   cAppCharP aItemData,
434   uInt16 aSetNo
435 )
436 {
437   bool stored = parseItemData(aItem,aItemData,aSetNo);
438   if (stored) {
439     // post-process
440     stored = postReadProcessItem(aItem,aSetNo);
441   }
442   return stored;
443 } // TPluginApiDS::parseItemData
444
445
446
447 // generate text representations of item's fields (BLOBs and parametrized fields not included)
448 // - returns true if at least one field appended
449 bool TPluginApiDS::generateDBItemData(
450   bool aAssignedOnly,
451   TMultiFieldItem &aItem,
452   uInt16 aSetNo,
453   string &aDataFields
454 )
455 {
456   TFieldMapList *fmlP = &(fPluginDSConfigP->fFieldMappings.fFieldMapList);
457   TFieldMapList::iterator pos;
458   TApiFieldMapItem *fmiP;
459   string val;
460   bool createdone=false;
461
462   // pre-process (run scripts)
463   if (!preWriteProcessItem(aItem)) return false;
464   // create text representation for all mapped and writable fields
465   for (pos=fmlP->begin(); pos!=fmlP->end(); pos++) {
466     fmiP = static_cast<TApiFieldMapItem *>(*pos);
467     if (
468       fmiP->writable &&
469       fmiP->setNo==aSetNo
470     ) {
471       // get field
472       TItemField *basefieldP;
473       sInt16 fid = fmiP->fid;
474       // determine base field (might be array)
475       basefieldP = getMappedBaseFieldOrVar(aItem,fid);
476       if (generateItemFieldData(
477         aAssignedOnly,
478         fPluginDSConfigP->fDataCharSet,
479         fPluginDSConfigP->fDataLineEndMode,
480         fPluginDSConfigP->fDataTimeZone,
481         basefieldP,
482         fmiP->getName(),
483         aDataFields
484       ))
485         createdone=true; // we now have at least one field
486     } // if writable field
487   } // for all field mappings
488   PDEBUGPRINTFX(DBG_USERDATA+DBG_DBAPI+DBG_EXOTIC+DBG_HOT,("generateDBItemData generated string for DBApi:"));
489   PDEBUGPUTSXX(DBG_USERDATA+DBG_DBAPI+DBG_EXOTIC,aDataFields.c_str(),0,true);
490   return createdone;
491 } // TPluginApiDS::generateDBItemData
492
493
494
495 #endif // DBAPI_TEXTITEMS
496
497
498 // - post process item after reading from DB (run script)
499 bool TPluginApiDS::postReadProcessItem(TMultiFieldItem &aItem, uInt16 aSetNo)
500 {
501   #if defined(DBAPI_ASKEYITEMS) && defined(STREAMFIELD_SUPPORT)
502   // for ItemKey mode, we need to create a BLOB proxy for each as_param mapped field
503   if (fPluginDSConfigP->fItemAsKey) {
504     // find all asParam fields that can have proxies and create BLOB proxies for these
505     TFieldMapList *fmlP = &(fPluginDSConfigP->fFieldMappings.fFieldMapList);
506     TFieldMapList::iterator pos;
507     TApiFieldMapItem *fmiP;
508
509     // post-process all mapped and writable fields
510     for (pos=fmlP->begin(); pos!=fmlP->end(); pos++) {
511       fmiP = static_cast<TApiFieldMapItem *>(*pos);
512       if (
513         fmiP->readable &&
514         fmiP->setNo==aSetNo
515       ) {
516         // get field
517         TItemField *basefieldP, *leaffieldP;
518         #ifdef ARRAYFIELD_SUPPORT
519         uInt16 arrayIndex=0;
520         #endif
521         sInt16 fid = fmiP->fid;
522         // determine base field (might be array)
523         basefieldP = getMappedBaseFieldOrVar(aItem,fid);
524         // ignore map if we have no field for it
525         if (!basefieldP) continue;
526         // We have a base field for this, check what to do
527         if (fPluginDSConfigP->fUserZoneOutput && !fmiP->floating_ts && basefieldP->elementsBasedOn(fty_timestamp)) {
528           // userzoneoutput requested for non-floating timestamp field, move it!
529           #ifdef ARRAYFIELD_SUPPORT
530           arrayIndex=0;
531           #endif
532           do {
533             #ifdef ARRAYFIELD_SUPPORT
534             if (basefieldP->isArray()) {
535               leaffieldP = basefieldP->getArrayField(arrayIndex,true); // get existing leaf fields only
536               arrayIndex++;
537             }
538             else
539               leaffieldP = basefieldP; // leaf is base field
540             // if no leaf field, we'll need to exit here (we're done with the array)
541             if (leaffieldP==NULL) break;
542             #else
543             leaffieldP = basefieldP; // no arrays: leaf is always base field
544             #endif
545             static_cast<TTimestampField *>(leaffieldP)->moveToContext(fSessionP->fUserTimeContext, false);
546           } while(basefieldP->isArray()); // only arrays do loop all array elements
547         }
548         if (fmiP->as_param && basefieldP->elementsBasedOn(fty_string)) {
549           // string based field (string or BLOB) mapped as parameter
550           // unlike with textItems that get the BLOBID from the DB,
551           // in asKey mode, BLOBID is just map name plus a possible array index.
552           // Plugin must be able to identify the BLOB using this plus the item ID.
553           // Plugin must also make sure an array element exists (value does not matter, can be empty)
554           // for each element that should be proxied here.
555           // - create the proxies (one for each array element)
556           #ifdef ARRAYFIELD_SUPPORT
557           arrayIndex=0;
558           #endif
559           do {
560             // first check if there is an element at all
561             string blobid = fmiP->getName(); // map name
562             #ifdef ARRAYFIELD_SUPPORT
563             if (basefieldP->isArray()) {
564               leaffieldP = basefieldP->getArrayField(arrayIndex,true); // get existing leaf fields only
565               StringObjAppendPrintf(blobid,"[%d]",arrayIndex); // add array index to blobid
566               arrayIndex++;
567             }
568             else
569               leaffieldP = basefieldP; // leaf is base field
570             // if no leaf field, we'll need to exit here (we're done with the array)
571             if (leaffieldP==NULL) break;
572             #else
573             leaffieldP = basefieldP; // no arrays: leaf is always base field
574             #endif
575             // this array element exists, create the proxy
576             TApiBlobProxy *apiProxyP = new TApiBlobProxy(this,!leaffieldP->isBasedOn(fty_blob),blobid.c_str(),aItem.getLocalID());
577             // Note: we do not support "READNOW" proxies here, as they are useless in ItemKey context: if the
578             //       BLOB cannot be read later, no need for as_aparam map exists and plugin should just put the value
579             //      directly via the SetKeyValue() API.
580             // attach proxy to the string or blob field
581             static_cast<TStringField *>(leaffieldP)->setBlobProxy(apiProxyP);
582           } while(basefieldP->isArray()); // only arrays do loop all array elements
583         }
584       } // readable field mapping with explicit as_param mark
585     } // for all field mappings
586   } // if AsKey access
587   #endif // DBAPI_ASKEYITEMS
588   // execute afterread script now
589   #ifdef SCRIPT_SUPPORT
590   // process afterread script
591   fPluginAgentP->fScriptContextDatastore=this;
592   if (!TScriptContext::execute(fScriptContextP,fPluginDSConfigP->fFieldMappings.fAfterReadScript,fPluginDSConfigP->getDSFuncTableP(),fPluginAgentP,&aItem,true))
593     SYSYNC_THROW(TSyncException("<afterreadscript> fatal error"));
594   #endif
595   // always ok for now %%%
596   return true;
597 } // TPluginApiDS::postReadProcessItem
598
599
600
601 // - pre-process item before writing to DB (run script)
602 bool TPluginApiDS::preWriteProcessItem(TMultiFieldItem &aItem)
603 {
604   #ifdef SCRIPT_SUPPORT
605   // process beforewrite script
606   fWriting=true;
607   fDeleting=false;
608   fParentKey=aItem.getLocalID();
609   fPluginAgentP->fScriptContextDatastore=this;
610   if (!TScriptContext::execute(fScriptContextP,fPluginDSConfigP->fFieldMappings.fBeforeWriteScript,fPluginDSConfigP->getDSFuncTableP(),fPluginAgentP,&aItem,true))
611     SYSYNC_THROW(TSyncException("<beforewritescript> fatal error"));
612   #endif
613   // always ok for now %%%
614   return true;
615 } // TPluginApiDS::preWriteProcessItem
616
617
618
619
620
621 // - send BLOBs of this item one by one. Returns true if we have a blob at all
622 bool TPluginApiDS::writeBlobs(
623   bool aAssignedOnly,
624   TMultiFieldItem &aItem,
625   uInt16 aSetNo
626 )
627 {
628   TFieldMapList *fmlP = &(fPluginDSConfigP->fFieldMappings.fFieldMapList);
629   TFieldMapList::iterator pos;
630   TApiFieldMapItem *fmiP;
631   string blobfieldname;
632   bool blobwritten=false;
633   TSyError dberr;
634
635   // store all parametrized fields or BLOBs one by one
636   for (pos=fmlP->begin(); pos!=fmlP->end(); pos++) {
637     fmiP = static_cast<TApiFieldMapItem *>(*pos);
638     if (
639       fmiP->writable &&
640       fmiP->setNo==aSetNo
641     ) {
642       TItemField *basefieldP,*leaffieldP;
643       sInt16 fid = fmiP->fid;
644       // determine base field (might be array)
645       basefieldP = getMappedBaseFieldOrVar(aItem,fid);
646       // ignore map if we have no field for it
647       if (!basefieldP) continue;
648       // ignore map if field is not assigned and assignedonly flag is set
649       if (aAssignedOnly && basefieldP->isUnassigned()) continue;
650       // omit all non-BLOB normal fields
651       // Note: in ItemKey mode, blobs must have the as_aparam flag to be written as blobs.
652       //       in textItem mode, all fty_blob based fields will ALWAYS be written as blobs.
653       if (!(fmiP->as_param || (basefieldP->elementsBasedOn(fty_blob) && !fPluginDSConfigP->fItemAsKey))) continue;
654       // yes, we want to write this field as a BLOB
655       #ifdef ARRAYFIELD_SUPPORT
656       uInt16 arrayIndex;
657       for (arrayIndex=0; true; arrayIndex++)
658       #endif
659       {
660         // first create BLOB ID
661         blobfieldname=fmiP->getName();
662         #ifdef ARRAYFIELD_SUPPORT
663         // append array index if this is an array field
664         if (basefieldP->isArray()) {
665           StringObjAppendPrintf(blobfieldname,"[%d]",arrayIndex);
666           // calculate leaf field
667           leaffieldP = basefieldP->getArrayField(arrayIndex,true); // get existing leaf fields only
668         }
669         else {
670           leaffieldP = basefieldP; // leaf is base field
671         }
672         // if no leaf field, we'll need to exit here (we're done with the array)
673         if (leaffieldP==NULL) break;
674         #else
675         leaffieldP = basefieldP; // leaf is base field
676         #endif
677         // Now write this BLOB or string field
678         size_t maxBlobWriteBlockSz;
679         size_t blobsize;
680         void *bufferP = NULL;
681         string strDB;
682         bool isString = !leaffieldP->isBasedOn(fty_blob);
683         if (!isString) {
684           #ifdef STREAMFIELD_SUPPORT
685           leaffieldP->resetStream(); // this might be the wrong place to do this ...
686           blobsize = leaffieldP->getStreamSize();
687           maxBlobWriteBlockSz = 16000;
688           // allocate buffer
689           if (blobsize)
690             bufferP = new unsigned char[blobsize>maxBlobWriteBlockSz ? maxBlobWriteBlockSz : blobsize];
691           #else
692           // we cannot stream, but just read string contents
693           blobsize = leaffieldP->getStringSize();
694           maxBlobWriteBlockSz = blobsize; // all at once
695           bufferP = (void *) leaffieldP->getCStr(); // get pointer to string
696           #endif
697         }
698         else {
699           // is string -> first convert to DB charset
700           blobsize = leaffieldP->getStringSize();
701           maxBlobWriteBlockSz = blobsize;
702           string s;
703           leaffieldP->getAsString(s);
704           // convert to DB charset and linefeeds
705           appendUTF8ToString(
706             s.c_str(),
707             strDB,
708             fPluginDSConfigP->fDataCharSet,
709             fPluginDSConfigP->fDataLineEndMode
710           );
711           bufferP = (void *)strDB.c_str();
712         }
713         SYSYNC_TRY {
714           // now read from stream field and send to API
715           bool first=true;
716           bool last=false;
717           size_t remaining=blobsize;
718           size_t bytes,actualbytes;
719           while (!last) {
720             // it's the last block when we can write remaining bytes now
721             last = remaining<=maxBlobWriteBlockSz;
722             // calculate block size of this iteration
723             bytes = last ? remaining : maxBlobWriteBlockSz;
724             #ifdef STREAMFIELD_SUPPORT
725             if (!isString) {
726               // read these from the stream field
727               actualbytes = leaffieldP->readStream(bufferP,bytes); // we need to read
728             }
729             else
730             #endif
731             {
732               // we have it already in the buffer
733               actualbytes = bytes;
734             }
735             if (actualbytes<bytes) last=false;
736             // write to the DB API
737             dberr= fDBApi_Data.WriteBlob(
738               aItem.getLocalID(),
739               blobfieldname.c_str(),
740               bufferP,
741               actualbytes,
742               blobsize,
743               first,
744               last
745             );
746             if (dberr!=LOCERR_OK) SYSYNC_THROW(TSyncException("DBapi::WriteBlob fatal error"));
747             // not first call any more
748             first=false;
749             // calculate new remaining
750             remaining-=actualbytes;
751           } // while not last
752           #ifdef STREAMFIELD_SUPPORT
753           if (!isString && bufferP) delete (char *)bufferP;
754           #endif
755         }
756         SYSYNC_CATCH (...)
757           #ifdef STREAMFIELD_SUPPORT
758           if (!isString && bufferP) delete (char *)bufferP;
759           #endif
760           SYSYNC_RETHROW;
761         SYSYNC_ENDCATCH
762         // we now have at least one field
763         blobwritten=true;
764         #ifdef ARRAYFIELD_SUPPORT
765         // non-array do not loop
766         if (!basefieldP->isArray()) break;
767         #endif
768       } // for all array elements
769     } // if writable BLOB/parametrized field
770   } // for all field mappings
771   return blobwritten;
772 } // TPluginApiDS::writeBlobs
773
774
775 bool TPluginApiDS::deleteBlobs(
776   bool aAssignedOnly,
777   TMultiFieldItem &aItem,
778   uInt16 aSetNo
779 )
780 {
781   TFieldMapList *fmlP = &(fPluginDSConfigP->fFieldMappings.fFieldMapList);
782   TFieldMapList::iterator pos;
783   TApiFieldMapItem *fmiP;
784   string blobfieldname;
785   bool blobdeleted=false;
786   TSyError dberr;
787
788   // store all parametrized fields or BLOBs one by one
789   for (pos=fmlP->begin(); pos!=fmlP->end(); pos++) {
790     fmiP = static_cast<TApiFieldMapItem *>(*pos);
791     if (fmiP->writable &&
792         fmiP->setNo==aSetNo) {
793       // get field
794       TItemField *basefieldP,*leaffieldP;
795       sInt16 fid = fmiP->fid;
796       // determine base field (might be array)
797       basefieldP = getMappedBaseFieldOrVar(aItem,fid);
798       // ignore map if we have no field for it
799       if (!basefieldP) continue;
800
801       // %%% what is this, obsolete??
802       // ignore map if field is not assigned and assignedonly flag is set
803       //if (aAssignedOnly && basefieldP->isUnassigned()) continue;
804
805       // omit all non-BLOB normal fields
806       // Note: in ItemKey mode, blobs must have the as_aparam flag to be written as blobs.
807       //       in textItem mode, all fty_blob based fields will ALWAYS be written as blobs.
808       if (!(fmiP->as_param || (basefieldP->elementsBasedOn(fty_blob) && !fPluginDSConfigP->fItemAsKey))) continue;
809       // yes, we want to write this field as a BLOB
810
811       #ifdef ARRAYFIELD_SUPPORT
812       uInt16 arrayIndex;
813       for (arrayIndex=0; true; arrayIndex++)
814       #endif
815       {
816         // first create BLOB ID
817         blobfieldname=fmiP->getName();
818         #ifdef ARRAYFIELD_SUPPORT
819         // append array index if this is an array field
820         if (basefieldP->isArray()) {
821           StringObjAppendPrintf(blobfieldname,"[%d]",arrayIndex);
822           // calculate leaf field
823           leaffieldP= basefieldP->getArrayField(arrayIndex,true); // get existing leaf fields only
824         }
825         else {
826           leaffieldP= basefieldP; // leaf is base field
827         }
828         // if no leaf field, we'll need to exit here (we're done with the array)
829         if (leaffieldP==NULL) break;
830         #else
831           leaffieldP= basefieldP; // leaf is base field
832         #endif
833
834         SYSYNC_TRY {
835           dberr= fDBApi_Data.DeleteBlob( aItem.getLocalID(),blobfieldname.c_str() );
836           if (dberr==LOCERR_NOTIMP) dberr= LOCERR_OK; // Not implemented is not an error
837           if (dberr!=LOCERR_OK) SYSYNC_THROW(TSyncException("DBapi::DeleteBlob fatal error"));
838         }
839         SYSYNC_CATCH (...)
840           SYSYNC_RETHROW;
841         SYSYNC_ENDCATCH
842
843         // we now have at least one field
844         blobdeleted= true;
845
846         #ifdef ARRAYFIELD_SUPPORT
847         // non-array do not loop
848         if (!basefieldP->isArray()) break;
849         #endif
850       } // for all array elements
851     } // if writable BLOB/parametrized field
852   } // for all field mappings
853   return blobdeleted;
854 } // TPluginApiDS::deleteBlobs
855
856
857
858 /// returns true if DB implementation supports resume (saving of resume marks, alert code, pending maps, tempGUIDs)
859 bool TPluginApiDS::dsResumeSupportedInDB(void)
860 {
861   if (fPluginDSConfigP->fDBApiConfig_Admin.Connected()) {
862     // we can do resume if plugin supports it
863     return fPluginDSConfigP->fResumeSupported && fPluginDSConfigP->fDBApiConfig_Admin.Version()>=sInt32(VE_InsertMapItem);
864   }
865   return inherited::dsResumeSupportedInDB();
866 } // TPluginApiDS::dsResumeSupportedInDB
867
868
869 #ifdef OBJECT_FILTERING
870
871 // - returns true if DB implementation can also apply special filters like CGI-options
872 //   /dr(x,y) etc. during fetching
873 bool TPluginApiDS::dsOptionFilterFetchesFromDB(void)
874 {
875   #ifndef SDK_ONLY_SUPPORT
876   // if we are not connected, let immediate ancestor check it (e.g. SQL/ODBC)
877   if (!fDBApi_Data.Created()) return inherited::dsOptionFilterFetchesFromDB();
878   #endif
879   #ifdef SYSYNC_TARGET_OPTIONS
880   string rangeFilter,s;
881   // check range filtering
882   // - date start end
883   rangeFilter = "daterangestart:";
884   TimestampToISO8601Str(s, fDateRangeStart, TCTX_UTC, false, false);
885   rangeFilter += s.c_str();
886   // - date range end
887   rangeFilter += "\r\ndaterangeend:";
888   TimestampToISO8601Str(s, fDateRangeEnd, TCTX_UTC, false, false);
889   rangeFilter += s.c_str();
890   // %%% tbd:
891   // - attachments inhibit
892   // - size limit
893   #if (!defined _MSC_VER || defined WINCE) && !defined(__GNUC__)
894   #warning "attachments and limit filters not yet supported"
895   #endif
896   // - let plugin know and check (we can filter at DBlevel if plugin understands both start/end)
897   rangeFilter += "\r\n";
898   bool canfilter =
899     (fDBApi_Data.FilterSupport(rangeFilter.c_str())>=2) ||
900     (fDateRangeStart==0 && fDateRangeEnd==0); // no range can always be "filtered"
901   // if we can filter, that's sufficient
902   if (canfilter) return true;
903   #else
904   // there is no range ever: yes, we can filter
905   return true;
906   #endif
907   // otherwise, let implementation test (not immediate anchestor, which is a different API like ODBC)
908   return TCustomImplDS::dsOptionFilterFetchesFromDB();
909 } // TPluginApiDS::dsOptionFilterFetchesFromDB
910
911
912 // - returns true if DB implementation can filter the standard filters
913 //   (LocalDBFilter, TargetFilter and InvisibleFilter) during database fetch
914 //   - otherwise, fetched items will be filtered after being read from DB.
915 bool TPluginApiDS::dsFilteredFetchesFromDB(bool aFilterChanged)
916 {
917   #ifndef SDK_ONLY_SUPPORT
918   // if we are not connected, let immediate ancestor check it (e.g. SQL/ODBC)
919   // Note that this can happen when dsFilteredFetchesFromDB() is called via Alertscript to resolve filter dependencies
920   //   before the DS is actually connected. In this case, the return value is not checked so that's ok.
921   //   Before actually laoding or zapping the sync set this is called once again with connected data plugin.
922   if (!fDBApi_Data.Created()) return inherited::dsFilteredFetchesFromDB(aFilterChanged);
923   #endif
924   if (aFilterChanged || !fAPIFiltersTested) {
925     fAPIFiltersTested = true;
926     // Anyway, let DBApi know (even if all filters are empty)
927     string filters;
928     // - local DB filter (=static filter, from config)
929     filters = "staticfilter:";
930     filters += fLocalDBFilter.c_str();
931     // - dynamic sync set filter
932     filters += "\r\ndynamicfilter:";
933     filters += fSyncSetFilter.c_str();
934     // - invisible filter (those that MATCH this filter should NOT be included)
935     filters += "\r\ninvisiblefilter:";
936     filters += fPluginDSConfigP->fInvisibleFilter.c_str();
937     // - let plugin know and check (we can filter at DBlevel if plugin understands both start/end)
938     filters += "\r\n";
939     fAPICanFilter =
940       (fDBApi_Data.FilterSupport(filters.c_str())>=3) ||
941       (fLocalDBFilter.empty() && fSyncSetFilter.empty() && fPluginDSConfigP->fInvisibleFilter.empty()); // no filter set = we can "filter"
942   }
943   // if we can filter, that's sufficient
944   if (fAPICanFilter) return true;
945   // otherwise, let implementation test (not immediate anchestor, which might be a different API like ODBC)
946   return TCustomImplDS::dsFilteredFetchesFromDB(aFilterChanged);
947 } // TPluginApiDS::dsFilteredFetchesFromDB
948
949 #endif
950
951
952
953 // can return 508 to force a slow sync. Other errors abort the sync
954 localstatus TPluginApiDS::apiEarlyDataAccessStart(void)
955 {
956   TSyError dberr = LOCERR_OK;
957   if (fPluginDSConfigP->fEarlyStartDataRead) {
958     // prepare
959     dberr = apiPrepareReadSyncSet();
960     if (dberr==LOCERR_OK) {
961       // start the reading phase anyway (to make sure call order is always StartRead/EndRead/StartWrite/EndWrite)
962       dberr = fDBApi_Data.StartDataRead(fPreviousToRemoteSyncIdentifier.c_str(),fPreviousSuspendIdentifier.c_str());
963       if (dberr!=LOCERR_OK) {
964         PDEBUGPRINTFX(DBG_ERROR,("apiEarlyDataAccessStart - DBapi::StartDataRead error: %hd",dberr));
965       }
966     }
967   }
968   return dberr;
969 }
970
971
972 // prepare for reading the sync set
973 localstatus TPluginApiDS::apiPrepareReadSyncSet(void)
974 {
975   TSyError dberr = LOCERR_OK;
976   #ifdef BASED_ON_BINFILE_CLIENT
977   if (binfileDSActive()) {
978     // we need to create the context for the data plugin here, as loadAdminData is not called in BASED_ON_BINFILE_CLIENT case.
979     dberr = connectDataPlugin();
980     if (dberr==LOCERR_OK) {
981       if (!fDBApi_Data.Created()) {
982         // - use datastore name as context name and link with session context
983         dberr = fDBApi_Data.CreateContext(
984           getName(), false,
985           &(fPluginDSConfigP->fDBApiConfig_Data),
986           "anydevice", // no real device key
987           "singleuser", // no real user key
988           NULL // no associated session level // fPluginAgentP->getDBApiSession()
989         );
990         if (dberr==LOCERR_OK) {
991           // make sure plugin now sees filters before starting to read sync set
992           // Note: due to late instantiation of the data plugin, previous calls to engFilteredFetchesFromDB() were not
993           //   evaluated by the plugin, so we need to do that here explicitly once again
994           engFilteredFetchesFromDB(false);
995         }
996       }
997     }
998     else if (dberr==LOCERR_NOTIMP)
999       dberr=LOCERR_OK; // we just don't have a data plugin, that's ok
1000   } // binfile active
1001   #endif // BASED_ON_BINFILE_CLIENT
1002   return dberr;
1003 }
1004
1005
1006
1007
1008 // read sync set IDs and mod dates (and rest of data if technically unavoidable or
1009 // requested by aNeedAll)
1010 localstatus TPluginApiDS::apiReadSyncSet(bool aNeedAll)
1011 {
1012   TSyError dberr=LOCERR_OK;
1013   #ifdef SYDEBUG
1014   string ts1,ts2;
1015   #endif
1016
1017   if (!fPluginDSConfigP->fEarlyStartDataRead) {
1018     // normal sequence, start data read is not called before starting to read the sync set
1019     dberr = apiPrepareReadSyncSet();
1020     if (dberr!=LOCERR_OK)
1021       goto endread;
1022   }
1023   #ifndef SDK_ONLY_SUPPORT
1024   // only handle here if we are in charge - otherwise let ancestor handle it
1025   if (!fDBApi_Data.Created())
1026     return inherited::apiReadSyncSet(aNeedAll);
1027   #endif
1028
1029   // just let plugin know if we want data (if it actually does is the plugin's choice)
1030   if (aNeedAll) {
1031     // we'll need all data in the datastore in the end, let datastore know
1032     // Note: this is a suggestion to the plugin only - plugin does not need to follow it
1033     //       and can return only ID/changed or all data for both states of this flag,
1034     //       even changing on a item-by-item basis (can make sense for optimization).
1035     //       The Plugin will return "1" here only in case it really follows the suggestion
1036     //       an WILL return all data at ReadNextItem(). Otherwise, it may or may
1037     //       not return data on a item by item basis (which is handled by the code
1038     //       below). Therefore, at this time, the engine does not make use of the
1039     //       ContextSupport() return value here.
1040     fDBApi_Data.ContextSupport("ReadNextItem:allfields\n\r");
1041   }
1042   #ifdef SCRIPT_SUPPORT
1043   // process init script
1044   fParentKey.erase();
1045   fWriting=false;
1046   fPluginAgentP->fScriptContextDatastore=this;
1047   if (!TScriptContext::executeTest(true,fScriptContextP,fPluginDSConfigP->fFieldMappings.fInitScript,fPluginDSConfigP->getDSFuncTableP(),fPluginAgentP)) {
1048     PDEBUGPRINTFX(DBG_ERROR,("<initscript> failed"));
1049     goto endread;
1050   }
1051   #endif
1052   // start reading
1053   // - read list of all local IDs that are in the current sync set
1054   DeleteSyncSet();
1055   #ifdef SYDEBUG
1056   StringObjTimestamp(ts1,getPreviousToRemoteSyncCmpRef());
1057   StringObjTimestamp(ts2,getPreviousSuspendCmpRef());
1058   PDEBUGPRINTFX(DBG_DATA,(
1059     "Now reading local sync set: report changes since reference1 at %s, and since reference2 at %s",
1060     ts1.c_str(),
1061     ts2.c_str()
1062   ));
1063   #endif
1064   if (!fPluginDSConfigP->fEarlyStartDataRead) {
1065     // start the reading phase anyway (to make sure call order is always StartRead/EndRead/StartWrite/EndWrite)
1066     dberr = fDBApi_Data.StartDataRead(fPreviousToRemoteSyncIdentifier.c_str(),fPreviousSuspendIdentifier.c_str());
1067     if (dberr!=LOCERR_OK) {
1068       PDEBUGPRINTFX(DBG_ERROR,("DBapi::StartDataRead fatal error: %hd",dberr));
1069       goto endread;
1070     }
1071   }
1072   // we don't need to load the syncset if we are only refreshing from remote
1073   // but we also must load it if we can't zap without it on slow refresh, or when we can't retrieve items on non-slow refresh
1074   // (we won't retrieve anything in case of slow refresh, because after zapping there's nothing left by definition)
1075   if (!fRefreshOnly || (fRefreshOnly && fCacheData) || (fSlowSync && apiNeedSyncSetToZap()) || (!fSlowSync && implNeedSyncSetToRetrieve())) {
1076     SYSYNC_TRY {
1077       // true for initial ReadNextItem*() call, false later on
1078       bool firstReadNextItem=true;
1079
1080       // read the items
1081       #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1082       TMultiFieldItem *mfitemP = NULL;
1083       #endif
1084       #ifdef DBAPI_TEXTITEMS
1085       TDB_Api_Str itemData;
1086       #endif
1087       do {
1088         // read next item
1089         int itemstatus;
1090         TSyncSetItem *syncSetItemP=NULL;
1091         TDB_Api_ItemID itemAndParentID;
1092         // two API variants
1093         #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1094         if (fPluginDSConfigP->fItemAsKey) {
1095           // ALWAYS prepare a multifield item, in case plugin wants to return data (it normally does not
1096           // unless queried with ContextSupport("ReadNextItem:allfields"), but it's a per-item decision
1097           // of the plugin itself (and probably overall optimization considerations for speed or memory)
1098           // if it wants to follow the recommendation set with "ReadNextItem:allfields".
1099           // Note: check for canCreateItemForRemote() should be always true now as loading syncset has been
1100           //       moved within the progress of client sync session to a point where types ARE known.
1101           //       Pre-3.2 engines however called this routine early so types could be unknown here.
1102           if (mfitemP==NULL && canCreateItemForRemote()) {
1103             mfitemP =
1104               (TMultiFieldItem *) newItemForRemote(
1105                 ity_multifield
1106               );
1107           }
1108           // as key (Note: will be functional key but w/o any fields in case we pass NULL item pointer)
1109           TDBItemKey *itemKeyP = newDBItemKey(mfitemP);
1110           dberr=fDBApi_Data.ReadNextItemAsKey(itemAndParentID, (KeyH)itemKeyP, itemstatus, firstReadNextItem);
1111           // check if plugin wrote something to our key. If so, we assume this is the item and save
1112           // it, EVEN IF we did not request getting item data.
1113           if (!itemKeyP->isWritten()) {
1114             // nothing in this item, forget it
1115             delete itemKeyP; // key first
1116             if (mfitemP) delete mfitemP; // then item if we had one at all
1117             mfitemP=NULL;
1118           }
1119           else {
1120             // got item, delete the key
1121             delete itemKeyP;
1122             // post-process (run scripts, create BLOB proxies if needed)
1123             postReadProcessItem(*mfitemP,0);
1124           }
1125         }
1126         else
1127         #endif
1128         #ifdef DBAPI_TEXTITEMS
1129         {
1130           // as text item
1131           dberr=fDBApi_Data.ReadNextItem(itemAndParentID, itemData, itemstatus, firstReadNextItem);
1132         }
1133         #else
1134         return LOCERR_WRONGUSAGE; // completely wrong usage - should never happen as compatibility is tested at module connect
1135         #endif
1136         firstReadNextItem=false;
1137         if (dberr!=LOCERR_OK) {
1138           PDEBUGPRINTFX(DBG_ERROR,("DBapi::ReadNextItem fatal error = %hd",dberr));
1139           #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1140           if (mfitemP) delete mfitemP;
1141           #endif
1142           goto endread;
1143         }
1144         // check if we have seen all items
1145         if (itemstatus==ReadNextItem_EOF)
1146           break;
1147         // we have received an item
1148         // - save returned data as item in the syncsetlist
1149         syncSetItemP = new TSyncSetItem;
1150         // - copy item object ID
1151         syncSetItemP->localid = itemAndParentID.item.c_str();
1152         // - copy parent ID
1153         // %%% tbd, now empty
1154         syncSetItemP->containerid = "";
1155         // - set modified status
1156         syncSetItemP->isModifiedAfterSuspend = itemstatus==ReadNextItem_Resumed;
1157         syncSetItemP->isModified = syncSetItemP->isModifiedAfterSuspend || itemstatus==ReadNextItem_Changed;
1158         #ifdef SYDEBUG
1159         PDEBUGPRINTFX(DBG_DATA+DBG_EXOTIC,(
1160           "read local item info in sync set: localid='%s'%s%s",
1161           syncSetItemP->localid.c_str(),
1162           syncSetItemP->isModified ? ", MODIFIED since reference1" : "",
1163           syncSetItemP->isModifiedAfterSuspend ? " AND since reference2" : ""
1164         ));
1165         #endif
1166         // no data yet, no item yet
1167         syncSetItemP->itemP = NULL;
1168         // two API variants
1169         #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1170         if (fPluginDSConfigP->fItemAsKey) {
1171           // as key
1172           if (mfitemP) {
1173             // we have read some data
1174             syncSetItemP->itemP = mfitemP;
1175             mfitemP = NULL; // now owned by syncSetItem
1176             syncSetItemP->itemP->setLocalID(itemAndParentID.item.c_str());
1177           }
1178         }
1179         else
1180         #endif
1181         #ifdef DBAPI_TEXTITEMS
1182         {
1183           // as text item
1184           // - if we have received actual item data already, create and store an item here
1185           if (!itemData.empty()) {
1186             // store data in new item now
1187             // - create new empty TMultiFieldItem
1188             syncSetItemP->itemP =
1189               (TMultiFieldItem *) newItemForRemote(
1190                 ity_multifield
1191               );
1192             // - set localid as we might need it for reading specials or arrays
1193             syncSetItemP->itemP->setLocalID(itemAndParentID.item.c_str());
1194             // - read data into item
1195             parseItemData(*(syncSetItemP->itemP),itemData.c_str(),0);
1196           }
1197         }
1198         #else
1199         return LOCERR_WRONGUSAGE; // completely wrong usage - should never happen as compatibility is tested at module connect
1200         #endif
1201         // now save syncset item
1202         fSyncSetList.push_back(syncSetItemP);
1203       } while (true);
1204     } // try
1205     SYSYNC_CATCH (...)
1206       dberr=LOCERR_EXCEPTION;
1207     SYSYNC_ENDCATCH
1208   } else {
1209     PDEBUGPRINTFX(DBG_DATA+DBG_EXOTIC,("skipped reading sync set because of refresh-from-peer sync"));
1210   } // if we need the syncset at all
1211 endread:
1212   // then end read here
1213   if (dberr==LOCERR_OK) {
1214     dberr=fDBApi_Data.EndDataRead();
1215     if (dberr!=LOCERR_OK) {
1216       PDEBUGPRINTFX(DBG_ERROR,("DBapi::EndDataRead failed, err=%hd",dberr));
1217     }
1218   }
1219   return dberr;
1220 } // TPluginApiDS::apiReadSyncSet
1221
1222
1223
1224 // Check if we need the syncset to zap
1225 bool TPluginApiDS::apiNeedSyncSetToZap(void)
1226 {
1227   #ifndef SDK_ONLY_SUPPORT
1228   // only handle here if we are in charge - otherwise let ancestor handle it
1229   if (!fDBApi_Data.Created()) return inherited::apiNeedSyncSetToZap();
1230   #endif
1231   // only if we have deleteSyncSet on API level AND api can also apply all filters, we don't need the syncset to zap the datastore
1232   return !(fPluginDSConfigP->fHasDeleteSyncSet && engFilteredFetchesFromDB(false));
1233 } // TPluginApiDS::apiNeedSyncSetToZap
1234
1235
1236 // Zap all data in syncset (note that everything outside the sync set will remain intact)
1237 localstatus TPluginApiDS::apiZapSyncSet(void)
1238 {
1239   #ifndef SDK_ONLY_SUPPORT
1240   // only handle here if we are in charge - otherwise let ancestor handle it
1241   if (!fDBApi_Data.Created()) return inherited::apiZapSyncSet();
1242   #endif
1243   TSyError dberr = LOCERR_OK;
1244   // API must be able to process current filters in order to execute a zap - otherwise we would delete
1245   // more than the sync set defined by local filters.
1246   bool apiCanZap = engFilteredFetchesFromDB(false);
1247   if (apiCanZap) {
1248     // try to use plugin's specialized implementation
1249     dberr = fDBApi_Data.DeleteSyncSet();
1250     apiCanZap = dberr!=LOCERR_NOTIMP; // API claims to be able to zap (but still might have failed with a DBerr in this case!)
1251   }
1252   // do it one by one if DeleteAllItems() is not implemented or plugin cannot apply current filters
1253   if (!apiCanZap) {
1254     dberr = zapSyncSetOneByOne();
1255   }
1256   // return status
1257   return dberr;
1258 } // TPluginApiDS::apiZapSyncSet
1259
1260
1261 // fetch actual record from DB by localID. SyncSetItem might be passed to give additional information
1262 // such as containerid
1263 localstatus TPluginApiDS::apiFetchItem(TMultiFieldItem &aItem, bool aReadPhase, TSyncSetItem *aSyncSetItemP)
1264 {
1265   #ifndef SDK_ONLY_SUPPORT
1266   // only handle here if we are in charge - otherwise let ancestor handle it
1267   if (!fDBApi_Data.Created()) return inherited::apiFetchItem(aItem, aReadPhase, aSyncSetItemP);
1268   #endif
1269
1270   TSyError dberr=LOCERR_OK;
1271   ItemID_Struct itemAndParentID;
1272
1273   // set up item ID and parent ID
1274   itemAndParentID.item=(appCharP)aItem.getLocalID();
1275   itemAndParentID.parent=const_cast<char *>("");
1276
1277   // two API variants
1278   #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1279   if (fPluginDSConfigP->fItemAsKey) {
1280     // get key
1281     TDBItemKey *itemKeyP = newDBItemKey(&aItem);
1282     // let plugin use it to fill item
1283     dberr=fDBApi_Data.ReadItemAsKey(itemAndParentID,(KeyH)itemKeyP);
1284     if (itemKeyP->isWritten()) {
1285       // post-process (run scripts, create BLOB proxies if needed)
1286       postReadProcessItem(aItem,0);
1287     }
1288     // done with the key
1289     delete itemKeyP;
1290   }
1291   else
1292   #endif
1293   #ifdef DBAPI_TEXTITEMS
1294   {
1295     TDB_Api_Str itemData;
1296     // read the item in text form from the DB
1297     dberr=fDBApi_Data.ReadItem(itemAndParentID,itemData);
1298     if (dberr==LOCERR_OK) {
1299       // put it into aItem
1300       parseItemData(aItem,itemData.c_str(),0);
1301     }
1302   }
1303   #else
1304   return LOCERR_WRONGUSAGE; // completely wrong usage - should never happen as compatibility is tested at module connect
1305   #endif
1306   // return status
1307   return dberr;
1308 } // TPluginApiDS::apiFetchItem
1309
1310
1311
1312 // start of write
1313 localstatus TPluginApiDS::apiStartDataWrite(void)
1314 {
1315   #ifndef SDK_ONLY_SUPPORT
1316   // only handle here if we are in charge - otherwise let ancestor handle it
1317   if (!fDBApi_Data.Created()) return inherited::apiStartDataWrite();
1318   #endif
1319
1320   TSyError dberr=fDBApi_Data.StartDataWrite();
1321   if (dberr!=LOCERR_OK) {
1322     PDEBUGPRINTFX(DBG_ERROR,("DBapi::StartDataWrite returns dberr=%hd",dberr));
1323   }
1324   return dberr;
1325 } // TPluginApiDS::apiStartDataWrite
1326
1327 struct TPluginItemAux : public TSyncItemAux
1328 {
1329 #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1330   TDBItemKey *fItemKeyP;
1331 #endif
1332 #ifdef DBAPI_TEXTITEMS
1333   string fItemData;
1334 #endif
1335 };
1336
1337 // add new item to datastore, returns created localID
1338 localstatus TPluginApiDS::apiAddItem(TMultiFieldItem &aItem, string &aLocalID)
1339 {
1340   #ifndef SDK_ONLY_SUPPORT
1341   // only handle here if we are in charge - otherwise let ancestor handle it
1342   if (!fDBApi_Data.Created()) return inherited::apiAddItem(aItem, aLocalID);
1343   #endif
1344
1345   TSyError dberr=LOCERR_OK;
1346   TDB_Api_ItemID itemAndParentID;
1347
1348   #ifdef SCRIPT_SUPPORT
1349   fInserting=true; // flag for script, we are inserting new record
1350   #endif
1351
1352   TPluginItemAux *aux = static_cast<TPluginItemAux *>(aItem.getAux(TSyncItem::PLUGIN_API));
1353   if (aux) {
1354     // Continue operation.
1355     #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1356     if (fPluginDSConfigP->fItemAsKey) {
1357       dberr=fDBApi_Data.InsertItemAsKey((KeyH)aux->fItemKeyP,"",itemAndParentID);
1358       if (dberr == LOCERR_AGAIN)
1359         return dberr;
1360       // done with the key
1361       delete aux->fItemKeyP;
1362       aux->fItemKeyP=NULL;
1363     }
1364     else
1365     #endif
1366     #ifdef DBAPI_TEXTITEMS
1367     {
1368       dberr=fDBApi_Data.InsertItem(aux->fItemData.c_str(),"",itemAndParentID);
1369       if (dberr == LOCERR_AGAIN)
1370         return dberr;
1371     }
1372     #else
1373     return LOCERR_WRONGUSAGE;
1374     #endif
1375   } else {
1376     // Two API variants for starting the operation.
1377     #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1378     if (fPluginDSConfigP->fItemAsKey) {
1379       // preprocess
1380       if (!preWriteProcessItem(aItem)) return 510; // DB error
1381       // get key
1382       TDBItemKey *itemKeyP = newDBItemKey(&aItem);
1383       // let plugin use it to obtain data to write
1384       dberr=fDBApi_Data.InsertItemAsKey((KeyH)itemKeyP,"",itemAndParentID);
1385       if (dberr == LOCERR_AGAIN) {
1386         TPluginItemAux *aux=new TPluginItemAux;
1387         aux->fItemKeyP=itemKeyP;
1388         aItem.setAux(TSyncItem::PLUGIN_API, aux);
1389         return LOCERR_AGAIN;
1390       }
1391       // done with the key
1392       delete itemKeyP;
1393     }
1394     else
1395     #endif
1396     #ifdef DBAPI_TEXTITEMS
1397     {
1398       string itemData;
1399       generateDBItemData(
1400         false, // all fields, not only assigned ones
1401         aItem,
1402         0, // we do not use different sets for now
1403         itemData // here we'll get the data
1404       );
1405       // now insert main record
1406       dberr=fDBApi_Data.InsertItem(itemData.c_str(),"",itemAndParentID);
1407       if (dberr == LOCERR_AGAIN) {
1408         TPluginItemAux *aux=new TPluginItemAux;
1409         aux->fItemData=itemData;
1410         aItem.setAux(TSyncItem::PLUGIN_API, aux);
1411         return LOCERR_AGAIN;
1412       }
1413     }
1414     #else
1415     return LOCERR_WRONGUSAGE; // completely wrong usage - should never happen as compatibility is tested at module connect
1416     #endif
1417   }
1418
1419   // now check result
1420   if (dberr==LOCERR_OK ||
1421       dberr==DB_Conflict ||
1422       dberr==DB_DataReplaced ||
1423       dberr==DB_DataMerged) {
1424     // save new ID
1425     aLocalID = itemAndParentID.item.c_str();
1426     aItem.setLocalID(aLocalID.c_str()); // make sure item itself has correct ID as well
1427     if (dberr!=DB_Conflict) {
1428       // now write all the BLOBs
1429       writeBlobs(false,aItem,0);
1430       #ifdef SCRIPT_SUPPORT
1431       // process overall afterwrite script
1432       fWriting=true;
1433       fInserting=true;
1434       fDeleting=false;
1435       fPluginAgentP->fScriptContextDatastore=this;
1436       if (!TScriptContext::execute(fScriptContextP,fPluginDSConfigP->fFieldMappings.fAfterWriteScript,fPluginDSConfigP->getDSFuncTableP(),fPluginAgentP,&aItem,true)) {
1437         PDEBUGPRINTFX(DBG_ERROR,("<afterwritescript> failed"));
1438         dberr = LOCERR_WRONGUSAGE;
1439       }
1440       #endif
1441     }
1442   }
1443   // return status
1444   return dberr;
1445 } // TPluginApiDS::apiAddItem
1446
1447
1448 #ifdef SYSYNC_CLIENT
1449
1450 /// finalize local ID (for datastores that can't efficiently produce these at insert)
1451 bool TPluginApiDS::dsFinalizeLocalID(string &aLocalID)
1452 {
1453   #ifndef SDK_ONLY_SUPPORT
1454   // only handle here if we are in charge - otherwise let ancestor handle it
1455   if (!fDBApi_Data.Created()) return inherited::dsFinalizeLocalID(aLocalID);
1456   #else
1457   // still check for DBAPi to be ready at this point, because when peer messes up protocol, we
1458   // can get here before the datastore has been initialized at all
1459   if (!fDBApi_Data.Created()) return false; // no dataset loaded -> all localids are final (from last session)
1460   #endif
1461   
1462   TDB_Api_Str finalizedID;
1463   localstatus sta = fDBApi_Data.FinalizeLocalID(aLocalID.c_str(),finalizedID);
1464   if (sta==LOCERR_OK && !finalizedID.empty()) {
1465     // pass modified ID back
1466     aLocalID = finalizedID.c_str();
1467     // ID was updated
1468     return true;
1469   }
1470   // no change - ID is ok as-is
1471   return false;
1472 } // TPluginApiDS::dsFinalizeLocalID
1473
1474 #endif // SYSYNC_CLIENT
1475
1476
1477
1478 // update existing item in datastore, returns 404 if item not found
1479 localstatus TPluginApiDS::apiUpdateItem(TMultiFieldItem &aItem)
1480 {
1481   #ifndef SDK_ONLY_SUPPORT
1482   // only handle here if we are in charge - otherwise let ancestor handle it
1483   if (!fDBApi_Data.Created()) return inherited::apiUpdateItem(aItem);
1484   #endif
1485
1486   TSyError dberr=LOCERR_OK;
1487   TDB_Api_ItemID updItemAndParentID;
1488   ItemID_Struct itemAndParentID;
1489
1490   // set up item ID and parent ID
1491   itemAndParentID.item=(appCharP)aItem.getLocalID();
1492   itemAndParentID.parent=const_cast<char *>("");
1493
1494   #ifdef SCRIPT_SUPPORT
1495   fInserting=false; // flag for script, we are updating, not inserting now
1496   #endif
1497
1498   TPluginItemAux *aux = static_cast<TPluginItemAux *>(aItem.getAux(TSyncItem::PLUGIN_API));
1499   if (aux) {
1500     // Continue operation.
1501     #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1502     if (fPluginDSConfigP->fItemAsKey) {
1503       dberr=fDBApi_Data.UpdateItemAsKey((KeyH)aux->fItemKeyP,itemAndParentID,updItemAndParentID);
1504       if (dberr == LOCERR_AGAIN)
1505         return dberr;
1506       // done with the key
1507       delete aux->fItemKeyP;
1508       aux->fItemKeyP=NULL;
1509     }
1510     else
1511     #endif
1512     #ifdef DBAPI_TEXTITEMS
1513     {
1514       dberr=fDBApi_Data.UpdateItem(aux->fItemData.c_str(),itemAndParentID,updItemAndParentID);
1515       if (dberr == LOCERR_AGAIN)
1516         return dberr;
1517     }
1518     #else
1519     return LOCERR_WRONGUSAGE; // completely wrong usage - should never happen as compatibility is tested at module connect
1520     #endif
1521   } else {
1522     // Two API variants for starting the operation.
1523     #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1524     if (fPluginDSConfigP->fItemAsKey) {
1525       // preprocess
1526       if (!preWriteProcessItem(aItem)) return 510; // DB error
1527       // get key
1528       TDBItemKey *itemKeyP = newDBItemKey(&aItem);
1529       // let plugin use it to obtain data to write
1530       dberr=fDBApi_Data.UpdateItemAsKey((KeyH)itemKeyP,itemAndParentID,updItemAndParentID);
1531       if (dberr == LOCERR_AGAIN) {
1532         TPluginItemAux *aux=new TPluginItemAux;
1533         aux->fItemKeyP=itemKeyP;
1534         aItem.setAux(TSyncItem::PLUGIN_API, aux);
1535         return LOCERR_AGAIN;
1536       }
1537       // done with the key
1538       delete itemKeyP;
1539     }
1540     else
1541     #endif
1542     #ifdef DBAPI_TEXTITEMS
1543     {
1544       string itemData;
1545       generateDBItemData(
1546         true, // only assigned fields
1547         aItem,
1548         0, // we do not use different sets for now
1549         itemData // here we'll get the data
1550       );
1551       // now update main record
1552       dberr=fDBApi_Data.UpdateItem(itemData.c_str(),itemAndParentID,updItemAndParentID);
1553       if (dberr == LOCERR_AGAIN) {
1554         TPluginItemAux *aux=new TPluginItemAux;
1555         aux->fItemData=itemData;
1556         aItem.setAux(TSyncItem::PLUGIN_API, aux);
1557         return LOCERR_AGAIN;
1558       }
1559     }
1560     #else
1561     return LOCERR_WRONGUSAGE; // completely wrong usage - should never happen as compatibility is tested at module connect
1562     #endif
1563   }
1564   if (dberr==LOCERR_OK) {
1565     // check if ID has changed
1566     if (!updItemAndParentID.item.empty() && strcmp(updItemAndParentID.item.c_str(),aItem.getLocalID())!=0) {
1567       if (IS_SERVER) {
1568         // update item ID and Map
1569         dsLocalIdHasChanged(aItem.getLocalID(),updItemAndParentID.item.c_str());
1570       }
1571       // - update in this item we have here as well
1572       aItem.setLocalID(updItemAndParentID.item.c_str());
1573       aItem.updateLocalIDDependencies();
1574     }
1575     // now write all the BLOBs
1576     writeBlobs(true,aItem,0);
1577     #ifdef SCRIPT_SUPPORT
1578     // process overall afterwrite script
1579     fWriting=true;
1580     fInserting=false;
1581     fDeleting=false;
1582     fPluginAgentP->fScriptContextDatastore=this;
1583     if (!TScriptContext::execute(fScriptContextP,fPluginDSConfigP->fFieldMappings.fAfterWriteScript,fPluginDSConfigP->getDSFuncTableP(),fPluginAgentP,&aItem,true)) {
1584       PDEBUGPRINTFX(DBG_ERROR,("<afterwritescript> failed"));
1585       dberr = LOCERR_WRONGUSAGE;
1586     }
1587     #endif
1588   }
1589   // return status
1590   return dberr;
1591 } // TPluginApiDS::apiUpdateItem
1592
1593
1594 // delete existing item in datastore, returns 211 if not existing any more
1595 localstatus TPluginApiDS::apiDeleteItem(TMultiFieldItem &aItem)
1596 {
1597   #ifndef SDK_ONLY_SUPPORT
1598   // only handle here if we are in charge - otherwise let ancestor handle it
1599   if (!fDBApi_Data.Created()) return inherited::apiDeleteItem(aItem);
1600   #endif
1601
1602   TSyError dberr=LOCERR_OK;
1603
1604   // delete item
1605   dberr=fDBApi_Data.DeleteItem( aItem.getLocalID() );
1606   if (dberr==LOCERR_OK) {
1607     deleteBlobs(true,aItem,0); // Item related blobs must be removed as well
1608     #ifdef SCRIPT_SUPPORT
1609     // process overall afterwrite script
1610     fWriting=true;
1611     fInserting=false;
1612     fDeleting=true;
1613     fPluginAgentP->fScriptContextDatastore=this;
1614     if (!TScriptContext::execute(fScriptContextP,fPluginDSConfigP->fFieldMappings.fAfterWriteScript,fPluginDSConfigP->getDSFuncTableP(),fPluginAgentP,&aItem,true)) {
1615       PDEBUGPRINTFX(DBG_ERROR,("<afterwritescript> failed"));
1616       dberr = LOCERR_WRONGUSAGE;
1617     }
1618     #endif
1619   } // if
1620
1621   // return status
1622   return dberr;
1623 } // TPluginApiDS::apiDeleteItem
1624
1625
1626
1627
1628 // - end DB data write sequence (but not yet admin data), returns DB-specific identifier for this sync (if any)
1629 localstatus TPluginApiDS::apiEndDataWrite(string &aThisSyncIdentifier)
1630 {
1631   #ifndef SDK_ONLY_SUPPORT
1632   // only handle here if we are in charge - otherwise let ancestor handle it
1633   if (!fDBApi_Data.Created()) return inherited::apiEndDataWrite(aThisSyncIdentifier);
1634   #endif
1635
1636   // nothing special to do in ODBC case, as we do not have a separate sync identifier
1637   TDB_Api_Str newSyncIdentifier;
1638   TSyError sta = fDBApi_Data.EndDataWrite(true, newSyncIdentifier);
1639   aThisSyncIdentifier=newSyncIdentifier.c_str();
1640   return sta;
1641 } // TPluginApiDS::apiEndDataWrite
1642
1643
1644
1645 // must be called before starting a thread. If returns false, starting a thread now
1646 // is not allowed and must be postponed.
1647 // Includes ThreadMayChange() call
1648 bool TPluginApiDS::startingThread(void)
1649 {
1650   // %%% tbd: if modules are completely independent, we might not need this in all cases
1651   if (!dbAccessLocked()) {
1652     static_cast<TPluginApiAgent *>(fSessionP)->fApiLocked=true;
1653     // Now post possible thread change to the API
1654     // - on the database level
1655     ThreadMayChangeNow();
1656     // - on the session level as well (to make sure, and because we will post a change back
1657     //   at the end of the thread, which will be called FROM the new thread, which constitutes
1658     //   executing something in the session context.
1659     static_cast<TPluginApiAgent *>(fSessionP)->getDBApiSession()->ThreadMayChangeNow();
1660     return true;
1661   }
1662   else
1663     return false;
1664 } // TPluginApiDS::startingThread
1665
1666
1667 // - must be called when a thread's activity has ended
1668 //   BUT THE CALL MUST BE FROM THE ENDING THREAD, not the main thread!
1669 void TPluginApiDS::endingThread(void) {
1670   // thread may change for the API now again
1671   // - on the database level
1672   ThreadMayChangeNow();
1673   // - on the session level as well
1674   static_cast<TPluginApiAgent *>(fSessionP)->getDBApiSession()->ThreadMayChangeNow();
1675   // Now other threads are allowed to access the API again
1676   static_cast<TPluginApiAgent *>(fSessionP)->fApiLocked=false;
1677 } // TPluginApiDS::endingThread
1678
1679
1680 // should be called before doing DB accesses that might be locked (e.g. because another thread is using the DB resources)
1681 bool TPluginApiDS::dbAccessLocked(void)
1682 {
1683   return static_cast<TPluginApiAgent *>(fSessionP)->fApiLocked;
1684 } // TPluginApiDS::dbAccessLocked
1685
1686
1687 // - alert possible thread change to plugins
1688 //   Does not check if API is locked or not, see dsThreadMayChangeNow()
1689 void TPluginApiDS::ThreadMayChangeNow(void)
1690 {
1691   // let API know, thread might change for next request (but not necessarily does!)
1692   if (fDBApi_Data.Created()) fDBApi_Data.ThreadMayChangeNow();
1693   if (fDBApi_Admin.Created()) fDBApi_Admin.ThreadMayChangeNow();
1694 } // TPluginApiDS::ThreadMayChangeNow
1695
1696
1697 // - engine Thread might change
1698 void TPluginApiDS::dsThreadMayChangeNow(void)
1699 {
1700   // Do not post thread change infos when DB access is locked.
1701   // If it is locked, the thread that locked it will call a
1702   // ThreadMayChangeNow() to the API when the thread terminates (in endingThread()).
1703   if (!dbAccessLocked()) {
1704     ThreadMayChangeNow();
1705   }
1706   // let ancestor do it's own stuff
1707   inherited::dsThreadMayChangeNow();
1708 } // TPluginApiDS::dsThreadMayChangeNow
1709
1710
1711
1712 // - connect data handling part of plugin, Returns LOCERR_NOTIMPL when no data plugin is selected
1713 //   Note: this is either called as part of apiLoadAdminData (even if plugin is NOT responsible for data!)
1714 //         or directly before startDataRead (in BASED_ON_BINFILE_CLIENT binfileDSActive() case)
1715 TSyError TPluginApiDS::connectDataPlugin(void)
1716 {
1717   TSyError err = LOCERR_NOTIMP;
1718   // filtering capabilities need to be reevaluated anyway
1719   fAPICanFilter = false;
1720   fAPIFiltersTested = false;
1721   // only connect if we have plugin data support
1722   if (fPluginDSConfigP->fDBApiConfig_Data.Connected()) {
1723     err = LOCERR_OK;
1724     DB_Callback cb= &fDBApi_Data.fCB.Callback;
1725     cb->callbackRef       = fSessionP; // the session
1726     #ifdef ENGINEINTERFACE_SUPPORT
1727     cb->thisBase          = fPluginDSConfigP->getSyncAppBase()->fEngineInterfaceP;
1728     #endif
1729     #ifdef SYDEBUG
1730     // Datastore Data access debug goes to session log
1731     cb->debugFlags        = PDEBUGTEST(DBG_DATA+DBG_DBAPI+DBG_PLUGIN) ? 0xFFFF : 0;
1732     cb->DB_DebugPuts      = SessionLogDebugPuts;
1733     cb->DB_DebugBlock     = SessionLogDebugBlock;
1734     cb->DB_DebugEndBlock  = SessionLogDebugEndBlock;
1735     cb->DB_DebugEndThread = SessionLogDebugEndThread;
1736     cb->DB_DebugExotic    = SessionLogDebugExotic;
1737     #endif // SYDEBUG
1738     #ifdef ENGINEINTERFACE_SUPPORT
1739     // Data module can use Get/SetValue for "AsKey" routines and for session script var access
1740     // Note: these are essentially context free and work without a global call-in structure
1741     //       (which is not necessarily there, for example in no-library case)
1742     CB_Connect_KeyAccess(cb); // connect generic key access routines
1743     // Version of OpenSessionKey that implicitly opens a key for the current session (DB plugins
1744     // do not have a session handle, as their use is always implicitly in a session context).
1745     cb->ui.OpenSessionKey = SessionOpenSessionKey;
1746     #endif // ENGINEINTERFACE_SUPPORT
1747   }
1748   return err;
1749 } // connectDataPlugin
1750
1751
1752 #ifndef BINFILE_ALWAYS_ACTIVE
1753
1754 /// @brief save admin data
1755 ///   Must save the following items:
1756 ///   - fRemoteSyncAnchor = anchor string used by remote party for this session
1757 ///   - fThisLocalAnchor  = anchor (beginning of session) timestamp for this sync
1758 ///   - fThisSync         = timestamp for this sync (same as fThisLocalAnchor unless fSyncTimeStampAtEnd config is set)
1759 ///   - fLastToRemoteLocalAnchor = timestamp for anchor (beginning of session) of last session that sent data to remote
1760 ///                         (same as fThisLocalAnchor unless we did a refrehs-from-remote session)
1761 ///   - fLastToRemoteSync = timestamp for last session that sent data to remote
1762 ///                         (same as fThisSync unless we did a refresh-from-remote session)
1763 ///   - fLastToRemoteSyncIdentifier = string identifying last session that sent data to remote (needs only be saved
1764 ///                         if derived datastore cannot work with timestamps and has its own identifier).
1765 ///   - fMapTable         = list<TMapEntry> containing map entries. For each entry the implementation must:
1766 ///                         - if changed==false: the entry hasn't been changed, so no DB operation is required
1767 ///                         - if changed==true and remoteid is not empty:
1768 ///                           - if added==true, add the entry as a new record to the DB
1769 ///                           - if added==false, update the entry in the DB with matching localid
1770 ///                         - if changed==true and remoteid is empty and added==false: delete the entry(s) in the DB with matching localid
1771 ///   For resumable datastores (fConfigP->fResumeSupport==true):
1772 ///   - fMapTable         = In addition to the above, the markforresume flag must be saved in the mapflags
1773 //                          when it is not equal to the savedmark flag - independently of added/deleted/changed.
1774 ///   - fResumeAlertCode  = alert code of current suspend state, 0 if none
1775 ///   - fPreviousSuspendCmpRef = reference time of last suspend (used to detect items modified during a suspend / resume)
1776 ///   - fPreviousSuspendIdentifier = identifier of last suspend (used to detect items modified during a suspend / resume)
1777 ///                         (needs only be saved if derived datastore cannot work with timestamps and has
1778 ///                         its own identifier)
1779 ///
1780 ///   For datastores that can resume in middle of a chunked item (fConfigP->fResumeItemSupport==true):
1781 ///   - fPartialItemState = state of partial item (TPartialItemState enum):
1782 ///                         - if pi_state_none: save params, delete BLOB data (empty data)
1783 ///                         - if pi_state_save_incoming: save params+BLOB, save as in DB such that we will get pi_state_loaded_incoming when loaded again
1784 ///                         - if pi_state_save_outgoing: save params+BLOB, save as in DB such that we will get pi_state_loaded_outgoing when loaded again
1785 ///                         - if pi_state_loaded_incoming: no need to save, as params+BLOB have not changed since last save (but currently saved params+BLOB in DB must be retained)
1786 ///                         - if pi_state_loaded_outgoing: no need to save, as params+BLOB have not changed since last save (but currently saved params+BLOB in DB must be retained)
1787 ///
1788 ///   - fLastItemStatus   = status code (TSyError) of last item
1789 ///   - fLastSourceURI    = item ID (string, if limited in length should be long enough for large IDs, >=64 chars recommended)
1790 ///   - fLastTargetURI    = item ID (string, if limited in length should be long enough for large IDs, >=64 chars recommended)
1791 ///   - fPITotalSize      = uInt32, total item size
1792 ///   - fPIUnconfirmedSize= uInt32, unconfirmed part of item size
1793 ///   - fPIStoredSize     = uInt32, size of BLOB to store, 0=none
1794 ///   - fPIStoredDataP    = void *, BLOB data, NULL if none
1795 ///
1796 /// @param aDataCommitted[in] indicates if data has been committed to the database already or not
1797 /// @param aSessionFinished[in] indicates if this is a final, end-of-session admin save (otherwise, it's only a resume state save)
1798 localstatus TPluginApiDS::apiSaveAdminData(bool aDataCommitted, bool aSessionFinished)
1799 {
1800   // security - don't use API when locked
1801   if (dbAccessLocked()) return 503; // service unavailable
1802
1803   const char* PIStored = "PIStored"; // blob name field
1804
1805   #ifndef SDK_ONLY_SUPPORT
1806   // only handle here if we are in charge - otherwise let ancestor handle it
1807   if (!fDBApi_Admin.Created()) return inherited::apiSaveAdminData(aDataCommitted, aSessionFinished);
1808   #endif
1809
1810   localstatus sta=LOCERR_OK;
1811   TMapContainer::iterator pos;
1812
1813   // save the entire map list differentially
1814   pos=fMapTable.begin();
1815   PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,("apiSaveAdminData: internal map table has %ld entries (normal and others)",(long)fMapTable.size()));
1816   while (pos!=fMapTable.end()) {
1817     DEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,(
1818       "apiSaveAdminData: entryType=%s, localid='%s', remoteID='%s', mapflags=0x%lX, changed=%d, deleted=%d, added=%d, markforresume=%d, savedmark=%d",
1819       MapEntryTypeNames[(*pos).entrytype],
1820       (*pos).localid.c_str(),
1821       (*pos).remoteid.c_str(),
1822       (long)(*pos).mapflags,
1823       (int)(*pos).changed,
1824       (int)(*pos).deleted,
1825       (int)(*pos).added,
1826       (int)(*pos).markforresume,
1827       (int)(*pos).savedmark
1828     ));
1829     // check if item has changed since map table was read, or if its markforresume has changed
1830     // or if this is a successful end of a session, when we can safely assume that any pending maps
1831     // are from adds to the client that have never reached the client (otherwise, we'd have got
1832     // a map for it, even if the add was in a previous session or session attempt)
1833     if (
1834       (*pos).changed || (*pos).added || (*pos).deleted || // update of DB needed
1835       ((*pos).markforresume!=(*pos).savedmark) // mark for resume changed
1836     ) {
1837       // make sure it does not get written again if not really modified again
1838       (*pos).changed=false;
1839       // update new mapflags w/o changing mapflag_useforresume in the actual flags (as we still need it while session goes on)
1840       uInt32 newmapflags = (*pos).mapflags & ~mapflag_useforresume;
1841       if ((*pos).markforresume)
1842         newmapflags |= mapflag_useforresume;
1843       // remember last saved state
1844       (*pos).savedmark=(*pos).markforresume;
1845       // do something!
1846       MapID_Struct mapid;
1847       mapid.ident=(int)(*pos).entrytype;
1848       mapid.localID=(char *)((*pos).localid.c_str());
1849       mapid.remoteID=(char *)((*pos).remoteid.c_str());
1850       mapid.flags=newmapflags;
1851       if ((*pos).deleted) {
1852         if (!(*pos).added) {
1853           // delete this entry (only needed if it was not also added since last save - otherwise, map entry was never saved to the DB yet)
1854           sta=fDBApi_Admin.DeleteMapItem(&mapid);
1855           if (sta!=LOCERR_OK) break;
1856         }
1857         // now remove it from the list, such that we don't try to delete it again
1858         TMapContainer::iterator delpos=pos++; // that's the next to have a look at
1859         fMapTable.erase(delpos); // remove it now
1860         continue; // pos is already updated
1861       } // deleted
1862       else if ((*pos).added) {
1863         // add a new entry
1864         sta=fDBApi_Admin.InsertMapItem(&mapid);
1865         if (sta!=LOCERR_OK) break;
1866         // is now added, don't add again later
1867         (*pos).added=false;
1868       }
1869       else {
1870         // explicitly changed or needs update because of resume mark or pendingmap flag
1871         // change existing entry
1872         sta=fDBApi_Admin.UpdateMapItem(&mapid);
1873         if (sta!=LOCERR_OK) break;
1874       }
1875     } // if something changed
1876     // anyway - reset mark for resume, it must be reconstructed before next save
1877     (*pos).markforresume=false;
1878     // next
1879     pos++;
1880   } // while
1881   if (sta!=LOCERR_OK) return sta;
1882   // collect admin data in a string
1883   string adminData,s;
1884   adminData.erase();
1885   // add remote sync anchor
1886   adminData+="remotesyncanchor:";
1887   StrToCStrAppend(fLastRemoteAnchor.c_str(),adminData,true); // allow 8-bit chars to be represented as-is (no \xXX escape needed)
1888   /* not needed any more
1889   // add local anchor
1890   adminData+="\r\nlastlocalanchor:";
1891   timeStampToISO8601(fThisLocalAnchor,s,true,true);
1892   adminData+=s.c_str();
1893   */
1894   // add last sync time
1895   adminData+="\r\nlastsync:";
1896   TimestampToISO8601Str(s, fPreviousSyncTime, TCTX_UTC, false, false);
1897   adminData+=s.c_str();
1898   /* not needed any more
1899   // add local anchor of last sync with sending data to remote
1900   adminData+="\r\nlasttoremotelocalanchor:";
1901   timeStampToISO8601(fLastToRemoteLocalAnchor,s,true,true);
1902   adminData+=s.c_str();
1903   */
1904   // add last to remote sync time
1905   adminData+="\r\nlasttoremotesync:";
1906   TimestampToISO8601Str(s, fPreviousToRemoteSyncCmpRef, TCTX_UTC, false, false);
1907   adminData+=s.c_str();
1908   // add identifier needed by datastore to identify records changes since last to-remote-sync
1909   if (fPluginDSConfigP->fStoreSyncIdentifiers) {
1910     adminData+="\r\nlasttoremotesyncid:";
1911     StrToCStrAppend(fPreviousToRemoteSyncIdentifier.c_str(),adminData,true); // allow 8-bit chars to be represented as-is (no \xXX escape needed)
1912   }
1913   // add resume alert code
1914   adminData+="\r\nresumealertcode:"; StringObjAppendPrintf(adminData,"%hd",fResumeAlertCode);
1915   // add last suspend time
1916   adminData+="\r\nlastsuspend:";
1917   TimestampToISO8601Str(s, fPreviousSuspendCmpRef, TCTX_UTC, false, false);
1918   adminData+=s.c_str();
1919   // add identifier needed by datastore to identify records changes since last suspend
1920   if (fPluginDSConfigP->fStoreSyncIdentifiers) {
1921     adminData+="\r\nlastsuspendid:";
1922     StrToCStrAppend(fPreviousSuspendIdentifier.c_str(),adminData,true); // allow 8-bit chars to be represented as-is (no \xXX escape needed)
1923   }
1924
1925   /// For datastores that can resume in middle of a chunked item (fConfigP->fResumeItemSupport==true):
1926   void*   blPtr = fPIStoredDataP; // position
1927   memSize blSize= fPIStoredSize;  // actualbytes
1928
1929   if (dsResumeChunkedSupportedInDB()) {
1930     ///   - fPartialItemState = state of partial item (TPartialItemState enum):
1931     ///                         - if pi_state_none: save params, delete BLOB data (empty data)
1932     ///                         - if pi_state_save_incoming: save params+BLOB, save as in DB such that we will get pi_state_loaded_incoming when loaded again
1933     ///                         - if pi_state_save_outgoing: save params+BLOB, save as in DB such that we will get pi_state_loaded_outgoing when loaded again
1934     ///                         - if pi_state_loaded_incoming  or
1935     ///                              pi_state_loaded_outgoing: no need to save, as  params+BLOB have not changed since last save
1936     ///                                                        (but currently saved params+BLOB in DB must be retained)
1937     if (
1938       fPartialItemState!=pi_state_loaded_incoming &&
1939       fPartialItemState!=pi_state_loaded_outgoing
1940     ) {
1941       // and create the new status for these cases
1942       TPartialItemState pp= fPartialItemState;
1943       if (pp==pi_state_save_incoming) pp= pi_state_loaded_incoming; // adapt them before
1944       if (pp==pi_state_save_outgoing) pp= pi_state_loaded_outgoing;
1945       adminData+="\r\npartialitemstate:"; StringObjAppendPrintf( adminData,"%d",pp );
1946       // - fLastItemStatus   = status code (TSyError) of last item
1947       adminData+="\r\nlastitemstatus:"; StringObjAppendPrintf( adminData,"%hd",fLastItemStatus );
1948       // - fLastSourceURI    = item ID (string, if limited in length should be long enough for large IDs, >=64 chars recommended)
1949       adminData+="\r\nlastsourceURI:";  StrToCStrAppend( fLastSourceURI.c_str(), adminData,true );
1950       // - fLastTargetURI    = item ID (string, if limited in length should be long enough for large IDs, >=64 chars recommended)
1951       adminData+="\r\nlasttargetURI:"; StrToCStrAppend( fLastTargetURI.c_str(), adminData,true );
1952       // - fPITotalSize      = uInt32, total item size
1953       adminData+="\r\ntotalsize:"; StringObjAppendPrintf( adminData,"%ld", (long)fPITotalSize );
1954       // - fPIUnconfirmedSize= uInt32, unconfirmed part of item size
1955       adminData+="\r\nunconfirmedsize:"; StringObjAppendPrintf( adminData,"%ld", (long)fPIUnconfirmedSize );
1956       // - fPIStoredSize     = uInt32, size of BLOB to store, store it as well to make ReadBlob easier (mallloc)
1957       adminData+="\r\nstoredsize:"; StringObjAppendPrintf( adminData,"%ld", (long)blSize );
1958       // - fPIStoredSize     = uInt32, size of BLOB to store, 0=none
1959       // - fPIStoredDataP    = void *, BLOB data, NULL if none
1960       adminData+="\r\nstored;BLOBID="; adminData+= PIStored;
1961     } // if
1962   } // if
1963   // CRLF at end
1964   adminData+="\r\n";
1965   // save admin data
1966   sta= fDBApi_Admin.SaveAdminData(adminData.c_str()); if (sta) return sta;
1967   // now write all the BLOBs, currently there is only the PIStored object of ResumeChunkedSupport
1968   if (dsResumeChunkedSupportedInDB()) {
1969     TPartialItemState pis= fPartialItemState;
1970     if (
1971       blSize==0 &&
1972       (pis==pi_state_save_incoming ||  pis==pi_state_save_outgoing)
1973     )
1974       pis= pi_state_none; // delete blob, if size==0
1975     // handle BLOB
1976     switch (pis) {
1977       // make sure BLOB is deleted when it is empty
1978       case pi_state_none:
1979         sta =
1980           fDBApi_Admin.DeleteBlob(
1981             "",         // aItem.getLocalID()
1982             PIStored    // blobfieldname.c_str()
1983           );
1984         if (sta==DB_NotFound)
1985           sta= LOCERR_OK;    // no error, if not existing
1986         break;
1987       // save BLOB contents
1988       case pi_state_save_incoming: // Write the whole BLOB at once
1989       case pi_state_save_outgoing:
1990         sta =
1991           fDBApi_Admin.WriteBlob (
1992             "", // aItem.getLocalID()
1993             PIStored,   // blobfieldname.c_str()
1994             blPtr,      // bufferP
1995             blSize,     // actualbytes
1996             blSize,     // blobsize
1997             true,       // first
1998             true        // last
1999           );
2000           break;
2001       case pi_state_loaded_incoming:
2002       case pi_state_loaded_outgoing:
2003         // do nothing, as the blob is saved already
2004         break;
2005     } // switch
2006   } // if
2007   return sta;
2008 } // TPluginApiDS::apiSaveAdminData
2009
2010
2011 /// @brief Load admin data from Plugin-implemented database
2012 ///   Must search for existing target record matching the triple (aDeviceID,aDatabaseID,aRemoteDBID)
2013 ///   - if there is a matching record: load it
2014 ///   - if there is no matching record, set fFirstTimeSync=true. The implementation may already create a
2015 ///     new record with the key (aDeviceID,aDatabaseID,aRemoteDBID) and initialize it with the data from
2016 ///     the items as shown below. At least, fTargetKey must be set to a value that will allow apiSaveAdminData to
2017 ///     update the record. In case implementation chooses not create the record only in apiSaveAdminData, it must
2018 ///     buffer the triple (aDeviceID,aDatabaseID,aRemoteDBID) such that it is available at apiSaveAdminData.
2019 ///   If a record exists implementation must load the following items:
2020 ///   - fTargetKey        = some key value that can be used to re-identify the target record later at SaveAdminData.
2021 ///                         If the database implementation has other means to re-identify the target, this can be
2022 ///                         left unassigned.
2023 ///   - fLastRemoteAnchor = anchor string used by remote party for last session (and saved to DB then)
2024 ///   - fPreviousSyncTime = anchor (beginning of session) timestamp of last session.
2025 ///   - fPreviousToRemoteSyncCmpRef = Reference time to determine items modified since last time sending data to remote
2026 ///                         (or last changelog update in case of BASED_ON_BINFILE_CLIENT && binfileDSActive())
2027 ///   - fPreviousToRemoteSyncIdentifier = string identifying last session that sent data to remote
2028 ///                         (or last changelog update in case of BASED_ON_BINFILE_CLIENT && binfileDSActive()). Needs
2029 ///                         only be saved if derived datastore cannot work with timestamps and has its own identifier.
2030 ///   - fMapTable         = list<TMapEntry> containing map entries. The implementation must load all map entries
2031 ///                         related to the current sync target identified by the triple of (aDeviceID,aDatabaseID,aRemoteDBID)
2032 ///                         or by fTargetKey. The entries added to fMapTable must have "changed", "added" and "deleted" flags
2033 ///                         set to false.
2034 ///   For resumable datastores (fConfigP->fResumeSupport==true):
2035 ///   - fMapTable         = In addition to the above, the markforresume flag must be saved in the mapflags
2036 //                          when it is not equal to the savedmark flag - independently of added/deleted/changed.
2037 ///   - fResumeAlertCode  = alert code of current suspend state, 0 if none
2038 ///   - fPreviousSuspendCmpRef = reference time of last suspend (used to detect items modified during a suspend / resume)
2039 ///   - fPreviousSuspendIdentifier = identifier of last suspend (used to detect items modified during a suspend / resume)
2040 ///                         (needs only be saved if derived datastore cannot work with timestamps and has
2041 ///                         its own identifier)
2042 ///   - fPendingAddMaps   = map<string,string>. The implementation must load all  all pending maps (client only) into
2043 ///                         fPendingAddMaps (and fUnconfirmedMaps must be left empty).
2044 ///   - fTempGUIDMap      = map<string,string>. The implementation must save all entries as temporary LUID to GUID mappings
2045 ///                         (server only)
2046 ///
2047 ///   For datastores that can resume in middle of a chunked item (fConfigP->fResumeItemSupport==true):
2048 ///   - fPartialItemState = state of partial item (TPartialItemState enum):
2049 ///                         - after load, value must always be pi_state_none, pi_state_loaded_incoming or pi_state_loaded_outgoing.
2050 ///                         - pi_state_save_xxx MUST NOT be set after loading (see apiSaveAdminData comments)
2051 ///   - fLastItemStatus   = status code (TSyError) of last item
2052 ///   - fLastSourceURI    = item ID (string, if limited in length should be long enough for large IDs, >=64 chars recommended)
2053 ///   - fLastTargetURI    = item ID (string, if limited in length should be long enough for large IDs, >=64 chars recommended)
2054 ///   - fPITotalSize      = uInt32, total item size
2055 ///   - fPIUnconfirmedSize= uInt32, unconfirmed part of item size
2056 ///   - fPIStoredSize     = uInt32, size of BLOB, 0=none
2057 ///   - fPIStoredDataP    = void *, BLOB data.
2058 ///                         - If this is not NULL on entry AND fPIStoredDataAllocated is set,
2059 ///                           the current block must be freed using smlLibFree() (and NOT JUST free()!!).
2060 ///                         - If no BLOB is loaded, this must be set to NULL
2061 ///                         - If a BLOB is loaded, an appropriate memory block should be allocated for it
2062 ///                           using smlLibMalloc() (and NOT JUST malloc()!!)
2063 ///   - fPIStoredDataAllocated: MUST BE SET to true when a memory block was allocated into fPIStoredDataP.
2064 /// @param aDeviceID[in]       remote device URI (device ID)
2065 /// @param aDatabaseID[in]     local database ID
2066 /// @param aRemoteDBID[in]     database ID of remote device
2067 localstatus TPluginApiDS::apiLoadAdminData(
2068   const char *aDeviceID,
2069   const char *aDatabaseID,
2070   const char *aRemoteDBID
2071 )
2072 {
2073   // security - don't use API when locked
2074   if (dbAccessLocked()) return 503; // service unavailable
2075
2076   const char* PIStored = "PIStored"; // blob name field
2077
2078   TSyError err = LOCERR_OK;
2079
2080   // In any case - this is the time to create the contexts for the datastore
2081   // - admin if selected
2082   if (fPluginDSConfigP->fDBApiConfig_Admin.Connected()) {
2083     DB_Callback cb= &fDBApi_Admin.fCB.Callback;
2084     cb->callbackRef       = fSessionP; // the session
2085     #ifdef ENGINEINTERFACE_SUPPORT
2086     cb->thisBase          = fSessionP->getSyncAppBase()->fEngineInterfaceP;
2087     #endif
2088     #ifdef SYDEBUG
2089     // Datastore Admin debug goes to session log
2090     cb->debugFlags        = PDEBUGTEST(DBG_ADMIN+DBG_DBAPI+DBG_PLUGIN) ? 0xFFFF : 0;
2091     cb->DB_DebugPuts      = SessionLogDebugPuts;
2092     cb->DB_DebugBlock     = SessionLogDebugBlock;
2093     cb->DB_DebugEndBlock  = SessionLogDebugEndBlock;
2094     cb->DB_DebugEndThread = SessionLogDebugEndThread;
2095     cb->DB_DebugExotic    = SessionLogDebugExotic;
2096     #endif // SYDEBUG
2097     #ifdef ENGINEINTERFACE_SUPPORT
2098     // Admin module can use Get/SetValue for session script var access
2099     // Note: these are essentially context free and work without a global call-in structure
2100     //       (which is not necessarily there, for example in no-library case)
2101     CB_Connect_KeyAccess(cb); // connect generic key access routines
2102     // Version of OpenSessionKey that implicitly opens a key for the current session (DB plugins
2103     // do not have a session handle, as their use is always implicitly in a session context).
2104     cb->ui.OpenSessionKey = SessionOpenSessionKey;
2105     #endif // ENGINEINTERFACE_SUPPORT
2106     if (!fDBApi_Admin.Created()) {
2107       // - use datastore name as context name and link with session context
2108       err= fDBApi_Admin.CreateContext(
2109         getName(), true,
2110         &(fPluginDSConfigP->fDBApiConfig_Admin),
2111         fPluginAgentP->fDeviceKey.c_str(),
2112         fPluginAgentP->fUserKey.c_str(),
2113         fPluginAgentP->getDBApiSession()
2114       );
2115     }
2116     if (err!=LOCERR_OK)
2117       SYSYNC_THROW(TSyncException("Error creating context for plugin module handling admin",err));
2118   }
2119   // - data if selected
2120   err = connectDataPlugin();
2121   if (err==LOCERR_OK) {
2122     if (!fDBApi_Data.Created()) {
2123       // - use datastore name as context name and link with session context
2124       err= fDBApi_Data.CreateContext(
2125         getName(), false,
2126         &(fPluginDSConfigP->fDBApiConfig_Data),
2127         fPluginAgentP->fDeviceKey.c_str(),
2128         fPluginAgentP->fUserKey.c_str(),
2129         fPluginAgentP->getDBApiSession()
2130       );
2131     }
2132   }
2133   else if (err==LOCERR_NOTIMP)
2134     err=LOCERR_OK; // we just don't have a data plugin, that's ok, inherited (SQL) will handle data
2135   if (err!=LOCERR_OK)
2136     SYSYNC_THROW(TSyncException("Error creating context for plugin module handling data",err));
2137   // Perform actual loading of admin data
2138   #ifndef SDK_ONLY_SUPPORT
2139   // only handle here if we are in charge - otherwise let ancestor handle it
2140   if (!fDBApi_Admin.Created())
2141     return inherited::apiLoadAdminData(aDeviceID, aDatabaseID, aRemoteDBID);
2142   #endif
2143   // find and read (or create) the admin data
2144   TDB_Api_Str adminData;
2145   err=fDBApi_Admin.LoadAdminData(aDatabaseID,aRemoteDBID,adminData);
2146   if (err==404) {
2147     // this means that this admin data set did not exists before
2148     fFirstTimeSync=true;
2149   }
2150   else if (err!=LOCERR_OK)
2151     return err; // failed
2152   else
2153     fFirstTimeSync=false; // we already have admin data, so it can't be first sync
2154   // parse data
2155   const char *p = adminData.c_str();
2156   // second check: if empty adminData returned, this is treated as first sync as well
2157   if (*p==0) fFirstTimeSync=true;
2158   const char *q;
2159   string fieldname,value;
2160   lineartime_t *ltP;
2161   string *strP;
2162   uInt16 *usP;
2163   uInt32 *ulP;
2164   // read all fields
2165   while(*p) {
2166     // find name
2167     for (q=p; *q && (*q!=':' && *q!=';');) q++;
2168     fieldname.assign(p,q-p);
2169     p=q;
2170     // p should now point to ':' or ';'
2171     if (*p==':' || *p==';') {
2172       p++; // consume colon or semicolon
2173       // get value
2174       value.erase();
2175       p += CStrToStrAppend(p, value, true); // stop at quote or ctrl char
2176       // analyze and store now
2177       // - no storage location found yet
2178       ltP=NULL;
2179       strP=NULL;
2180       usP=NULL;
2181       ulP=NULL;
2182
2183       // - find where we need to store this
2184       if (strucmp(fieldname.c_str(),"remotesyncanchor")==0) {
2185         strP=&fLastRemoteAnchor;
2186       }
2187       else if (strucmp(fieldname.c_str(),"lastsync")==0) {
2188         ltP=&fPreviousSyncTime;
2189       }
2190       else if (strucmp(fieldname.c_str(),"lasttoremotesync")==0) {
2191         ltP=&fPreviousToRemoteSyncCmpRef;
2192       }
2193       else if (strucmp(fieldname.c_str(),"lasttoremotesyncid")==0) {
2194         strP=&fPreviousToRemoteSyncIdentifier;
2195       }
2196       else if (strucmp(fieldname.c_str(),"resumealertcode")==0) {
2197         usP=&fResumeAlertCode;
2198       }
2199       else if (strucmp(fieldname.c_str(),"lastsuspend")==0) {
2200         ltP=&fPreviousSuspendCmpRef;
2201       }
2202       else if (strucmp(fieldname.c_str(),"lastsuspendid")==0) {
2203         strP=&fPreviousSuspendIdentifier;
2204       }
2205
2206       /// For datastores that can resume in middle of a chunked item (fConfigP->fResumeItemSupport==true):
2207       else {
2208         if (dsResumeChunkedSupportedInDB()) {
2209           if (strucmp(fieldname.c_str(),"partialitemstate")==0) {
2210             usP = (TSyError*)&fPartialItemState;  // enum
2211           }
2212           else if (strucmp(fieldname.c_str(),"lastitemstatus")==0) {
2213             usP= &fLastItemStatus; // status code (TSyError) of last item
2214           }
2215           else if (strucmp(fieldname.c_str(),"lastsourceURI"   )==0) {
2216             strP= &fLastSourceURI; // item ID (string, if limited in len should be long enough for large IDs, >=64 chars recommended)
2217           }
2218           else if (strucmp(fieldname.c_str(),"lasttargetURI"   )==0) {
2219             strP= &fLastTargetURI; // item ID (string, if limited in len should be long enough for large IDs, >=64 chars recommended)
2220           }
2221           else if (strucmp(fieldname.c_str(),"totalsize"      )==0) {
2222             ulP= &fPITotalSize; // uInt32, total item size
2223           }
2224           else if (strucmp(fieldname.c_str(),"unconfirmedsize")==0) {
2225             ulP= &fPIUnconfirmedSize; // uInt32, unconfirmed part of item size
2226           }
2227           else if (strucmp(fieldname.c_str(),"storedsize")==0) {
2228             ulP= &fPIStoredSize; // uInt32, size of BLOB, 0=none
2229           }
2230           ///   - fPIStoredDataP    = void *, BLOB data.
2231           ///                         - If this is not NULL on entry AND fPIStoredDataAllocated is set,
2232           ///                           the current block must be freed using smlLibFree() (and NOT JUST free()!!).
2233           ///                         - If no BLOB is loaded, this must be set to NULL
2234           ///                         - If a BLOB is loaded, an appropriate memory block should be allocated for it
2235           ///                           using smlLibMalloc() (and NOT JUST malloc()!!)
2236           ///   - fPIStoredDataAllocated: MUST BE SET to true when a memory block was allocated into fPIStoredDataP.
2237           else if (strucmp(fieldname.c_str(),"stored")==0) {
2238             if (
2239               fPIStoredDataP!=NULL &&
2240               fPIStoredDataAllocated
2241             )
2242               smlLibFree(fPIStoredDataP);
2243             fPIStoredDataP= NULL;
2244             fPIStoredDataAllocated= false;
2245
2246             TDB_Api_Blk b;
2247             memSize totSize;
2248             bool last;
2249
2250             if (fPIStoredSize>0) {
2251               fPIStoredDataP= smlLibMalloc( fPIStoredSize ); // now prepare for the full blob
2252               fPIStoredDataAllocated= true;
2253               unsigned char* dp = (unsigned char*)fPIStoredDataP;
2254               unsigned char* lim = dp + fPIStoredSize;
2255               bool first= true;
2256               do {
2257                 err= fDBApi_Admin.ReadBlob(
2258                   "",       // fParentObjectID.c_str(), // the item ID
2259                   PIStored, // fBlobID.c_str(), // the ID of the blob
2260                   0,        // neededBytes, // how much we need
2261                   b,        // blobData, // blob data
2262                   totSize,  // totalsize, // will receive total size or 0 if unknown
2263                   first,    // first,
2264                   last      // last
2265                 );
2266                 if (err)
2267                   break;
2268
2269                 memSize rema= b.fSize;
2270                 if  (dp+rema > lim)
2271                   rema= lim-dp;    // avoid overflow
2272                 memcpy( dp, b.fPtr, rema ); dp+= rema;
2273                 fDBApi_Admin.DisposeBlk( b );         // we have now a copy => remove it
2274                 first= false;
2275               } while (!last);
2276             } // if
2277           }
2278         } // if (dsResume ...)
2279       } // if
2280
2281       // - store
2282       if (strP) {
2283         // - is a string
2284         (*strP) = value;
2285       }
2286       else if (usP) {
2287         // - is a uInt16
2288         StrToUShort(value.c_str(),*usP);
2289       }
2290       else if (ulP) {
2291         // - is a uInt32
2292         StrToULong(value.c_str(),*ulP);
2293       }
2294       else if (ltP) {
2295         // - is a ISO8601
2296         lineartime_t tim;
2297         timecontext_t tctx;
2298         if (ISO8601StrToTimestamp(value.c_str(), tim, tctx)!=0) {
2299           // converted ok, now make sure we get UTC
2300           TzConvertTimestamp(tim,tctx,TCTX_UTC,getSessionZones(),fPluginDSConfigP->fDataTimeZone);
2301           *ltP=tim;
2302         }
2303         else {
2304           // no valid date/time = empty
2305           *ltP=0;
2306         }
2307       }
2308     }
2309     // skip everything up to next end of line (in case value was terminated by a quote or other ctrl char)
2310     while (*p && *p!='\r' && *p!='\n') p++;
2311     // skip all line end chars up to beginning of next line or end of record
2312     while (*p && (*p=='\r' || *p=='\n')) p++;
2313     // p now points to next line's beginning
2314   };
2315   // then read maps
2316   bool firstEntry=true;
2317   fMapTable.clear();
2318   TDB_Api_MapID mapid;
2319   TMapEntry mapEntry;
2320   while (fDBApi_Admin.ReadNextMapItem(mapid, firstEntry)) {
2321     // get entry
2322     mapEntry.localid=mapid.localID.c_str();
2323     mapEntry.remoteid=mapid.remoteID.c_str();
2324     mapEntry.mapflags=mapid.flags;
2325     // check for old API which did not support entry types
2326     if (fPluginDSConfigP->fDBApiConfig_Admin.Version()<sInt32(VE_InsertMapItem)) {
2327       mapEntry.entrytype = mapentry_normal; // DB has no entry types, treat all as normal entries
2328     }
2329     else {
2330       if (mapid.ident>=numMapEntryTypes)
2331         mapEntry.entrytype = mapentry_invalid;
2332       else
2333         mapEntry.entrytype = (TMapEntryType)mapid.ident;
2334     }
2335     PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,(
2336       "read map entry (type=%s): localid='%s', remoteid='%s', mapflags=0x%lX",
2337       MapEntryTypeNames[mapEntry.entrytype],
2338       mapEntry.localid.c_str(),
2339       mapEntry.remoteid.c_str(),
2340       (long)mapEntry.mapflags
2341     ));
2342     // save entry in list
2343     mapEntry.changed=false; // not yet changed
2344     mapEntry.added=false; // already there
2345     // remember saved state of suspend mark
2346     mapEntry.markforresume=false; // not yet marked for this session (mark of last session is in mapflag_useforresume!)
2347     mapEntry.savedmark=mapEntry.mapflags & mapflag_useforresume;
2348     // IMPORTANT: non-normal entries must be saved as deleted in the main map - they will be re-activated at the
2349     // next save if needed
2350     mapEntry.deleted = mapEntry.entrytype!=mapentry_normal; // only normal ones may be saved as existing in the main map
2351     // save to main map list anyway to allow differential updates to map table (instead of writing everything all the time)
2352     fMapTable.push_back(mapEntry);
2353     // now save special maps to extra lists according to type
2354     // Note: in the main map, these are marked deleted. Before the next saveAdminData, these will
2355     //       be re-added (=re-activated) from the extra lists if they still exist.
2356     switch (mapEntry.entrytype) {
2357       #ifdef SYSYNC_SERVER
2358       case mapentry_tempidmap:
2359         if (IS_SERVER) {
2360           PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,(
2361             "fTempGUIDMap: restore mapping from %s to %s",
2362             mapEntry.remoteid.c_str(),
2363             mapEntry.localid.c_str()
2364           ));
2365
2366           fTempGUIDMap[mapEntry.remoteid]=mapEntry.localid; // tempGUIDs are accessed by remoteID=tempID
2367         }
2368         break;
2369       #endif
2370       #ifdef SYSYNC_CLIENT
2371       case mapentry_pendingmap:
2372         if (IS_CLIENT)
2373           fPendingAddMaps[mapEntry.localid]=mapEntry.remoteid;
2374         break;
2375       #endif
2376     case mapentry_invalid:
2377     case mapentry_normal:
2378     case numMapEntryTypes:
2379     default:
2380       // nothing to do or should not occur
2381       break;
2382     }
2383     // next is not first entry any more
2384     firstEntry=false;
2385   }
2386   return LOCERR_OK;
2387 } // TPluginApiDS::apiLoadAdminData
2388
2389
2390 #endif // not BINFILE_ALWAYS_ACTIVE
2391
2392
2393 /// @brief log datastore sync result, called at end of sync with this datastore
2394 /// Available log information is:
2395 /// - fCurrentSyncTime                  : timestamp of start this sync session (anchor time)
2396 /// - fCurrentSyncCmpRef                : timestamp used by next session to detect changes since this one
2397 ///                                       (end of session time of last session that send data to remote)
2398 /// - fTargetKey                        : string, identifies target (user/device/datastore/remotedatastore)-tuple
2399 /// - fSessionP->fUserKey               : string, identifies user
2400 /// - fSessionP->fDeviceKey             : string, identifies device
2401 /// - fSessionP->fDomainName            : string, identifies domain (aka clientID, aka enterpriseID)
2402 /// - getName()                         : string, local datastore's configured name (such as "contacts", "events" etc.)
2403 /// - getRemoteDBPath()                 : string, remote datastore's path (things like EriCalDB or "Z:\System\Contacts.cdb")
2404 /// - getRemoteViewOfLocalURI()         : string, shows how remote specifies local datastore URI (including cgi, or
2405 ///                                       in case of symbian "calendar", this can be different from getName(), as "calendar"
2406 ///                                       is internally split-processed by "events" and "tasks".
2407 /// - fSessionP->getRemoteURI()         : string, remote Device URI (usually IMEI or other globally unique ID)
2408 /// - fSessionP->getRemoteDescName()    : string, remote Device's descriptive name (constructed from DevInf <man> and <mod>, or
2409 ///                                       as set by <remoterule>'s <descriptivename>.
2410 /// - fSessionP->getRemoteInfoString()  : string, Remote Device Version Info ("Type (HWV, FWV, SWV) Oem")
2411 /// - fSessionP->getSyncUserName()      : string, User name as sent by the device
2412 /// - fSessionP->getLocalSessionID()    : string, the local session ID (the one that is used to construct log file names)
2413 /// - fAbortStatusCode                  : TSyError, if == 0, sync with this datastore was ok, if <>0, there was an error.
2414 /// - fSessionP->getAbortReasonStatus() : TSyError, shows status of entire session at the point when datastore finishes syncing.
2415 /// - fSyncMode                         : TSyncModes, (smo_twoway,smo_fromserver,smo_fromclient)
2416 /// - isSlowSync()                      : boolean, true if slow sync (or refresh from sync, which is a one-way-slow-sync)
2417 /// - isFirstTimeSync()                 : boolean, true if first time sync of this device with this datastore
2418 /// - isResuming()                      : boolean, true if this is a resumed session
2419 /// - SyncMLVerDTDNames[fSessionP->getSyncMLVersion()] : SyncML version string ("1.0", "1.1". "1.2" ...)
2420 /// - fLocalItemsAdded                  : number of locally added Items
2421 /// - fRemoteItemsAdded                 : number of remotely added Items
2422 /// - fLocalItemsDeleted                : number of locally deleted Items
2423 /// - fRemoteItemsDeleted               : number of remotely deleted Items
2424 /// - fLocalItemsError                  : number of locally rejected Items (caused error to be sent to remote)
2425 /// - fRemoteItemsError                 : number of remotely rejected Items (returned error, will be resent)
2426 /// - fLocalItemsUpdated                : number of locally updated Items
2427 /// - fRemoteItemsUpdated               : number of remotely updated Items
2428 /// - fSlowSyncMatches                  : number of items matched in Slow Sync
2429 /// - fConflictsServerWins              : number of server won conflicts
2430 /// - fConflictsClientWins              : number of client won conflicts
2431 /// - fConflictsDuplicated              : number of conflicts solved by duplicating item
2432 /// - fSessionP->getIncomingBytes()     : total number of incoming bytes in this session so far
2433 /// - fSessionP->getOutgoingBytes()     : total number of outgoing bytes in this session so far
2434 /// - fIncomingDataBytes                : net incoming data bytes for this datastore (= payload data, without SyncML protocol overhead)
2435 /// - fOutgoingDataBytes                : net outgoing data bytes for this datastore (= payload data, without SyncML protocol overhead)
2436 void TPluginApiDS::dsLogSyncResult(void)
2437 {
2438   // security - don't use API when locked
2439   if (dbAccessLocked()) return;
2440
2441   // format for DB Api
2442   string logData,s;
2443   logData.erase();
2444   logData+="lastsync:"; TimestampToISO8601Str(s,fCurrentSyncTime,TCTX_UTC); logData+=s.c_str();
2445   logData+="\r\ntargetkey:"; StrToCStrAppend(fTargetKey.c_str(),logData,true);
2446   #ifndef BINFILE_ALWAYS_ACTIVE
2447   logData+="\r\nuserkey:"; StrToCStrAppend(fPluginAgentP->fUserKey.c_str(),logData,true);
2448   logData+="\r\ndevicekey:"; StrToCStrAppend(fPluginAgentP->fDeviceKey.c_str(),logData,true);
2449   #ifdef SCRIPT_SUPPORT
2450   logData+="\r\ndomain:"; StrToCStrAppend(fPluginAgentP->fDomainName.c_str(),logData,true);
2451   #endif
2452   #endif // BINFILE_ALWAYS_ACTIVE
2453   logData+="\r\ndsname:"; StrToCStrAppend(getName(),logData,true);
2454   #ifndef MINIMAL_CODE
2455   logData+="\r\ndsremotepath:"; StrToCStrAppend(getRemoteDBPath(),logData,true);
2456   #endif
2457   logData+="\r\ndslocalpath:"; StrToCStrAppend(getRemoteViewOfLocalURI(),logData,true);
2458   logData+="\r\nfolderkey:"; StrToCStrAppend(fFolderKey.c_str(),logData,true);
2459   logData+="\r\nremoteuri:"; StrToCStrAppend(fSessionP->getRemoteURI(),logData,true);
2460   logData+="\r\nremotedesc:"; StrToCStrAppend(fSessionP->getRemoteDescName(),logData,true);
2461   logData+="\r\nremoteinfo:"; StrToCStrAppend(fSessionP->getRemoteInfoString(),logData,true);
2462   logData+="\r\nremoteuser:"; StrToCStrAppend(fSessionP->getSyncUserName(),logData,true);
2463   logData+="\r\nsessionid:"; StrToCStrAppend(fSessionP->getLocalSessionID(),logData,true);
2464   logData+="\r\nsyncstatus:"; StringObjAppendPrintf(logData,"%hd",fAbortStatusCode);
2465   logData+="\r\nsessionstatus:"; StringObjAppendPrintf(logData,"%hd",fSessionP->getAbortReasonStatus());
2466   logData+="\r\nsyncmlvers:"; StrToCStrAppend(SyncMLVerDTDNames[fSessionP->getSyncMLVersion()],logData,true);
2467   logData+="\r\nsyncmode:"; StringObjAppendPrintf(logData,"%hd",(uInt16)fSyncMode);
2468   logData+="\r\nsynctype:"; StringObjAppendPrintf(logData,"%d",(fSlowSync ? (fFirstTimeSync ? 2 : 1) : 0) + (isResuming() ? 10 : 0));
2469   logData+="\r\nlocaladded:"; StringObjAppendPrintf(logData,"%ld",(long)fLocalItemsAdded);
2470   logData+="\r\ndeviceadded:"; StringObjAppendPrintf(logData,"%ld",(long)fRemoteItemsAdded);
2471   logData+="\r\nlocaldeleted:"; StringObjAppendPrintf(logData,"%ld",(long)fLocalItemsDeleted);
2472   logData+="\r\ndevicedeleted:"; StringObjAppendPrintf(logData,"%ld",(long)fRemoteItemsDeleted);
2473   logData+="\r\nlocalrejected:"; StringObjAppendPrintf(logData,"%ld",(long)fLocalItemsError);
2474   logData+="\r\ndevicerejected:"; StringObjAppendPrintf(logData,"%ld",(long)fRemoteItemsError);
2475   logData+="\r\nlocalupdated:"; StringObjAppendPrintf(logData,"%ld",(long)fLocalItemsUpdated);
2476   logData+="\r\ndeviceupdated:"; StringObjAppendPrintf(logData,"%ld",(long)fRemoteItemsUpdated);
2477   #ifdef SYSYNC_SERVER
2478   if (IS_SERVER) {
2479     logData+="\r\nslowsyncmatches:"; StringObjAppendPrintf(logData,"%ld",(long)fSlowSyncMatches);
2480     logData+="\r\nserverwins:"; StringObjAppendPrintf(logData,"%ld",(long)fConflictsServerWins);
2481     logData+="\r\nclientwins:"; StringObjAppendPrintf(logData,"%ld",(long)fConflictsClientWins);
2482     logData+="\r\nduplicated:"; StringObjAppendPrintf(logData,"%ld",(long)fConflictsDuplicated);
2483   } // server
2484   #endif // SYSYNC_SERVER
2485   logData+="\r\nsessionbytesin:"; StringObjAppendPrintf(logData,"%ld",(long)fSessionP->getIncomingBytes());
2486   logData+="\r\nsessionbytesout:"; StringObjAppendPrintf(logData,"%ld",(long)fSessionP->getOutgoingBytes());
2487   logData+="\r\ndatabytesin:"; StringObjAppendPrintf(logData,"%ld",(long)fIncomingDataBytes);
2488   logData+="\r\ndatabytesout:"; StringObjAppendPrintf(logData,"%ld",(long)fOutgoingDataBytes);
2489   logData+="\r\n";
2490   if (fDBApi_Admin.Created()) {
2491     fDBApi_Admin.WriteLogData(logData.c_str());
2492     if (fPluginDSConfigP->fDBAPIModule_Data != fPluginDSConfigP->fDBAPIModule_Admin) {
2493       // admin and data are different modules, show log to data module as well
2494       if (fDBApi_Data.Created())
2495         fDBApi_Data.WriteLogData(logData.c_str());
2496     }
2497   }
2498   else if (fDBApi_Data.Created()) {
2499     fDBApi_Data.WriteLogData(logData.c_str());
2500   }
2501   // anyway: let ancestor save log info as well (if it is configured so)
2502   inherited::dsLogSyncResult();
2503 } // TPluginApiDS::dsLogSyncResult
2504
2505
2506
2507 #ifdef STREAMFIELD_SUPPORT
2508
2509 // TApiBlobProxy
2510 // =============
2511
2512 TApiBlobProxy::TApiBlobProxy(
2513   TPluginApiDS *aApiDsP,
2514   bool aIsStringBLOB,
2515   const char *aBlobID,
2516   const char *aParentID
2517 )
2518 {
2519   // save values
2520   fApiDsP = aApiDsP;
2521   fIsStringBLOB = aIsStringBLOB;
2522   fBlobID = aBlobID;
2523   fParentObjectID = aParentID;
2524   fBlobSize = 0;
2525   fBlobSizeKnown = false;
2526   fFetchedSize = 0;
2527   fBufferSize = 0;
2528   fBlobBuffer = NULL; // nothing retrieved yet
2529 } // TApiBlobProxy::TApiBlobProxy
2530
2531
2532 TApiBlobProxy::~TApiBlobProxy()
2533 {
2534   if (fBlobBuffer) delete (char *)fBlobBuffer; // gcc 3.2.2 needs cast to suppress warning
2535   fBlobBuffer=NULL;
2536 } // TApiBlobProxy::~TApiBlobProxy
2537
2538
2539 // fetch BLOB from DPAPI
2540 void TApiBlobProxy::fetchBlob(size_t aNeededSize, bool aNeedsTotalSize, bool aNeedsAllData)
2541 {
2542   TSyError dberr=LOCERR_OK;
2543
2544   if (fBufferSize==0 || aNeededSize>fFetchedSize) {
2545     // if do not have anything yet or not enough yet, we need to read
2546     uInt8P bufP = NULL;
2547     bool last = false;
2548     bool first = fFetchedSize==0; // first if we haven't fetched anything so far
2549     TDB_Api_Blk blobData;
2550     memSize neededBytes = aNeededSize-fFetchedSize; // how much we need to read more
2551     memSize totalsize = 0; // not known
2552
2553     if (fIsStringBLOB)
2554       aNeedsAllData = true; // strings must be fetched entirely, as they need to be converted before we can measure size or get data
2555     if (!fBlobBuffer && aNeededSize==0 && (aNeedsTotalSize || aNeedsAllData))
2556       neededBytes=200; // just read a bit to possibly obtain the total size
2557     do {
2558       // read a block
2559       dberr = fApiDsP->fDBApi_Data.ReadBlob(
2560         fParentObjectID.c_str(), // the item ID
2561         fBlobID.c_str(), // the ID of the blob
2562         neededBytes, // how much we need
2563         blobData, // blob data
2564         totalsize, // will receive total size or 0 if unknown
2565         first,
2566         last
2567       );
2568       if (dberr!=LOCERR_OK)
2569         SYSYNC_THROW(TSyncException("ReadBlob fatal error",dberr));
2570       // sanity check
2571       if (blobData.fSize>neededBytes)
2572         SYSYNC_THROW(TSyncException("ReadBlob returned more data than requested"));
2573       // check if we know the total size reliably now
2574       if (totalsize) {
2575         // non-zero return means we know the total size now
2576         fBlobSize = totalsize;
2577         fBlobSizeKnown = true;
2578       }
2579       else {
2580         // could be unknown size OR zero blob
2581         if (neededBytes>0 && blobData.fSize==0) {
2582           // we tried to read, but got nothing, and total size is zero -> this means explicit zero size
2583           fBlobSize = 0;
2584           fBlobSizeKnown = true;
2585         }
2586       }
2587       // calculate how large the buffer needs to be
2588       size_t newBufSiz = (aNeededSize ? aNeededSize : fFetchedSize+blobData.fSize) + 1; // +1 for string terminator possibly needed
2589       if (fBufferSize<newBufSiz) {
2590         // we need a larger buffer
2591         bufP = new uInt8[newBufSiz];
2592         fBufferSize=newBufSiz; // save new buffer size
2593         // if there's something in the old buffer, we need to copy it and delete it
2594         if (fBlobBuffer) {
2595           if (fFetchedSize)
2596             memcpy(bufP,fBlobBuffer,fFetchedSize); // copy fetched portion from old buffer
2597           delete (uInt8P)fBlobBuffer; // dispose old buffer
2598         } // if
2599         fBlobBuffer = bufP; // save new one, in EVERY CASE
2600       }
2601       // get pointer where to copy data to
2602       bufP = fBlobBuffer+fFetchedSize; // append to what is already in the buffer
2603       // actually copy data from DBApi block to buffer
2604       memcpy(bufP,blobData.fPtr,blobData.fSize);
2605       // calculate how much we want to read next time
2606       // if neededBytes now gets zero, this will request as much as possible for the next call to ReadBlob
2607       fFetchedSize += blobData.fSize;
2608       neededBytes -= blobData.fSize; // see what's remaining until what we originally requested
2609       blobData.DisposeBlk();         // <blobData.fSize> will be set back to 0 here !!
2610       // check end of data from API
2611       if (last) {
2612         if (!fBlobSizeKnown) {
2613           fBlobSize = fFetchedSize;
2614           fBlobSizeKnown = true;
2615         }
2616         // end of BLOB: done fetching ANYWAY
2617         break;
2618       }
2619       // the BLOB is bigger than what we have fetched so far
2620       // - check if we are done even if not at end of blob
2621       if (!aNeedsAllData && fFetchedSize>=aNeededSize && (!aNeedsTotalSize || fBlobSizeKnown))
2622         break; // we have what was requested
2623       // - we need to load more data
2624       if (aNeedsAllData) {
2625         if (fBlobSizeKnown)
2626           neededBytes = fBlobSize-fFetchedSize; // try to get rest in one chunk
2627         else
2628           neededBytes = 4096; // we don't know how much is coming, continue reading in 4k chunks
2629       }
2630       // we need to continue until we get the total size or last
2631       first=false;
2632     } while(true);
2633
2634     // for strings, we need to convert the data and re-adjust the size
2635     if (fIsStringBLOB) {
2636       // we KNOW that we have the entire BLOB text here (because we set aNeedsAllData above when this is a string BLOB)
2637       // - set a terminator
2638       *((char *)fBlobBuffer+fFetchedSize) = 0; // set terminator
2639       // - convert to UTF8 and internal linefeeds
2640       string strUtf8;
2641       appendStringAsUTF8((const char *)fBlobBuffer, strUtf8, fApiDsP->fPluginDSConfigP->fDataCharSet, lem_cstr);
2642       // set actual size
2643       fBlobSize=strUtf8.size();
2644       // copy from string to buffer
2645       if (fBlobSize+1<=fBufferSize) {
2646         bufP = fBlobBuffer; // use old buffer
2647       }
2648       else {
2649         fBufferSize=fBlobSize+1;
2650         bufP = new unsigned char [fBufferSize];
2651         delete (unsigned char *)fBlobBuffer;
2652         fBlobBuffer = bufP;
2653       }
2654       memcpy(bufP,strUtf8.c_str(),strUtf8.size());
2655       fFetchedSize=strUtf8.size();
2656       *((char *)fBlobBuffer+fFetchedSize)=0; // set terminator
2657     } // if
2658   }
2659 } // TApiBlobProxy::fetchBlob
2660
2661
2662
2663
2664 // returns size of entire blob
2665 size_t TApiBlobProxy::getBlobSize(TStringField *aFieldP)
2666 {
2667   fetchBlob(0,true,false); // only needs the size, but no data
2668   return fBlobSize;
2669 } // TApiBlobProxy::getBlobSize
2670
2671
2672 // read from Blob from specified stream position and update stream pos
2673 size_t TApiBlobProxy::readBlobStream(TStringField *aFieldP, size_t &aPos, void *aBuffer, size_t aMaxBytes)
2674 {
2675   if (fFetchedSize<aPos+aMaxBytes || !fBlobBuffer) {
2676     // we need to read (more of) the body
2677     if (!fBlobSizeKnown || fFetchedSize<fBlobSize) {
2678       // we know that we need to fetch more, or we are not sure that we have fetched everything already -> fetch more
2679       fetchBlob(aPos+aMaxBytes,false,false); // fetch at least up to the given size (unless blob is actually smaller)
2680     }
2681   }
2682   // now copy from our buffer
2683   if (aPos>fFetchedSize) return 0; // position obviously out of range
2684   if (aPos+aMaxBytes>fFetchedSize) aMaxBytes=fFetchedSize-aPos; // reduce to what we have
2685   if (aMaxBytes==0) return 0; // safety
2686   // copy data from fBlobBuffer (which contains beginning or all of the BLOB) to caller's buffer
2687   memcpy(aBuffer,(char *)fBlobBuffer+aPos,aMaxBytes);
2688   aPos += aMaxBytes;
2689   return aMaxBytes; // return number of bytes actually read
2690 } // TApiBlobProxy::readBlobStream
2691
2692
2693 #endif // STREAMFIELD_SUPPORT
2694
2695
2696
2697 } // namespace sysync
2698
2699 /* end of TPluginApiDS implementation */
2700
2701 // eof