2 * @File pluginapids.cpp
4 * @Author Lukas Zeller (luz@plan44.ch)
7 * Plugin based datastore API implementation
9 * Copyright (c) 2001-2011 by Synthesis AG + plan44.ch
11 * @Date 2005-10-06 : luz : created from apidbdatastore
16 #include "pluginapids.h"
17 #include "pluginapiagent.h"
19 #ifdef SYSER_REGISTRATION
23 #ifdef ENGINEINTERFACE_SUPPORT
24 #include "engineentry.h"
27 #include "SDK_support.h"
42 TApiFieldMapItem::TApiFieldMapItem(const char *aElementName, TConfigElement *aParentElement) :
43 inherited(aElementName,aParentElement)
46 } // TApiFieldMapItem::TApiFieldMapItem
49 void TApiFieldMapItem::checkAttrs(const char **aAttributes)
52 inherited::checkAttrs(aAttributes);
53 } // TApiFieldMapItem::checkAttrs
56 #ifdef ARRAYDBTABLES_SUPPORT
61 TApiFieldMapArrayItem::TApiFieldMapArrayItem(TCustomDSConfig *aCustomDSConfigP, TConfigElement *aParentElement) :
62 inherited(aCustomDSConfigP,aParentElement)
65 } // TApiFieldMapArrayItem::TApiFieldMapArrayItem
68 void TApiFieldMapArrayItem::checkAttrs(const char **aAttributes)
71 inherited::checkAttrs(aAttributes);
72 } // TApiFieldMapArrayItem::checkAttrs
81 TPluginDSConfig::TPluginDSConfig(const char* aName, TConfigElement *aParentElement) :
82 inherited(aName,aParentElement),
83 fPluginParams_Admin(this),
84 fPluginParams_Data(this)
88 } // TPluginDSConfig::TPluginDSConfig
91 TPluginDSConfig::~TPluginDSConfig()
94 // disconnect from the API module
95 fDBApiConfig_Data.Disconnect();
96 fDBApiConfig_Admin.Disconnect();
97 } // TPluginDSConfig::~TPluginDSConfig
101 void TPluginDSConfig::clear(void)
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
116 fResumeSupported = true;
117 fHasDeleteSyncSet = false;
120 } // TPluginDSConfig::clear
123 // config element parsing
124 bool TPluginDSConfig::localStartElement(const char *aElementName, const char **aAttributes, sInt32 aLine)
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);
144 return inherited::localStartElement(aElementName,aAttributes,aLine);
147 } // TPluginDSConfig::localStartElement
151 void TPluginDSConfig::localResolve(bool aLastPass)
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
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)
167 SYSYNC_THROW(TConfigParseException("License does not allow using <datastore type=\"plugin\">"));
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;
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;
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
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"));
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"));
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;
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;
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;
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>"));
245 inherited::localResolve(aLastPass);
246 } // TPluginDSConfig::localResolve
249 // - create appropriate datastore from config, calls addTypeSupport as well
250 TLocalEngineDS *TPluginDSConfig::newLocalDataStore(TSyncSession *aSessionP)
252 // Synccap defaults to normal set supported by the engine by default
253 TLocalEngineDS *ldsP;
255 ldsP = new TPluginApiDS(this,aSessionP,getName(),aSessionP->getSyncCapMask() & ~(isOneWayFromRemoteSupported() ? 0 : SCAP_MASK_ONEWAY_SERVER));
258 ldsP = new TPluginApiDS(this,aSessionP,getName(),aSessionP->getSyncCapMask() & ~(isOneWayFromRemoteSupported() ? 0 : SCAP_MASK_ONEWAY_CLIENT));
261 addTypes(ldsP,aSessionP);
264 } // TPluginDSConfig::newLocalDataStore
268 * Implementation of TPluginApiDS
272 TPluginApiDS::TPluginApiDS(
273 TPluginDSConfig *aConfigP,
274 sysync::TSyncSession *aSessionP,
276 uInt32 aCommonSyncCapMask
278 #ifdef SDK_ONLY_SUPPORT
279 TCustomImplDS(aConfigP,aSessionP, aName, aCommonSyncCapMask)
281 TODBCApiDS(aConfigP,aSessionP, aName, aCommonSyncCapMask)
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
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.
296 InternalResetDataStore();
297 } // TPluginApiDS::TPluginApiDS
300 TPluginApiDS::~TPluginApiDS()
302 InternalResetDataStore();
303 } // TPluginApiDS::~TPluginApiDS
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)
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();
318 inherited::announceAgentDestruction();
319 } // TPluginApiDS::announceAgentDestruction
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)
326 // filtering capabilities need to be evaluated first
327 fAPICanFilter = false;
328 fAPIFiltersTested = false;
329 } // TPluginApiDS::InternalResetDataStore
334 #ifdef DBAPI_TEXTITEMS
336 // store API key/value pair field in mapped field, if one is defined
337 bool TPluginApiDS::storeField(
341 TMultiFieldItem &aItem,
346 TFieldMapList *fmlP = &(fPluginDSConfigP->fFieldMappings.fFieldMapList);
347 TFieldMapList::iterator pos;
348 TApiFieldMapItem *fmiP;
351 // search field map list for matching map entry
352 for (pos=fmlP->begin(); pos!=fmlP->end(); pos++) {
353 fmiP = static_cast<TApiFieldMapItem *>(*pos);
357 fmiP->setNo==aSetNo &&
358 strucmp(fmiP->getName(),aName)==0
360 // DB-readable field with matching name
361 TDBFieldType dbfty = fmiP->dbfieldtype;
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
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
386 // store according to database field type
389 // for explicit strings, perform character set and line feed conversion
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());
396 // blob is treated as 1:1 string if there's no proxy for it
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
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)
412 // just set as string
413 fieldP->setAsString(aValue);
417 // field successfully stored, do NOT exit loop
418 // because there could be a second map for the same attribute!
420 } // if map found for attribute
421 } // for all field mappings
423 } // TPluginApiDS::storeField
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,
437 bool stored = parseItemData(aItem,aItemData,aSetNo);
440 stored = postReadProcessItem(aItem,aSetNo);
443 } // TPluginApiDS::parseItemData
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(
451 TMultiFieldItem &aItem,
456 TFieldMapList *fmlP = &(fPluginDSConfigP->fFieldMappings.fFieldMapList);
457 TFieldMapList::iterator pos;
458 TApiFieldMapItem *fmiP;
460 bool createdone=false;
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);
472 TItemField *basefieldP;
473 sInt16 fid = fmiP->fid;
474 // determine base field (might be array)
475 basefieldP = getMappedBaseFieldOrVar(aItem,fid);
476 if (generateItemFieldData(
478 fPluginDSConfigP->fDataCharSet,
479 fPluginDSConfigP->fDataLineEndMode,
480 fPluginDSConfigP->fDataTimeZone,
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);
491 } // TPluginApiDS::generateDBItemData
495 #endif // DBAPI_TEXTITEMS
498 // - post process item after reading from DB (run script)
499 bool TPluginApiDS::postReadProcessItem(TMultiFieldItem &aItem, uInt16 aSetNo)
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;
509 // post-process all mapped and writable fields
510 for (pos=fmlP->begin(); pos!=fmlP->end(); pos++) {
511 fmiP = static_cast<TApiFieldMapItem *>(*pos);
517 TItemField *basefieldP, *leaffieldP;
518 #ifdef ARRAYFIELD_SUPPORT
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
533 #ifdef ARRAYFIELD_SUPPORT
534 if (basefieldP->isArray()) {
535 leaffieldP = basefieldP->getArrayField(arrayIndex,true); // get existing leaf fields only
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;
543 leaffieldP = basefieldP; // no arrays: leaf is always base field
545 static_cast<TTimestampField *>(leaffieldP)->moveToContext(fSessionP->fUserTimeContext, false);
546 } while(basefieldP->isArray()); // only arrays do loop all array elements
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
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
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;
573 leaffieldP = basefieldP; // no arrays: leaf is always base field
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
584 } // readable field mapping with explicit as_param mark
585 } // for all field mappings
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"));
595 // always ok for now %%%
597 } // TPluginApiDS::postReadProcessItem
601 // - pre-process item before writing to DB (run script)
602 bool TPluginApiDS::preWriteProcessItem(TMultiFieldItem &aItem)
604 #ifdef SCRIPT_SUPPORT
605 // process beforewrite script
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"));
613 // always ok for now %%%
615 } // TPluginApiDS::preWriteProcessItem
621 // - send BLOBs of this item one by one. Returns true if we have a blob at all
622 bool TPluginApiDS::writeBlobs(
624 TMultiFieldItem &aItem,
628 TFieldMapList *fmlP = &(fPluginDSConfigP->fFieldMappings.fFieldMapList);
629 TFieldMapList::iterator pos;
630 TApiFieldMapItem *fmiP;
631 string blobfieldname;
632 bool blobwritten=false;
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);
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
657 for (arrayIndex=0; true; arrayIndex++)
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
670 leaffieldP = basefieldP; // leaf is base field
672 // if no leaf field, we'll need to exit here (we're done with the array)
673 if (leaffieldP==NULL) break;
675 leaffieldP = basefieldP; // leaf is base field
677 // Now write this BLOB or string field
678 size_t maxBlobWriteBlockSz;
680 void *bufferP = NULL;
682 bool isString = !leaffieldP->isBasedOn(fty_blob);
684 #ifdef STREAMFIELD_SUPPORT
685 leaffieldP->resetStream(); // this might be the wrong place to do this ...
686 blobsize = leaffieldP->getStreamSize();
687 maxBlobWriteBlockSz = 16000;
690 bufferP = new unsigned char[blobsize>maxBlobWriteBlockSz ? maxBlobWriteBlockSz : blobsize];
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
699 // is string -> first convert to DB charset
700 blobsize = leaffieldP->getStringSize();
701 maxBlobWriteBlockSz = blobsize;
703 leaffieldP->getAsString(s);
704 // convert to DB charset and linefeeds
708 fPluginDSConfigP->fDataCharSet,
709 fPluginDSConfigP->fDataLineEndMode
711 bufferP = (void *)strDB.c_str();
714 // now read from stream field and send to API
717 size_t remaining=blobsize;
718 size_t bytes,actualbytes;
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
726 // read these from the stream field
727 actualbytes = leaffieldP->readStream(bufferP,bytes); // we need to read
732 // we have it already in the buffer
735 if (actualbytes<bytes) last=false;
736 // write to the DB API
737 dberr= fDBApi_Data.WriteBlob(
739 blobfieldname.c_str(),
746 if (dberr!=LOCERR_OK) SYSYNC_THROW(TSyncException("DBapi::WriteBlob fatal error"));
747 // not first call any more
749 // calculate new remaining
750 remaining-=actualbytes;
752 #ifdef STREAMFIELD_SUPPORT
753 if (!isString && bufferP) delete (char *)bufferP;
757 #ifdef STREAMFIELD_SUPPORT
758 if (!isString && bufferP) delete (char *)bufferP;
762 // we now have at least one field
764 #ifdef ARRAYFIELD_SUPPORT
765 // non-array do not loop
766 if (!basefieldP->isArray()) break;
768 } // for all array elements
769 } // if writable BLOB/parametrized field
770 } // for all field mappings
772 } // TPluginApiDS::writeBlobs
775 bool TPluginApiDS::deleteBlobs(
777 TMultiFieldItem &aItem,
781 TFieldMapList *fmlP = &(fPluginDSConfigP->fFieldMappings.fFieldMapList);
782 TFieldMapList::iterator pos;
783 TApiFieldMapItem *fmiP;
784 string blobfieldname;
785 bool blobdeleted=false;
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) {
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;
801 // %%% what is this, obsolete??
802 // ignore map if field is not assigned and assignedonly flag is set
803 //if (aAssignedOnly && basefieldP->isUnassigned()) continue;
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
811 #ifdef ARRAYFIELD_SUPPORT
813 for (arrayIndex=0; true; arrayIndex++)
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
826 leaffieldP= basefieldP; // leaf is base field
828 // if no leaf field, we'll need to exit here (we're done with the array)
829 if (leaffieldP==NULL) break;
831 leaffieldP= basefieldP; // leaf is base field
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"));
843 // we now have at least one field
846 #ifdef ARRAYFIELD_SUPPORT
847 // non-array do not loop
848 if (!basefieldP->isArray()) break;
850 } // for all array elements
851 } // if writable BLOB/parametrized field
852 } // for all field mappings
854 } // TPluginApiDS::deleteBlobs
858 /// returns true if DB implementation supports resume (saving of resume marks, alert code, pending maps, tempGUIDs)
859 bool TPluginApiDS::dsResumeSupportedInDB(void)
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);
865 return inherited::dsResumeSupportedInDB();
866 } // TPluginApiDS::dsResumeSupportedInDB
869 #ifdef OBJECT_FILTERING
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)
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();
879 #ifdef SYSYNC_TARGET_OPTIONS
880 string rangeFilter,s;
881 // check range filtering
883 rangeFilter = "daterangestart:";
884 TimestampToISO8601Str(s, fDateRangeStart, TCTX_UTC, false, false);
885 rangeFilter += s.c_str();
887 rangeFilter += "\r\ndaterangeend:";
888 TimestampToISO8601Str(s, fDateRangeEnd, TCTX_UTC, false, false);
889 rangeFilter += s.c_str();
891 // - attachments inhibit
893 #if (!defined _MSC_VER || defined WINCE) && !defined(__GNUC__)
894 #warning "attachments and limit filters not yet supported"
896 // - let plugin know and check (we can filter at DBlevel if plugin understands both start/end)
897 rangeFilter += "\r\n";
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;
904 // there is no range ever: yes, we can filter
907 // otherwise, let implementation test (not immediate anchestor, which is a different API like ODBC)
908 return TCustomImplDS::dsOptionFilterFetchesFromDB();
909 } // TPluginApiDS::dsOptionFilterFetchesFromDB
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)
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);
924 if (aFilterChanged || !fAPIFiltersTested) {
925 fAPIFiltersTested = true;
926 // Anyway, let DBApi know (even if all filters are empty)
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)
940 (fDBApi_Data.FilterSupport(filters.c_str())>=3) ||
941 (fLocalDBFilter.empty() && fSyncSetFilter.empty() && fPluginDSConfigP->fInvisibleFilter.empty()); // no filter set = we can "filter"
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
953 // can return 508 to force a slow sync. Other errors abort the sync
954 localstatus TPluginApiDS::apiEarlyDataAccessStart(void)
956 TSyError dberr = LOCERR_OK;
957 if (fPluginDSConfigP->fEarlyStartDataRead) {
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));
972 // prepare for reading the sync set
973 localstatus TPluginApiDS::apiPrepareReadSyncSet(void)
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(
985 &(fPluginDSConfigP->fDBApiConfig_Data),
986 "anydevice", // no real device key
987 "singleuser", // no real user key
988 NULL // no associated session level // fPluginAgentP->getDBApiSession()
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);
998 else if (dberr==LOCERR_NOTIMP)
999 dberr=LOCERR_OK; // we just don't have a data plugin, that's ok
1001 #endif // BASED_ON_BINFILE_CLIENT
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)
1012 TSyError dberr=LOCERR_OK;
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)
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);
1029 // just let plugin know if we want data (if it actually does is the plugin's choice)
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");
1042 #ifdef SCRIPT_SUPPORT
1043 // process init script
1046 fPluginAgentP->fScriptContextDatastore=this;
1047 if (!TScriptContext::executeTest(true,fScriptContextP,fPluginDSConfigP->fFieldMappings.fInitScript,fPluginDSConfigP->getDSFuncTableP(),fPluginAgentP)) {
1048 PDEBUGPRINTFX(DBG_ERROR,("<initscript> failed"));
1053 // - read list of all local IDs that are in the current sync set
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",
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));
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())) {
1077 // true for initial ReadNextItem*() call, false later on
1078 bool firstReadNextItem=true;
1081 #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1082 TMultiFieldItem *mfitemP = NULL;
1084 #ifdef DBAPI_TEXTITEMS
1085 TDB_Api_Str itemData;
1090 TSyncSetItem *syncSetItemP=NULL;
1091 TDB_Api_ItemID itemAndParentID;
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()) {
1104 (TMultiFieldItem *) newItemForRemote(
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
1120 // got item, delete the key
1122 // post-process (run scripts, create BLOB proxies if needed)
1123 postReadProcessItem(*mfitemP,0);
1128 #ifdef DBAPI_TEXTITEMS
1131 dberr=fDBApi_Data.ReadNextItem(itemAndParentID, itemData, itemstatus, firstReadNextItem);
1134 return LOCERR_WRONGUSAGE; // completely wrong usage - should never happen as compatibility is tested at module connect
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;
1144 // check if we have seen all items
1145 if (itemstatus==ReadNextItem_EOF)
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();
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;
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" : ""
1166 // no data yet, no item yet
1167 syncSetItemP->itemP = NULL;
1169 #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1170 if (fPluginDSConfigP->fItemAsKey) {
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());
1181 #ifdef DBAPI_TEXTITEMS
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(
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);
1199 return LOCERR_WRONGUSAGE; // completely wrong usage - should never happen as compatibility is tested at module connect
1201 // now save syncset item
1202 fSyncSetList.push_back(syncSetItemP);
1206 dberr=LOCERR_EXCEPTION;
1209 PDEBUGPRINTFX(DBG_DATA+DBG_EXOTIC,("skipped reading sync set because of refresh-from-peer sync"));
1210 } // if we need the syncset at all
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));
1220 } // TPluginApiDS::apiReadSyncSet
1224 // Check if we need the syncset to zap
1225 bool TPluginApiDS::apiNeedSyncSetToZap(void)
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();
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
1236 // Zap all data in syncset (note that everything outside the sync set will remain intact)
1237 localstatus TPluginApiDS::apiZapSyncSet(void)
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();
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);
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!)
1252 // do it one by one if DeleteAllItems() is not implemented or plugin cannot apply current filters
1254 dberr = zapSyncSetOneByOne();
1258 } // TPluginApiDS::apiZapSyncSet
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)
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);
1270 TSyError dberr=LOCERR_OK;
1271 ItemID_Struct itemAndParentID;
1273 // set up item ID and parent ID
1274 itemAndParentID.item=(appCharP)aItem.getLocalID();
1275 itemAndParentID.parent=const_cast<char *>("");
1278 #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1279 if (fPluginDSConfigP->fItemAsKey) {
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);
1288 // done with the key
1293 #ifdef DBAPI_TEXTITEMS
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);
1304 return LOCERR_WRONGUSAGE; // completely wrong usage - should never happen as compatibility is tested at module connect
1308 } // TPluginApiDS::apiFetchItem
1313 localstatus TPluginApiDS::apiStartDataWrite(void)
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();
1320 TSyError dberr=fDBApi_Data.StartDataWrite();
1321 if (dberr!=LOCERR_OK) {
1322 PDEBUGPRINTFX(DBG_ERROR,("DBapi::StartDataWrite returns dberr=%hd",dberr));
1325 } // TPluginApiDS::apiStartDataWrite
1327 struct TPluginItemAux : public TSyncItemAux
1329 #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1330 TDBItemKey *fItemKeyP;
1332 #ifdef DBAPI_TEXTITEMS
1337 // add new item to datastore, returns created localID
1338 localstatus TPluginApiDS::apiAddItem(TMultiFieldItem &aItem, string &aLocalID)
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);
1345 TSyError dberr=LOCERR_OK;
1346 TDB_Api_ItemID itemAndParentID;
1348 #ifdef SCRIPT_SUPPORT
1349 fInserting=true; // flag for script, we are inserting new record
1352 TPluginItemAux *aux = static_cast<TPluginItemAux *>(aItem.getAux(TSyncItem::PLUGIN_API));
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)
1360 // done with the key
1361 delete aux->fItemKeyP;
1362 aux->fItemKeyP=NULL;
1366 #ifdef DBAPI_TEXTITEMS
1368 dberr=fDBApi_Data.InsertItem(aux->fItemData.c_str(),"",itemAndParentID);
1369 if (dberr == LOCERR_AGAIN)
1373 return LOCERR_WRONGUSAGE;
1376 // Two API variants for starting the operation.
1377 #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1378 if (fPluginDSConfigP->fItemAsKey) {
1380 if (!preWriteProcessItem(aItem)) return 510; // DB error
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;
1391 // done with the key
1396 #ifdef DBAPI_TEXTITEMS
1400 false, // all fields, not only assigned ones
1402 0, // we do not use different sets for now
1403 itemData // here we'll get the data
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;
1415 return LOCERR_WRONGUSAGE; // completely wrong usage - should never happen as compatibility is tested at module connect
1420 if (dberr==LOCERR_OK ||
1421 dberr==DB_Conflict ||
1422 dberr==DB_DataReplaced ||
1423 dberr==DB_DataMerged) {
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
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;
1445 } // TPluginApiDS::apiAddItem
1448 #ifdef SYSYNC_CLIENT
1450 /// finalize local ID (for datastores that can't efficiently produce these at insert)
1451 bool TPluginApiDS::dsFinalizeLocalID(string &aLocalID)
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);
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)
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();
1470 // no change - ID is ok as-is
1472 } // TPluginApiDS::dsFinalizeLocalID
1474 #endif // SYSYNC_CLIENT
1478 // update existing item in datastore, returns 404 if item not found
1479 localstatus TPluginApiDS::apiUpdateItem(TMultiFieldItem &aItem)
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);
1486 TSyError dberr=LOCERR_OK;
1487 TDB_Api_ItemID updItemAndParentID;
1488 ItemID_Struct itemAndParentID;
1490 // set up item ID and parent ID
1491 itemAndParentID.item=(appCharP)aItem.getLocalID();
1492 itemAndParentID.parent=const_cast<char *>("");
1494 #ifdef SCRIPT_SUPPORT
1495 fInserting=false; // flag for script, we are updating, not inserting now
1498 TPluginItemAux *aux = static_cast<TPluginItemAux *>(aItem.getAux(TSyncItem::PLUGIN_API));
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)
1506 // done with the key
1507 delete aux->fItemKeyP;
1508 aux->fItemKeyP=NULL;
1512 #ifdef DBAPI_TEXTITEMS
1514 dberr=fDBApi_Data.UpdateItem(aux->fItemData.c_str(),itemAndParentID,updItemAndParentID);
1515 if (dberr == LOCERR_AGAIN)
1519 return LOCERR_WRONGUSAGE; // completely wrong usage - should never happen as compatibility is tested at module connect
1522 // Two API variants for starting the operation.
1523 #if defined(DBAPI_ASKEYITEMS) && defined(ENGINEINTERFACE_SUPPORT)
1524 if (fPluginDSConfigP->fItemAsKey) {
1526 if (!preWriteProcessItem(aItem)) return 510; // DB error
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;
1537 // done with the key
1542 #ifdef DBAPI_TEXTITEMS
1546 true, // only assigned fields
1548 0, // we do not use different sets for now
1549 itemData // here we'll get the data
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;
1561 return LOCERR_WRONGUSAGE; // completely wrong usage - should never happen as compatibility is tested at module connect
1564 if (dberr==LOCERR_OK) {
1565 // check if ID has changed
1566 if (!updItemAndParentID.item.empty() && strcmp(updItemAndParentID.item.c_str(),aItem.getLocalID())!=0) {
1568 // update item ID and Map
1569 dsLocalIdHasChanged(aItem.getLocalID(),updItemAndParentID.item.c_str());
1571 // - update in this item we have here as well
1572 aItem.setLocalID(updItemAndParentID.item.c_str());
1573 aItem.updateLocalIDDependencies();
1575 // now write all the BLOBs
1576 writeBlobs(true,aItem,0);
1577 #ifdef SCRIPT_SUPPORT
1578 // process overall afterwrite script
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;
1591 } // TPluginApiDS::apiUpdateItem
1594 // delete existing item in datastore, returns 211 if not existing any more
1595 localstatus TPluginApiDS::apiDeleteItem(TMultiFieldItem &aItem)
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);
1602 TSyError dberr=LOCERR_OK;
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
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;
1623 } // TPluginApiDS::apiDeleteItem
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)
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);
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();
1641 } // TPluginApiDS::apiEndDataWrite
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)
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();
1664 } // TPluginApiDS::startingThread
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
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)
1683 return static_cast<TPluginApiAgent *>(fSessionP)->fApiLocked;
1684 } // TPluginApiDS::dbAccessLocked
1687 // - alert possible thread change to plugins
1688 // Does not check if API is locked or not, see dsThreadMayChangeNow()
1689 void TPluginApiDS::ThreadMayChangeNow(void)
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
1697 // - engine Thread might change
1698 void TPluginApiDS::dsThreadMayChangeNow(void)
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();
1706 // let ancestor do it's own stuff
1707 inherited::dsThreadMayChangeNow();
1708 } // TPluginApiDS::dsThreadMayChangeNow
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)
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()) {
1724 DB_Callback cb= &fDBApi_Data.fCB.Callback;
1725 cb->callbackRef = fSessionP; // the session
1726 #ifdef ENGINEINTERFACE_SUPPORT
1727 cb->thisBase = fPluginDSConfigP->getSyncAppBase()->fEngineInterfaceP;
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;
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
1749 } // connectDataPlugin
1752 #ifndef BINFILE_ALWAYS_ACTIVE
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)
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)
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
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)
1800 // security - don't use API when locked
1801 if (dbAccessLocked()) return 503; // service unavailable
1803 const char* PIStored = "PIStored"; // blob name field
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);
1810 localstatus sta=LOCERR_OK;
1811 TMapContainer::iterator pos;
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,
1826 (int)(*pos).markforresume,
1827 (int)(*pos).savedmark
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)
1834 (*pos).changed || (*pos).added || (*pos).deleted || // update of DB needed
1835 ((*pos).markforresume!=(*pos).savedmark) // mark for resume changed
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;
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;
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
1862 else if ((*pos).added) {
1864 sta=fDBApi_Admin.InsertMapItem(&mapid);
1865 if (sta!=LOCERR_OK) break;
1866 // is now added, don't add again later
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;
1875 } // if something changed
1876 // anyway - reset mark for resume, it must be reconstructed before next save
1877 (*pos).markforresume=false;
1881 if (sta!=LOCERR_OK) return sta;
1882 // collect admin data in a string
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
1890 adminData+="\r\nlastlocalanchor:";
1891 timeStampToISO8601(fThisLocalAnchor,s,true,true);
1892 adminData+=s.c_str();
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();
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)
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)
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
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)
1938 fPartialItemState!=pi_state_loaded_incoming &&
1939 fPartialItemState!=pi_state_loaded_outgoing
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;
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;
1972 (pis==pi_state_save_incoming || pis==pi_state_save_outgoing)
1974 pis= pi_state_none; // delete blob, if size==0
1977 // make sure BLOB is deleted when it is empty
1980 fDBApi_Admin.DeleteBlob(
1981 "", // aItem.getLocalID()
1982 PIStored // blobfieldname.c_str()
1984 if (sta==DB_NotFound)
1985 sta= LOCERR_OK; // no error, if not existing
1987 // save BLOB contents
1988 case pi_state_save_incoming: // Write the whole BLOB at once
1989 case pi_state_save_outgoing:
1991 fDBApi_Admin.WriteBlob (
1992 "", // aItem.getLocalID()
1993 PIStored, // blobfieldname.c_str()
1995 blSize, // actualbytes
2001 case pi_state_loaded_incoming:
2002 case pi_state_loaded_outgoing:
2003 // do nothing, as the blob is saved already
2008 } // TPluginApiDS::apiSaveAdminData
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
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
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
2073 // security - don't use API when locked
2074 if (dbAccessLocked()) return 503; // service unavailable
2076 const char* PIStored = "PIStored"; // blob name field
2078 TSyError err = LOCERR_OK;
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;
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;
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(
2110 &(fPluginDSConfigP->fDBApiConfig_Admin),
2111 fPluginAgentP->fDeviceKey.c_str(),
2112 fPluginAgentP->fUserKey.c_str(),
2113 fPluginAgentP->getDBApiSession()
2117 SYSYNC_THROW(TSyncException("Error creating context for plugin module handling admin",err));
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(
2126 &(fPluginDSConfigP->fDBApiConfig_Data),
2127 fPluginAgentP->fDeviceKey.c_str(),
2128 fPluginAgentP->fUserKey.c_str(),
2129 fPluginAgentP->getDBApiSession()
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
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);
2143 // find and read (or create) the admin data
2144 TDB_Api_Str adminData;
2145 err=fDBApi_Admin.LoadAdminData(aDatabaseID,aRemoteDBID,adminData);
2147 // this means that this admin data set did not exists before
2148 fFirstTimeSync=true;
2150 else if (err!=LOCERR_OK)
2151 return err; // failed
2153 fFirstTimeSync=false; // we already have admin data, so it can't be first sync
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;
2159 string fieldname,value;
2167 for (q=p; *q && (*q!=':' && *q!=';');) q++;
2168 fieldname.assign(p,q-p);
2170 // p should now point to ':' or ';'
2171 if (*p==':' || *p==';') {
2172 p++; // consume colon or semicolon
2175 p += CStrToStrAppend(p, value, true); // stop at quote or ctrl char
2176 // analyze and store now
2177 // - no storage location found yet
2183 // - find where we need to store this
2184 if (strucmp(fieldname.c_str(),"remotesyncanchor")==0) {
2185 strP=&fLastRemoteAnchor;
2187 else if (strucmp(fieldname.c_str(),"lastsync")==0) {
2188 ltP=&fPreviousSyncTime;
2190 else if (strucmp(fieldname.c_str(),"lasttoremotesync")==0) {
2191 ltP=&fPreviousToRemoteSyncCmpRef;
2193 else if (strucmp(fieldname.c_str(),"lasttoremotesyncid")==0) {
2194 strP=&fPreviousToRemoteSyncIdentifier;
2196 else if (strucmp(fieldname.c_str(),"resumealertcode")==0) {
2197 usP=&fResumeAlertCode;
2199 else if (strucmp(fieldname.c_str(),"lastsuspend")==0) {
2200 ltP=&fPreviousSuspendCmpRef;
2202 else if (strucmp(fieldname.c_str(),"lastsuspendid")==0) {
2203 strP=&fPreviousSuspendIdentifier;
2206 /// For datastores that can resume in middle of a chunked item (fConfigP->fResumeItemSupport==true):
2208 if (dsResumeChunkedSupportedInDB()) {
2209 if (strucmp(fieldname.c_str(),"partialitemstate")==0) {
2210 usP = (TSyError*)&fPartialItemState; // enum
2212 else if (strucmp(fieldname.c_str(),"lastitemstatus")==0) {
2213 usP= &fLastItemStatus; // status code (TSyError) of last item
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)
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)
2221 else if (strucmp(fieldname.c_str(),"totalsize" )==0) {
2222 ulP= &fPITotalSize; // uInt32, total item size
2224 else if (strucmp(fieldname.c_str(),"unconfirmedsize")==0) {
2225 ulP= &fPIUnconfirmedSize; // uInt32, unconfirmed part of item size
2227 else if (strucmp(fieldname.c_str(),"storedsize")==0) {
2228 ulP= &fPIStoredSize; // uInt32, size of BLOB, 0=none
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) {
2239 fPIStoredDataP!=NULL &&
2240 fPIStoredDataAllocated
2242 smlLibFree(fPIStoredDataP);
2243 fPIStoredDataP= NULL;
2244 fPIStoredDataAllocated= false;
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;
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
2269 memSize rema= b.fSize;
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
2278 } // if (dsResume ...)
2288 StrToUShort(value.c_str(),*usP);
2292 StrToULong(value.c_str(),*ulP);
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);
2304 // no valid date/time = empty
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
2316 bool firstEntry=true;
2318 TDB_Api_MapID mapid;
2320 while (fDBApi_Admin.ReadNextMapItem(mapid, firstEntry)) {
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
2330 if (mapid.ident>=numMapEntryTypes)
2331 mapEntry.entrytype = mapentry_invalid;
2333 mapEntry.entrytype = (TMapEntryType)mapid.ident;
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
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:
2360 PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,(
2361 "fTempGUIDMap: restore mapping from %s to %s",
2362 mapEntry.remoteid.c_str(),
2363 mapEntry.localid.c_str()
2366 fTempGUIDMap[mapEntry.remoteid]=mapEntry.localid; // tempGUIDs are accessed by remoteID=tempID
2370 #ifdef SYSYNC_CLIENT
2371 case mapentry_pendingmap:
2373 fPendingAddMaps[mapEntry.localid]=mapEntry.remoteid;
2376 case mapentry_invalid:
2377 case mapentry_normal:
2378 case numMapEntryTypes:
2380 // nothing to do or should not occur
2383 // next is not first entry any more
2387 } // TPluginApiDS::apiLoadAdminData
2390 #endif // not BINFILE_ALWAYS_ACTIVE
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)
2438 // security - don't use API when locked
2439 if (dbAccessLocked()) return;
2441 // format for DB Api
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);
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);
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
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);
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);
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());
2498 else if (fDBApi_Data.Created()) {
2499 fDBApi_Data.WriteLogData(logData.c_str());
2501 // anyway: let ancestor save log info as well (if it is configured so)
2502 inherited::dsLogSyncResult();
2503 } // TPluginApiDS::dsLogSyncResult
2507 #ifdef STREAMFIELD_SUPPORT
2512 TApiBlobProxy::TApiBlobProxy(
2513 TPluginApiDS *aApiDsP,
2515 const char *aBlobID,
2516 const char *aParentID
2521 fIsStringBLOB = aIsStringBLOB;
2523 fParentObjectID = aParentID;
2525 fBlobSizeKnown = false;
2528 fBlobBuffer = NULL; // nothing retrieved yet
2529 } // TApiBlobProxy::TApiBlobProxy
2532 TApiBlobProxy::~TApiBlobProxy()
2534 if (fBlobBuffer) delete (char *)fBlobBuffer; // gcc 3.2.2 needs cast to suppress warning
2536 } // TApiBlobProxy::~TApiBlobProxy
2539 // fetch BLOB from DPAPI
2540 void TApiBlobProxy::fetchBlob(size_t aNeededSize, bool aNeedsTotalSize, bool aNeedsAllData)
2542 TSyError dberr=LOCERR_OK;
2544 if (fBufferSize==0 || aNeededSize>fFetchedSize) {
2545 // if do not have anything yet or not enough yet, we need to read
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
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
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
2568 if (dberr!=LOCERR_OK)
2569 SYSYNC_THROW(TSyncException("ReadBlob fatal error",dberr));
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
2575 // non-zero return means we know the total size now
2576 fBlobSize = totalsize;
2577 fBlobSizeKnown = true;
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
2584 fBlobSizeKnown = true;
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
2596 memcpy(bufP,fBlobBuffer,fFetchedSize); // copy fetched portion from old buffer
2597 delete (uInt8P)fBlobBuffer; // dispose old buffer
2599 fBlobBuffer = bufP; // save new one, in EVERY CASE
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
2612 if (!fBlobSizeKnown) {
2613 fBlobSize = fFetchedSize;
2614 fBlobSizeKnown = true;
2616 // end of BLOB: done fetching ANYWAY
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) {
2626 neededBytes = fBlobSize-fFetchedSize; // try to get rest in one chunk
2628 neededBytes = 4096; // we don't know how much is coming, continue reading in 4k chunks
2630 // we need to continue until we get the total size or last
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
2641 appendStringAsUTF8((const char *)fBlobBuffer, strUtf8, fApiDsP->fPluginDSConfigP->fDataCharSet, lem_cstr);
2643 fBlobSize=strUtf8.size();
2644 // copy from string to buffer
2645 if (fBlobSize+1<=fBufferSize) {
2646 bufP = fBlobBuffer; // use old buffer
2649 fBufferSize=fBlobSize+1;
2650 bufP = new unsigned char [fBufferSize];
2651 delete (unsigned char *)fBlobBuffer;
2654 memcpy(bufP,strUtf8.c_str(),strUtf8.size());
2655 fFetchedSize=strUtf8.size();
2656 *((char *)fBlobBuffer+fFetchedSize)=0; // set terminator
2659 } // TApiBlobProxy::fetchBlob
2664 // returns size of entire blob
2665 size_t TApiBlobProxy::getBlobSize(TStringField *aFieldP)
2667 fetchBlob(0,true,false); // only needs the size, but no data
2669 } // TApiBlobProxy::getBlobSize
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)
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)
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);
2689 return aMaxBytes; // return number of bytes actually read
2690 } // TApiBlobProxy::readBlobStream
2693 #endif // STREAMFIELD_SUPPORT
2697 } // namespace sysync
2699 /* end of TPluginApiDS implementation */