]>
Commit | Line | Data |
---|---|---|
4ddfc222 | 1 | // $Id$ |
2 | ||
82dd07bb | 3 | ///************************************************************************** |
4e8187d8 | 4 | ///* This file is property of and copyright by the * |
82dd07bb | 5 | ///* ALICE Experiment at CERN, All rights reserved. * |
6 | ///* * | |
7 | ///* Primary Authors: Matthias Richter <Matthias.Richter@ift.uib.no> * | |
8 | ///* for The ALICE HLT Project. * | |
9 | ///* * | |
10 | ///* Permission to use, copy, modify and distribute this software and its * | |
11 | ///* documentation strictly for non-commercial purposes is hereby granted * | |
12 | ///* without fee, provided that the above copyright notice appears in all * | |
13 | ///* copies and that both the copyright notice and this permission notice * | |
14 | ///* appear in the supporting documentation. The authors make no claims * | |
15 | ///* about the suitability of this software for any purpose. It is * | |
16 | ///* provided "as is" without express or implied warranty. * | |
17 | ///************************************************************************** | |
18 | ||
19 | /// @file AliHLTFilePublisher.cxx | |
20 | /// @author Matthias Richter | |
21 | /// @date | |
22 | /// @brief HLT file publisher component implementation. */ | |
23 | /// | |
4ddfc222 | 24 | |
4ddfc222 | 25 | #include "AliHLTFilePublisher.h" |
82dd07bb | 26 | #include "AliHLTErrorGuard.h" |
0449b3c8 | 27 | #include "AliLog.h" |
4ddfc222 | 28 | #include <TMath.h> |
29 | #include <TFile.h> | |
30 | ||
4e8187d8 | 31 | using namespace std; |
4ddfc222 | 32 | |
33 | /** ROOT macro for the implementation of ROOT specific class methods */ | |
34 | ClassImp(AliHLTFilePublisher) | |
35 | ||
36 | AliHLTFilePublisher::AliHLTFilePublisher() | |
37 | : | |
38 | AliHLTDataSource(), | |
4ddfc222 | 39 | fpCurrent(NULL), |
d397a3b2 | 40 | fEvents(), |
0449b3c8 | 41 | fMaxSize(0), |
8a106878 | 42 | fOpenFilesAtStart(false), |
992ea13b | 43 | fOutputDataTypes(), |
44 | fIsRaw(kTRUE) | |
4ddfc222 | 45 | { |
46 | // see header file for class documentation | |
47 | // or | |
48 | // refer to README to build package | |
49 | // or | |
50 | // visit http://web.ift.uib.no/~kjeks/doc/alice-hlt | |
51 | ||
52 | // make the lists owners of their objects in order to automatically | |
53 | // de-allocate the objects | |
d397a3b2 | 54 | fEvents.SetOwner(); |
4ddfc222 | 55 | } |
56 | ||
57 | AliHLTFilePublisher::~AliHLTFilePublisher() | |
58 | { | |
4e8187d8 | 59 | // destructor |
4ddfc222 | 60 | |
61 | // file list and file name list are owner of their objects and | |
62 | // delete all the objects | |
63 | } | |
64 | ||
65 | const char* AliHLTFilePublisher::GetComponentID() | |
66 | { | |
4e8187d8 | 67 | // overloaded from AliHLTComponent |
4ddfc222 | 68 | return "FilePublisher"; |
69 | } | |
70 | ||
71 | AliHLTComponentDataType AliHLTFilePublisher::GetOutputDataType() | |
72 | { | |
4e8187d8 | 73 | // overloaded from AliHLTComponent |
8a106878 | 74 | if (fOutputDataTypes.size()==0) return kAliHLTVoidDataType; |
75 | else if (fOutputDataTypes.size()==1) return fOutputDataTypes[0]; | |
76 | return kAliHLTMultipleDataType; | |
77 | } | |
78 | ||
79 | int AliHLTFilePublisher::GetOutputDataTypes(AliHLTComponentDataTypeList& tgtList) | |
80 | { | |
4e8187d8 | 81 | // overloaded from AliHLTComponent |
8a106878 | 82 | tgtList.assign(fOutputDataTypes.begin(), fOutputDataTypes.end()); |
83 | HLTInfo("%s %p provides %d output data types", GetComponentID(), this, fOutputDataTypes.size()); | |
84 | return fOutputDataTypes.size(); | |
4ddfc222 | 85 | } |
86 | ||
87 | void AliHLTFilePublisher::GetOutputDataSize( unsigned long& constBase, double& inputMultiplier ) | |
88 | { | |
4e8187d8 | 89 | // overloaded from AliHLTComponent |
4ddfc222 | 90 | constBase=fMaxSize; |
91 | inputMultiplier=1.0; | |
92 | } | |
93 | ||
94 | AliHLTComponent* AliHLTFilePublisher::Spawn() | |
95 | { | |
4e8187d8 | 96 | // overloaded from AliHLTComponent |
4ddfc222 | 97 | return new AliHLTFilePublisher; |
98 | } | |
99 | ||
100 | int AliHLTFilePublisher::DoInit( int argc, const char** argv ) | |
101 | { | |
4e8187d8 | 102 | // overloaded from AliHLTComponent: initialization |
a9c4e244 | 103 | int iResult=0; |
18076d18 | 104 | fOpenFilesAtStart = false; |
a9c4e244 | 105 | if ((iResult=ConfigureFromArgumentString(argc, argv))<0) return iResult; |
106 | ||
107 | if (iResult>=0 && fEvents.GetSize()==0) { | |
108 | HLTError("the publisher needs at least one file argument"); | |
109 | iResult=-EINVAL; | |
110 | } | |
111 | if (iResult>=0) iResult=OpenFiles(fOpenFilesAtStart); | |
112 | if (iResult<0) { | |
113 | fEvents.Clear(); | |
114 | } | |
115 | return iResult; | |
116 | } | |
4ddfc222 | 117 | |
a9c4e244 | 118 | int AliHLTFilePublisher::ScanConfigurationArgument(int argc, const char** argv) |
119 | { | |
4e8187d8 | 120 | // overloaded from AliHLTComponent: argument scan |
4ddfc222 | 121 | //HLTDebug("%d %s", argc, argv[0]); |
122 | int iResult=0; | |
123 | TString argument=""; | |
124 | int bMissingParam=0; | |
fe050813 | 125 | int bHaveDatatype=0; |
126 | int bHaveSpecification=0; | |
d397a3b2 | 127 | AliHLTComponentDataType currDataType=kAliHLTVoidDataType; |
128 | AliHLTUInt32_t currSpecification=kAliHLTVoidDataSpec; | |
129 | EventFiles* pCurrEvent=NULL; | |
a9c4e244 | 130 | int i=0; |
131 | for (; i<argc && iResult>=0; i++) { | |
4ddfc222 | 132 | argument=argv[i]; |
133 | if (argument.IsNull()) continue; | |
134 | ||
135 | // -datafile | |
136 | if (argument.CompareTo("-datafile")==0) { | |
137 | if ((bMissingParam=(++i>=argc))) break; | |
fe050813 | 138 | if (!bHaveDatatype) { |
139 | HLTWarning("no data type available so far, please set data type and specification before the file name. The first available data type will be set for all files preceding it"); | |
140 | } | |
992ea13b | 141 | FileDesc* pDesc=new FileDesc(argv[i], currDataType, currSpecification, fIsRaw); |
d397a3b2 | 142 | if (pDesc) { |
143 | iResult=InsertFile(pCurrEvent, pDesc); | |
4ddfc222 | 144 | } else { |
145 | iResult=-ENOMEM; | |
146 | } | |
147 | ||
148 | // -datafilelist | |
149 | } else if (argument.CompareTo("-datafilelist")==0) { | |
150 | if ((bMissingParam=(++i>=argc))) break; | |
a9c4e244 | 151 | TString input=argv[i]; |
152 | input+="?filetype=raw"; | |
153 | TFile* pFile=new TFile(input); | |
154 | if (pFile && !pFile->IsZombie()) { | |
155 | pFile->Seek(0); | |
156 | TArrayC buffer; | |
18076d18 | 157 | buffer.Set(pFile->GetSize()+1); |
158 | if (pFile->ReadBuffer(buffer.GetArray(), pFile->GetSize())==0) { | |
a9c4e244 | 159 | const char* argbuffer=buffer.GetArray(); |
160 | if ((iResult=ConfigureFromArgumentString(1, &argbuffer))<0) { | |
161 | iResult=-EPROTO; | |
162 | } | |
163 | } else { | |
164 | HLTError("failed to read configuration from file %s", argv[i]); | |
165 | iResult=-EIO; | |
166 | } | |
167 | } else { | |
168 | HLTError("can not open configuration file %s", argv[i]); | |
169 | iResult=-ENOENT; | |
170 | } | |
4ddfc222 | 171 | |
172 | // -datatype | |
173 | } else if (argument.CompareTo("-datatype")==0) { | |
9f75c734 | 174 | currDataType=kAliHLTVoidDataType; |
4ddfc222 | 175 | if ((bMissingParam=(++i>=argc))) break; |
d397a3b2 | 176 | memcpy(&currDataType.fID, argv[i], TMath::Min(kAliHLTComponentDataTypefIDsize, (Int_t)strlen(argv[i]))); |
4ddfc222 | 177 | if ((bMissingParam=(++i>=argc))) break; |
d397a3b2 | 178 | memcpy(&currDataType.fOrigin, argv[i], TMath::Min(kAliHLTComponentDataTypefOriginSize, (Int_t)strlen(argv[i]))); |
8a106878 | 179 | |
180 | // add all different data types to the list | |
181 | AliHLTComponentDataTypeList::iterator element=fOutputDataTypes.begin(); | |
182 | while (element!=fOutputDataTypes.end() && *element!=currDataType) element++; | |
183 | if (element==fOutputDataTypes.end()) fOutputDataTypes.push_back(currDataType); | |
184 | ||
fe050813 | 185 | if (bHaveDatatype==0 && pCurrEvent && iResult>=0) { |
186 | // this is a workaround to make old tutorials working which contain | |
187 | // the arguments in the wrong sequence | |
188 | TList& files=*pCurrEvent; // type conversion operator defined | |
189 | TObjLink *flnk=files.FirstLink(); | |
fe050813 | 190 | while (flnk) { |
191 | FileDesc* pFileDesc=dynamic_cast<FileDesc*>(flnk->GetObject()); | |
192 | if (pFileDesc) { | |
193 | pFileDesc->SetDataType(currDataType); | |
194 | } | |
195 | flnk=flnk->Next(); | |
196 | } | |
197 | } | |
198 | bHaveDatatype=1; | |
4ddfc222 | 199 | |
200 | // -dataspec | |
201 | } else if (argument.CompareTo("-dataspec")==0) { | |
202 | if ((bMissingParam=(++i>=argc))) break; | |
203 | TString parameter(argv[i]); | |
204 | parameter.Remove(TString::kLeading, ' '); // remove all blanks | |
205 | if (parameter.IsDigit()) { | |
d397a3b2 | 206 | currSpecification=(AliHLTUInt32_t)parameter.Atoi(); |
4ddfc222 | 207 | } else if (parameter.BeginsWith("0x") && |
208 | parameter.Replace(0,2,"",0).IsHex()) { | |
d397a3b2 | 209 | sscanf(parameter.Data(),"%x", &currSpecification); |
4ddfc222 | 210 | } else { |
211 | HLTError("wrong parameter for argument %s, number expected", argument.Data()); | |
212 | iResult=-EINVAL; | |
213 | } | |
fe050813 | 214 | if (bHaveSpecification==0 && pCurrEvent && iResult>=0) { |
215 | // this is a workaround to make old tutorials working which contain | |
216 | // the arguments in the wrong sequence | |
217 | TList& files=*pCurrEvent; // type conversion operator defined | |
218 | TObjLink *flnk=files.FirstLink(); | |
fe050813 | 219 | while (flnk) { |
220 | FileDesc* pFileDesc=dynamic_cast<FileDesc*>(flnk->GetObject()); | |
221 | if (pFileDesc) { | |
222 | pFileDesc->SetSpecification(currSpecification); | |
223 | } | |
224 | flnk=flnk->Next(); | |
225 | } | |
226 | } | |
227 | bHaveSpecification=1; | |
d397a3b2 | 228 | // -nextevent |
229 | } else if (argument.CompareTo("-nextevent")==0) { | |
230 | InsertEvent(pCurrEvent); | |
0449b3c8 | 231 | } else if (argument.CompareTo("-open_files_at_start")==0) { |
232 | fOpenFilesAtStart = true; | |
4ddfc222 | 233 | } else { |
234 | if ((iResult=ScanArgument(argc-i, &argv[i]))==-EINVAL) { | |
235 | HLTError("unknown argument %s", argument.Data()); | |
236 | break; | |
237 | } else if (iResult==-EPROTO) { | |
238 | bMissingParam=1; | |
239 | break; | |
240 | } else if (iResult>=0) { | |
241 | i+=iResult; | |
242 | iResult=0; | |
243 | } | |
244 | } | |
245 | } | |
d397a3b2 | 246 | InsertEvent(pCurrEvent); |
247 | ||
4ddfc222 | 248 | if (bMissingParam) { |
249 | HLTError("missing parameter for argument %s", argument.Data()); | |
250 | iResult=-EINVAL; | |
251 | } | |
a9c4e244 | 252 | |
253 | if (iResult>=0) return i; | |
d397a3b2 | 254 | return iResult; |
255 | } | |
256 | ||
257 | int AliHLTFilePublisher::InsertFile(EventFiles* &pCurrEvent, FileDesc* pDesc) | |
258 | { | |
4e8187d8 | 259 | // add file to event descriptor |
d397a3b2 | 260 | int iResult=0; |
261 | if (pDesc) { | |
262 | if (pCurrEvent==NULL) { | |
263 | pCurrEvent=new EventFiles; | |
264 | if (pCurrEvent!=NULL) { | |
265 | } else { | |
266 | iResult=-ENOMEM; | |
267 | } | |
268 | } | |
269 | if (iResult>=0 && pCurrEvent!=NULL) { | |
270 | HLTDebug("Insert file %p to event %p", pDesc, pCurrEvent); | |
271 | pCurrEvent->Add(pDesc); | |
272 | } | |
273 | } else { | |
274 | iResult=-EINVAL; | |
275 | } | |
276 | return iResult; | |
277 | } | |
278 | ||
279 | int AliHLTFilePublisher::InsertEvent(EventFiles* &pEvent) | |
280 | { | |
4e8187d8 | 281 | // insert eventfiles descriptor to publishing list |
d397a3b2 | 282 | int iResult=0; |
283 | if (pEvent) { | |
284 | HLTDebug("Inserted event %p", pEvent); | |
285 | fEvents.Add(pEvent); | |
286 | pEvent=NULL; | |
4ddfc222 | 287 | } |
288 | return iResult; | |
289 | } | |
290 | ||
291 | int AliHLTFilePublisher::ScanArgument(int argc, const char** argv) | |
292 | { | |
4e8187d8 | 293 | // scan argument |
4ddfc222 | 294 | |
295 | // there are no other arguments than the standard ones | |
296 | if (argc==0 && argv==NULL) { | |
297 | // this is just to get rid of the warning "unused parameter" | |
298 | } | |
0449b3c8 | 299 | return -EINVAL; |
4ddfc222 | 300 | } |
301 | ||
0449b3c8 | 302 | int AliHLTFilePublisher::OpenFiles(bool keepOpen) |
4ddfc222 | 303 | { |
4e8187d8 | 304 | // open files for consistency check, and keep optionally open |
4ddfc222 | 305 | int iResult=0; |
d397a3b2 | 306 | TObjLink *lnk=fEvents.FirstLink(); |
4ddfc222 | 307 | while (lnk && iResult>=0) { |
d397a3b2 | 308 | EventFiles* pEventDesc=dynamic_cast<EventFiles*>(lnk->GetObject()); |
309 | if (pEventDesc) { | |
310 | HLTDebug("open files for event %p", pEventDesc); | |
311 | TList& files=*pEventDesc; // type conversion operator defined | |
312 | TObjLink *flnk=files.FirstLink(); | |
313 | int eventSize=0; | |
314 | while (flnk && iResult>=0) { | |
315 | FileDesc* pFileDesc=dynamic_cast<FileDesc*>(flnk->GetObject()); | |
316 | if (pFileDesc) { | |
317 | int size=pFileDesc->OpenFile(); | |
0449b3c8 | 318 | if (not keepOpen) pFileDesc->CloseFile(); |
d397a3b2 | 319 | if (size<0) { |
320 | iResult=size; | |
321 | HLTError("can not open file %s", pFileDesc->GetName()); | |
322 | } else { | |
323 | eventSize+=size; | |
324 | } | |
4ddfc222 | 325 | } |
d397a3b2 | 326 | flnk=flnk->Next(); |
4ddfc222 | 327 | } |
d397a3b2 | 328 | HLTDebug("event %p size %d", pEventDesc, eventSize); |
329 | if (fMaxSize<eventSize) fMaxSize=eventSize; | |
330 | } else { | |
331 | HLTError("can not get event descriptor for TObjLink"); | |
4ddfc222 | 332 | } |
333 | lnk = lnk->Next(); | |
334 | } | |
335 | ||
336 | return iResult; | |
337 | } | |
338 | ||
339 | int AliHLTFilePublisher::DoDeinit() | |
340 | { | |
4e8187d8 | 341 | // overloaded from AliHLTComponent: cleanup |
4ddfc222 | 342 | int iResult=0; |
d397a3b2 | 343 | fEvents.Clear(); |
4ddfc222 | 344 | return iResult; |
345 | } | |
346 | ||
b6800be0 | 347 | int AliHLTFilePublisher::GetEvent( const AliHLTComponentEventData& /*evtData*/, |
348 | AliHLTComponentTriggerData& /*trigData*/, | |
d397a3b2 | 349 | AliHLTUInt8_t* outputPtr, |
350 | AliHLTUInt32_t& size, | |
7bcd6cad | 351 | AliHLTComponentBlockDataList& outputBlocks ) |
4ddfc222 | 352 | { |
4e8187d8 | 353 | // overloaded from AliHLTDataSource: event processing |
587c9cf9 | 354 | |
355 | // process data events only | |
356 | if (!IsDataEvent()) return 0; | |
53f79557 | 357 | AliHLTUInt32_t capacity=size; |
358 | size=0; | |
587c9cf9 | 359 | |
4ddfc222 | 360 | int iResult=0; |
b6800be0 | 361 | TObjLink *lnk=fpCurrent; |
d397a3b2 | 362 | if (lnk==NULL) lnk=fEvents.FirstLink(); |
4ddfc222 | 363 | fpCurrent=lnk; |
364 | if (lnk) { | |
d397a3b2 | 365 | EventFiles* pEventDesc=dynamic_cast<EventFiles*>(lnk->GetObject()); |
366 | if (pEventDesc) { | |
367 | HLTDebug("publishing files for event %p", pEventDesc); | |
368 | TList& files=*pEventDesc; // type conversion operator defined | |
369 | TObjLink *flnk=files.FirstLink(); | |
370 | int iTotalSize=0; | |
371 | while (flnk && iResult>=0) { | |
82dd07bb | 372 | if (!flnk->GetObject()) { |
373 | ALIHLTERRORGUARD(5, "internal mismatch in Root list iterator"); | |
374 | continue; | |
375 | } | |
d397a3b2 | 376 | FileDesc* pFileDesc=dynamic_cast<FileDesc*>(flnk->GetObject()); |
82dd07bb | 377 | if (!pFileDesc) { |
378 | ALIHLTERRORGUARD(5, "internal mismatch, invalid object type for dynamic_cast"); | |
379 | continue; | |
380 | } | |
381 | ||
0449b3c8 | 382 | if (not fOpenFilesAtStart) pFileDesc->OpenFile(); |
d397a3b2 | 383 | TFile* pFile=NULL; |
384 | if (pFileDesc && (pFile=*pFileDesc)!=NULL) { | |
385 | int iCopy=pFile->GetSize(); | |
386 | pFile->Seek(0); | |
53f79557 | 387 | if (iCopy+iTotalSize<=(int)capacity) { |
d397a3b2 | 388 | if (pFile->ReadBuffer((char*)outputPtr+iTotalSize, iCopy)!=0) { |
389 | // ReadBuffer returns 1 in case of failure and 0 in case of success | |
390 | iResult=-EIO; | |
391 | } else { | |
392 | AliHLTComponentBlockData bd; | |
393 | FillBlockData(bd); | |
394 | bd.fPtr=outputPtr; | |
395 | bd.fOffset=iTotalSize; | |
396 | bd.fSize=iCopy; | |
397 | bd.fDataType=*pFileDesc; // type conversion operator defined | |
398 | bd.fSpecification=*pFileDesc; // type conversion operator defined | |
399 | outputBlocks.push_back(bd); | |
400 | iTotalSize+=iCopy; | |
8a106878 | 401 | // TString msg; |
402 | // msg.Form("get file %s ", pFile->GetName()); | |
403 | // msg+="data type \'%s\'"; | |
404 | // PrintDataTypeContent(bd.fDataType, msg.Data()); | |
d397a3b2 | 405 | } |
406 | } else { | |
407 | // output buffer too small, update GetOutputDataSize for the second trial | |
408 | fMaxSize=iCopy; | |
409 | iResult=-ENOSPC; | |
410 | } | |
0449b3c8 | 411 | if (not fOpenFilesAtStart) pFileDesc->CloseFile(); |
d397a3b2 | 412 | } else { |
413 | HLTError("no file available"); | |
414 | iResult=-EFAULT; | |
415 | } | |
416 | flnk=flnk->Next(); | |
b6800be0 | 417 | } |
d397a3b2 | 418 | size=iTotalSize; |
4ddfc222 | 419 | } else { |
d397a3b2 | 420 | HLTError("can not get event descriptor from list link"); |
4ddfc222 | 421 | iResult=-EFAULT; |
422 | } | |
423 | } else { | |
424 | iResult=-ENOENT; | |
425 | } | |
b6800be0 | 426 | if (iResult>=0 && fpCurrent) fpCurrent=fpCurrent->Next(); |
427 | ||
4ddfc222 | 428 | return iResult; |
429 | } | |
430 | ||
992ea13b | 431 | AliHLTFilePublisher::FileDesc::FileDesc(const char* name, AliHLTComponentDataType dt, AliHLTUInt32_t spec, Bool_t isRaw) |
d397a3b2 | 432 | : |
433 | TObject(), | |
992ea13b | 434 | fIsRaw(isRaw), |
d397a3b2 | 435 | fName(name), |
436 | fpInstance(NULL), | |
437 | fDataType(dt), | |
438 | fSpecification(spec) | |
439 | { | |
4e8187d8 | 440 | // file descriptor helper structure |
d397a3b2 | 441 | } |
0449b3c8 | 442 | |
d397a3b2 | 443 | AliHLTFilePublisher::FileDesc::~FileDesc() |
4ddfc222 | 444 | { |
4e8187d8 | 445 | // destructor |
0449b3c8 | 446 | CloseFile(); |
447 | } | |
448 | ||
449 | void AliHLTFilePublisher::FileDesc::CloseFile() | |
450 | { | |
4e8187d8 | 451 | // close file |
0449b3c8 | 452 | if (fpInstance) |
453 | { | |
454 | // Unfortunately had to use AliLog mechanisms rather that AliHLTLogging because | |
455 | // AliHLTFilePublisher::FileDesc does not derive from AliHLTLogging. It would | |
456 | // become a rather heavy class if it did. | |
457 | #ifdef __DEBUG | |
458 | AliDebugGeneral("AliHLTFilePublisher::FileDesc", | |
459 | 2, Form("File %s has been closed.", fName.Data()) | |
460 | ); | |
461 | #endif | |
462 | delete fpInstance; | |
463 | } | |
d397a3b2 | 464 | fpInstance=NULL; |
4ddfc222 | 465 | } |
466 | ||
d397a3b2 | 467 | int AliHLTFilePublisher::FileDesc::OpenFile() |
4ddfc222 | 468 | { |
4e8187d8 | 469 | // open file |
d397a3b2 | 470 | int iResult=0; |
992ea13b | 471 | |
472 | TString fullFN=""; | |
473 | ||
474 | if ( fIsRaw ) fullFN = fName + "?filetype=raw"; | |
475 | else fullFN = fName; | |
476 | ||
6a5b2007 | 477 | fpInstance = TFile::Open(fullFN); |
d397a3b2 | 478 | if (fpInstance) { |
479 | if (fpInstance->IsZombie()==0) { | |
480 | iResult=fpInstance->GetSize(); | |
0449b3c8 | 481 | #ifdef __DEBUG |
482 | AliDebugGeneral("AliHLTFilePublisher::FileDesc", | |
483 | 2, Form("File %s has been opened.", fName.Data()) | |
484 | ); | |
485 | #endif | |
d397a3b2 | 486 | } else { |
487 | iResult=-ENOENT; | |
488 | } | |
489 | } | |
490 | return iResult; | |
4ddfc222 | 491 | } |