File: | libsynthesis/src/DB_interfaces/api_db/pluginapids.cpp |
Warning: | line 1123, column 13 Forming reference to null pointer |
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 | ||||
30 | namespace sysync { | |||
31 | ||||
32 | // Config | |||
33 | // ====== | |||
34 | ||||
35 | // Helpers | |||
36 | // ======= | |||
37 | ||||
38 | ||||
39 | // Field Map item | |||
40 | // ============== | |||
41 | ||||
42 | TApiFieldMapItem::TApiFieldMapItem(const char *aElementName, TConfigElement *aParentElement) : | |||
43 | inherited(aElementName,aParentElement) | |||
44 | { | |||
45 | /* nop for now */ | |||
46 | } // TApiFieldMapItem::TApiFieldMapItem | |||
47 | ||||
48 | ||||
49 | void TApiFieldMapItem::checkAttrs(const char **aAttributes) | |||
50 | { | |||
51 | /* nop for now */ | |||
52 | inherited::checkAttrs(aAttributes); | |||
53 | } // TApiFieldMapItem::checkAttrs | |||
54 | ||||
55 | ||||
56 | #ifdef ARRAYDBTABLES_SUPPORT1 | |||
57 | ||||
58 | // Array Map item | |||
59 | // =============== | |||
60 | ||||
61 | TApiFieldMapArrayItem::TApiFieldMapArrayItem(TCustomDSConfig *aCustomDSConfigP, TConfigElement *aParentElement) : | |||
62 | inherited(aCustomDSConfigP,aParentElement) | |||
63 | { | |||
64 | /* nop for now */ | |||
65 | } // TApiFieldMapArrayItem::TApiFieldMapArrayItem | |||
66 | ||||
67 | ||||
68 | void TApiFieldMapArrayItem::checkAttrs(const char **aAttributes) | |||
69 | { | |||
70 | /* nop for now */ | |||
71 | inherited::checkAttrs(aAttributes); | |||
72 | } // TApiFieldMapArrayItem::checkAttrs | |||
73 | ||||
74 | #endif | |||
75 | ||||
76 | ||||
77 | ||||
78 | // TPluginDSConfig | |||
79 | // ============ | |||
80 | ||||
81 | TPluginDSConfig::TPluginDSConfig(const char* aName, TConfigElement *aParentElement) : | |||
82 | inherited(aName,aParentElement), | |||
83 | fPluginParams_Admin(this), | |||
84 | fPluginParams_Data(this) | |||
85 | { | |||
86 | // nop so far | |||
87 | clear(); | |||
88 | } // TPluginDSConfig::TPluginDSConfig | |||
89 | ||||
90 | ||||
91 | TPluginDSConfig::~TPluginDSConfig() | |||
92 | { | |||
93 | clear(); | |||
94 | // disconnect from the API module | |||
95 | fDBApiConfig_Data.Disconnect(); | |||
96 | fDBApiConfig_Admin.Disconnect(); | |||
97 | } // TPluginDSConfig::~TPluginDSConfig | |||
98 | ||||
99 | ||||
100 | // init defaults | |||
101 | void TPluginDSConfig::clear(void) | |||
102 | { | |||
103 | // init defaults | |||
104 | fDBAPIModule_Admin.erase(); | |||
105 | fDBAPIModule_Data.erase(); | |||
106 | fDataModuleAlsoHandlesAdmin=false; | |||
107 | fEarlyStartDataRead = false; | |||
108 | // - default to use all debug flags set (if debug for plugin is enabled at all) | |||
109 | fPluginDbgMask_Admin=0xFFFF; | |||
110 | fPluginDbgMask_Data=0xFFFF; | |||
111 | // - clear plugin params | |||
112 | fPluginParams_Admin.clear(); | |||
113 | fPluginParams_Data.clear(); | |||
114 | // - clear capabilities | |||
115 | fItemAsKey = false; | |||
116 | fResumeSupported = true; | |||
117 | fHasDeleteSyncSet = false; | |||
118 | // clear inherited | |||
119 | inherited::clear(); | |||
120 | } // TPluginDSConfig::clear | |||
121 | ||||
122 | ||||
123 | // config element parsing | |||
124 | bool TPluginDSConfig::localStartElement(const char *aElementName, const char **aAttributes, sInt32 aLine) | |||
125 | { | |||
126 | // checking the elements | |||
127 | if (strucmp(aElementName,"plugin_module")==0) | |||
128 | expectMacroString(fDBAPIModule_Data); | |||
129 | else if (strucmp(aElementName,"plugin_datastoreadmin")==0) | |||
130 | expectBool(fDataModuleAlsoHandlesAdmin); | |||
131 | else if (strucmp(aElementName,"plugin_earlystartdataread")==0) | |||
132 | expectBool(fEarlyStartDataRead); | |||
133 | else if (strucmp(aElementName,"plugin_params")==0) | |||
134 | expectChildParsing(fPluginParams_Data); | |||
135 | else if (strucmp(aElementName,"plugin_debugflags")==0) | |||
136 | expectUInt16(fPluginDbgMask_Data); | |||
137 | else if (strucmp(aElementName,"plugin_module_admin")==0) | |||
138 | expectMacroString(fDBAPIModule_Admin); | |||
139 | else if (strucmp(aElementName,"plugin_params_admin")==0) | |||
140 | expectChildParsing(fPluginParams_Admin); | |||
141 | else if (strucmp(aElementName,"plugin_debugflags_admin")==0) | |||
142 | expectUInt16(fPluginDbgMask_Admin); | |||
143 | else | |||
144 | return inherited::localStartElement(aElementName,aAttributes,aLine); | |||
145 | // ok | |||
146 | return true; | |||
147 | } // TPluginDSConfig::localStartElement | |||
148 | ||||
149 | ||||
150 | // resolve | |||
151 | void TPluginDSConfig::localResolve(bool aLastPass) | |||
152 | { | |||
153 | // resolve plugin specific config leaf | |||
154 | fPluginParams_Admin.Resolve(aLastPass); | |||
155 | fPluginParams_Data.Resolve(aLastPass); | |||
156 | // try to resolve configured API-module name into set of function pointers | |||
157 | if (aLastPass) { | |||
158 | // Determine if we may use non-built-in plugins | |||
159 | bool allowDLL= true; // by default, it is allowed, but if PLUGIN_DLL is not set, it will be disabled anyway. | |||
160 | #if defined(SYSER_REGISTRATION) && !defined(DLL_PLUGINS_ALWAYS_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 | |||
250 | TLocalEngineDS *TPluginDSConfig::newLocalDataStore(TSyncSession *aSessionP) | |||
251 | { | |||
252 | // Synccap defaults to normal set supported by the engine by default | |||
253 | TLocalEngineDS *ldsP; | |||
254 | if (IS_CLIENT(!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 | |||
272 | TPluginApiDS::TPluginApiDS( | |||
273 | TPluginDSConfig *aConfigP, | |||
274 | sysync::TSyncSession *aSessionP, | |||
275 | const char *aName, | |||
276 | uInt32 aCommonSyncCapMask | |||
277 | ) : | |||
278 | #ifdef SDK_ONLY_SUPPORT | |||
279 | TCustomImplDS(aConfigP,aSessionP, aName, aCommonSyncCapMask) | |||
280 | #else | |||
281 | TODBCApiDS(aConfigP,aSessionP, aName, aCommonSyncCapMask) | |||
282 | #endif | |||
283 | { | |||
284 | // save a type casted pointer to the agent | |||
285 | fPluginAgentP=static_cast<TPluginApiAgent *>(aSessionP); | |||
286 | // save pointer to config record | |||
287 | fPluginDSConfigP=aConfigP; | |||
288 | // make a local copy of the typed agent pointer (note that the agent itself does | |||
289 | // NOT YET have its constructor completely run so we can't just copy the agents pointer) | |||
290 | fPluginAgentConfigP = DYN_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 | ||||
300 | TPluginApiDS::~TPluginApiDS() | |||
301 | { | |||
302 | InternalResetDataStore(); | |||
303 | } // TPluginApiDS::~TPluginApiDS | |||
304 | ||||
305 | ||||
306 | /// @brief called while agent is still fully ok, so we must clean up such that later call of destructor does NOT access agent any more | |||
307 | void TPluginApiDS::announceAgentDestruction(void) | |||
308 | { | |||
309 | // reset myself | |||
310 | InternalResetDataStore(); | |||
311 | // make sure we don't access the agent any more | |||
312 | engTerminateDatastore(); | |||
313 | fPluginAgentP = NULL__null; | |||
314 | // destroy API context | |||
315 | fDBApi_Data.DeleteContext(); | |||
316 | fDBApi_Admin.DeleteContext(); | |||
317 | // call inherited | |||
318 | inherited::announceAgentDestruction(); | |||
319 | } // TPluginApiDS::announceAgentDestruction | |||
320 | ||||
321 | ||||
322 | /// @brief called to reset datastore | |||
323 | /// @note must be safe to be called multiple times and even after announceAgentDestruction() | |||
324 | void TPluginApiDS::InternalResetDataStore(void) | |||
325 | { | |||
326 | // filtering capabilities need to be evaluated first | |||
327 | fAPICanFilter = false; | |||
328 | fAPIFiltersTested = false; | |||
329 | } // TPluginApiDS::InternalResetDataStore | |||
330 | ||||
331 | ||||
332 | ||||
333 | ||||
334 | #ifdef DBAPI_TEXTITEMS1 | |||
335 | ||||
336 | // store API key/value pair field in mapped field, if one is defined | |||
337 | bool TPluginApiDS::storeField( | |||
338 | cAppCharP aName, | |||
339 | cAppCharP aParams, | |||
340 | cAppCharP aValue, | |||
341 | TMultiFieldItem &aItem, | |||
342 | uInt16 aSetNo, | |||
343 | sInt16 aArrayIndex | |||
344 | ) | |||
345 | { | |||
346 | TFieldMapList *fmlP = &(fPluginDSConfigP->fFieldMappings.fFieldMapList); | |||
347 | TFieldMapList::iterator pos; | |||
348 | TApiFieldMapItem *fmiP; | |||
349 | string s; | |||
350 | bool stored=false; | |||
351 | // search field map list for matching map entry | |||
352 | for (pos=fmlP->begin(); pos!=fmlP->end(); pos++) { | |||
353 | fmiP = static_cast<TApiFieldMapItem *>(*pos); | |||
354 | // check name | |||
355 | if ( | |||
356 | fmiP->readable && | |||
357 | fmiP->setNo==aSetNo && | |||
358 | strucmp(fmiP->getName(),aName)==0 | |||
359 | ) { | |||
360 | // DB-readable field with matching name | |||
361 | TDBFieldType dbfty = fmiP->dbfieldtype; | |||
362 | TItemField *fieldP; | |||
363 | sInt16 fid = fmiP->fid; | |||
364 | // determine leaf field | |||
365 | fieldP = getMappedFieldOrVar(aItem,fid,aArrayIndex); | |||
366 | // continue only if we have a field | |||
367 | if (!fieldP) continue; | |||
368 | // check if the field is proxyable and input defines a BLOB id | |||
369 | #ifdef STREAMFIELD_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. | |||
431 | bool TPluginApiDS::parseDBItemData( | |||
432 | TMultiFieldItem &aItem, | |||
433 | cAppCharP aItemData, | |||
434 | uInt16 aSetNo | |||
435 | ) | |||
436 | { | |||
437 | bool stored = parseItemData(aItem,aItemData,aSetNo); | |||
438 | if (stored) { | |||
439 | // post-process | |||
440 | stored = postReadProcessItem(aItem,aSetNo); | |||
441 | } | |||
442 | return stored; | |||
443 | } // TPluginApiDS::parseItemData | |||
444 | ||||
445 | ||||
446 | ||||
447 | // generate text representations of item's fields (BLOBs and parametrized fields not included) | |||
448 | // - returns true if at least one field appended | |||
449 | bool TPluginApiDS::generateDBItemData( | |||
450 | bool aAssignedOnly, | |||
451 | TMultiFieldItem &aItem, | |||
452 | uInt16 aSetNo, | |||
453 | string &aDataFields | |||
454 | ) | |||
455 | { | |||
456 | TFieldMapList *fmlP = &(fPluginDSConfigP->fFieldMappings.fFieldMapList); | |||
457 | TFieldMapList::iterator pos; | |||
458 | TApiFieldMapItem *fmiP; | |||
459 | string val; | |||
460 | bool createdone=false; | |||
461 | ||||
462 | // pre-process (run scripts) | |||
463 | if (!preWriteProcessItem(aItem)) return false; | |||
464 | // create text representation for all mapped and writable fields | |||
465 | for (pos=fmlP->begin(); pos!=fmlP->end(); pos++) { | |||
466 | fmiP = static_cast<TApiFieldMapItem *>(*pos); | |||
467 | if ( | |||
468 | fmiP->writable && | |||
469 | fmiP->setNo==aSetNo | |||
470 | ) { | |||
471 | // get field | |||
472 | TItemField *basefieldP; | |||
473 | sInt16 fid = fmiP->fid; | |||
474 | // determine base field (might be array) | |||
475 | basefieldP = getMappedBaseFieldOrVar(aItem,fid); | |||
476 | if (generateItemFieldData( | |||
477 | aAssignedOnly, | |||
478 | fPluginDSConfigP->fDataCharSet, | |||
479 | fPluginDSConfigP->fDataLineEndMode, | |||
480 | fPluginDSConfigP->fDataTimeZone, | |||
481 | basefieldP, | |||
482 | fmiP->getName(), | |||
483 | aDataFields | |||
484 | )) | |||
485 | createdone=true; // we now have at least one field | |||
486 | } // if writable field | |||
487 | } // for all field mappings | |||
488 | PDEBUGPRINTFX(DBG_USERDATA+DBG_DBAPI+DBG_EXOTIC+DBG_HOT,("generateDBItemData generated string for DBApi:")){ 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) | |||
499 | bool 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) | |||
602 | bool 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 | |||
622 | bool TPluginApiDS::writeBlobs( | |||
623 | bool aAssignedOnly, | |||
624 | TMultiFieldItem &aItem, | |||
625 | uInt16 aSetNo | |||
626 | ) | |||
627 | { | |||
628 | TFieldMapList *fmlP = &(fPluginDSConfigP->fFieldMappings.fFieldMapList); | |||
629 | TFieldMapList::iterator pos; | |||
630 | TApiFieldMapItem *fmiP; | |||
631 | string blobfieldname; | |||
632 | bool blobwritten=false; | |||
633 | TSyError dberr; | |||
634 | ||||
635 | // store all parametrized fields or BLOBs one by one | |||
636 | for (pos=fmlP->begin(); pos!=fmlP->end(); pos++) { | |||
637 | fmiP = static_cast<TApiFieldMapItem *>(*pos); | |||
638 | if ( | |||
639 | fmiP->writable && | |||
640 | fmiP->setNo==aSetNo | |||
641 | ) { | |||
642 | TItemField *basefieldP,*leaffieldP; | |||
643 | sInt16 fid = fmiP->fid; | |||
644 | // determine base field (might be array) | |||
645 | basefieldP = getMappedBaseFieldOrVar(aItem,fid); | |||
646 | // ignore map if we have no field for it | |||
647 | if (!basefieldP) continue; | |||
648 | // ignore map if field is not assigned and assignedonly flag is set | |||
649 | if (aAssignedOnly && basefieldP->isUnassigned()) continue; | |||
650 | // omit all non-BLOB normal fields | |||
651 | // Note: in ItemKey mode, blobs must have the as_aparam flag to be written as blobs. | |||
652 | // in textItem mode, all fty_blob based fields will ALWAYS be written as blobs. | |||
653 | if (!(fmiP->as_param || (basefieldP->elementsBasedOn(fty_blob) && !fPluginDSConfigP->fItemAsKey))) continue; | |||
654 | // yes, we want to write this field as a BLOB | |||
655 | #ifdef ARRAYFIELD_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 | ||||
775 | bool TPluginApiDS::deleteBlobs( | |||
776 | bool aAssignedOnly, | |||
777 | TMultiFieldItem &aItem, | |||
778 | uInt16 aSetNo | |||
779 | ) | |||
780 | { | |||
781 | TFieldMapList *fmlP = &(fPluginDSConfigP->fFieldMappings.fFieldMapList); | |||
782 | TFieldMapList::iterator pos; | |||
783 | TApiFieldMapItem *fmiP; | |||
784 | string blobfieldname; | |||
785 | bool blobdeleted=false; | |||
786 | TSyError dberr; | |||
787 | ||||
788 | // store all parametrized fields or BLOBs one by one | |||
789 | for (pos=fmlP->begin(); pos!=fmlP->end(); pos++) { | |||
790 | fmiP = static_cast<TApiFieldMapItem *>(*pos); | |||
791 | if (fmiP->writable && | |||
792 | fmiP->setNo==aSetNo) { | |||
793 | // get field | |||
794 | TItemField *basefieldP,*leaffieldP; | |||
795 | sInt16 fid = fmiP->fid; | |||
796 | // determine base field (might be array) | |||
797 | basefieldP = getMappedBaseFieldOrVar(aItem,fid); | |||
798 | // ignore map if we have no field for it | |||
799 | if (!basefieldP) continue; | |||
800 | ||||
801 | // %%% what is this, obsolete?? | |||
802 | // ignore map if field is not assigned and assignedonly flag is set | |||
803 | //if (aAssignedOnly && basefieldP->isUnassigned()) continue; | |||
804 | ||||
805 | // omit all non-BLOB normal fields | |||
806 | // Note: in ItemKey mode, blobs must have the as_aparam flag to be written as blobs. | |||
807 | // in textItem mode, all fty_blob based fields will ALWAYS be written as blobs. | |||
808 | if (!(fmiP->as_param || (basefieldP->elementsBasedOn(fty_blob) && !fPluginDSConfigP->fItemAsKey))) continue; | |||
809 | // yes, we want to write this field as a BLOB | |||
810 | ||||
811 | #ifdef ARRAYFIELD_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) | |||
859 | bool TPluginApiDS::dsResumeSupportedInDB(void) | |||
860 | { | |||
861 | if (fPluginDSConfigP->fDBApiConfig_Admin.Connected()) { | |||
862 | // we can do resume if plugin supports it | |||
863 | return fPluginDSConfigP->fResumeSupported && fPluginDSConfigP->fDBApiConfig_Admin.Version()>=sInt32(VE_InsertMapItem); | |||
864 | } | |||
865 | return inherited::dsResumeSupportedInDB(); | |||
866 | } // TPluginApiDS::dsResumeSupportedInDB | |||
867 | ||||
868 | ||||
869 | #ifdef OBJECT_FILTERING1 | |||
870 | ||||
871 | // - returns true if DB implementation can also apply special filters like CGI-options | |||
872 | // /dr(x,y) etc. during fetching | |||
873 | bool TPluginApiDS::dsOptionFilterFetchesFromDB(void) | |||
874 | { | |||
875 | #ifndef SDK_ONLY_SUPPORT | |||
876 | // if we are not connected, let immediate ancestor check it (e.g. SQL/ODBC) | |||
877 | if (!fDBApi_Data.Created()) return inherited::dsOptionFilterFetchesFromDB(); | |||
878 | #endif | |||
879 | #ifdef SYSYNC_TARGET_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. | |||
915 | bool TPluginApiDS::dsFilteredFetchesFromDB(bool aFilterChanged) | |||
916 | { | |||
917 | #ifndef SDK_ONLY_SUPPORT | |||
918 | // if we are not connected, let immediate ancestor check it (e.g. SQL/ODBC) | |||
919 | // Note that this can happen when dsFilteredFetchesFromDB() is called via Alertscript to resolve filter dependencies | |||
920 | // before the DS is actually connected. In this case, the return value is not checked so that's ok. | |||
921 | // Before actually laoding or zapping the sync set this is called once again with connected data plugin. | |||
922 | if (!fDBApi_Data.Created()) return inherited::dsFilteredFetchesFromDB(aFilterChanged); | |||
923 | #endif | |||
924 | if (aFilterChanged || !fAPIFiltersTested) { | |||
925 | fAPIFiltersTested = true; | |||
926 | // Anyway, let DBApi know (even if all filters are empty) | |||
927 | string filters; | |||
928 | // - local DB filter (=static filter, from config) | |||
929 | filters = "staticfilter:"; | |||
930 | filters += fLocalDBFilter.c_str(); | |||
931 | // - dynamic sync set filter | |||
932 | filters += "\r\ndynamicfilter:"; | |||
933 | filters += fSyncSetFilter.c_str(); | |||
934 | // - invisible filter (those that MATCH this filter should NOT be included) | |||
935 | filters += "\r\ninvisiblefilter:"; | |||
936 | filters += fPluginDSConfigP->fInvisibleFilter.c_str(); | |||
937 | // - let plugin know and check (we can filter at DBlevel if plugin understands both start/end) | |||
938 | filters += "\r\n"; | |||
939 | fAPICanFilter = | |||
940 | (fDBApi_Data.FilterSupport(filters.c_str())>=3) || | |||
941 | (fLocalDBFilter.empty() && fSyncSetFilter.empty() && fPluginDSConfigP->fInvisibleFilter.empty()); // no filter set = we can "filter" | |||
942 | } | |||
943 | // if we can filter, that's sufficient | |||
944 | if (fAPICanFilter) return true; | |||
945 | // otherwise, let implementation test (not immediate anchestor, which might be a different API like ODBC) | |||
946 | return TCustomImplDS::dsFilteredFetchesFromDB(aFilterChanged); | |||
947 | } // TPluginApiDS::dsFilteredFetchesFromDB | |||
948 | ||||
949 | #endif | |||
950 | ||||
951 | ||||
952 | ||||
953 | // can return 508 to force a slow sync. Other errors abort the sync | |||
954 | localstatus TPluginApiDS::apiEarlyDataAccessStart(void) | |||
955 | { | |||
956 | TSyError dberr = LOCERR_OK; | |||
957 | if (fPluginDSConfigP->fEarlyStartDataRead) { | |||
958 | // prepare | |||
959 | dberr = apiPrepareReadSyncSet(); | |||
960 | if (dberr==LOCERR_OK) { | |||
961 | // start the reading phase anyway (to make sure call order is always StartRead/EndRead/StartWrite/EndWrite) | |||
962 | dberr = fDBApi_Data.StartDataRead(fPreviousToRemoteSyncIdentifier.c_str(),fPreviousSuspendIdentifier.c_str()); | |||
963 | if (dberr!=LOCERR_OK) { | |||
964 | PDEBUGPRINTFX(DBG_ERROR,("apiEarlyDataAccessStart - DBapi::StartDataRead error: %hd",dberr)){ 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 | |||
973 | localstatus 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) | |||
1010 | localstatus TPluginApiDS::apiReadSyncSet(bool aNeedAll) | |||
1011 | { | |||
1012 | TSyError dberr=LOCERR_OK; | |||
1013 | #ifdef SYDEBUG2 | |||
1014 | string ts1,ts2; | |||
1015 | #endif | |||
1016 | ||||
1017 | if (!fPluginDSConfigP->fEarlyStartDataRead) { | |||
| ||||
1018 | // normal sequence, start data read is not called before starting to read the sync set | |||
1019 | dberr = apiPrepareReadSyncSet(); | |||
1020 | if (dberr!=LOCERR_OK) | |||
1021 | goto endread; | |||
1022 | } | |||
1023 | #ifndef SDK_ONLY_SUPPORT | |||
1024 | // only handle here if we are in charge - otherwise let ancestor handle it | |||
1025 | if (!fDBApi_Data.Created()) | |||
1026 | return inherited::apiReadSyncSet(aNeedAll); | |||
1027 | #endif | |||
1028 | ||||
1029 | // just let plugin know if we want data (if it actually does is the plugin's choice) | |||
1030 | if (aNeedAll) { | |||
1031 | // we'll need all data in the datastore in the end, let datastore know | |||
1032 | // Note: this is a suggestion to the plugin only - plugin does not need to follow it | |||
1033 | // and can return only ID/changed or all data for both states of this flag, | |||
1034 | // even changing on a item-by-item basis (can make sense for optimization). | |||
1035 | // The Plugin will return "1" here only in case it really follows the suggestion | |||
1036 | // an WILL return all data at ReadNextItem(). Otherwise, it may or may | |||
1037 | // not return data on a item by item basis (which is handled by the code | |||
1038 | // below). Therefore, at this time, the engine does not make use of the | |||
1039 | // ContextSupport() return value here. | |||
1040 | fDBApi_Data.ContextSupport("ReadNextItem:allfields\n\r"); | |||
1041 | } | |||
1042 | #ifdef SCRIPT_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)) { | |||
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) { | |||
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())) { | |||
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; | |||
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) { | |||
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()) { | |||
1103 | mfitemP = | |||
1104 | (TMultiFieldItem *) newItemForRemote( | |||
1105 | ity_multifield | |||
1106 | ); | |||
1107 | } | |||
1108 | // as key (Note: will be functional key but w/o any fields in case we pass NULL item pointer) | |||
1109 | TDBItemKey *itemKeyP = newDBItemKey(mfitemP); | |||
1110 | dberr=fDBApi_Data.ReadNextItemAsKey(itemAndParentID, (KeyH)itemKeyP, itemstatus, firstReadNextItem); | |||
1111 | // check if plugin wrote something to our key. If so, we assume this is the item and save | |||
1112 | // it, EVEN IF we did not request getting item data. | |||
1113 | if (!itemKeyP->isWritten()) { | |||
1114 | // nothing in this item, forget it | |||
1115 | delete itemKeyP; // key first | |||
1116 | if (mfitemP) delete mfitemP; // then item if we had one at all | |||
1117 | mfitemP=NULL__null; | |||
1118 | } | |||
1119 | else { | |||
1120 | // got item, delete the key | |||
1121 | delete itemKeyP; | |||
1122 | // post-process (run scripts, create BLOB proxies if needed) | |||
1123 | postReadProcessItem(*mfitemP,0); | |||
| ||||
1124 | } | |||
1125 | } | |||
1126 | else | |||
1127 | #endif | |||
1128 | #ifdef DBAPI_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 | |||
1211 | endread: | |||
1212 | // then end read here | |||
1213 | if (dberr==LOCERR_OK) { | |||
1214 | dberr=fDBApi_Data.EndDataRead(); | |||
1215 | if (dberr!=LOCERR_OK) { | |||
1216 | PDEBUGPRINTFX(DBG_ERROR,("DBapi::EndDataRead failed, err=%hd",dberr)){ 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 | |||
1225 | bool TPluginApiDS::apiNeedSyncSetToZap(void) | |||
1226 | { | |||
1227 | #ifndef SDK_ONLY_SUPPORT | |||
1228 | // only handle here if we are in charge - otherwise let ancestor handle it | |||
1229 | if (!fDBApi_Data.Created()) return inherited::apiNeedSyncSetToZap(); | |||
1230 | #endif | |||
1231 | // only if we have deleteSyncSet on API level AND api can also apply all filters, we don't need the syncset to zap the datastore | |||
1232 | return !(fPluginDSConfigP->fHasDeleteSyncSet && engFilteredFetchesFromDB(false)); | |||
1233 | } // TPluginApiDS::apiNeedSyncSetToZap | |||
1234 | ||||
1235 | ||||
1236 | // Zap all data in syncset (note that everything outside the sync set will remain intact) | |||
1237 | localstatus TPluginApiDS::apiZapSyncSet(void) | |||
1238 | { | |||
1239 | #ifndef SDK_ONLY_SUPPORT | |||
1240 | // only handle here if we are in charge - otherwise let ancestor handle it | |||
1241 | if (!fDBApi_Data.Created()) return inherited::apiZapSyncSet(); | |||
1242 | #endif | |||
1243 | TSyError dberr = LOCERR_OK; | |||
1244 | // API must be able to process current filters in order to execute a zap - otherwise we would delete | |||
1245 | // more than the sync set defined by local filters. | |||
1246 | bool apiCanZap = engFilteredFetchesFromDB(false); | |||
1247 | if (apiCanZap) { | |||
1248 | // try to use plugin's specialized implementation | |||
1249 | dberr = fDBApi_Data.DeleteSyncSet(); | |||
1250 | apiCanZap = dberr!=LOCERR_NOTIMP; // API claims to be able to zap (but still might have failed with a DBerr in this case!) | |||
1251 | } | |||
1252 | // do it one by one if DeleteAllItems() is not implemented or plugin cannot apply current filters | |||
1253 | if (!apiCanZap) { | |||
1254 | dberr = zapSyncSetOneByOne(); | |||
1255 | } | |||
1256 | // return status | |||
1257 | return dberr; | |||
1258 | } // TPluginApiDS::apiZapSyncSet | |||
1259 | ||||
1260 | ||||
1261 | // fetch actual record from DB by localID. SyncSetItem might be passed to give additional information | |||
1262 | // such as containerid | |||
1263 | localstatus TPluginApiDS::apiFetchItem(TMultiFieldItem &aItem, bool aReadPhase, TSyncSetItem *aSyncSetItemP) | |||
1264 | { | |||
1265 | #ifndef SDK_ONLY_SUPPORT | |||
1266 | // only handle here if we are in charge - otherwise let ancestor handle it | |||
1267 | if (!fDBApi_Data.Created()) return inherited::apiFetchItem(aItem, aReadPhase, aSyncSetItemP); | |||
1268 | #endif | |||
1269 | ||||
1270 | TSyError dberr=LOCERR_OK; | |||
1271 | ItemID_Struct itemAndParentID; | |||
1272 | ||||
1273 | // set up item ID and parent ID | |||
1274 | itemAndParentID.item=(appCharP)aItem.getLocalID(); | |||
1275 | itemAndParentID.parent=const_cast<char *>(""); | |||
1276 | ||||
1277 | // two API variants | |||
1278 | #if defined(DBAPI_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 | |||
1313 | localstatus TPluginApiDS::apiStartDataWrite(void) | |||
1314 | { | |||
1315 | #ifndef SDK_ONLY_SUPPORT | |||
1316 | // only handle here if we are in charge - otherwise let ancestor handle it | |||
1317 | if (!fDBApi_Data.Created()) return inherited::apiStartDataWrite(); | |||
1318 | #endif | |||
1319 | ||||
1320 | TSyError dberr=fDBApi_Data.StartDataWrite(); | |||
1321 | if (dberr!=LOCERR_OK) { | |||
1322 | PDEBUGPRINTFX(DBG_ERROR,("DBapi::StartDataWrite returns dberr=%hd",dberr)){ if (((0x00000002) & getDbgMask()) == (0x00000002)) getDbgLogger ()->setNextMask(0x00000002).DebugPrintfLastMask ("DBapi::StartDataWrite returns dberr=%hd" ,dberr); }; | |||
1323 | } | |||
1324 | return dberr; | |||
1325 | } // TPluginApiDS::apiStartDataWrite | |||
1326 | ||||
1327 | struct 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 | |||
1338 | localstatus TPluginApiDS::apiAddItem(TMultiFieldItem &aItem, string &aLocalID) | |||
1339 | { | |||
1340 | #ifndef SDK_ONLY_SUPPORT | |||
1341 | // only handle here if we are in charge - otherwise let ancestor handle it | |||
1342 | if (!fDBApi_Data.Created()) return inherited::apiAddItem(aItem, aLocalID); | |||
1343 | #endif | |||
1344 | ||||
1345 | TSyError dberr=LOCERR_OK; | |||
1346 | TDB_Api_ItemID itemAndParentID; | |||
1347 | ||||
1348 | #ifdef SCRIPT_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) | |||
1451 | bool TPluginApiDS::dsFinalizeLocalID(string &aLocalID) | |||
1452 | { | |||
1453 | #ifndef SDK_ONLY_SUPPORT | |||
1454 | // only handle here if we are in charge - otherwise let ancestor handle it | |||
1455 | if (!fDBApi_Data.Created()) return inherited::dsFinalizeLocalID(aLocalID); | |||
1456 | #else | |||
1457 | // still check for DBAPi to be ready at this point, because when peer messes up protocol, we | |||
1458 | // can get here before the datastore has been initialized at all | |||
1459 | if (!fDBApi_Data.Created()) return false; // no dataset loaded -> all localids are final (from last session) | |||
1460 | #endif | |||
1461 | ||||
1462 | TDB_Api_Str finalizedID; | |||
1463 | localstatus sta = fDBApi_Data.FinalizeLocalID(aLocalID.c_str(),finalizedID); | |||
1464 | if (sta==LOCERR_OK && !finalizedID.empty()) { | |||
1465 | // pass modified ID back | |||
1466 | aLocalID = finalizedID.c_str(); | |||
1467 | // ID was updated | |||
1468 | return true; | |||
1469 | } | |||
1470 | // no change - ID is ok as-is | |||
1471 | return false; | |||
1472 | } // TPluginApiDS::dsFinalizeLocalID | |||
1473 | ||||
1474 | #endif // SYSYNC_CLIENT | |||
1475 | ||||
1476 | ||||
1477 | ||||
1478 | // update existing item in datastore, returns 404 if item not found | |||
1479 | localstatus TPluginApiDS::apiUpdateItem(TMultiFieldItem &aItem) | |||
1480 | { | |||
1481 | #ifndef SDK_ONLY_SUPPORT | |||
1482 | // only handle here if we are in charge - otherwise let ancestor handle it | |||
1483 | if (!fDBApi_Data.Created()) return inherited::apiUpdateItem(aItem); | |||
1484 | #endif | |||
1485 | ||||
1486 | TSyError dberr=LOCERR_OK; | |||
1487 | TDB_Api_ItemID updItemAndParentID; | |||
1488 | ItemID_Struct itemAndParentID; | |||
1489 | ||||
1490 | // set up item ID and parent ID | |||
1491 | itemAndParentID.item=(appCharP)aItem.getLocalID(); | |||
1492 | itemAndParentID.parent=const_cast<char *>(""); | |||
1493 | ||||
1494 | #ifdef SCRIPT_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 | |||
1595 | localstatus TPluginApiDS::apiDeleteItem(TMultiFieldItem &aItem) | |||
1596 | { | |||
1597 | #ifndef SDK_ONLY_SUPPORT | |||
1598 | // only handle here if we are in charge - otherwise let ancestor handle it | |||
1599 | if (!fDBApi_Data.Created()) return inherited::apiDeleteItem(aItem); | |||
1600 | #endif | |||
1601 | ||||
1602 | TSyError dberr=LOCERR_OK; | |||
1603 | ||||
1604 | // delete item | |||
1605 | dberr=fDBApi_Data.DeleteItem( aItem.getLocalID() ); | |||
1606 | if (dberr==LOCERR_OK) { | |||
1607 | deleteBlobs(true,aItem,0); // Item related blobs must be removed as well | |||
1608 | #ifdef SCRIPT_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) | |||
1629 | localstatus TPluginApiDS::apiEndDataWrite(string &aThisSyncIdentifier) | |||
1630 | { | |||
1631 | #ifndef SDK_ONLY_SUPPORT | |||
1632 | // only handle here if we are in charge - otherwise let ancestor handle it | |||
1633 | if (!fDBApi_Data.Created()) return inherited::apiEndDataWrite(aThisSyncIdentifier); | |||
1634 | #endif | |||
1635 | ||||
1636 | // nothing special to do in ODBC case, as we do not have a separate sync identifier | |||
1637 | TDB_Api_Str newSyncIdentifier; | |||
1638 | TSyError sta = fDBApi_Data.EndDataWrite(true, newSyncIdentifier); | |||
1639 | aThisSyncIdentifier=newSyncIdentifier.c_str(); | |||
1640 | return sta; | |||
1641 | } // TPluginApiDS::apiEndDataWrite | |||
1642 | ||||
1643 | ||||
1644 | ||||
1645 | // must be called before starting a thread. If returns false, starting a thread now | |||
1646 | // is not allowed and must be postponed. | |||
1647 | // Includes ThreadMayChange() call | |||
1648 | bool TPluginApiDS::startingThread(void) | |||
1649 | { | |||
1650 | // %%% tbd: if modules are completely independent, we might not need this in all cases | |||
1651 | if (!dbAccessLocked()) { | |||
1652 | static_cast<TPluginApiAgent *>(fSessionP)->fApiLocked=true; | |||
1653 | // Now post possible thread change to the API | |||
1654 | // - on the database level | |||
1655 | ThreadMayChangeNow(); | |||
1656 | // - on the session level as well (to make sure, and because we will post a change back | |||
1657 | // at the end of the thread, which will be called FROM the new thread, which constitutes | |||
1658 | // executing something in the session context. | |||
1659 | static_cast<TPluginApiAgent *>(fSessionP)->getDBApiSession()->ThreadMayChangeNow(); | |||
1660 | return true; | |||
1661 | } | |||
1662 | else | |||
1663 | return false; | |||
1664 | } // TPluginApiDS::startingThread | |||
1665 | ||||
1666 | ||||
1667 | // - must be called when a thread's activity has ended | |||
1668 | // BUT THE CALL MUST BE FROM THE ENDING THREAD, not the main thread! | |||
1669 | void TPluginApiDS::endingThread(void) { | |||
1670 | // thread may change for the API now again | |||
1671 | // - on the database level | |||
1672 | ThreadMayChangeNow(); | |||
1673 | // - on the session level as well | |||
1674 | static_cast<TPluginApiAgent *>(fSessionP)->getDBApiSession()->ThreadMayChangeNow(); | |||
1675 | // Now other threads are allowed to access the API again | |||
1676 | static_cast<TPluginApiAgent *>(fSessionP)->fApiLocked=false; | |||
1677 | } // TPluginApiDS::endingThread | |||
1678 | ||||
1679 | ||||
1680 | // should be called before doing DB accesses that might be locked (e.g. because another thread is using the DB resources) | |||
1681 | bool TPluginApiDS::dbAccessLocked(void) | |||
1682 | { | |||
1683 | return static_cast<TPluginApiAgent *>(fSessionP)->fApiLocked; | |||
1684 | } // TPluginApiDS::dbAccessLocked | |||
1685 | ||||
1686 | ||||
1687 | // - alert possible thread change to plugins | |||
1688 | // Does not check if API is locked or not, see dsThreadMayChangeNow() | |||
1689 | void TPluginApiDS::ThreadMayChangeNow(void) | |||
1690 | { | |||
1691 | // let API know, thread might change for next request (but not necessarily does!) | |||
1692 | if (fDBApi_Data.Created()) fDBApi_Data.ThreadMayChangeNow(); | |||
1693 | if (fDBApi_Admin.Created()) fDBApi_Admin.ThreadMayChangeNow(); | |||
1694 | } // TPluginApiDS::ThreadMayChangeNow | |||
1695 | ||||
1696 | ||||
1697 | // - engine Thread might change | |||
1698 | void TPluginApiDS::dsThreadMayChangeNow(void) | |||
1699 | { | |||
1700 | // Do not post thread change infos when DB access is locked. | |||
1701 | // If it is locked, the thread that locked it will call a | |||
1702 | // ThreadMayChangeNow() to the API when the thread terminates (in endingThread()). | |||
1703 | if (!dbAccessLocked()) { | |||
1704 | ThreadMayChangeNow(); | |||
1705 | } | |||
1706 | // let ancestor do it's own stuff | |||
1707 | inherited::dsThreadMayChangeNow(); | |||
1708 | } // TPluginApiDS::dsThreadMayChangeNow | |||
1709 | ||||
1710 | ||||
1711 | ||||
1712 | // - connect data handling part of plugin, Returns LOCERR_NOTIMPL when no data plugin is selected | |||
1713 | // Note: this is either called as part of apiLoadAdminData (even if plugin is NOT responsible for data!) | |||
1714 | // or directly before startDataRead (in BASED_ON_BINFILE_CLIENT binfileDSActive() case) | |||
1715 | TSyError TPluginApiDS::connectDataPlugin(void) | |||
1716 | { | |||
1717 | TSyError err = LOCERR_NOTIMP; | |||
1718 | // filtering capabilities need to be reevaluated anyway | |||
1719 | fAPICanFilter = false; | |||
1720 | fAPIFiltersTested = false; | |||
1721 | // only connect if we have plugin data support | |||
1722 | if (fPluginDSConfigP->fDBApiConfig_Data.Connected()) { | |||
1723 | err = LOCERR_OK; | |||
1724 | DB_Callback cb= &fDBApi_Data.fCB.Callback; | |||
1725 | cb->callbackRef = fSessionP; // the session | |||
1726 | #ifdef ENGINEINTERFACE_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) | |||
1798 | localstatus TPluginApiDS::apiSaveAdminData(bool aDataCommitted, bool aSessionFinished) | |||
1799 | { | |||
1800 | // security - don't use API when locked | |||
1801 | if (dbAccessLocked()) return 503; // service unavailable | |||
1802 | ||||
1803 | const char* PIStored = "PIStored"; // blob name field | |||
1804 | ||||
1805 | #ifndef SDK_ONLY_SUPPORT | |||
1806 | // only handle here if we are in charge - otherwise let ancestor handle it | |||
1807 | if (!fDBApi_Admin.Created()) return inherited::apiSaveAdminData(aDataCommitted, aSessionFinished); | |||
1808 | #endif | |||
1809 | ||||
1810 | localstatus sta=LOCERR_OK; | |||
1811 | TMapContainer::iterator pos; | |||
1812 | ||||
1813 | // save the entire map list differentially | |||
1814 | pos=fMapTable.begin(); | |||
1815 | PDEBUGPRINTFX(DBG_ADMIN+DBG_EXOTIC,("apiSaveAdminData: internal map table has %ld entries (normal and others)",(long)fMapTable.size())){ 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 | |||
2067 | localstatus TPluginApiDS::apiLoadAdminData( | |||
2068 | const char *aDeviceID, | |||
2069 | const char *aDatabaseID, | |||
2070 | const char *aRemoteDBID | |||
2071 | ) | |||
2072 | { | |||
2073 | // security - don't use API when locked | |||
2074 | if (dbAccessLocked()) return 503; // service unavailable | |||
2075 | ||||
2076 | const char* PIStored = "PIStored"; // blob name field | |||
2077 | ||||
2078 | TSyError err = LOCERR_OK; | |||
2079 | ||||
2080 | // In any case - this is the time to create the contexts for the datastore | |||
2081 | // - admin if selected | |||
2082 | if (fPluginDSConfigP->fDBApiConfig_Admin.Connected()) { | |||
2083 | DB_Callback cb= &fDBApi_Admin.fCB.Callback; | |||
2084 | cb->callbackRef = fSessionP; // the session | |||
2085 | #ifdef ENGINEINTERFACE_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) | |||
2436 | void TPluginApiDS::dsLogSyncResult(void) | |||
2437 | { | |||
2438 | // security - don't use API when locked | |||
2439 | if (dbAccessLocked()) return; | |||
2440 | ||||
2441 | // format for DB Api | |||
2442 | string logData,s; | |||
2443 | logData.erase(); | |||
2444 | logData+="lastsync:"; TimestampToISO8601Str(s,fCurrentSyncTime,TCTX_UTC((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 | ||||
2512 | TApiBlobProxy::TApiBlobProxy( | |||
2513 | TPluginApiDS *aApiDsP, | |||
2514 | bool aIsStringBLOB, | |||
2515 | const char *aBlobID, | |||
2516 | const char *aParentID | |||
2517 | ) | |||
2518 | { | |||
2519 | // save values | |||
2520 | fApiDsP = aApiDsP; | |||
2521 | fIsStringBLOB = aIsStringBLOB; | |||
2522 | fBlobID = aBlobID; | |||
2523 | fParentObjectID = aParentID; | |||
2524 | fBlobSize = 0; | |||
2525 | fBlobSizeKnown = false; | |||
2526 | fFetchedSize = 0; | |||
2527 | fBufferSize = 0; | |||
2528 | fBlobBuffer = NULL__null; // nothing retrieved yet | |||
2529 | } // TApiBlobProxy::TApiBlobProxy | |||
2530 | ||||
2531 | ||||
2532 | TApiBlobProxy::~TApiBlobProxy() | |||
2533 | { | |||
2534 | if (fBlobBuffer) delete [] (char *)fBlobBuffer; // gcc 3.2.2 needs cast to suppress warning | |||
2535 | fBlobBuffer=NULL__null; | |||
2536 | } // TApiBlobProxy::~TApiBlobProxy | |||
2537 | ||||
2538 | ||||
2539 | // fetch BLOB from DPAPI | |||
2540 | void TApiBlobProxy::fetchBlob(size_t aNeededSize, bool aNeedsTotalSize, bool aNeedsAllData) | |||
2541 | { | |||
2542 | TSyError dberr=LOCERR_OK; | |||
2543 | ||||
2544 | if (fBufferSize==0 || aNeededSize>fFetchedSize) { | |||
2545 | // if do not have anything yet or not enough yet, we need to read | |||
2546 | uInt8P bufP = NULL__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 | |||
2665 | size_t TApiBlobProxy::getBlobSize(TStringField *aFieldP) | |||
2666 | { | |||
2667 | fetchBlob(0,true,false); // only needs the size, but no data | |||
2668 | return fBlobSize; | |||
2669 | } // TApiBlobProxy::getBlobSize | |||
2670 | ||||
2671 | ||||
2672 | // read from Blob from specified stream position and update stream pos | |||
2673 | size_t TApiBlobProxy::readBlobStream(TStringField *aFieldP, size_t &aPos, void *aBuffer, size_t aMaxBytes) | |||
2674 | { | |||
2675 | if (fFetchedSize<aPos+aMaxBytes || !fBlobBuffer) { | |||
2676 | // we need to read (more of) the body | |||
2677 | if (!fBlobSizeKnown || fFetchedSize<fBlobSize) { | |||
2678 | // we know that we need to fetch more, or we are not sure that we have fetched everything already -> fetch more | |||
2679 | fetchBlob(aPos+aMaxBytes,false,false); // fetch at least up to the given size (unless blob is actually smaller) | |||
2680 | } | |||
2681 | } | |||
2682 | // now copy from our buffer | |||
2683 | if (aPos>fFetchedSize) return 0; // position obviously out of range | |||
2684 | if (aPos+aMaxBytes>fFetchedSize) aMaxBytes=fFetchedSize-aPos; // reduce to what we have | |||
2685 | if (aMaxBytes==0) return 0; // safety | |||
2686 | // copy data from fBlobBuffer (which contains beginning or all of the BLOB) to caller's buffer | |||
2687 | memcpy(aBuffer,(char *)fBlobBuffer+aPos,aMaxBytes); | |||
2688 | aPos += aMaxBytes; | |||
2689 | return aMaxBytes; // return number of bytes actually read | |||
2690 | } // TApiBlobProxy::readBlobStream | |||
2691 | ||||
2692 | ||||
2693 | #endif // STREAMFIELD_SUPPORT | |||
2694 | ||||
2695 | ||||
2696 | ||||
2697 | } // namespace sysync | |||
2698 | ||||
2699 | /* end of TPluginApiDS implementation */ | |||
2700 | ||||
2701 | // eof |