Bug Summary

File:libsynthesis/src/DB_interfaces/api_db/pluginapids.cpp
Warning:line 1123, column 13
Forming reference to null pointer

Annotated Source Code

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