1 /*
2 *
3 * Copyright (C) 1993-2019, OFFIS e.V.
4 * All rights reserved. See COPYRIGHT file for details.
5 *
6 * This software and supporting documentation were developed by
7 *
8 * OFFIS e.V.
9 * R&D Division Health
10 * Escherweg 2
11 * D-26121 Oldenburg, Germany
12 *
13 *
14 * Module: dcmqrdb
15 *
16 * Author: Marco Eichelberg
17 *
18 * Purpose: classes DcmQueryRetrieveIndexDatabaseHandle,
19 * DcmQueryRetrieveIndexDatabaseHandleFactory
20 *
21 */
22
23 #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */
24
25 BEGIN_EXTERN_C
26 #ifdef HAVE_SYS_STAT_H
27 #include <sys/stat.h>
28 #endif
29 #ifdef HAVE_FCNTL_H
30 #include <fcntl.h>
31 #endif
32 #ifdef HAVE_SYS_PARAM_H
33 #include <sys/param.h>
34 #endif
35 END_EXTERN_C
36
37 #define INCLUDE_CCTYPE
38 #define INCLUDE_CSTDARG
39 #include "dcmtk/ofstd/ofstdinc.h"
40 #include "dcmtk/ofstd/ofstd.h"
41
42 #include "dcmtk/dcmqrdb/dcmqrdbs.h"
43 #include "dcmtk/dcmqrdb/dcmqrdbi.h"
44 #include "dcmtk/dcmqrdb/dcmqrcnf.h"
45 #include "dcmtk/dcmqrdb/dcmqropt.h"
46
47 #include "dcmtk/dcmqrdb/dcmqridx.h"
48 #include "dcmtk/dcmnet/diutil.h"
49 #include "dcmtk/dcmdata/dcfilefo.h"
50 #include "dcmtk/dcmdata/dcmatch.h"
51
52 /* ========================= static data ========================= */
53
54 /**** The TbFindAttr table contains the description of tags (keys) supported
55 **** by the DB Module.
56 **** Tags described here have to be present in the Index Record file.
57 **** The order is insignificant.
58 ****
59 **** Each element of this table is described by
60 **** The tag value
61 **** The level of this tag (from patient to image)
62 **** The Key Type (only UNIQUE_KEY values is used)
63 ****
64 **** This table and the IndexRecord structure should contain at least
65 **** all Unique and Required keys.
66 ***/
67
68 static const DB_FindAttr TbFindAttr [] = {
69 DB_FindAttr( DCM_PatientBirthDate, PATIENT_LEVEL, OPTIONAL_KEY ),
70 DB_FindAttr( DCM_PatientSex, PATIENT_LEVEL, OPTIONAL_KEY ),
71 DB_FindAttr( DCM_PatientName, PATIENT_LEVEL, REQUIRED_KEY ),
72 DB_FindAttr( DCM_PatientID, PATIENT_LEVEL, UNIQUE_KEY ),
73 DB_FindAttr( DCM_PatientBirthTime, PATIENT_LEVEL, OPTIONAL_KEY ),
74 DB_FindAttr( DCM_RETIRED_OtherPatientIDs, PATIENT_LEVEL, OPTIONAL_KEY ),
75 DB_FindAttr( DCM_OtherPatientNames, PATIENT_LEVEL, OPTIONAL_KEY ),
76 DB_FindAttr( DCM_EthnicGroup, PATIENT_LEVEL, OPTIONAL_KEY ),
77 DB_FindAttr( DCM_PatientComments, PATIENT_LEVEL, OPTIONAL_KEY ),
78 DB_FindAttr( DCM_IssuerOfPatientID, PATIENT_LEVEL, OPTIONAL_KEY ),
79 DB_FindAttr( DCM_StudyDate, STUDY_LEVEL, REQUIRED_KEY ),
80 DB_FindAttr( DCM_StudyTime, STUDY_LEVEL, REQUIRED_KEY ),
81 DB_FindAttr( DCM_StudyID, STUDY_LEVEL, REQUIRED_KEY ),
82 DB_FindAttr( DCM_AccessionNumber, STUDY_LEVEL, REQUIRED_KEY ),
83 DB_FindAttr( DCM_ReferringPhysicianName, STUDY_LEVEL, OPTIONAL_KEY ),
84 DB_FindAttr( DCM_StudyDescription, STUDY_LEVEL, OPTIONAL_KEY ),
85 DB_FindAttr( DCM_NameOfPhysiciansReadingStudy, STUDY_LEVEL, OPTIONAL_KEY ),
86 DB_FindAttr( DCM_StudyInstanceUID, STUDY_LEVEL, UNIQUE_KEY ),
87 DB_FindAttr( DCM_RETIRED_OtherStudyNumbers, STUDY_LEVEL, OPTIONAL_KEY ),
88 DB_FindAttr( DCM_AdmittingDiagnosesDescription, STUDY_LEVEL, OPTIONAL_KEY ),
89 DB_FindAttr( DCM_PatientAge, STUDY_LEVEL, OPTIONAL_KEY ),
90 DB_FindAttr( DCM_PatientSize, STUDY_LEVEL, OPTIONAL_KEY ),
91 DB_FindAttr( DCM_PatientWeight, STUDY_LEVEL, OPTIONAL_KEY ),
92 DB_FindAttr( DCM_Occupation, STUDY_LEVEL, OPTIONAL_KEY ),
93 DB_FindAttr( DCM_AdditionalPatientHistory, STUDY_LEVEL, OPTIONAL_KEY ),
94 DB_FindAttr( DCM_SeriesNumber, SERIE_LEVEL, REQUIRED_KEY ),
95 DB_FindAttr( DCM_SeriesInstanceUID, SERIE_LEVEL, UNIQUE_KEY ),
96 DB_FindAttr( DCM_Modality, SERIE_LEVEL, OPTIONAL_KEY ),
97 DB_FindAttr( DCM_InstanceNumber, IMAGE_LEVEL, REQUIRED_KEY ),
98 DB_FindAttr( DCM_SOPInstanceUID, IMAGE_LEVEL, UNIQUE_KEY )
99 };
100
101 /**** The NbFindAttr variable contains the length of the TbFindAttr table
102 ***/
103
104 static int NbFindAttr = ((sizeof (TbFindAttr)) / (sizeof (TbFindAttr [0])));
105
106 /* ========================= static functions ========================= */
107
DB_strdup(const char * str)108 static char *DB_strdup(const char* str)
109 {
110 if (str == NULL) return NULL;
111 size_t buflen = strlen(str)+1;
112 char* s = (char*)malloc(buflen);
113 OFStandard::strlcpy(s, str, buflen);
114 return s;
115 }
116
117 /************
118 ** Add UID in Index Record to the UID found list
119 */
120
DB_UIDAddFound(DB_Private_Handle * phandle,IdxRecord * idxRec)121 static void DB_UIDAddFound (
122 DB_Private_Handle *phandle,
123 IdxRecord *idxRec
124 )
125 {
126 DB_UidList *plist ;
127
128 plist = (DB_UidList *) malloc (sizeof (DB_UidList)) ;
129 if (plist == NULL) {
130 DCMQRDB_ERROR("DB_UIDAddFound: out of memory");
131 return;
132 }
133 plist->next = phandle->uidList ;
134 plist->patient = NULL ;
135 plist->study = NULL ;
136 plist->serie = NULL ;
137 plist->image = NULL ;
138
139 if ((int)phandle->queryLevel >= PATIENT_LEVEL)
140 plist->patient = DB_strdup ((char *) idxRec->PatientID) ;
141 if ((int)phandle->queryLevel >= STUDY_LEVEL)
142 plist->study = DB_strdup ((char *) idxRec->StudyInstanceUID) ;
143 if ((int)phandle->queryLevel >= SERIE_LEVEL)
144 plist->serie = DB_strdup ((char *) idxRec->SeriesInstanceUID) ;
145 if ((int)phandle->queryLevel >= IMAGE_LEVEL)
146 plist->image = DB_strdup ((char *) idxRec->SOPInstanceUID) ;
147
148 phandle->uidList = plist ;
149 }
150
151
152 /************
153 ** Search if an Index Record has already been found
154 */
155
DB_UIDAlreadyFound(DB_Private_Handle * phandle,IdxRecord * idxRec)156 static int DB_UIDAlreadyFound (
157 DB_Private_Handle *phandle,
158 IdxRecord *idxRec
159 )
160 {
161 DB_UidList *plist ;
162
163 for (plist = phandle->uidList ; plist ; plist = plist->next) {
164 if ( ((int)phandle->queryLevel >= PATIENT_LEVEL)
165 && (strcmp (plist->patient, (char *) idxRec->PatientID) != 0)
166 )
167 continue ;
168 if ( ((int)phandle->queryLevel >= STUDY_LEVEL)
169 && (strcmp (plist->study, (char *) idxRec->StudyInstanceUID) != 0)
170 )
171 continue ;
172 if ( ((int)phandle->queryLevel >= SERIE_LEVEL)
173 && (strcmp (plist->serie, (char *) idxRec->SeriesInstanceUID) != 0)
174 )
175 continue ;
176 if ( ((int)phandle->queryLevel >= IMAGE_LEVEL)
177 && (strcmp (plist->image, (char *) idxRec->SOPInstanceUID) != 0)
178 )
179 continue ;
180 return (OFTrue) ;
181 }
182 return (OFFalse) ;
183 }
184
185 /************
186 * Initializes addresses in an IdxRecord
187 */
188
DB_IdxInitRecord(IdxRecord * idx,int linksOnly)189 static void DB_IdxInitRecord (IdxRecord *idx, int linksOnly)
190 {
191 if (! linksOnly)
192 {
193 idx -> param[RECORDIDX_PatientBirthDate]. XTag = DCM_PatientBirthDate ;
194 idx -> param[RECORDIDX_PatientBirthDate]. ValueLength = DA_MAX_LENGTH ;
195 idx -> PatientBirthDate[0] = '\0' ;
196 idx -> param[RECORDIDX_PatientSex]. XTag = DCM_PatientSex ;
197 idx -> param[RECORDIDX_PatientSex]. ValueLength = CS_MAX_LENGTH ;
198 idx -> PatientSex[0] = '\0' ;
199 idx -> param[RECORDIDX_PatientName]. XTag = DCM_PatientName ;
200 idx -> param[RECORDIDX_PatientName]. ValueLength = PN_MAX_LENGTH ;
201 idx -> PatientName[0] = '\0' ;
202 idx -> param[RECORDIDX_PatientID]. XTag = DCM_PatientID ;
203 idx -> param[RECORDIDX_PatientID]. ValueLength = LO_MAX_LENGTH ;
204 idx -> PatientID[0] = '\0' ;
205 idx -> param[RECORDIDX_PatientBirthTime]. XTag = DCM_PatientBirthTime ;
206 idx -> param[RECORDIDX_PatientBirthTime]. ValueLength = TM_MAX_LENGTH ;
207 idx -> PatientBirthTime[0] = '\0' ;
208 idx -> param[RECORDIDX_OtherPatientIDs]. XTag = DCM_RETIRED_OtherPatientIDs ;
209 idx -> param[RECORDIDX_OtherPatientIDs]. ValueLength = LO_MAX_LENGTH ;
210 idx -> OtherPatientIDs[0] = '\0' ;
211 idx -> param[RECORDIDX_OtherPatientNames]. XTag = DCM_OtherPatientNames ;
212 idx -> param[RECORDIDX_OtherPatientNames]. ValueLength = PN_MAX_LENGTH ;
213 idx -> OtherPatientNames[0] = '\0' ;
214 idx -> param[RECORDIDX_EthnicGroup]. XTag = DCM_EthnicGroup ;
215 idx -> param[RECORDIDX_EthnicGroup]. ValueLength = SH_MAX_LENGTH ;
216 idx -> EthnicGroup[0] = '\0' ;
217 idx -> param[RECORDIDX_StudyDate]. XTag = DCM_StudyDate ;
218 idx -> param[RECORDIDX_StudyDate]. ValueLength = DA_MAX_LENGTH ;
219 idx -> StudyDate[0] = '\0' ;
220 idx -> param[RECORDIDX_StudyTime]. XTag = DCM_StudyTime ;
221 idx -> param[RECORDIDX_StudyTime]. ValueLength = TM_MAX_LENGTH ;
222 idx -> StudyTime[0] = '\0' ;
223 idx -> param[RECORDIDX_StudyID]. XTag = DCM_StudyID ;
224 idx -> param[RECORDIDX_StudyID]. ValueLength = CS_MAX_LENGTH ;
225 idx -> StudyID[0] = '\0' ;
226 idx -> param[RECORDIDX_StudyDescription]. XTag = DCM_StudyDescription ;
227 idx -> param[RECORDIDX_StudyDescription]. ValueLength = LO_MAX_LENGTH ;
228 idx -> StudyDescription[0] = '\0' ;
229 idx -> param[RECORDIDX_NameOfPhysiciansReadingStudy]. XTag = DCM_NameOfPhysiciansReadingStudy ;
230 idx -> param[RECORDIDX_NameOfPhysiciansReadingStudy]. ValueLength = PN_MAX_LENGTH ;
231 idx -> NameOfPhysiciansReadingStudy[0] = '\0' ;
232 idx -> param[RECORDIDX_AccessionNumber]. XTag = DCM_AccessionNumber ;
233 idx -> param[RECORDIDX_AccessionNumber]. ValueLength = CS_MAX_LENGTH ;
234 idx -> AccessionNumber[0] = '\0' ;
235 idx -> param[RECORDIDX_ReferringPhysicianName]. XTag = DCM_ReferringPhysicianName ;
236 idx -> param[RECORDIDX_ReferringPhysicianName]. ValueLength = PN_MAX_LENGTH ;
237 idx -> ReferringPhysicianName[0] = '\0' ;
238 idx -> param[RECORDIDX_ProcedureDescription]. XTag = DCM_StudyDescription ;
239 idx -> param[RECORDIDX_ProcedureDescription]. ValueLength = LO_MAX_LENGTH ;
240 idx -> ProcedureDescription[0] = '\0' ;
241 idx -> param[RECORDIDX_AttendingPhysiciansName]. XTag = DCM_NameOfPhysiciansReadingStudy ;
242 idx -> param[RECORDIDX_AttendingPhysiciansName]. ValueLength = PN_MAX_LENGTH ;
243 idx -> AttendingPhysiciansName[0] = '\0' ;
244 idx -> param[RECORDIDX_StudyInstanceUID]. XTag = DCM_StudyInstanceUID ;
245 idx -> param[RECORDIDX_StudyInstanceUID]. ValueLength = UI_MAX_LENGTH ;
246 idx -> StudyInstanceUID[0] = '\0' ;
247 idx -> param[RECORDIDX_OtherStudyNumbers]. XTag = DCM_RETIRED_OtherStudyNumbers ;
248 idx -> param[RECORDIDX_OtherStudyNumbers]. ValueLength = IS_MAX_LENGTH ;
249 idx -> OtherStudyNumbers[0] = '\0' ;
250 idx -> param[RECORDIDX_AdmittingDiagnosesDescription]. XTag = DCM_AdmittingDiagnosesDescription ;
251 idx -> param[RECORDIDX_AdmittingDiagnosesDescription]. ValueLength = LO_MAX_LENGTH ;
252 idx -> AdmittingDiagnosesDescription[0] = '\0' ;
253 idx -> param[RECORDIDX_PatientAge]. XTag = DCM_PatientAge ;
254 idx -> param[RECORDIDX_PatientAge]. ValueLength = AS_MAX_LENGTH ;
255 idx -> PatientAge[0] = '\0' ;
256 idx -> param[RECORDIDX_PatientSize]. XTag = DCM_PatientSize ;
257 idx -> param[RECORDIDX_PatientSize]. ValueLength = DS_MAX_LENGTH ;
258 idx -> PatientSize[0] = '\0' ;
259 idx -> param[RECORDIDX_PatientWeight]. XTag = DCM_PatientWeight ;
260 idx -> param[RECORDIDX_PatientWeight]. ValueLength = DS_MAX_LENGTH ;
261 idx -> PatientWeight[0] = '\0' ;
262 idx -> param[RECORDIDX_Occupation]. XTag = DCM_Occupation ;
263 idx -> param[RECORDIDX_Occupation]. ValueLength = SH_MAX_LENGTH ;
264 idx -> Occupation[0] = '\0' ;
265 idx -> param[RECORDIDX_SeriesNumber]. XTag = DCM_SeriesNumber ;
266 idx -> param[RECORDIDX_SeriesNumber]. ValueLength = IS_MAX_LENGTH ;
267 idx -> SeriesNumber[0] = '\0' ;
268 idx -> param[RECORDIDX_SeriesInstanceUID]. XTag = DCM_SeriesInstanceUID ;
269 idx -> param[RECORDIDX_SeriesInstanceUID]. ValueLength = UI_MAX_LENGTH ;
270 idx -> SeriesInstanceUID[0] = '\0' ;
271 idx -> param[RECORDIDX_Modality]. XTag = DCM_Modality ;
272 idx -> param[RECORDIDX_Modality]. ValueLength = CS_MAX_LENGTH ;
273 idx -> ImageNumber[0] = '\0' ;
274 idx -> param[RECORDIDX_ImageNumber]. XTag = DCM_InstanceNumber ;
275 idx -> param[RECORDIDX_ImageNumber]. ValueLength = IS_MAX_LENGTH ;
276 idx -> ImageNumber[0] = '\0' ;
277 idx -> param[RECORDIDX_SOPInstanceUID]. XTag = DCM_SOPInstanceUID ;
278 idx -> param[RECORDIDX_SOPInstanceUID]. ValueLength = UI_MAX_LENGTH ;
279 idx -> SOPInstanceUID[0] = '\0' ;
280 idx -> param[RECORDIDX_SeriesDate]. XTag = DCM_SeriesDate ;
281 idx -> param[RECORDIDX_SeriesDate]. ValueLength = DA_MAX_LENGTH ;
282 idx -> SeriesDate[0] = '\0' ;
283 idx -> param[RECORDIDX_SeriesTime]. XTag = DCM_SeriesTime ;
284 idx -> param[RECORDIDX_SeriesTime]. ValueLength = TM_MAX_LENGTH ;
285 idx -> SeriesTime[0] = '\0' ;
286 idx -> param[RECORDIDX_SeriesDescription]. XTag = DCM_SeriesDescription ;
287 idx -> param[RECORDIDX_SeriesDescription]. ValueLength = LO_MAX_LENGTH ;
288 idx -> SeriesDescription[0] = '\0' ;
289 idx -> param[RECORDIDX_ProtocolName]. XTag = DCM_ProtocolName ;
290 idx -> param[RECORDIDX_ProtocolName]. ValueLength = LO_MAX_LENGTH ;
291 idx -> ProtocolName[0] = '\0' ;
292 idx -> param[RECORDIDX_OperatorsName ]. XTag = DCM_OperatorsName ;
293 idx -> param[RECORDIDX_OperatorsName ]. ValueLength = PN_MAX_LENGTH ;
294 idx -> OperatorsName[0] = '\0' ;
295 idx -> param[RECORDIDX_PerformingPhysicianName]. XTag = DCM_PerformingPhysicianName ;
296 idx -> param[RECORDIDX_PerformingPhysicianName]. ValueLength = PN_MAX_LENGTH ;
297 idx -> PerformingPhysicianName[0] = '\0' ;
298 idx -> param[RECORDIDX_PresentationLabel]. XTag = DCM_ContentLabel ;
299 idx -> param[RECORDIDX_PresentationLabel]. ValueLength = CS_LABEL_MAX_LENGTH ;
300 idx -> PresentationLabel[0] = '\0' ;
301 idx -> param[RECORDIDX_IssuerOfPatientID]. XTag = DCM_IssuerOfPatientID ;
302 idx -> param[RECORDIDX_IssuerOfPatientID]. ValueLength = LO_MAX_LENGTH ;
303 idx -> IssuerOfPatientID[0] = '\0' ;
304 idx -> param[RECORDIDX_SpecificCharacterSet]. XTag = DCM_SpecificCharacterSet ;
305 idx -> param[RECORDIDX_SpecificCharacterSet]. ValueLength = CS_MAX_LENGTH*8 ;
306 idx -> SpecificCharacterSet[0] = '\0' ;
307 }
308 idx -> param[RECORDIDX_PatientBirthDate]. PValueField = (char *)idx -> PatientBirthDate ;
309 idx -> param[RECORDIDX_PatientSex]. PValueField = (char *)idx -> PatientSex ;
310 idx -> param[RECORDIDX_PatientName]. PValueField = (char *)idx -> PatientName ;
311 idx -> param[RECORDIDX_PatientID]. PValueField = (char *)idx -> PatientID ;
312 idx -> param[RECORDIDX_PatientBirthTime]. PValueField = (char *)idx -> PatientBirthTime ;
313 idx -> param[RECORDIDX_OtherPatientIDs]. PValueField = (char *)idx -> OtherPatientIDs ;
314 idx -> param[RECORDIDX_OtherPatientNames]. PValueField = (char *)idx -> OtherPatientNames ;
315 idx -> param[RECORDIDX_EthnicGroup]. PValueField = (char *)idx -> EthnicGroup ;
316 idx -> param[RECORDIDX_StudyDate]. PValueField = (char *) idx -> StudyDate ;
317 idx -> param[RECORDIDX_StudyTime]. PValueField = (char *) idx -> StudyTime ;
318 idx -> param[RECORDIDX_StudyID]. PValueField = (char *) idx -> StudyID ;
319 idx -> param[RECORDIDX_StudyDescription]. PValueField = (char *) idx -> StudyDescription ;
320 idx -> param[RECORDIDX_NameOfPhysiciansReadingStudy]. PValueField = (char *) idx ->NameOfPhysiciansReadingStudy;
321 idx -> param[RECORDIDX_AccessionNumber]. PValueField = (char *) idx -> AccessionNumber ;
322 idx -> param[RECORDIDX_ReferringPhysicianName]. PValueField = (char *) idx -> ReferringPhysicianName ;
323 idx -> param[RECORDIDX_ProcedureDescription]. PValueField = (char *) idx -> ProcedureDescription ;
324 idx -> param[RECORDIDX_AttendingPhysiciansName]. PValueField = (char *) idx -> AttendingPhysiciansName ;
325 idx -> param[RECORDIDX_StudyInstanceUID]. PValueField = (char *) idx -> StudyInstanceUID ;
326 idx -> param[RECORDIDX_OtherStudyNumbers]. PValueField = (char *) idx -> OtherStudyNumbers ;
327 idx -> param[RECORDIDX_AdmittingDiagnosesDescription]. PValueField = (char *) idx -> AdmittingDiagnosesDescription ;
328 idx -> param[RECORDIDX_PatientAge]. PValueField = (char *) idx -> PatientAge ;
329 idx -> param[RECORDIDX_PatientSize]. PValueField = (char *) idx -> PatientSize ;
330 idx -> param[RECORDIDX_PatientWeight]. PValueField = (char *) idx -> PatientWeight ;
331 idx -> param[RECORDIDX_Occupation]. PValueField = (char *) idx -> Occupation ;
332 idx -> param[RECORDIDX_SeriesNumber]. PValueField = (char *) idx -> SeriesNumber ;
333 idx -> param[RECORDIDX_SeriesInstanceUID]. PValueField = (char *) idx -> SeriesInstanceUID ;
334 idx -> param[RECORDIDX_Modality]. PValueField = (char *) idx -> Modality ;
335 idx -> param[RECORDIDX_ImageNumber]. PValueField = (char *) idx -> ImageNumber ;
336 idx -> param[RECORDIDX_SOPInstanceUID]. PValueField = (char *) idx -> SOPInstanceUID ;
337 idx -> param[RECORDIDX_SeriesDate]. PValueField = (char *) idx -> SeriesDate ;
338 idx -> param[RECORDIDX_SeriesTime]. PValueField = (char *) idx -> SeriesTime ;
339 idx -> param[RECORDIDX_SeriesDescription]. PValueField = (char *) idx -> SeriesDescription ;
340 idx -> param[RECORDIDX_ProtocolName]. PValueField = (char *) idx -> ProtocolName ;
341 idx -> param[RECORDIDX_OperatorsName ]. PValueField = (char *) idx -> OperatorsName ;
342 idx -> param[RECORDIDX_PerformingPhysicianName]. PValueField = (char *) idx -> PerformingPhysicianName ;
343 idx -> param[RECORDIDX_PresentationLabel]. PValueField = (char *) idx -> PresentationLabel ;
344 idx -> param[RECORDIDX_IssuerOfPatientID]. PValueField = (char *) idx -> IssuerOfPatientID ;
345 idx -> param[RECORDIDX_SpecificCharacterSet]. PValueField = (char *) idx -> SpecificCharacterSet ;
346 }
347
348 /******************************
349 * Seek to a file position and do error checking
350 *
351 * Motivation:
352 * We have had situations during demonstrations where size of the DB index file
353 * has exploded. It seems that a record is being written to a position
354 * way past the end of file.
355 * This seek function does some sanity error checking to try to identify
356 * the problem.
357 */
DB_lseek(int fildes,long offset,int whence)358 static long DB_lseek(int fildes, long offset, int whence)
359 {
360 long pos;
361 long curpos;
362 long endpos;
363
364 /*
365 ** we should not be seeking to an offset < 0
366 */
367 if (offset < 0) {
368 DCMQRDB_ERROR("*** DB ALERT: attempt to seek before beginning of file");
369 }
370
371 /* get the current position */
372 curpos = lseek(fildes, 0, SEEK_CUR);
373 if (curpos < 0) {
374 DCMQRDB_ERROR("DB_lseek: cannot get current position: " << OFStandard::getLastSystemErrorCode().message());
375 return curpos;
376 }
377 /* get the end of file position */
378 endpos = lseek(fildes, 0, SEEK_END);
379 if (endpos < 0) {
380 DCMQRDB_ERROR("DB_lseek: cannot get end of file position: " << OFStandard::getLastSystemErrorCode().message());
381 return endpos;
382 }
383
384 /* return to current position */
385 curpos = lseek(fildes, curpos, SEEK_SET);
386 if (curpos < 0) {
387 DCMQRDB_ERROR("DB_lseek: cannot reset current position: " << OFStandard::getLastSystemErrorCode().message());
388 return curpos;
389 }
390
391 /* do the requested seek */
392 pos = lseek(fildes, offset, whence);
393 if (pos < 0) {
394 DCMQRDB_ERROR("DB_lseek: cannot seek to " << offset << ": " << OFStandard::getLastSystemErrorCode().message());
395 return pos;
396 }
397
398 /*
399 ** print an alert if we are seeking to far
400 ** what is the limit? We don't expect the index file to be
401 ** larger than 32Mb
402 */
403 const long maxFileSize = 33554432;
404 if (pos > maxFileSize) {
405 DCMQRDB_ERROR("*** DB ALERT: attempt to seek beyond " << maxFileSize << " bytes");
406 }
407
408 /* print an alert if we are seeking beyond the end of file.
409 * ignore when file is empty or contains only the version information.
410 */
411 if ((endpos > DBHEADERSIZE) && (pos > endpos)) {
412 DCMQRDB_ERROR("*** DB ALERT: attempt to seek beyond end of file" << OFendl
413 << " offset=" << offset << " filesize=" << endpos);
414 }
415
416 return pos;
417 }
418
419 /******************************
420 * Read an Index record
421 */
422
DB_IdxRead(int idx,IdxRecord * idxRec)423 OFCondition DcmQueryRetrieveIndexDatabaseHandle::DB_IdxRead (int idx, IdxRecord *idxRec)
424 {
425
426 /*** Goto the right index in file
427 **/
428
429 DB_lseek (handle_ -> pidx, OFstatic_cast(long, DBHEADERSIZE + SIZEOF_STUDYDESC + idx * SIZEOF_IDXRECORD), SEEK_SET) ;
430
431 /*** Read the record
432 **/
433
434 if (read (handle_ -> pidx, (char *) idxRec, SIZEOF_IDXRECORD) != SIZEOF_IDXRECORD)
435 return (QR_EC_IndexDatabaseError) ;
436
437 DB_lseek (handle_ -> pidx, OFstatic_cast(long, DBHEADERSIZE), SEEK_SET) ;
438
439 /*** Initialize record links
440 **/
441
442 DB_IdxInitRecord (idxRec, 1) ;
443 return EC_Normal ;
444 }
445
446
447 /******************************
448 * Add an Index record
449 * Returns the index allocated for this record
450 */
451
DB_IdxAdd(DB_Private_Handle * phandle,int * idx,IdxRecord * idxRec)452 static OFCondition DB_IdxAdd (DB_Private_Handle *phandle, int *idx, IdxRecord *idxRec)
453 {
454 IdxRecord rec ;
455 OFCondition cond = EC_Normal;
456
457 /*** Find free place for the record
458 *** A place is free if filename is empty
459 **/
460
461 *idx = 0 ;
462
463 DB_lseek (phandle -> pidx, OFstatic_cast(long, DBHEADERSIZE + SIZEOF_STUDYDESC), SEEK_SET) ;
464 while (read (phandle -> pidx, (char *) &rec, SIZEOF_IDXRECORD) == SIZEOF_IDXRECORD) {
465 if (rec. filename [0] == '\0')
466 break ;
467 (*idx)++ ;
468 }
469
470 /*** We have either found a free place or we are at the end of file. **/
471
472
473 DB_lseek (phandle -> pidx, OFstatic_cast(long, DBHEADERSIZE + SIZEOF_STUDYDESC + (*idx) * SIZEOF_IDXRECORD), SEEK_SET) ;
474
475 if (write (phandle -> pidx, (char *) idxRec, SIZEOF_IDXRECORD) != SIZEOF_IDXRECORD)
476 cond = QR_EC_IndexDatabaseError ;
477 else
478 cond = EC_Normal ;
479
480 DB_lseek (phandle -> pidx, OFstatic_cast(long, DBHEADERSIZE), SEEK_SET) ;
481
482 return cond ;
483 }
484
485
486 /******************************
487 * Change the StudyDescRecord
488 */
489
DB_StudyDescChange(StudyDescRecord * pStudyDesc)490 OFCondition DcmQueryRetrieveIndexDatabaseHandle::DB_StudyDescChange(StudyDescRecord *pStudyDesc)
491 {
492 OFCondition cond = EC_Normal;
493 DB_lseek (handle_ -> pidx, OFstatic_cast(long, DBHEADERSIZE), SEEK_SET) ;
494 if (write (handle_ -> pidx, (char *) pStudyDesc, SIZEOF_STUDYDESC) != SIZEOF_STUDYDESC)
495 cond = QR_EC_IndexDatabaseError;
496 DB_lseek (handle_ -> pidx, OFstatic_cast(long, DBHEADERSIZE), SEEK_SET) ;
497 return cond ;
498 }
499
500 /******************************
501 * Init an Index record loop
502 */
503
DB_IdxInitLoop(int * idx)504 OFCondition DcmQueryRetrieveIndexDatabaseHandle::DB_IdxInitLoop(int *idx)
505 {
506 DB_lseek (handle_ -> pidx, OFstatic_cast(long, DBHEADERSIZE + SIZEOF_STUDYDESC), SEEK_SET) ;
507 *idx = -1 ;
508 return EC_Normal ;
509 }
510
511 /******************************
512 * Get next Index record
513 * On return, idx is initialized with the index of the record read
514 */
515
DB_IdxGetNext(int * idx,IdxRecord * idxRec)516 OFCondition DcmQueryRetrieveIndexDatabaseHandle::DB_IdxGetNext(int *idx, IdxRecord *idxRec)
517 {
518
519 (*idx)++ ;
520 DB_lseek (handle_ -> pidx, OFstatic_cast(long, DBHEADERSIZE + SIZEOF_STUDYDESC + OFstatic_cast(long, *idx) * SIZEOF_IDXRECORD), SEEK_SET) ;
521 while (read (handle_ -> pidx, (char *) idxRec, SIZEOF_IDXRECORD) == SIZEOF_IDXRECORD) {
522 if (idxRec -> filename [0] != '\0') {
523 DB_IdxInitRecord (idxRec, 1) ;
524
525 return EC_Normal ;
526 }
527 (*idx)++ ;
528 }
529
530 DB_lseek (handle_ -> pidx, OFstatic_cast(long, DBHEADERSIZE), SEEK_SET) ;
531
532 return QR_EC_IndexDatabaseError ;
533 }
534
535
536 /******************************
537 * Get next Index record
538 * On return, idx is initialized with the index of the record read
539 */
540
DB_GetStudyDesc(StudyDescRecord * pStudyDesc)541 OFCondition DcmQueryRetrieveIndexDatabaseHandle::DB_GetStudyDesc (StudyDescRecord *pStudyDesc)
542 {
543
544 DB_lseek (handle_ -> pidx, OFstatic_cast(long, DBHEADERSIZE), SEEK_SET) ;
545 if ( read (handle_ -> pidx, (char *) pStudyDesc, SIZEOF_STUDYDESC) == SIZEOF_STUDYDESC )
546 return EC_Normal ;
547
548 DB_lseek (handle_ -> pidx, OFstatic_cast(long, DBHEADERSIZE), SEEK_SET) ;
549
550 return QR_EC_IndexDatabaseError ;
551 }
552
553
554 /******************************
555 * Remove an Index record
556 * Just put a record with filename == ""
557 */
558
DB_IdxRemove(int idx)559 OFCondition DcmQueryRetrieveIndexDatabaseHandle::DB_IdxRemove(int idx)
560 {
561 IdxRecord rec ;
562 OFCondition cond = EC_Normal;
563
564 DB_lseek (handle_ -> pidx, OFstatic_cast(long, DBHEADERSIZE + SIZEOF_STUDYDESC + OFstatic_cast(long, idx) * SIZEOF_IDXRECORD), SEEK_SET) ;
565 DB_IdxInitRecord (&rec, 0) ;
566
567 rec. filename [0] = '\0' ;
568 if (write (handle_ -> pidx, (char *) &rec, SIZEOF_IDXRECORD) == SIZEOF_IDXRECORD)
569 cond = EC_Normal ;
570 else
571 cond = QR_EC_IndexDatabaseError ;
572
573 DB_lseek (handle_ -> pidx, OFstatic_cast(long, DBHEADERSIZE), SEEK_SET) ;
574
575 return cond ;
576 }
577
DB_lock(OFBool exclusive)578 OFCondition DcmQueryRetrieveIndexDatabaseHandle::DB_lock(OFBool exclusive)
579 {
580 int lockmode;
581
582 if (exclusive) {
583 lockmode = LOCK_EX; /* exclusive lock */
584 } else {
585 lockmode = LOCK_SH; /* shared lock */
586 }
587 if (dcmtk_flock(handle_->pidx, lockmode) < 0) {
588 dcmtk_plockerr("DB_lock");
589 return QR_EC_IndexDatabaseError;
590 }
591 return EC_Normal;
592 }
593
DB_unlock()594 OFCondition DcmQueryRetrieveIndexDatabaseHandle::DB_unlock()
595 {
596 if (dcmtk_flock(handle_->pidx, LOCK_UN) < 0) {
597 dcmtk_plockerr("DB_unlock");
598 return QR_EC_IndexDatabaseError;
599 }
600 return EC_Normal;
601 }
602
603 /*******************
604 * Free an element List
605 */
606
DB_FreeUidList(DB_UidList * lst)607 static OFCondition DB_FreeUidList (DB_UidList *lst)
608 {
609 while (lst != NULL) {
610 if (lst -> patient)
611 free (lst -> patient);
612 if (lst -> study)
613 free (lst -> study);
614 if (lst -> serie)
615 free (lst -> serie);
616 if (lst -> image)
617 free (lst -> image);
618 DB_UidList *curlst = lst;
619 lst = lst->next;
620 free (curlst);
621 }
622 return EC_Normal;
623 }
624
625
626 /*******************
627 * Free a UID List
628 */
629
DB_FreeElementList(DB_ElementList * lst)630 static OFCondition DB_FreeElementList (DB_ElementList *lst)
631 {
632 if (lst == NULL) return EC_Normal;
633
634 OFCondition cond = DB_FreeElementList (lst -> next);
635 if (lst->elem.PValueField != NULL) {
636 free ((char *) lst -> elem. PValueField);
637 }
638 delete lst;
639 return (cond);
640 }
641
642 /*******************
643 * Is the specified tag supported
644 */
645
DB_TagSupported(DcmTagKey tag)646 static int DB_TagSupported (DcmTagKey tag)
647 {
648 int i;
649
650 for (i = 0; i < NbFindAttr; i++)
651 if (TbFindAttr[i]. tag == tag)
652 return (OFTrue);
653
654 return (OFFalse);
655
656 }
657
658 /*******************
659 * Get UID tag of a specified level
660 */
661
DB_GetUIDTag(DB_LEVEL level,DcmTagKey * tag)662 static OFCondition DB_GetUIDTag (DB_LEVEL level, DcmTagKey *tag)
663 {
664 int i;
665
666 for (i = 0; i < NbFindAttr; i++)
667 if ((TbFindAttr[i]. level == level) && (TbFindAttr[i]. keyAttr == UNIQUE_KEY))
668 break;
669
670 if (i < NbFindAttr) {
671 *tag = TbFindAttr[i].tag;
672 return (EC_Normal);
673 }
674 else
675 return (QR_EC_IndexDatabaseError);
676
677 }
678
679 /*******************
680 * Get tag level of a specified tag
681 */
682
DB_GetTagLevel(DcmTagKey tag,DB_LEVEL * level)683 static OFCondition DB_GetTagLevel (DcmTagKey tag, DB_LEVEL *level)
684 {
685 int i;
686
687 for (i = 0; i < NbFindAttr; i++)
688 if (TbFindAttr[i]. tag == tag)
689 break;
690
691 if (i < NbFindAttr) {
692 *level = TbFindAttr[i]. level;
693 return (EC_Normal);
694 }
695 else
696 return (QR_EC_IndexDatabaseError);
697 }
698
699 /*******************
700 * Get tag key attribute of a specified tag
701 */
702
DB_GetTagKeyAttr(DcmTagKey tag,DB_KEY_TYPE * keyAttr)703 static OFCondition DB_GetTagKeyAttr (DcmTagKey tag, DB_KEY_TYPE *keyAttr)
704 {
705 int i;
706
707 for (i = 0; i < NbFindAttr; i++)
708 if (TbFindAttr[i]. tag == tag)
709 break;
710
711 if (i < NbFindAttr) {
712 *keyAttr = TbFindAttr[i]. keyAttr;
713 return (EC_Normal);
714 }
715 else
716 return (QR_EC_IndexDatabaseError);
717 }
718
719 /***********************
720 * Duplicate a DICOM element
721 * dst space is supposed provided by the caller
722 */
723
DB_DuplicateElement(DB_SmallDcmElmt * src,DB_SmallDcmElmt * dst)724 static void DB_DuplicateElement (DB_SmallDcmElmt *src, DB_SmallDcmElmt *dst)
725 {
726 bzero( (char*)dst, sizeof (DB_SmallDcmElmt));
727 dst -> XTag = src -> XTag;
728 dst -> ValueLength = src -> ValueLength;
729
730 if (src -> ValueLength == 0)
731 dst -> PValueField = NULL;
732 else {
733 dst -> PValueField = (char *)malloc ((int) src -> ValueLength+1);
734 bzero(dst->PValueField, (size_t)(src->ValueLength+1));
735 if (dst->PValueField != NULL) {
736 memcpy (dst -> PValueField, src -> PValueField,
737 (size_t) src -> ValueLength);
738 } else {
739 DCMQRDB_ERROR("DB_DuplicateElement: out of memory");
740 }
741 }
742 }
743
744
745 /***********************
746 * Compare two ImagesofStudyArray elements
747 */
748
DB_Compare(const void * ve1,const void * ve2)749 extern "C" int DB_Compare(const void *ve1, const void *ve2)
750 {
751 ImagesofStudyArray *e1 = (ImagesofStudyArray *)ve1;
752 ImagesofStudyArray *e2 = (ImagesofStudyArray *)ve2;
753 if ( e1 -> RecordedDate > e2 -> RecordedDate )
754 return (1);
755 else
756 if ( e1 -> RecordedDate == e2 -> RecordedDate )
757 return (0);
758 else
759 return (-1);
760
761 }
762
763
764 /* ==================================================================== */
765
~DcmQueryRetrieveDatabaseHandle()766 DcmQueryRetrieveDatabaseHandle::~DcmQueryRetrieveDatabaseHandle()
767 {
768 }
769
770 /* ========================= FIND ========================= */
771
772 // helper function to print 'ASCII' instead of an empty string for the value of
773 // Specific Character Set
characterSetName(const OFString & charset)774 static const char* characterSetName( const OFString& charset )
775 {
776 if (charset.empty())
777 return "ASCII";
778 return charset.c_str();
779 }
780
781 class DcmQueryRetrieveIndexDatabaseHandle::CharsetConsideringMatcher
782 {
783 public:
784
785 // Constructor, remember references to the find request character set and converter
CharsetConsideringMatcher(DB_Private_Handle & handle)786 CharsetConsideringMatcher(DB_Private_Handle& handle)
787 : findRequestCharacterSet(handle.findRequestCharacterSet)
788 , findRequestConverter(handle.findRequestConverter)
789 , candidateCharacterSet()
790 , candidateConverter()
791 , isFindRequestConversionNecessary(isConversionToUTF8Necessary(findRequestCharacterSet))
792 , isCandidateConversionNecessary()
793 , isConversionNecessary()
794 {
795
796 }
797
798 // read access to the candidate's character set value
getCandidateCharacterSet() const799 const OFString& getCandidateCharacterSet() const
800 {
801 return candidateCharacterSet;
802 }
803
804 // prepare character set conversion for specific index record
setRecord(IdxRecord & idxRec)805 void setRecord(IdxRecord& idxRec)
806 {
807 // copy value of specific character set of the entry, since the converter
808 // would need it as an OFString anyway.
809 candidateCharacterSet.assign(idxRec.param[RECORDIDX_SpecificCharacterSet].PValueField,
810 idxRec.param[RECORDIDX_SpecificCharacterSet].ValueLength);
811 // test if conversion is potentially necessary since the character sets differ
812 if (findRequestCharacterSet != candidateCharacterSet) {
813 // determine if the candidate is compatible to UTF-8 or must be converted
814 isCandidateConversionNecessary = isConversionToUTF8Necessary(candidateCharacterSet);
815 // if it must be converted, clear the converter if it was previously initialized,
816 // but for a different character set
817 if (isCandidateConversionNecessary && candidateConverter &&
818 candidateConverter.getSourceCharacterSet() != candidateCharacterSet) {
819 candidateConverter.clear();
820 }
821 // even if the character sets differ, they may both be compatible to UTF-8,
822 // in which case conversion is still not necessary
823 isConversionNecessary = isCandidateConversionNecessary || isFindRequestConversionNecessary;
824 } else {
825 // conversion is not necessary
826 isConversionNecessary = OFFalse;
827 }
828 }
829
830 // Try to match Two DB_ElementList elements
831 // The first one is the query key, the second one the candidate
832 // from the database entry.
833 // Returns OFTrue if both values match, OFFalse otherwise
operator ()(DB_ElementList * query,DB_SmallDcmElmt * candidate)834 OFBool operator()(DB_ElementList* query, DB_SmallDcmElmt* candidate)
835 {
836 // Universal matching is applied if the query value is empty:
837 // always return OFTrue
838 if (!query->elem.ValueLength)
839 return OFTrue;
840
841 OFString buffer;
842 const char* pQuery = query->elem.PValueField;
843 const char* pQueryEnd = pQuery + query->elem.ValueLength;
844 const char* pCandidate = candidate->PValueField;
845 const char* pCandidateEnd = pCandidate + candidate->ValueLength;
846
847 DcmVR vr = DcmTag(query->elem.XTag).getVR();
848 if (isConversionNecessary && vr.isAffectedBySpecificCharacterSet()) {
849 #ifdef DCMTK_ENABLE_CHARSET_CONVERSION
850 // convert query, if it isn't UTF-8 or ASCII already
851 if (isFindRequestConversionNecessary) {
852 // does a value already exist in the cache?
853 if (!query->utf8Value) {
854 // fill the cache if it doesn't
855 query->utf8Value = OFString();
856 // initialize the converter, if this is the first
857 // time we need it
858 OFCondition cond = EC_Normal;
859 if (!findRequestConverter)
860 cond = findRequestConverter.selectCharacterSet(findRequestCharacterSet);
861 if (cond.good()) {
862 // covert the string and cache the result, using the
863 // specific delimitation characters for this VR
864 cond = findRequestConverter.convertString(
865 query->elem.PValueField,
866 query->elem.ValueLength,
867 *query->utf8Value,
868 vr.getDelimiterChars()
869 );
870 }
871 if (cond.bad()) {
872 DCMQRDB_WARN("Character set conversion of the query key failed with the following error: '" << cond.text()
873 << "', will compare values that use different (incompatible) character sets: \""
874 << characterSetName(findRequestCharacterSet) << "\" and \"" << characterSetName(candidateCharacterSet) << '"');
875 // put the original value in the cache, since retrying the conversion on the next encounter does not make sense
876 // (it would only fail again).
877 query->utf8Value = OFString(query->elem.PValueField, query->elem.ValueLength);
878 }
879 }
880 // use the value from the cache for the following match
881 // operations
882 pQuery = query->utf8Value->c_str();
883 pQueryEnd = pQuery + query->utf8Value->size();
884 }
885 // convert the candidate, if it isn't already UTF-8 or ASCII
886 if (isCandidateConversionNecessary) {
887 // initialize the converter, if this is the first time
888 // we need it for this entry
889 OFCondition cond = EC_Normal;
890 if (!candidateConverter)
891 cond = candidateConverter.selectCharacterSet(candidateCharacterSet);
892 if (cond.good()) {
893 // convert the string using the local buffer and the
894 // specific delimitation characters for this VR
895 cond = candidateConverter.convertString(
896 candidate->PValueField,
897 candidate->ValueLength,
898 buffer,
899 vr.getDelimiterChars()
900 );
901 }
902 if (cond.good()) {
903 // assign the buffer contents to the value being used
904 // in the following match operations
905 pCandidate = buffer.c_str();
906 pCandidateEnd = pCandidate + buffer.size();
907 } else {
908 DCMQRDB_WARN("Character set conversion of the candidate failed with the following error: '" << cond.text()
909 << "', will compare values that use different (incompatible) character sets: \""
910 << characterSetName(findRequestCharacterSet) << "\" and \"" << characterSetName(candidateCharacterSet) << '"');
911 }
912 }
913 #else
914 DCMQRDB_WARN("Character set conversion is not available, comparing values that use different (incompatible) character sets: \""
915 << characterSetName(findRequestCharacterSet) << "\" and \"" << characterSetName(candidateCharacterSet) << '"');
916 #endif
917 }
918
919 // remove leading and trailing spaces before matching
920 if (vr.isaString()) {
921 OFStandard::trimString(pQuery, pQueryEnd);
922 OFStandard::trimString(pCandidate, pCandidateEnd);
923 }
924
925 // use DcmAttributeMatching to perform the appropriate matching function
926 // for the given VR
927 return DcmAttributeMatching( vr )( pQuery, pQueryEnd - pQuery,
928 pCandidate, pCandidateEnd - pCandidate );
929 }
930
931 private:
932 const OFString& findRequestCharacterSet;
933 DcmSpecificCharacterSet& findRequestConverter;
934 OFString candidateCharacterSet;
935 DcmSpecificCharacterSet candidateConverter;
936 const OFBool isFindRequestConversionNecessary;
937 OFBool isCandidateConversionNecessary;
938 OFBool isConversionNecessary;
939 };
940
isConversionToUTF8Necessary(const OFString & characterSet)941 OFBool DcmQueryRetrieveIndexDatabaseHandle::isConversionToUTF8Necessary(const OFString& characterSet)
942 {
943 // empty -> ASCII, subset of UTF-8
944 // ISO_IR 6 -> ASCII, subset of UTF-8
945 // ISO_IR 192 -> UTF-8
946 return !characterSet.empty() &&
947 characterSet != "ISO_IR 192" &&
948 characterSet != "ISO_IR 6"
949 ;
950 }
951
isConversionNecessary(const OFString & sourceCharacterSet,const OFString & destinationCharacterSet)952 OFBool DcmQueryRetrieveIndexDatabaseHandle::isConversionNecessary(const OFString& sourceCharacterSet,
953 const OFString& destinationCharacterSet)
954 {
955 // conversion is unnecessary if both are the same character set or if
956 // the destination is UTF-8 and the source is compatible to UTF-8
957 // (i.e. ASCII).
958 return sourceCharacterSet != destinationCharacterSet &&
959 (
960 destinationCharacterSet != "ISO_IR 192" ||
961 isConversionToUTF8Necessary(sourceCharacterSet)
962 );
963 }
964
965 /************
966 ** Create the response list in specified handle,
967 ** using informations found in an index record.
968 ** Old response list is supposed freed
969 **/
970
makeResponseList(DB_Private_Handle * phandle,IdxRecord * idxRec)971 void DcmQueryRetrieveIndexDatabaseHandle::makeResponseList (
972 DB_Private_Handle *phandle,
973 IdxRecord *idxRec
974 )
975 {
976 int i ;
977 DB_ElementList *pRequestList = NULL;
978 DB_ElementList *plist = NULL;
979 DB_ElementList *last = NULL;
980
981 phandle->findResponseList = NULL ;
982
983 /*** For each element in Request identifier
984 **/
985
986 for (pRequestList = phandle->findRequestList ; pRequestList ; pRequestList = pRequestList->next) {
987
988 /*** Find Corresponding Tag in index record
989 **/
990
991 for (i = 0 ; i < NBPARAMETERS ; i++)
992 if (idxRec->param [i]. XTag == pRequestList->elem. XTag)
993 break ;
994
995 /*** If Tag not found, skip the element
996 **/
997
998 if (i >= NBPARAMETERS)
999 continue ;
1000
1001 /*** Append index record element to response list
1002 **/
1003
1004 plist = new DB_ElementList ;
1005 if (plist == NULL) {
1006 DCMQRDB_ERROR("makeResponseList: out of memory");
1007 return;
1008 }
1009
1010 DB_DuplicateElement(&idxRec->param[i], &plist->elem);
1011
1012 if (phandle->findResponseList == NULL) {
1013 phandle->findResponseList = last = plist ;
1014 }
1015 else {
1016 last->next = plist ;
1017 last = plist ;
1018 }
1019
1020 }
1021
1022 /** Specific Character Set stuff
1023 **/
1024
1025 if (idxRec->param[RECORDIDX_SpecificCharacterSet].ValueLength) {
1026 plist = new DB_ElementList ;
1027 if (plist == NULL) {
1028 DCMQRDB_ERROR("makeResponseList: out of memory");
1029 return;
1030 }
1031
1032 DB_DuplicateElement(&idxRec->param[RECORDIDX_SpecificCharacterSet], &plist->elem);
1033
1034 if (phandle->findResponseList == NULL) {
1035 phandle->findResponseList = last = plist ;
1036 }
1037 else {
1038 last->next = plist ;
1039 last = plist ;
1040 }
1041 }
1042 }
1043
1044
1045
1046 /************
1047 ** Test a Find Request List
1048 ** Returns EC_Normal if OK, else returns QR_EC_IndexDatabaseError
1049 */
1050
testFindRequestList(DB_ElementList * findRequestList,DB_LEVEL queryLevel,DB_LEVEL infLevel,DB_LEVEL lowestLevel)1051 OFCondition DcmQueryRetrieveIndexDatabaseHandle::testFindRequestList (
1052 DB_ElementList *findRequestList,
1053 DB_LEVEL queryLevel,
1054 DB_LEVEL infLevel,
1055 DB_LEVEL lowestLevel
1056 )
1057 {
1058 DB_ElementList *plist ;
1059 DB_LEVEL XTagLevel = PATIENT_LEVEL; // DB_GetTagLevel() will set this correctly
1060 DB_KEY_TYPE XTagType = OPTIONAL_KEY; // DB_GetTagKeyAttr() will set this
1061 int level ;
1062
1063 /**** Query level must be at least the infLevel
1064 ***/
1065
1066 if (queryLevel < infLevel) {
1067 DCMQRDB_INFO("Level incompatible with Information Model (level " << queryLevel << ")");
1068 return QR_EC_IndexDatabaseError ;
1069 }
1070
1071 if (queryLevel > lowestLevel) {
1072 DCMQRDB_DEBUG("Level incompatible with Information Model (level " << queryLevel << ")");
1073 return QR_EC_IndexDatabaseError ;
1074 }
1075
1076 for (level = PATIENT_LEVEL ; level <= IMAGE_LEVEL ; level++) {
1077
1078 /**** Manage exception due to StudyRoot Information Model :
1079 **** In this information model, queries may include Patient attributes
1080 **** but only if they are made at the study level
1081 ***/
1082
1083 if ((level == PATIENT_LEVEL) && (infLevel == STUDY_LEVEL)) {
1084 /** In Study Root Information Model, accept only Patient Tags
1085 ** if the Query Level is the Study level
1086 */
1087
1088 int atLeastOneKeyFound = OFFalse ;
1089 for (plist = findRequestList ; plist ; plist = plist->next) {
1090 DB_GetTagLevel (plist->elem. XTag, &XTagLevel) ;
1091 if (XTagLevel != level)
1092 continue ;
1093 atLeastOneKeyFound = OFTrue ;
1094 }
1095 if (atLeastOneKeyFound && (queryLevel != STUDY_LEVEL)) {
1096 DCMQRDB_DEBUG("Key found in Study Root Information Model (level " << level << ")");
1097 return QR_EC_IndexDatabaseError ;
1098 }
1099 }
1100
1101 /**** If current level is above the QueryLevel
1102 ***/
1103
1104 else if (level < queryLevel) {
1105
1106 /** For this level, only unique keys are allowed
1107 ** Parse the request list elements referring to
1108 ** this level.
1109 ** Check that only unique key attr are provided
1110 */
1111
1112 int uniqueKeyFound = OFFalse ;
1113 for (plist = findRequestList ; plist ; plist = plist->next) {
1114 DB_GetTagLevel (plist->elem. XTag, &XTagLevel) ;
1115 if (XTagLevel != level)
1116 continue ;
1117 DB_GetTagKeyAttr (plist->elem. XTag, &XTagType) ;
1118 if (XTagType != UNIQUE_KEY) {
1119 DCMQRDB_DEBUG("Non Unique Key found (level " << level << ")");
1120 return QR_EC_IndexDatabaseError ;
1121 }
1122 else if (uniqueKeyFound) {
1123 DCMQRDB_DEBUG("More than one Unique Key found (level " << level << ")");
1124 return QR_EC_IndexDatabaseError ;
1125 }
1126 else
1127 uniqueKeyFound = OFTrue ;
1128 }
1129 }
1130
1131 /**** If current level is the QueryLevel
1132 ***/
1133
1134 else if (level == queryLevel) {
1135
1136 /** For this level, all keys are allowed
1137 ** Parse the request list elements referring to
1138 ** this level.
1139 ** Check that at least one key is provided
1140 */
1141
1142 int atLeastOneKeyFound = OFFalse ;
1143 for (plist = findRequestList ; plist ; plist = plist->next) {
1144 DB_GetTagLevel (plist->elem. XTag, &XTagLevel) ;
1145 if (XTagLevel != level)
1146 continue ;
1147 atLeastOneKeyFound = OFTrue ;
1148 }
1149 if (! atLeastOneKeyFound) {
1150 DCMQRDB_DEBUG("No Key found at query level (level " << level << ")");
1151 return QR_EC_IndexDatabaseError ;
1152 }
1153 }
1154
1155 /**** If current level beyond the QueryLevel
1156 ***/
1157
1158 else if (level > queryLevel) {
1159
1160 /** For this level, no key is allowed
1161 ** Parse the request list elements referring to
1162 ** this level.
1163 ** Check that no key is provided
1164 */
1165
1166 int atLeastOneKeyFound = OFFalse ;
1167 for (plist = findRequestList ; plist ; plist = plist->next) {
1168 DB_GetTagLevel (plist->elem. XTag, &XTagLevel) ;
1169 if (XTagLevel != level)
1170 continue ;
1171 atLeastOneKeyFound = OFTrue ;
1172 }
1173 if (atLeastOneKeyFound) {
1174 DCMQRDB_DEBUG("Key found beyond query level (level " << level << ")");
1175 return QR_EC_IndexDatabaseError ;
1176 }
1177 }
1178
1179 }
1180 return EC_Normal ;
1181 }
1182
1183
1184 /************
1185 ** Hierarchical Search Algorithm
1186 ** Returns OFTrue if matching is OK, else returns OFFalse
1187 */
1188
hierarchicalCompare(DB_Private_Handle * phandle,IdxRecord * idxRec,DB_LEVEL level,DB_LEVEL infLevel,int * match,CharsetConsideringMatcher & dbmatch)1189 OFCondition DcmQueryRetrieveIndexDatabaseHandle::hierarchicalCompare (
1190 DB_Private_Handle *phandle,
1191 IdxRecord *idxRec,
1192 DB_LEVEL level,
1193 DB_LEVEL infLevel,
1194 int *match,
1195 CharsetConsideringMatcher& dbmatch)
1196 {
1197 int i ;
1198 DcmTagKey XTag ;
1199 DB_ElementList *plist ;
1200 DB_LEVEL XTagLevel = PATIENT_LEVEL; // DB_GetTagLevel() will set this correctly
1201
1202 /**** If current level is above the QueryLevel
1203 ***/
1204
1205 if (level < phandle->queryLevel) {
1206
1207 /** Get UID Tag for current level
1208 */
1209
1210 DB_GetUIDTag (level, &XTag) ;
1211
1212 /** Find Element with this XTag in Identifier list
1213 */
1214
1215 for (plist = phandle->findRequestList ; plist ; plist = plist->next)
1216 if (plist->elem. XTag == XTag)
1217 break ;
1218
1219 /** Element not found
1220 */
1221
1222 if (plist == NULL) {
1223 *match = OFFalse ;
1224 DCMQRDB_WARN("hierarchicalCompare : No UID Key found at level " << (int) level);
1225 return QR_EC_IndexDatabaseError ;
1226 }
1227
1228 /** Find element with the same XTag in index record
1229 */
1230
1231 for (i = 0 ; i < NBPARAMETERS ; i++)
1232 if (idxRec->param [i]. XTag == XTag)
1233 break ;
1234
1235 /** Compare with Single value matching
1236 ** If Match fails, return OFFalse
1237 */
1238
1239 if (!dbmatch(plist, &idxRec->param[i])) {
1240 *match = OFFalse ;
1241 return EC_Normal ;
1242 }
1243
1244 /** Match succeeded.
1245 ** Try at next level
1246 */
1247
1248 return hierarchicalCompare (phandle, idxRec, (DB_LEVEL)(level + 1), infLevel, match, dbmatch) ;
1249 }
1250
1251 /**** If current level is the QueryLevel
1252 ***/
1253
1254 else if (level == phandle->queryLevel) {
1255
1256 /*** For each element in Identifier list
1257 **/
1258
1259 for (plist = phandle->findRequestList ; plist ; plist = plist->next) {
1260
1261 /** Get the Tag level of this element
1262 */
1263
1264 DB_GetTagLevel (plist->elem. XTag, &XTagLevel) ;
1265
1266 /** If we are in the Study Root Information Model exception
1267 ** we must accept patients keys at the study level
1268 */
1269
1270 if ( (XTagLevel == PATIENT_LEVEL)
1271 && (phandle->queryLevel == STUDY_LEVEL)
1272 && (infLevel == STUDY_LEVEL)
1273 ) ;
1274
1275 /** In other cases, only keys at the current level are
1276 ** taken into account. So skip this element.
1277 */
1278
1279 else if (XTagLevel != level)
1280 continue ;
1281
1282 /** Find element with the same XTag in index record
1283 */
1284
1285 for (i = 0 ; i < NBPARAMETERS ; i++)
1286 if (idxRec->param [i]. XTag == plist->elem. XTag)
1287 break ;
1288
1289 /** Compare with appropriate Matching.
1290 ** If Match fails, return OFFalse
1291 */
1292
1293
1294 if (!dbmatch(plist, &idxRec->param[i])) {
1295 *match = OFFalse ;
1296 return EC_Normal ;
1297 }
1298 }
1299
1300 /*** If we are here, all matches succeeded at the current level.
1301 *** Perhaps check that we have tried at least one match ??
1302 **/
1303
1304 *match = OFTrue ;
1305 return EC_Normal ;
1306
1307 }
1308 return QR_EC_IndexDatabaseError;
1309 }
1310
1311 /********************
1312 ** Start find in Database
1313 **/
1314
startFindRequest(const char * SOPClassUID,DcmDataset * findRequestIdentifiers,DcmQueryRetrieveDatabaseStatus * status)1315 OFCondition DcmQueryRetrieveIndexDatabaseHandle::startFindRequest(
1316 const char *SOPClassUID,
1317 DcmDataset *findRequestIdentifiers,
1318 DcmQueryRetrieveDatabaseStatus *status)
1319 {
1320 DB_SmallDcmElmt elem ;
1321 DB_ElementList *plist = NULL;
1322 DB_ElementList *last = NULL;
1323 int MatchFound ;
1324 IdxRecord idxRec ;
1325 DB_LEVEL qLevel = PATIENT_LEVEL; // highest legal level for a query in the current model
1326 DB_LEVEL lLevel = IMAGE_LEVEL; // lowest legal level for a query in the current model
1327
1328 OFCondition cond = EC_Normal;
1329 OFBool qrLevelFound = OFFalse;
1330
1331 /**** Is SOPClassUID supported ?
1332 ***/
1333
1334 if (strcmp( SOPClassUID, UID_FINDPatientRootQueryRetrieveInformationModel) == 0)
1335 handle_->rootLevel = PATIENT_ROOT ;
1336 else if (strcmp( SOPClassUID, UID_FINDStudyRootQueryRetrieveInformationModel) == 0)
1337 handle_->rootLevel = STUDY_ROOT ;
1338 #ifndef NO_PATIENTSTUDYONLY_SUPPORT
1339 else if (strcmp( SOPClassUID, UID_RETIRED_FINDPatientStudyOnlyQueryRetrieveInformationModel) == 0)
1340 handle_->rootLevel = PATIENT_STUDY ;
1341 #endif
1342 else {
1343 status->setStatus(STATUS_FIND_Refused_SOPClassNotSupported);
1344 return (QR_EC_IndexDatabaseError) ;
1345 }
1346
1347 /**** Parse Identifiers in the Dicom Object
1348 **** Find Query Level and construct a list
1349 **** of query identifiers
1350 ***/
1351
1352 if (findRequestIdentifiers->findAndGetOFStringArray(DCM_SpecificCharacterSet, handle_->findRequestCharacterSet).bad())
1353 handle_->findRequestCharacterSet.clear();
1354 if (handle_->findRequestConverter && handle_->findRequestConverter.getSourceCharacterSet() != handle_->findRequestCharacterSet)
1355 handle_->findRequestConverter.clear();
1356
1357 handle_->findRequestList = NULL ;
1358
1359 int elemCount = OFstatic_cast(int, findRequestIdentifiers->card());
1360 for (int elemIndex=0; elemIndex<elemCount; elemIndex++) {
1361
1362 DcmElement* dcelem = findRequestIdentifiers->getElement(elemIndex);
1363
1364 elem.XTag = dcelem->getTag().getXTag();
1365 if (elem.XTag == DCM_QueryRetrieveLevel || DB_TagSupported(elem.XTag)) {
1366 elem.ValueLength = dcelem->getLength();
1367 if (elem.ValueLength == 0) {
1368 elem.PValueField = NULL ;
1369 } else if ((elem.PValueField = OFstatic_cast(char*, malloc(OFstatic_cast(size_t, elem.ValueLength+1)))) == NULL) {
1370 status->setStatus(STATUS_FIND_Refused_OutOfResources);
1371 return (QR_EC_IndexDatabaseError) ;
1372 } else {
1373 /* only char string type tags are supported at the moment */
1374 char *s = NULL;
1375 dcelem->getString(s);
1376 /* the available space is always elem.ValueLength+1 */
1377 OFStandard::strlcpy(elem.PValueField, s, elem.ValueLength+1);
1378 }
1379 /** If element is the Query Level, store it in handle
1380 */
1381
1382 if (elem.XTag == DCM_QueryRetrieveLevel && elem.PValueField) {
1383 char *pc ;
1384 char level [50] ;
1385
1386 strncpy(level, (char*)elem.PValueField,
1387 (elem.ValueLength<50)? (size_t)(elem.ValueLength) : 49) ;
1388
1389 /*** Skip this two lines if you want strict comparison
1390 **/
1391
1392 for (pc = level ; *pc ; pc++)
1393 *pc = ((*pc >= 'a') && (*pc <= 'z')) ? 'A' - 'a' + *pc : *pc ;
1394
1395 if (strncmp (level, PATIENT_LEVEL_STRING,
1396 strlen (PATIENT_LEVEL_STRING)) == 0)
1397 handle_->queryLevel = PATIENT_LEVEL ;
1398 else if (strncmp (level, STUDY_LEVEL_STRING,
1399 strlen (STUDY_LEVEL_STRING)) == 0)
1400 handle_->queryLevel = STUDY_LEVEL ;
1401 else if (strncmp (level, SERIE_LEVEL_STRING,
1402 strlen (SERIE_LEVEL_STRING)) == 0)
1403 handle_->queryLevel = SERIE_LEVEL ;
1404 else if (strncmp (level, IMAGE_LEVEL_STRING,
1405 strlen (IMAGE_LEVEL_STRING)) == 0)
1406 handle_->queryLevel = IMAGE_LEVEL ;
1407 else {
1408 if (elem. PValueField)
1409 free (elem. PValueField) ;
1410 #ifdef DEBUG
1411 DCMQRDB_DEBUG("DB_startFindRequest () : Illegal query level (" << level << ")");
1412 #endif
1413 status->setStatus(STATUS_FIND_Failed_UnableToProcess);
1414 return (QR_EC_IndexDatabaseError) ;
1415 }
1416 qrLevelFound = OFTrue;
1417 } else {
1418 /** Else it is a query identifier.
1419 ** Append it to our RequestList if it is supported
1420 */
1421 if (DB_TagSupported (elem. XTag)) {
1422
1423 plist = new DB_ElementList ;
1424 if (plist == NULL) {
1425 status->setStatus(STATUS_FIND_Refused_OutOfResources);
1426 return (QR_EC_IndexDatabaseError) ;
1427 }
1428 DB_DuplicateElement (&elem, &(plist->elem)) ;
1429 if (handle_->findRequestList == NULL) {
1430 handle_->findRequestList = last = plist ;
1431 } else {
1432 last->next = plist ;
1433 last = plist ;
1434 }
1435 }
1436 }
1437
1438 if ( elem. PValueField ) {
1439 free (elem. PValueField) ;
1440 }
1441 }
1442 }
1443
1444 if (!qrLevelFound) {
1445 /* The Query/Retrieve Level is missing */
1446 status->setStatus(STATUS_FIND_Failed_IdentifierDoesNotMatchSOPClass);
1447 DCMQRDB_WARN("DB_startFindRequest(): missing Query/Retrieve Level");
1448 handle_->idxCounter = -1 ;
1449 DB_FreeElementList (handle_->findRequestList) ;
1450 handle_->findRequestList = NULL ;
1451 return (QR_EC_IndexDatabaseError) ;
1452 }
1453
1454 switch (handle_->rootLevel)
1455 {
1456 case PATIENT_ROOT :
1457 qLevel = PATIENT_LEVEL ;
1458 lLevel = IMAGE_LEVEL ;
1459 break ;
1460 case STUDY_ROOT :
1461 qLevel = STUDY_LEVEL ;
1462 lLevel = IMAGE_LEVEL ;
1463 break ;
1464 case PATIENT_STUDY:
1465 qLevel = PATIENT_LEVEL ;
1466 lLevel = STUDY_LEVEL ;
1467 break ;
1468 }
1469
1470 /**** Test the consistency of the request list
1471 ***/
1472
1473 if (doCheckFindIdentifier) {
1474 cond = testFindRequestList (handle_->findRequestList, handle_->queryLevel, qLevel, lLevel) ;
1475 if (cond != EC_Normal) {
1476 handle_->idxCounter = -1 ;
1477 DB_FreeElementList (handle_->findRequestList) ;
1478 handle_->findRequestList = NULL ;
1479 #ifdef DEBUG
1480 DCMQRDB_DEBUG("DB_startFindRequest () : STATUS_FIND_Failed_IdentifierDoesNotMatchSOPClass - Invalid RequestList");
1481 #endif
1482 status->setStatus(STATUS_FIND_Failed_IdentifierDoesNotMatchSOPClass);
1483 return (cond) ;
1484 }
1485 }
1486
1487 /**** Goto the beginning of Index File
1488 **** Then find the first matching image
1489 ***/
1490
1491 DB_lock(OFFalse);
1492
1493 DB_IdxInitLoop (&(handle_->idxCounter)) ;
1494 MatchFound = OFFalse ;
1495 cond = EC_Normal ;
1496
1497 CharsetConsideringMatcher dbmatch(*handle_);
1498 while (1) {
1499
1500 /*** Exit loop if read error (or end of file)
1501 **/
1502
1503 if (DB_IdxGetNext (&(handle_->idxCounter), &idxRec) != EC_Normal)
1504 break ;
1505
1506 /*** Exit loop if error or matching OK
1507 **/
1508
1509 dbmatch.setRecord(idxRec);
1510 cond = hierarchicalCompare (handle_, &idxRec, qLevel, qLevel, &MatchFound, dbmatch) ;
1511 if (cond != EC_Normal)
1512 break ;
1513 if (MatchFound)
1514 break ;
1515 }
1516
1517 /**** If an error occurred in Matching function
1518 **** return a failed status
1519 ***/
1520
1521 if (cond != EC_Normal) {
1522 handle_->idxCounter = -1 ;
1523 DB_FreeElementList (handle_->findRequestList) ;
1524 handle_->findRequestList = NULL ;
1525 #ifdef DEBUG
1526 DCMQRDB_DEBUG("DB_startFindRequest () : STATUS_FIND_Failed_UnableToProcess");
1527 #endif
1528 status->setStatus(STATUS_FIND_Failed_UnableToProcess);
1529
1530 DB_unlock();
1531
1532 return (cond) ;
1533 }
1534
1535
1536 /**** If a matching image has been found,
1537 **** add index record to UID found list
1538 **** prepare Response List in handle
1539 **** return status is pending
1540 ***/
1541
1542 if (MatchFound) {
1543 DB_UIDAddFound (handle_, &idxRec) ;
1544 makeResponseList (handle_, &idxRec) ;
1545 #ifdef DEBUG
1546 DCMQRDB_DEBUG("DB_startFindRequest () : STATUS_Pending");
1547 #endif
1548 status->setStatus(STATUS_Pending);
1549 return (EC_Normal) ;
1550 }
1551
1552 /**** else no matching image has been found,
1553 **** free query identifiers list
1554 **** status is success
1555 ***/
1556
1557 else {
1558 handle_->idxCounter = -1 ;
1559 DB_FreeElementList (handle_->findRequestList) ;
1560 handle_->findRequestList = NULL ;
1561 #ifdef DEBUG
1562 DCMQRDB_DEBUG("DB_startFindRequest () : STATUS_Success");
1563 #endif
1564 status->setStatus(STATUS_Success);
1565
1566 DB_unlock();
1567
1568 return (EC_Normal) ;
1569 }
1570
1571 }
1572
1573 /********************
1574 ** Get next find response in Database
1575 */
1576
nextFindResponse(DcmDataset ** findResponseIdentifiers,DcmQueryRetrieveDatabaseStatus * status,const DcmQueryRetrieveCharacterSetOptions & characterSetOptions)1577 OFCondition DcmQueryRetrieveIndexDatabaseHandle::nextFindResponse (
1578 DcmDataset **findResponseIdentifiers,
1579 DcmQueryRetrieveDatabaseStatus *status,
1580 const DcmQueryRetrieveCharacterSetOptions& characterSetOptions)
1581 {
1582
1583 DB_ElementList *plist = NULL;
1584 int MatchFound = OFFalse;
1585 IdxRecord idxRec ;
1586 DB_LEVEL qLevel = PATIENT_LEVEL;
1587 const char *queryLevelString = NULL;
1588 OFCondition cond = EC_Normal;
1589
1590 if (handle_->findResponseList == NULL) {
1591 #ifdef DEBUG
1592 DCMQRDB_DEBUG("DB_nextFindResponse () : STATUS_Success");
1593 #endif
1594 *findResponseIdentifiers = NULL ;
1595 status->setStatus(STATUS_Success);
1596
1597 DB_unlock();
1598
1599 return (EC_Normal) ;
1600 }
1601
1602 /***** Create the response (findResponseIdentifiers) using
1603 ***** the last find done and saved in handle findResponseList
1604 ****/
1605
1606 *findResponseIdentifiers = new DcmDataset ;
1607 if ( *findResponseIdentifiers != NULL ) {
1608
1609 /*** Put responses
1610 **/
1611
1612 for ( plist = handle_->findResponseList ; plist != NULL ; plist = plist->next ) {
1613 DcmTag t(plist->elem.XTag);
1614 DcmElement *dce = DcmItem::newDicomElement(t);
1615 if (dce == NULL) {
1616 status->setStatus(STATUS_FIND_Refused_OutOfResources);
1617 return QR_EC_IndexDatabaseError;
1618 }
1619 if (plist->elem.PValueField != NULL &&
1620 strlen(plist->elem.PValueField) > 0) {
1621 OFCondition ec = dce->putString(plist->elem.PValueField);
1622 if (ec != EC_Normal) {
1623 DCMQRDB_WARN("dbfind: DB_nextFindResponse: cannot put()");
1624 status->setStatus(STATUS_FIND_Failed_UnableToProcess);
1625 return QR_EC_IndexDatabaseError;
1626 }
1627 }
1628 OFCondition ec = (*findResponseIdentifiers)->insert(dce, OFTrue /*replaceOld*/);
1629 if (ec != EC_Normal) {
1630 DCMQRDB_WARN("dbfind: DB_nextFindResponse: cannot insert()");
1631 status->setStatus(STATUS_FIND_Failed_UnableToProcess);
1632 return QR_EC_IndexDatabaseError;
1633 }
1634 }
1635
1636 /*** Append the Query level
1637 **/
1638
1639 switch (handle_->queryLevel) {
1640 case PATIENT_LEVEL :
1641 queryLevelString = PATIENT_LEVEL_STRING ;
1642 break ;
1643 case STUDY_LEVEL :
1644 queryLevelString = STUDY_LEVEL_STRING ;
1645 break ;
1646 case SERIE_LEVEL :
1647 queryLevelString = SERIE_LEVEL_STRING ;
1648 break ;
1649 case IMAGE_LEVEL :
1650 queryLevelString = IMAGE_LEVEL_STRING ;
1651 break ;
1652 }
1653 DU_putStringDOElement(*findResponseIdentifiers,
1654 DCM_QueryRetrieveLevel, queryLevelString);
1655
1656 #ifdef DCMTK_ENABLE_CHARSET_CONVERSION
1657 OFString specificCharacterSet;
1658 if ((*findResponseIdentifiers)->findAndGetOFStringArray(DCM_SpecificCharacterSet, specificCharacterSet).bad())
1659 specificCharacterSet.clear();
1660
1661 const OFString* destinationCharacterSet = NULL;
1662 const OFString* fallbackCharacterSet = NULL;
1663
1664 if (characterSetOptions.flags & DcmQueryRetrieveCharacterSetOptions::Override) {
1665 destinationCharacterSet = &characterSetOptions.characterSet;
1666 if (
1667 (characterSetOptions.flags & DcmQueryRetrieveCharacterSetOptions::Fallback) &&
1668 characterSetOptions.characterSet != handle_->findRequestCharacterSet
1669 ) {
1670 fallbackCharacterSet = &handle_->findRequestCharacterSet;
1671 }
1672 } else {
1673 destinationCharacterSet = &handle_->findRequestCharacterSet;
1674 if (
1675 (characterSetOptions.flags & DcmQueryRetrieveCharacterSetOptions::Fallback) &&
1676 characterSetOptions.characterSet != handle_->findRequestCharacterSet
1677 ) {
1678 fallbackCharacterSet = &characterSetOptions.characterSet;
1679 }
1680 }
1681
1682 if (isConversionNecessary(specificCharacterSet, *destinationCharacterSet)) {
1683 OFCondition charset_status = (*findResponseIdentifiers)->convertCharacterSet(
1684 specificCharacterSet,
1685 *destinationCharacterSet,
1686 characterSetOptions.conversionFlags,
1687 OFTrue);
1688 if (charset_status.bad()) {
1689 DCMQRDB_WARN("Converting response from character set \""
1690 << characterSetName(specificCharacterSet)
1691 << "\" to character set \""
1692 << characterSetName(*destinationCharacterSet)
1693 << "\" failed, (error message: " << charset_status.text() << ')');
1694 if (fallbackCharacterSet && isConversionNecessary(specificCharacterSet, *fallbackCharacterSet)) {
1695 DCMQRDB_INFO("Trying to convert response from character set \""
1696 << characterSetName(specificCharacterSet)
1697 << "\" to fall-back character set \""
1698 << characterSetName(*fallbackCharacterSet) << "\" instead");
1699 charset_status = (*findResponseIdentifiers)->convertCharacterSet(
1700 specificCharacterSet,
1701 *fallbackCharacterSet,
1702 characterSetOptions.conversionFlags,
1703 OFTrue);
1704 if (charset_status.bad()) {
1705 DCMQRDB_WARN("Converting response from character set \""
1706 << characterSetName(specificCharacterSet)
1707 << "\" to character set \""
1708 << characterSetName(*fallbackCharacterSet)
1709 << "\" failed, (error message: " << charset_status.text() << ')');
1710 } else {
1711 DCMQRDB_INFO("Successfully converted response from character set \""
1712 << characterSetName(specificCharacterSet)
1713 << "\" to character set \""
1714 << characterSetName(*fallbackCharacterSet) << "\"");
1715 }
1716 } else if (fallbackCharacterSet) {
1717 DCMQRDB_INFO("Conversion to fall-back character set \""
1718 << characterSetName(*fallbackCharacterSet)
1719 << "\" is not necessary, since the original character set is compatible");
1720 }
1721 } else {
1722 DCMQRDB_INFO("Successfully converted response from character set \""
1723 << characterSetName(specificCharacterSet)
1724 << "\" to character set \""
1725 << characterSetName(*destinationCharacterSet)
1726 << "\"");
1727 }
1728 }
1729 #endif
1730
1731 #ifdef DEBUG
1732 DCMQRDB_DEBUG("DB: findResponseIdentifiers:" << OFendl
1733 << DcmObject::PrintHelper(**findResponseIdentifiers));
1734 #endif
1735 } else {
1736
1737 DB_unlock();
1738
1739 return (QR_EC_IndexDatabaseError) ;
1740 }
1741
1742 switch (handle_->rootLevel) {
1743 case PATIENT_ROOT : qLevel = PATIENT_LEVEL ; break ;
1744 case STUDY_ROOT : qLevel = STUDY_LEVEL ; break ;
1745 case PATIENT_STUDY: qLevel = PATIENT_LEVEL ; break ;
1746 }
1747
1748 /***** Free the last response...
1749 ****/
1750
1751 DB_FreeElementList (handle_->findResponseList) ;
1752 handle_->findResponseList = NULL ;
1753
1754 /***** ... and find the next one
1755 ****/
1756
1757 MatchFound = OFFalse ;
1758 cond = EC_Normal ;
1759
1760 CharsetConsideringMatcher dbmatch(*handle_);
1761 while (1) {
1762
1763 /*** Exit loop if read error (or end of file)
1764 **/
1765
1766 if (DB_IdxGetNext (&(handle_->idxCounter), &idxRec) != EC_Normal)
1767 break ;
1768
1769 /*** If Response already found
1770 **/
1771
1772 if (DB_UIDAlreadyFound (handle_, &idxRec))
1773 continue ;
1774
1775 /*** Exit loop if error or matching OK
1776 **/
1777
1778 dbmatch.setRecord(idxRec);
1779 cond = hierarchicalCompare (handle_, &idxRec, qLevel, qLevel, &MatchFound, dbmatch) ;
1780 if (cond != EC_Normal)
1781 break ;
1782 if (MatchFound)
1783 break ;
1784
1785 }
1786
1787 /**** If an error occurred in Matching function
1788 **** return status is pending
1789 ***/
1790
1791 if (cond != EC_Normal) {
1792 handle_->idxCounter = -1 ;
1793 DB_FreeElementList (handle_->findRequestList) ;
1794 handle_->findRequestList = NULL ;
1795 #ifdef DEBUG
1796 DCMQRDB_DEBUG("DB_nextFindResponse () : STATUS_FIND_Failed_UnableToProcess");
1797 #endif
1798 status->setStatus(STATUS_FIND_Failed_UnableToProcess);
1799
1800 DB_unlock();
1801
1802 return (cond) ;
1803 }
1804
1805 /**** If a matching image has been found
1806 **** add index records UIDs in found UID list
1807 **** prepare Response List in handle
1808 ***/
1809
1810 if (MatchFound) {
1811 DB_UIDAddFound (handle_, &idxRec) ;
1812 makeResponseList (handle_, &idxRec) ;
1813 #ifdef DEBUG
1814 DCMQRDB_DEBUG("DB_nextFindResponse () : STATUS_Pending");
1815 #endif
1816 status->setStatus(STATUS_Pending);
1817 return (EC_Normal) ;
1818 }
1819
1820 /**** else no matching image has been found,
1821 **** free query identifiers list
1822 **** Response list is null, so next call will return STATUS_Success
1823 ***/
1824
1825 else {
1826 handle_->idxCounter = -1 ;
1827 DB_FreeElementList (handle_->findRequestList) ;
1828 handle_->findRequestList = NULL ;
1829 DB_FreeUidList (handle_->uidList) ;
1830 handle_->uidList = NULL ;
1831 }
1832
1833 #ifdef DEBUG
1834 DCMQRDB_DEBUG("DB_nextFindResponse () : STATUS_Pending");
1835 #endif
1836 status->setStatus(STATUS_Pending);
1837 return (EC_Normal) ;
1838 }
1839
1840 /********************
1841 ** Cancel find request
1842 */
1843
cancelFindRequest(DcmQueryRetrieveDatabaseStatus * status)1844 OFCondition DcmQueryRetrieveIndexDatabaseHandle::cancelFindRequest (DcmQueryRetrieveDatabaseStatus *status)
1845 {
1846
1847 handle_->idxCounter = -1 ;
1848 DB_FreeElementList (handle_->findRequestList) ;
1849 handle_->findRequestList = NULL ;
1850 DB_FreeElementList (handle_->findResponseList) ;
1851 handle_->findResponseList = NULL ;
1852 DB_FreeUidList (handle_->uidList) ;
1853 handle_->uidList = NULL ;
1854
1855 status->setStatus(STATUS_FIND_Cancel_MatchingTerminatedDueToCancelRequest);
1856
1857 DB_unlock();
1858
1859 return (EC_Normal) ;
1860 }
1861
1862 /* ========================= MOVE ========================= */
1863
1864 /************
1865 * Test a Move Request List
1866 * Returns EC_Normal if OK, else returns QR_EC_IndexDatabaseError
1867 */
1868
testMoveRequestList(DB_ElementList * findRequestList,DB_LEVEL queryLevel,DB_LEVEL infLevel,DB_LEVEL lowestLevel)1869 OFCondition DcmQueryRetrieveIndexDatabaseHandle::testMoveRequestList (
1870 DB_ElementList *findRequestList,
1871 DB_LEVEL queryLevel,
1872 DB_LEVEL infLevel,
1873 DB_LEVEL lowestLevel
1874 )
1875 {
1876 DB_ElementList *plist ;
1877 DB_LEVEL XTagLevel = PATIENT_LEVEL; // DB_GetTagLevel() will set this correctly
1878 DB_KEY_TYPE XTagType = OPTIONAL_KEY; // DB_GetTagKeyAttr() will set this
1879 int level ;
1880
1881 /**** Query level must be at least the infLevel
1882 ***/
1883
1884 if (queryLevel < infLevel) {
1885 DCMQRDB_DEBUG("Level incompatible with Information Model (level " << (int)queryLevel << ")");
1886 return QR_EC_IndexDatabaseError ;
1887 }
1888
1889 if (queryLevel > lowestLevel) {
1890 DCMQRDB_DEBUG("Level incompatible with Information Model (level " << (int)queryLevel << ")");
1891 return QR_EC_IndexDatabaseError ;
1892 }
1893
1894 for (level = PATIENT_LEVEL ; level <= IMAGE_LEVEL ; level++) {
1895
1896 /**** Manage exception due to StudyRoot Information Model :
1897 **** In this information model, move may not include any
1898 **** Patient attributes.
1899 ***/
1900
1901 if ((level == PATIENT_LEVEL) && (infLevel == STUDY_LEVEL)) {
1902
1903 /** In Study Root Information Model, do not accept any
1904 ** Patient Tag
1905 */
1906
1907 int atLeastOneKeyFound = OFFalse ;
1908 for (plist = findRequestList ; plist ; plist = plist->next) {
1909 DB_GetTagLevel (plist->elem. XTag, &XTagLevel) ;
1910 if (XTagLevel != level)
1911 continue ;
1912 atLeastOneKeyFound = OFTrue ;
1913 }
1914 if (atLeastOneKeyFound) {
1915 DCMQRDB_DEBUG("Key found in Study Root Information Model (level " << level << ")");
1916 return QR_EC_IndexDatabaseError ;
1917 }
1918 }
1919
1920 /**** If current level is above or equal to the QueryLevel
1921 ***/
1922
1923 else if (level <= queryLevel) {
1924
1925 /** For these levels, only unique keys are allowed
1926 ** Parse the request list elements referring to
1927 ** this level.
1928 ** Check that only unique key attr are provided
1929 */
1930
1931 int uniqueKeyFound = OFFalse ;
1932 for (plist = findRequestList ; plist ; plist = plist->next) {
1933 DB_GetTagLevel (plist->elem. XTag, &XTagLevel) ;
1934 if (XTagLevel != level)
1935 continue ;
1936 DB_GetTagKeyAttr (plist->elem. XTag, &XTagType) ;
1937 if (XTagType != UNIQUE_KEY) {
1938 DCMQRDB_DEBUG("Non Unique Key found (level " << level << ")");
1939 return QR_EC_IndexDatabaseError ;
1940 }
1941 else if (uniqueKeyFound) {
1942 DCMQRDB_DEBUG("More than one Unique Key found (level " << level << ")");
1943 return QR_EC_IndexDatabaseError ;
1944 }
1945 else
1946 uniqueKeyFound = OFTrue ;
1947 }
1948 if (! uniqueKeyFound) {
1949 DCMQRDB_DEBUG("No Unique Key found (level " << level << ")");
1950 return QR_EC_IndexDatabaseError ;
1951 }
1952 }
1953
1954 /**** If current level beyond the QueryLevel
1955 ***/
1956
1957 else if (level > queryLevel) {
1958
1959 /** For this level, no key is allowed
1960 ** Parse the request list elements referring to
1961 ** this level.
1962 ** Check that no key is provided
1963 */
1964
1965 int atLeastOneKeyFound = OFFalse ;
1966 for (plist = findRequestList ; plist ; plist = plist->next) {
1967 DB_GetTagLevel (plist->elem. XTag, &XTagLevel) ;
1968 if (XTagLevel != level)
1969 continue ;
1970 atLeastOneKeyFound = OFTrue ;
1971 }
1972 if (atLeastOneKeyFound) {
1973 DCMQRDB_DEBUG("Key found beyond query level (level " << level << ")");
1974 return QR_EC_IndexDatabaseError ;
1975 }
1976 }
1977
1978 }
1979 return EC_Normal ;
1980 }
1981
1982
1983
startMoveRequest(const char * SOPClassUID,DcmDataset * moveRequestIdentifiers,DcmQueryRetrieveDatabaseStatus * status)1984 OFCondition DcmQueryRetrieveIndexDatabaseHandle::startMoveRequest(
1985 const char *SOPClassUID,
1986 DcmDataset *moveRequestIdentifiers,
1987 DcmQueryRetrieveDatabaseStatus *status)
1988 {
1989
1990 DB_SmallDcmElmt elem ;
1991 DB_ElementList *plist = NULL;
1992 DB_ElementList *last = NULL;
1993 DB_CounterList *pidxlist = NULL;
1994 DB_CounterList *lastidxlist = NULL;
1995 int MatchFound = OFFalse;
1996 IdxRecord idxRec ;
1997 DB_LEVEL qLevel = PATIENT_LEVEL; // highest legal level for a query in the current model
1998 DB_LEVEL lLevel = IMAGE_LEVEL; // lowest legal level for a query in the current model
1999 OFCondition cond = EC_Normal;
2000 OFBool qrLevelFound = OFFalse;
2001
2002 /**** Is SOPClassUID supported ?
2003 ***/
2004
2005 if (strcmp( SOPClassUID, UID_MOVEPatientRootQueryRetrieveInformationModel) == 0)
2006 handle_->rootLevel = PATIENT_ROOT ;
2007 else if (strcmp( SOPClassUID, UID_MOVEStudyRootQueryRetrieveInformationModel) == 0)
2008 handle_->rootLevel = STUDY_ROOT ;
2009 #ifndef NO_PATIENTSTUDYONLY_SUPPORT
2010 else if (strcmp( SOPClassUID, UID_RETIRED_MOVEPatientStudyOnlyQueryRetrieveInformationModel) == 0)
2011 handle_->rootLevel = PATIENT_STUDY ;
2012 #endif
2013 #ifndef NO_GET_SUPPORT
2014 /* experimental support for GET */
2015 else if (strcmp( SOPClassUID, UID_GETPatientRootQueryRetrieveInformationModel) == 0)
2016 handle_->rootLevel = PATIENT_ROOT ;
2017 else if (strcmp( SOPClassUID, UID_GETStudyRootQueryRetrieveInformationModel) == 0)
2018 handle_->rootLevel = STUDY_ROOT ;
2019 #ifndef NO_PATIENTSTUDYONLY_SUPPORT
2020 else if (strcmp( SOPClassUID, UID_RETIRED_GETPatientStudyOnlyQueryRetrieveInformationModel) == 0)
2021 handle_->rootLevel = PATIENT_STUDY ;
2022 #endif
2023 #endif
2024
2025 else {
2026 status->setStatus(STATUS_MOVE_Failed_SOPClassNotSupported);
2027 return (QR_EC_IndexDatabaseError) ;
2028 }
2029
2030
2031 /**** Parse Identifiers in the Dicom Object
2032 **** Find Query Level and construct a list
2033 **** of query identifiers
2034 ***/
2035
2036 int elemCount = (int)(moveRequestIdentifiers->card());
2037 for (int elemIndex=0; elemIndex<elemCount; elemIndex++) {
2038
2039 DcmElement* dcelem = moveRequestIdentifiers->getElement(elemIndex);
2040
2041 elem.XTag = dcelem->getTag().getXTag();
2042 if (elem.XTag == DCM_QueryRetrieveLevel || DB_TagSupported(elem.XTag)) {
2043 elem.ValueLength = dcelem->getLength();
2044 if (elem.ValueLength == 0) {
2045 elem.PValueField = NULL ;
2046 } else if ((elem.PValueField = (char*)malloc((size_t)(elem.ValueLength+1))) == NULL) {
2047 status->setStatus(STATUS_MOVE_Failed_UnableToProcess);
2048 return (QR_EC_IndexDatabaseError) ;
2049 } else {
2050 /* only char string type tags are supported at the moment */
2051 char *s = NULL;
2052 dcelem->getString(s);
2053 /* the available space is always elem.ValueLength+1 */
2054 OFStandard::strlcpy(elem.PValueField, s, elem.ValueLength+1);
2055 }
2056
2057 /** If element is the Query Level, store it in handle
2058 */
2059
2060 if (elem. XTag == DCM_QueryRetrieveLevel && elem.PValueField) {
2061 char *pc ;
2062 char level [50] ;
2063
2064 strncpy (level, (char *) elem. PValueField, (size_t)((elem. ValueLength < 50) ? elem. ValueLength : 49)) ;
2065
2066 /*** Skip this two lines if you want strict comparison
2067 **/
2068
2069 for (pc = level ; *pc ; pc++)
2070 *pc = ((*pc >= 'a') && (*pc <= 'z')) ? 'A' - 'a' + *pc : *pc ;
2071
2072 if (strncmp (level, PATIENT_LEVEL_STRING,
2073 strlen (PATIENT_LEVEL_STRING)) == 0)
2074 handle_->queryLevel = PATIENT_LEVEL ;
2075 else if (strncmp (level, STUDY_LEVEL_STRING,
2076 strlen (STUDY_LEVEL_STRING)) == 0)
2077 handle_->queryLevel = STUDY_LEVEL ;
2078 else if (strncmp (level, SERIE_LEVEL_STRING,
2079 strlen (SERIE_LEVEL_STRING)) == 0)
2080 handle_->queryLevel = SERIE_LEVEL ;
2081 else if (strncmp (level, IMAGE_LEVEL_STRING,
2082 strlen (IMAGE_LEVEL_STRING)) == 0)
2083 handle_->queryLevel = IMAGE_LEVEL ;
2084 else {
2085 #ifdef DEBUG
2086 DCMQRDB_DEBUG("DB_startMoveRequest : STATUS_MOVE_Failed_UnableToProcess");
2087 #endif
2088 status->setStatus(STATUS_MOVE_Failed_UnableToProcess);
2089 return (QR_EC_IndexDatabaseError) ;
2090 }
2091 qrLevelFound = OFTrue;
2092 } else {
2093 /** Else it is a query identifier
2094 ** Append it to our RequestList
2095 */
2096 if (! DB_TagSupported (elem. XTag))
2097 continue ;
2098
2099 plist = new DB_ElementList ;
2100 if (plist == NULL) {
2101 status->setStatus(STATUS_FIND_Refused_OutOfResources);
2102 return (QR_EC_IndexDatabaseError) ;
2103 }
2104 DB_DuplicateElement (&elem, & (plist->elem)) ;
2105 if (handle_->findRequestList == NULL) {
2106 handle_->findRequestList = last = plist ;
2107 } else {
2108 last->next = plist ;
2109 last = plist ;
2110 }
2111 }
2112
2113 if ( elem. PValueField ) {
2114 free (elem. PValueField) ;
2115 }
2116 }
2117 }
2118
2119 if (!qrLevelFound) {
2120 /* The Query/Retrieve Level is missing */
2121 status->setStatus(STATUS_MOVE_Failed_IdentifierDoesNotMatchSOPClass);
2122 DCMQRDB_WARN("DB_startMoveRequest(): missing Query/Retrieve Level");
2123 handle_->idxCounter = -1 ;
2124 DB_FreeElementList (handle_->findRequestList) ;
2125 handle_->findRequestList = NULL ;
2126 return (QR_EC_IndexDatabaseError) ;
2127 }
2128
2129 switch (handle_->rootLevel)
2130 {
2131 case PATIENT_ROOT :
2132 qLevel = PATIENT_LEVEL ;
2133 lLevel = IMAGE_LEVEL ;
2134 break ;
2135 case STUDY_ROOT :
2136 qLevel = STUDY_LEVEL ;
2137 lLevel = IMAGE_LEVEL ;
2138 break ;
2139 case PATIENT_STUDY:
2140 qLevel = PATIENT_LEVEL ;
2141 lLevel = STUDY_LEVEL ;
2142 break ;
2143 }
2144
2145 /**** Test the consistency of the request list
2146 ***/
2147
2148 if (doCheckMoveIdentifier) {
2149 cond = testMoveRequestList (handle_->findRequestList,
2150 handle_->queryLevel, qLevel, lLevel) ;
2151 if (cond != EC_Normal) {
2152 handle_->idxCounter = -1 ;
2153 DB_FreeElementList (handle_->findRequestList) ;
2154 handle_->findRequestList = NULL ;
2155 #ifdef DEBUG
2156 DCMQRDB_DEBUG("DB_startMoveRequest () : STATUS_MOVE_Failed_IdentifierDoesNotMatchSOPClass - Invalid RequestList");
2157 #endif
2158 status->setStatus(STATUS_MOVE_Failed_IdentifierDoesNotMatchSOPClass);
2159 return (cond) ;
2160 }
2161 }
2162
2163 /**** Goto the beginning of Index File
2164 **** Then find all matching images
2165 ***/
2166
2167 MatchFound = OFFalse ;
2168 handle_->moveCounterList = NULL ;
2169 handle_->NumberRemainOperations = 0 ;
2170
2171 /**** Find matching images
2172 ***/
2173
2174 DB_lock(OFFalse);
2175
2176 CharsetConsideringMatcher dbmatch(*handle_);
2177 DB_IdxInitLoop (&(handle_->idxCounter)) ;
2178 while (1) {
2179
2180 /*** Exit loop if read error (or end of file)
2181 **/
2182
2183 if (DB_IdxGetNext (&(handle_->idxCounter), &idxRec) != EC_Normal)
2184 break ;
2185
2186 /*** If matching found
2187 **/
2188
2189 dbmatch.setRecord(idxRec);
2190 cond = hierarchicalCompare (handle_, &idxRec, qLevel, qLevel, &MatchFound, dbmatch) ;
2191 if (MatchFound) {
2192 pidxlist = (DB_CounterList *) malloc (sizeof( DB_CounterList ) ) ;
2193 if (pidxlist == NULL) {
2194 status->setStatus(STATUS_FIND_Refused_OutOfResources);
2195 return (QR_EC_IndexDatabaseError) ;
2196 }
2197
2198 pidxlist->next = NULL ;
2199 pidxlist->idxCounter = handle_->idxCounter ;
2200 handle_->NumberRemainOperations++ ;
2201 if ( handle_->moveCounterList == NULL )
2202 handle_->moveCounterList = lastidxlist = pidxlist ;
2203 else {
2204 lastidxlist->next = pidxlist ;
2205 lastidxlist = pidxlist ;
2206 }
2207 }
2208 }
2209
2210 DB_FreeElementList (handle_->findRequestList) ;
2211 handle_->findRequestList = NULL ;
2212
2213 /**** If a matching image has been found,
2214 **** status is pending
2215 ***/
2216
2217 if ( handle_->NumberRemainOperations > 0 ) {
2218 #ifdef DEBUG
2219 DCMQRDB_DEBUG("DB_startMoveRequest : STATUS_Pending");
2220 #endif
2221 status->setStatus(STATUS_Pending);
2222 return (EC_Normal) ;
2223 }
2224
2225 /**** else no matching image has been found,
2226 **** free query identifiers list
2227 **** status is success
2228 ***/
2229
2230 else {
2231 handle_->idxCounter = -1 ;
2232 #ifdef DEBUG
2233 DCMQRDB_DEBUG("DB_startMoveRequest : STATUS_Success");
2234 #endif
2235 status->setStatus(STATUS_Success);
2236
2237 DB_unlock();
2238
2239 return (EC_Normal) ;
2240 }
2241
2242
2243 }
2244
nextMoveResponse(char * SOPClassUID,size_t SOPClassUIDSize,char * SOPInstanceUID,size_t SOPInstanceUIDSize,char * imageFileName,size_t imageFileNameSize,unsigned short * numberOfRemainingSubOperations,DcmQueryRetrieveDatabaseStatus * status)2245 OFCondition DcmQueryRetrieveIndexDatabaseHandle::nextMoveResponse(
2246 char *SOPClassUID,
2247 size_t SOPClassUIDSize,
2248 char *SOPInstanceUID,
2249 size_t SOPInstanceUIDSize,
2250 char *imageFileName,
2251 size_t imageFileNameSize,
2252 unsigned short *numberOfRemainingSubOperations,
2253 DcmQueryRetrieveDatabaseStatus *status)
2254 {
2255 IdxRecord idxRec ;
2256 DB_CounterList *nextlist ;
2257
2258 /**** If all matching images have been retrieved,
2259 **** status is success
2260 ***/
2261
2262 if ( handle_->NumberRemainOperations <= 0 ) {
2263 status->setStatus(STATUS_Success);
2264
2265 DB_unlock();
2266
2267 return (EC_Normal) ;
2268 }
2269
2270 /**** Goto the next matching image number of Index File
2271 ***/
2272
2273 if (DB_IdxRead (handle_->moveCounterList->idxCounter, &idxRec) != EC_Normal) {
2274 #ifdef DEBUG
2275 DCMQRDB_DEBUG("DB_nextMoveResponse : STATUS_MOVE_Failed_UnableToProcess");
2276 #endif
2277 status->setStatus(STATUS_MOVE_Failed_UnableToProcess);
2278
2279 DB_unlock();
2280
2281 return (QR_EC_IndexDatabaseError) ;
2282 }
2283
2284 OFStandard::strlcpy(SOPClassUID, (char *) idxRec. SOPClassUID, SOPClassUIDSize) ;
2285 OFStandard::strlcpy(SOPInstanceUID, (char *) idxRec. SOPInstanceUID, SOPInstanceUIDSize) ;
2286 OFStandard::strlcpy(imageFileName, (char *) idxRec. filename, imageFileNameSize) ;
2287
2288 *numberOfRemainingSubOperations = --handle_->NumberRemainOperations ;
2289
2290 nextlist = handle_->moveCounterList->next ;
2291 free (handle_->moveCounterList) ;
2292 handle_->moveCounterList = nextlist ;
2293 status->setStatus(STATUS_Pending);
2294 #ifdef DEBUG
2295 DCMQRDB_DEBUG("DB_nextMoveResponse : STATUS_Pending");
2296 #endif
2297 return (EC_Normal) ;
2298 }
2299
2300
2301
cancelMoveRequest(DcmQueryRetrieveDatabaseStatus * status)2302 OFCondition DcmQueryRetrieveIndexDatabaseHandle::cancelMoveRequest (DcmQueryRetrieveDatabaseStatus *status)
2303 {
2304 DB_CounterList *plist ;
2305
2306 while (handle_->moveCounterList) {
2307 plist = handle_->moveCounterList ;
2308 handle_->moveCounterList = handle_->moveCounterList->next ;
2309 free (plist) ;
2310 }
2311
2312 status->setStatus(STATUS_MOVE_Cancel_SubOperationsTerminatedDueToCancelIndication);
2313
2314 DB_unlock();
2315
2316 return (EC_Normal) ;
2317 }
2318
2319
2320 /* ========================= STORE ========================= */
2321
2322
enableQuotaSystem(OFBool enable)2323 void DcmQueryRetrieveIndexDatabaseHandle::enableQuotaSystem(OFBool enable)
2324 {
2325 quotaSystemEnabled = enable;
2326 }
2327
2328
2329 /*
2330 ** Image file deleting
2331 */
2332
2333
deleteImageFile(char * imgFile)2334 OFCondition DcmQueryRetrieveIndexDatabaseHandle::deleteImageFile(char* imgFile)
2335 {
2336 if (!quotaSystemEnabled) {
2337 DCMQRDB_WARN("file delete operations are disabled, keeping file: " << imgFile << " despite duplicate SOP Instance UID");
2338 return EC_Normal;
2339 } else {
2340 DCMQRDB_WARN("Deleting file: " << imgFile << " due to quota or duplicate SOP instance UID");
2341 }
2342
2343 #ifdef LOCK_IMAGE_FILES
2344 int lockfd;
2345 #ifdef O_BINARY
2346 lockfd = open(imgFile, O_RDWR | O_BINARY, 0666); /* obtain file descriptor */
2347 #else
2348 lockfd = open(imgFile, O_RDWR, 0666); /* obtain file descriptor */
2349 #endif
2350 if (lockfd < 0) {
2351 DCMQRDB_WARN("DB ERROR: cannot open image file for deleting: " << imgFile);
2352 return QR_EC_IndexDatabaseError;
2353 }
2354 if (dcmtk_flock(lockfd, LOCK_EX) < 0) { /* exclusive lock (blocking) */
2355 DCMQRDB_WARN("DB ERROR: cannot lock image file for deleting: " << imgFile);
2356 dcmtk_plockerr("DB ERROR");
2357 }
2358 #endif
2359
2360 if (unlink(imgFile) < 0) {
2361 /* delete file */
2362 DCMQRDB_ERROR("DB ERROR: cannot delete image file: " << imgFile << OFendl
2363 << "QR_EC_IndexDatabaseError: " << OFStandard::getLastSystemErrorCode().message());
2364 }
2365
2366 #ifdef LOCK_IMAGE_FILES
2367 if (dcmtk_flock(lockfd, LOCK_UN) < 0) { /* unlock */
2368 DCMQRDB_WARN("DB ERROR: cannot unlock image file for deleting: " << imgFile);
2369 dcmtk_plockerr("DB ERROR");
2370 }
2371 close(lockfd); /* release file descriptor */
2372 #endif
2373
2374 return EC_Normal;
2375 }
2376
2377
2378 /*************************
2379 ** Delete oldest study in database
2380 */
2381
deleteOldestStudy(StudyDescRecord * pStudyDesc)2382 int DcmQueryRetrieveIndexDatabaseHandle::deleteOldestStudy(StudyDescRecord *pStudyDesc)
2383 {
2384
2385 int oldestStudy ;
2386 double OldestDate ;
2387 int s ;
2388 size_t n ;
2389 int idx = 0 ;
2390 IdxRecord idxRec ;
2391
2392 oldestStudy = 0 ;
2393 OldestDate = 0.0 ;
2394
2395 #ifdef DEBUG
2396 DCMQRDB_DEBUG("deleteOldestStudy");
2397 #endif
2398
2399 for ( s = 0 ; s < handle_ -> maxStudiesAllowed ; s++ ) {
2400 if ( ( pStudyDesc[s]. NumberofRegistratedImages != 0 ) &&
2401 ( ( OldestDate == 0.0 ) || ( pStudyDesc[s]. LastRecordedDate < OldestDate ) ) ) {
2402 OldestDate = pStudyDesc[s]. LastRecordedDate ;
2403 oldestStudy = s ;
2404 }
2405 }
2406
2407 #ifdef DEBUG
2408 DCMQRDB_DEBUG("deleteOldestStudy oldestStudy = " << oldestStudy);
2409 #endif
2410
2411 n = strlen(pStudyDesc[oldestStudy].StudyInstanceUID) ;
2412 while ( DB_IdxRead (idx, &idxRec) == EC_Normal ) {
2413
2414 if ( ! ( strncmp(idxRec. StudyInstanceUID, pStudyDesc[oldestStudy].StudyInstanceUID, n) ) ) {
2415 DB_IdxRemove (idx) ;
2416 deleteImageFile(idxRec.filename);
2417 }
2418 idx++ ;
2419 }
2420
2421 pStudyDesc[oldestStudy].NumberofRegistratedImages = 0 ;
2422 pStudyDesc[oldestStudy].StudySize = 0 ;
2423 return(oldestStudy) ;
2424 }
2425
2426
2427
2428
2429 /*************************
2430 ** Delete oldest images in database
2431 */
2432
deleteOldestImages(StudyDescRecord * pStudyDesc,int StudyNum,char * StudyUID,long RequiredSize)2433 OFCondition DcmQueryRetrieveIndexDatabaseHandle::deleteOldestImages(StudyDescRecord *pStudyDesc, int StudyNum, char *StudyUID, long RequiredSize)
2434 {
2435
2436 ImagesofStudyArray *StudyArray ;
2437 IdxRecord idxRec ;
2438 int nbimages = 0 , s = 0;
2439 size_t n ;
2440 long DeletedSize ;
2441
2442 #ifdef DEBUG
2443 DCMQRDB_DEBUG("deleteOldestImages RequiredSize = " << RequiredSize);
2444 #endif
2445 n = strlen(StudyUID) ;
2446 StudyArray = (ImagesofStudyArray *)malloc(MAX_NUMBER_OF_IMAGES * sizeof(ImagesofStudyArray)) ;
2447
2448 if (StudyArray == NULL) {
2449 DCMQRDB_WARN("deleteOldestImages: out of memory");
2450 return QR_EC_IndexDatabaseError;
2451 }
2452
2453 /** Find all images having the same StudyUID
2454 */
2455
2456 DB_IdxInitLoop (&(handle_ -> idxCounter)) ;
2457 while ( DB_IdxGetNext(&(handle_ -> idxCounter), &idxRec) == EC_Normal ) {
2458 if ( ! ( strncmp(idxRec. StudyInstanceUID, StudyUID, n) ) ) {
2459
2460 StudyArray[nbimages]. idxCounter = handle_ -> idxCounter ;
2461 StudyArray[nbimages]. RecordedDate = idxRec. RecordedDate ;
2462 StudyArray[nbimages++]. ImageSize = idxRec. ImageSize ;
2463 }
2464 }
2465
2466 /** Sort the StudyArray in order to have the oldest images first
2467 */
2468 qsort((char *)StudyArray, nbimages, sizeof(ImagesofStudyArray), DB_Compare) ;
2469
2470 #ifdef DEBUG
2471 {
2472 int i ;
2473 DCMQRDB_DEBUG("deleteOldestImages : Sorted images ref array");
2474 for (i = 0 ; i < nbimages ; i++)
2475 DCMQRDB_DEBUG("[" << STD_NAMESPACE setw(2) << i << "] : Size " << StudyArray[i].ImageSize
2476 << " Date " << STD_NAMESPACE setw(20) << STD_NAMESPACE setprecision(3) << StudyArray[i].RecordedDate
2477 << " Ref " << StudyArray[i].idxCounter);
2478 DCMQRDB_DEBUG("deleteOldestImages : end of ref array");
2479 }
2480 #endif
2481
2482 s = 0 ;
2483 DeletedSize = 0 ;
2484
2485 while ( DeletedSize < RequiredSize ) {
2486
2487 IdxRecord idxRemoveRec ;
2488 DB_IdxRead (StudyArray[s]. idxCounter, &idxRemoveRec) ;
2489 #ifdef DEBUG
2490 DCMQRDB_DEBUG("Removing file : " << idxRemoveRec. filename);
2491 #endif
2492 deleteImageFile(idxRemoveRec.filename);
2493
2494 DB_IdxRemove (StudyArray[s]. idxCounter) ;
2495 pStudyDesc[StudyNum].NumberofRegistratedImages -= 1 ;
2496 pStudyDesc[StudyNum].StudySize -= StudyArray[s]. ImageSize ;
2497 DeletedSize += StudyArray[s++]. ImageSize ;
2498 }
2499
2500 #ifdef DEBUG
2501 DCMQRDB_DEBUG("deleteOldestImages DeletedSize = " << (int)DeletedSize);
2502 #endif
2503 free(StudyArray) ;
2504 return( EC_Normal ) ;
2505
2506 }
2507
2508
2509
2510 /*************************
2511 * Verify if study UID already exists
2512 * If the study UID exists, its index in the study descriptor is returned.
2513 * If the study UID does not exist, the index of the first unused descriptor entry is returned.
2514 * If no entries are free, maxStudiesAllowed is returned.
2515 */
2516
matchStudyUIDInStudyDesc(StudyDescRecord * pStudyDesc,char * StudyUID,int maxStudiesAllowed)2517 int DcmQueryRetrieveIndexDatabaseHandle::matchStudyUIDInStudyDesc (StudyDescRecord *pStudyDesc, char *StudyUID, int maxStudiesAllowed)
2518 {
2519 int s = 0 ;
2520 while (s < maxStudiesAllowed)
2521 {
2522 if ((pStudyDesc[s].NumberofRegistratedImages > 0) && (0 == strcmp(pStudyDesc[s].StudyInstanceUID, StudyUID))) break;
2523 s++ ;
2524 }
2525 if (s==maxStudiesAllowed) // study uid does not exist, look for free descriptor
2526 {
2527 s=0;
2528 while (s < maxStudiesAllowed)
2529 {
2530 if (pStudyDesc[s].NumberofRegistratedImages == 0) break;
2531 s++ ;
2532 }
2533 }
2534 return s;
2535 }
2536
2537
2538 /*************************
2539 ** Check up storage rights in Study Desk record
2540 */
2541
checkupinStudyDesc(StudyDescRecord * pStudyDesc,char * StudyUID,long imageSize)2542 OFCondition DcmQueryRetrieveIndexDatabaseHandle::checkupinStudyDesc(StudyDescRecord *pStudyDesc, char *StudyUID, long imageSize)
2543 {
2544 int s ;
2545 long RequiredSize ;
2546
2547 s = matchStudyUIDInStudyDesc (pStudyDesc, StudyUID,
2548 (int)(handle_ -> maxStudiesAllowed)) ;
2549
2550 /** If Study already exists
2551 */
2552
2553 if ( pStudyDesc[s]. NumberofRegistratedImages != 0 ) {
2554
2555 #ifdef DEBUG
2556 DCMQRDB_DEBUG("checkupinStudyDesc: study already exists : " << s) ;
2557 #endif
2558 if ( OFstatic_cast(size_t, pStudyDesc[s]. StudySize) + imageSize >
2559 OFstatic_cast(size_t, handle_ -> maxBytesPerStudy) )
2560 {
2561 if ( imageSize > handle_ -> maxBytesPerStudy ) {
2562 #ifdef DEBUG
2563 DCMQRDB_DEBUG("checkupinStudyDesc: imageSize = " << imageSize << " too large");
2564 #endif
2565 return ( QR_EC_IndexDatabaseError ) ;
2566 }
2567
2568 RequiredSize = imageSize -
2569 ( handle_ -> maxBytesPerStudy - pStudyDesc[s]. StudySize ) ;
2570 deleteOldestImages(pStudyDesc, s, StudyUID, RequiredSize) ;
2571 }
2572
2573
2574 }
2575 else {
2576 #ifdef DEBUG
2577 DCMQRDB_DEBUG("checkupinStudyDesc: study doesn't already exist");
2578 #endif
2579 if ( imageSize > handle_ -> maxBytesPerStudy ) {
2580 #ifdef DEBUG
2581 DCMQRDB_DEBUG("checkupinStudyDesc: imageSize = " << imageSize << " too large");
2582 #endif
2583 return ( QR_EC_IndexDatabaseError ) ;
2584 }
2585 if ( s > ( handle_ -> maxStudiesAllowed - 1 ) )
2586 s = deleteOldestStudy(pStudyDesc) ;
2587
2588 }
2589
2590 pStudyDesc[s]. StudySize += imageSize ;
2591 #ifdef DEBUG
2592 DCMQRDB_DEBUG("checkupinStudyDesc: ~~~~~~~~ StudySize = " << pStudyDesc[s]. StudySize);
2593 #endif
2594
2595 /* we only have second accuracy */
2596 pStudyDesc[s]. LastRecordedDate = (double) time(NULL);
2597
2598 pStudyDesc[s]. NumberofRegistratedImages++ ;
2599 OFStandard::strlcpy(pStudyDesc[s].StudyInstanceUID, StudyUID, UI_MAX_LENGTH+1) ;
2600
2601 if ( DB_StudyDescChange (pStudyDesc) == EC_Normal)
2602 return ( EC_Normal ) ;
2603 else
2604 return ( QR_EC_IndexDatabaseError ) ;
2605 }
2606
2607 /*
2608 * If the image is already stored remove it from the database.
2609 * hewett - Nov. 1, 93
2610 */
removeDuplicateImage(const char * SOPInstanceUID,const char * StudyInstanceUID,StudyDescRecord * pStudyDesc,const char * newImageFileName)2611 OFCondition DcmQueryRetrieveIndexDatabaseHandle::removeDuplicateImage(
2612 const char *SOPInstanceUID, const char *StudyInstanceUID,
2613 StudyDescRecord *pStudyDesc, const char *newImageFileName)
2614 {
2615
2616 int idx = 0;
2617 IdxRecord idxRec ;
2618 int studyIdx = 0;
2619
2620 studyIdx = matchStudyUIDInStudyDesc (pStudyDesc, (char*)StudyInstanceUID,
2621 (int)(handle_ -> maxStudiesAllowed)) ;
2622
2623 if ( pStudyDesc[studyIdx].NumberofRegistratedImages == 0 ) {
2624 /* no study images, cannot be any old images */
2625 return EC_Normal;
2626 }
2627
2628 while (DB_IdxRead(idx, &idxRec) == EC_Normal) {
2629
2630 if (strcmp(idxRec.SOPInstanceUID, SOPInstanceUID) == 0) {
2631
2632 #ifdef DEBUG
2633 DCMQRDB_DEBUG("--- Removing Existing DB Image Record: " << idxRec.filename);
2634 #endif
2635 /* remove the idx record */
2636 DB_IdxRemove (idx);
2637 /* only remove the image file if it is different than that
2638 * being entered into the database.
2639 */
2640 if (strcmp(idxRec.filename, newImageFileName) != 0) {
2641 deleteImageFile(idxRec.filename);
2642 }
2643 /* update the study info */
2644 pStudyDesc[studyIdx].NumberofRegistratedImages--;
2645 pStudyDesc[studyIdx].StudySize -= idxRec.ImageSize;
2646 }
2647 idx++;
2648 }
2649 /* the study record should be written to file later */
2650 return EC_Normal;
2651 }
2652
2653
2654 /*************************
2655 ** Add data from imageFileName to database
2656 */
2657
storeRequest(const char * SOPClassUID,const char *,const char * imageFileName,DcmQueryRetrieveDatabaseStatus * status,OFBool isNew)2658 OFCondition DcmQueryRetrieveIndexDatabaseHandle::storeRequest (
2659 const char *SOPClassUID,
2660 const char * /*SOPInstanceUID*/,
2661 const char *imageFileName,
2662 DcmQueryRetrieveDatabaseStatus *status,
2663 OFBool isNew)
2664 {
2665 IdxRecord idxRec ;
2666 StudyDescRecord *pStudyDesc ;
2667 int i ;
2668 struct stat stat_buf ;
2669
2670 /**** Initialize an IdxRecord
2671 ***/
2672
2673 bzero((char*)&idxRec, sizeof(idxRec));
2674
2675 DB_IdxInitRecord (&idxRec, 0) ;
2676
2677 strncpy(idxRec.filename, imageFileName, DBC_MAXSTRING);
2678 #ifdef DEBUG
2679 DCMQRDB_DEBUG("DB_storeRequest () : storage request of file : " << idxRec.filename);
2680 #endif
2681 strncpy (idxRec.SOPClassUID, SOPClassUID, UI_MAX_LENGTH);
2682
2683 /**** Get IdxRec values from ImageFile
2684 ***/
2685
2686 DcmFileFormat dcmff;
2687 if (dcmff.loadFile(imageFileName).bad())
2688 {
2689 DCMQRDB_WARN("DB: Cannot open file: " << imageFileName << ": "
2690 << OFStandard::getLastSystemErrorCode().message());
2691 status->setStatus(STATUS_STORE_Error_CannotUnderstand);
2692 return (QR_EC_IndexDatabaseError) ;
2693 }
2694
2695 DcmDataset *dset = dcmff.getDataset();
2696
2697 assert(dset);
2698
2699 OFCondition ec;
2700
2701 for (i = 0 ; i < NBPARAMETERS ; i++ ) {
2702 DB_SmallDcmElmt *se = idxRec.param + i;
2703
2704 const char *strPtr = NULL;
2705 ec = dset->findAndGetString(se->XTag, strPtr);
2706 if ((ec != EC_Normal) || (strPtr == NULL)) {
2707 /* not found or empty */
2708 se->PValueField[0] = '\0';
2709 se->ValueLength = 0;
2710 } else {
2711 /* found and non-empty */
2712 strncpy(se->PValueField, strPtr, (size_t)(se->ValueLength));
2713 /* important: do not change the ValueLength field before the string is copied! */
2714 se->ValueLength = OFstatic_cast(int, strlen(se->PValueField));
2715 }
2716 }
2717
2718 /* InstanceStatus */
2719 idxRec.hstat = (isNew) ? DVIF_objectIsNew : DVIF_objectIsNotNew;
2720
2721 /* InstanceDescription */
2722 OFBool useDescrTag = OFTrue;
2723 DcmTagKey descrTag = DCM_ImageComments;
2724 if (SOPClassUID != NULL)
2725 {
2726 /* fill in value depending on SOP class UID (content might be improved) */
2727 if (strcmp(SOPClassUID, UID_GrayscaleSoftcopyPresentationStateStorage) == 0)
2728 {
2729 descrTag = DCM_ContentDescription;
2730 } else if (strcmp(SOPClassUID, UID_RETIRED_HardcopyGrayscaleImageStorage) == 0)
2731 {
2732 OFStandard::strlcpy(idxRec.InstanceDescription, "Hardcopy Grayscale Image", DESCRIPTION_MAX_LENGTH+1);
2733 useDescrTag = OFFalse;
2734 } else if ((strcmp(SOPClassUID, UID_BasicTextSRStorage) == 0) ||
2735 (strcmp(SOPClassUID, UID_EnhancedSRStorage) == 0) ||
2736 (strcmp(SOPClassUID, UID_ComprehensiveSRStorage) == 0) ||
2737 (strcmp(SOPClassUID, UID_Comprehensive3DSRStorage) == 0) ||
2738 (strcmp(SOPClassUID, UID_ExtensibleSRStorage) == 0) ||
2739 (strcmp(SOPClassUID, UID_ProcedureLogStorage) == 0) ||
2740 (strcmp(SOPClassUID, UID_MammographyCADSRStorage) == 0) ||
2741 (strcmp(SOPClassUID, UID_KeyObjectSelectionDocumentStorage) == 0) ||
2742 (strcmp(SOPClassUID, UID_ChestCADSRStorage) == 0) ||
2743 (strcmp(SOPClassUID, UID_ColonCADSRStorage) == 0) ||
2744 (strcmp(SOPClassUID, UID_XRayRadiationDoseSRStorage) == 0) ||
2745 (strcmp(SOPClassUID, UID_SpectaclePrescriptionReportStorage) == 0) ||
2746 (strcmp(SOPClassUID, UID_MacularGridThicknessAndVolumeReportStorage) == 0) ||
2747 (strcmp(SOPClassUID, UID_ImplantationPlanSRDocumentStorage) == 0) ||
2748 (strcmp(SOPClassUID, UID_RadiopharmaceuticalRadiationDoseSRStorage) == 0) ||
2749 (strcmp(SOPClassUID, UID_AcquisitionContextSRStorage) == 0) ||
2750 (strcmp(SOPClassUID, UID_SimplifiedAdultEchoSRStorage) == 0) ||
2751 (strcmp(SOPClassUID, UID_PatientRadiationDoseSRStorage) == 0) ||
2752 (strcmp(SOPClassUID, UID_PerformedImagingAgentAdministrationSRStorage) == 0) ||
2753 (strcmp(SOPClassUID, UID_PlannedImagingAgentAdministrationSRStorage) == 0))
2754 {
2755 OFString string;
2756 OFString description = "unknown SR";
2757 const char *name = dcmFindNameOfUID(SOPClassUID);
2758 if (name != NULL)
2759 description = name;
2760 if (dset->findAndGetOFString(DCM_VerificationFlag, string) == EC_Normal)
2761 {
2762 description += ", ";
2763 description += string;
2764 }
2765 if (dset->findAndGetOFString(DCM_CompletionFlag, string) == EC_Normal)
2766 {
2767 description += ", ";
2768 description += string;
2769 }
2770 if (dset->findAndGetOFString(DCM_CompletionFlagDescription, string) == EC_Normal)
2771 {
2772 description += ", ";
2773 description += string;
2774 }
2775 OFStandard::strlcpy(idxRec.InstanceDescription, description.c_str(), DESCRIPTION_MAX_LENGTH+1);
2776 useDescrTag = OFFalse;
2777 } else if (strcmp(SOPClassUID, UID_RETIRED_StoredPrintStorage) == 0)
2778 {
2779 OFStandard::strlcpy(idxRec.InstanceDescription, "Stored Print", DESCRIPTION_MAX_LENGTH+1);
2780 useDescrTag = OFFalse;
2781 }
2782 }
2783 /* get description from attribute specified above */
2784 if (useDescrTag)
2785 {
2786 OFString string;
2787 /* return value is irrelevant */
2788 dset->findAndGetOFString(descrTag, string);
2789 strncpy(idxRec.InstanceDescription, string.c_str(), DESCRIPTION_MAX_LENGTH);
2790 }
2791 /* is dataset digitally signed? */
2792 if (strlen(idxRec.InstanceDescription) + 9 < DESCRIPTION_MAX_LENGTH)
2793 {
2794 DcmStack stack;
2795 if (dset->search(DCM_DigitalSignaturesSequence, stack, ESM_fromHere, OFTrue /* searchIntoSub */) == EC_Normal)
2796 {
2797 /* in principle it should be checked whether there is _any_ non-empty digital signatures sequence, but ... */
2798 if (((DcmSequenceOfItems *)stack.top())->card() > 0)
2799 {
2800 if (strlen(idxRec.InstanceDescription) > 0)
2801 OFStandard::strlcat(idxRec.InstanceDescription, " (Signed)", DESCRIPTION_MAX_LENGTH+1);
2802 else
2803 OFStandard::strlcpy(idxRec.InstanceDescription, "Signed Instance", DESCRIPTION_MAX_LENGTH+1);
2804 }
2805 }
2806 }
2807
2808 /**** Print Elements
2809 ***/
2810
2811 #ifdef DEBUG
2812 DCMQRDB_DEBUG("-- BEGIN Parameters to Register in DB");
2813 for (i = 0 ; i < NBPARAMETERS ; i++) { /* new definition */
2814 DB_SmallDcmElmt *se = idxRec.param + i;
2815 const char* value = "";
2816 if (se->PValueField != NULL) value = se->PValueField;
2817 DcmTag tag(se->XTag);
2818 DCMQRDB_DEBUG(" " << tag.getTagName() << ": \"" << value << "\"");
2819 }
2820 DCMQRDB_DEBUG("-- END Parameters to Register in DB");
2821 #endif
2822
2823 /**** Goto the end of IndexFile, and write the record
2824 ***/
2825
2826 DB_lock(OFTrue);
2827
2828 pStudyDesc = (StudyDescRecord *)malloc (SIZEOF_STUDYDESC) ;
2829 if (pStudyDesc == NULL) {
2830 DCMQRDB_ERROR("DB_storeRequest: out of memory");
2831 status->setStatus(STATUS_STORE_Refused_OutOfResources);
2832 DB_unlock();
2833 return (QR_EC_IndexDatabaseError) ;
2834 }
2835
2836 bzero((char *)pStudyDesc, SIZEOF_STUDYDESC);
2837 DB_GetStudyDesc(pStudyDesc) ;
2838
2839 stat(imageFileName, &stat_buf) ;
2840 idxRec. ImageSize = (int)(stat_buf. st_size) ;
2841
2842 /* we only have second accuracy */
2843 idxRec. RecordedDate = (double) time(NULL);
2844
2845 /*
2846 * If the image is already stored remove it from the database.
2847 * hewett - Nov. 1, 93
2848 */
2849
2850 removeDuplicateImage(idxRec.SOPInstanceUID,
2851 idxRec.StudyInstanceUID, pStudyDesc,
2852 imageFileName);
2853
2854
2855 if ( checkupinStudyDesc(pStudyDesc, idxRec. StudyInstanceUID, idxRec. ImageSize) != EC_Normal ) {
2856 free (pStudyDesc) ;
2857 status->setStatus(STATUS_STORE_Refused_OutOfResources);
2858
2859 DB_unlock();
2860
2861 return (QR_EC_IndexDatabaseError) ;
2862 }
2863
2864 free (pStudyDesc) ;
2865
2866 if (DB_IdxAdd (handle_, &i, &idxRec) == EC_Normal)
2867 {
2868 status->setStatus(STATUS_Success);
2869 DB_unlock();
2870 return (EC_Normal) ;
2871 }
2872 else
2873 {
2874 status->setStatus(STATUS_STORE_Refused_OutOfResources);
2875 DB_unlock();
2876 }
2877 return QR_EC_IndexDatabaseError;
2878 }
2879
2880 /*
2881 ** Prune invalid DB records.
2882 */
2883
pruneInvalidRecords()2884 OFCondition DcmQueryRetrieveIndexDatabaseHandle::pruneInvalidRecords()
2885 {
2886 int idx = 0;
2887 IdxRecord idxRec ;
2888 StudyDescRecord *pStudyDesc;
2889
2890 DB_lock(OFTrue);
2891
2892 pStudyDesc = (StudyDescRecord *)malloc (SIZEOF_STUDYDESC) ;
2893 if (pStudyDesc == NULL) {
2894 DCMQRDB_WARN("DB_pruneInvalidRecords: out of memory");
2895 DB_unlock();
2896 return (QR_EC_IndexDatabaseError) ;
2897 }
2898
2899 for (int i = 0 ; i < handle_ -> maxStudiesAllowed ; i++ )
2900 pStudyDesc[i]. NumberofRegistratedImages = 0 ;
2901
2902 DB_GetStudyDesc(pStudyDesc) ;
2903
2904 while (DB_IdxRead(idx, &idxRec) == EC_Normal)
2905 {
2906 if (access(idxRec.filename, R_OK) < 0)
2907 {
2908 #ifdef DEBUG
2909 DCMQRDB_DEBUG("*** Pruning Invalid DB Image Record: " << idxRec.filename);
2910 #endif
2911 /* update the study info */
2912 int studyIdx = matchStudyUIDInStudyDesc(pStudyDesc, idxRec.StudyInstanceUID, (int)(handle_->maxStudiesAllowed)) ;
2913 if (studyIdx < handle_->maxStudiesAllowed)
2914 {
2915 if (pStudyDesc[studyIdx].NumberofRegistratedImages > 0)
2916 {
2917 pStudyDesc[studyIdx].NumberofRegistratedImages--;
2918 } else {
2919 pStudyDesc[studyIdx].NumberofRegistratedImages = 0;
2920 pStudyDesc[studyIdx].StudySize = 0;
2921 pStudyDesc[studyIdx].StudyInstanceUID[0] = '\0';
2922 }
2923 if (pStudyDesc[studyIdx].StudySize > idxRec.ImageSize)
2924 {
2925 pStudyDesc[studyIdx].StudySize -= idxRec.ImageSize;
2926 }
2927 }
2928
2929 /* remove the idx record */
2930 DB_IdxRemove (idx);
2931 }
2932 idx++;
2933 }
2934
2935 DB_StudyDescChange (pStudyDesc);
2936 DB_unlock();
2937 free (pStudyDesc) ;
2938 return EC_Normal;
2939 }
2940
2941
2942 /* ========================= INDEX ========================= */
2943
2944
2945 /************************
2946 * Dump an index file
2947 */
2948
printIndexFile(char * storeArea)2949 void DcmQueryRetrieveIndexDatabaseHandle::printIndexFile (char *storeArea)
2950 {
2951 int i ;
2952 int j ;
2953 IdxRecord idxRec ;
2954 StudyDescRecord *pStudyDesc;
2955
2956 OFCondition result;
2957 DcmQueryRetrieveIndexDatabaseHandle handle(storeArea, -1, -1, result);
2958 if (result.bad()) return;
2959
2960 pStudyDesc = (StudyDescRecord *)malloc (SIZEOF_STUDYDESC) ;
2961 if (pStudyDesc == NULL) {
2962 DCMQRDB_ERROR("printIndexFile: out of memory");
2963 return;
2964 }
2965
2966 handle.DB_lock(OFFalse);
2967
2968 handle.DB_GetStudyDesc(pStudyDesc);
2969
2970 for (i=0; i<handle.handle_->maxStudiesAllowed; i++) {
2971 if (pStudyDesc[i].NumberofRegistratedImages != 0 ) {
2972 COUT << "******************************************************" << OFendl
2973 << "STUDY DESCRIPTOR: " << i << OFendl
2974 << " Study UID: " << pStudyDesc[i].StudyInstanceUID << OFendl
2975 << " StudySize: " << pStudyDesc[i].StudySize << OFendl
2976 << " LastRecDate: " << pStudyDesc[i].LastRecordedDate << OFendl
2977 << " NumOfImages: " << pStudyDesc[i].NumberofRegistratedImages << OFendl;
2978 }
2979 }
2980
2981 handle.DB_IdxInitLoop (&j) ;
2982 while (1) {
2983 if (handle.DB_IdxGetNext(&j, &idxRec) != EC_Normal)
2984 break ;
2985
2986 COUT << "*******************************************************" << OFendl;
2987 COUT << "RECORD NUMBER: " << j << OFendl << " Status: ";
2988 if (idxRec.hstat == DVIF_objectIsNotNew)
2989 COUT << "is NOT new" << OFendl;
2990 else
2991 COUT << "is new" << OFendl;
2992 COUT << " Filename: " << idxRec.filename << OFendl
2993 << " ImageSize: " << idxRec.ImageSize << OFendl
2994 << " RecordedDate: " << idxRec.RecordedDate << OFendl;
2995 for (i = 0 ; i < NBPARAMETERS ; i++) { /* new definition */
2996 DB_SmallDcmElmt *se = idxRec.param + i;
2997 const char* value = "";
2998 if (se->PValueField != NULL) value = se->PValueField;
2999 DcmTag tag(se->XTag);
3000 COUT << " " << tag.getTagName() << ": \"" << value << "\"" << OFendl;
3001 }
3002 COUT << " InstanceDescription: \"" << idxRec.InstanceDescription << "\"" << OFendl;
3003 }
3004 COUT << "*******************************************************" << OFendl
3005 << "RECORDS IN THIS INDEXFILE: " << j << OFendl;
3006
3007 handle.DB_unlock();
3008
3009 }
3010
3011 /************************
3012 * Search in index file for SOP Class UID and SOP Instance UID. Used for the storage commitment server
3013 */
3014
findSOPInstance(const char * storeArea,const OFString & sopClassUID,const OFString & sopInstanceUID)3015 OFBool DcmQueryRetrieveIndexDatabaseHandle::findSOPInstance(const char *storeArea, const OFString &sopClassUID,const OFString &sopInstanceUID)
3016 {
3017 int j ;
3018 IdxRecord idxRec ;
3019
3020 OFCondition result;
3021 OFBool Found = OFFalse;
3022
3023 if (sopClassUID.empty() || sopInstanceUID.empty()) return Found;
3024
3025 DcmQueryRetrieveIndexDatabaseHandle handle(storeArea, -1, -1, result);
3026 if (result.bad()) return Found;
3027
3028 handle.DB_lock(OFFalse);
3029
3030 handle.DB_IdxInitLoop (&j) ;
3031 while (1) {
3032 if (handle.DB_IdxGetNext(&j, &idxRec) != EC_Normal)
3033 break ;
3034 if (sopClassUID.compare(idxRec.SOPClassUID)==0 && sopInstanceUID.compare(idxRec.SOPInstanceUID)==0)
3035 {
3036 Found=OFTrue;
3037 break;
3038 }
3039 }
3040 handle.DB_unlock();
3041 return Found;
3042 }
3043
3044
3045 /* ========================= UTILS ========================= */
3046
3047
getStorageArea() const3048 const char *DcmQueryRetrieveIndexDatabaseHandle::getStorageArea() const
3049 {
3050 return handle_->storageArea;
3051 }
3052
getIndexFilename() const3053 const char *DcmQueryRetrieveIndexDatabaseHandle::getIndexFilename() const
3054 {
3055 return handle_->indexFilename;
3056 }
3057
setIdentifierChecking(OFBool checkFind,OFBool checkMove)3058 void DcmQueryRetrieveIndexDatabaseHandle::setIdentifierChecking(OFBool checkFind, OFBool checkMove)
3059 {
3060 doCheckFindIdentifier = checkFind;
3061 doCheckMoveIdentifier = checkMove;
3062 }
3063
3064
3065 /***********************
3066 * Creates a handle
3067 */
3068
DcmQueryRetrieveIndexDatabaseHandle(const char * storageArea,long maxStudiesPerStorageArea,long maxBytesPerStudy,OFCondition & result)3069 DcmQueryRetrieveIndexDatabaseHandle::DcmQueryRetrieveIndexDatabaseHandle(
3070 const char *storageArea,
3071 long maxStudiesPerStorageArea,
3072 long maxBytesPerStudy,
3073 OFCondition& result)
3074 : handle_(NULL)
3075 , quotaSystemEnabled(OFTrue)
3076 , doCheckFindIdentifier(OFFalse)
3077 , doCheckMoveIdentifier(OFFalse)
3078 , fnamecreator()
3079 {
3080
3081 handle_ = new DB_Private_Handle;
3082
3083 #ifdef DEBUG
3084 DCMQRDB_DEBUG("DB_createHandle () : Handle created for " << storageArea);
3085 DCMQRDB_DEBUG(" maxStudiesPerStorageArea: " << maxStudiesPerStorageArea
3086 << " maxBytesPerStudy: " << maxBytesPerStudy);
3087 #endif
3088
3089 /* check maximum number of studies for valid value */
3090 if (maxStudiesPerStorageArea < 0) {
3091 maxStudiesPerStorageArea = DB_UpperMaxStudies;
3092 }
3093 else if (maxStudiesPerStorageArea > DB_UpperMaxStudies) {
3094 DCMQRDB_WARN("maxStudiesPerStorageArea too large" << OFendl
3095 << " setting to " << DB_UpperMaxStudies);
3096 maxStudiesPerStorageArea = DB_UpperMaxStudies;
3097 }
3098 /* check maximum study size for valid value value */
3099 if (maxBytesPerStudy < 0) {
3100 maxBytesPerStudy = DB_UpperMaxBytesPerStudy;
3101 }
3102 else if (maxBytesPerStudy > DB_UpperMaxBytesPerStudy) {
3103 DCMQRDB_WARN("maxBytesPerStudy too large" << OFendl
3104 << " setting to " << DB_UpperMaxBytesPerStudy);
3105 maxBytesPerStudy = DB_UpperMaxBytesPerStudy;
3106 }
3107
3108 if (handle_) {
3109 sprintf (handle_ -> storageArea,"%s", storageArea);
3110 sprintf (handle_ -> indexFilename,"%s%c%s", storageArea, PATH_SEPARATOR, DBINDEXFILE);
3111
3112 /* create index file if it does not already exist */
3113 FILE* f = fopen(handle_->indexFilename, "ab");
3114 if (f == NULL) {
3115 DCMQRDB_ERROR(handle_->indexFilename << ": " << OFStandard::getLastSystemErrorCode().message());
3116 result = QR_EC_IndexDatabaseError;
3117 return;
3118 }
3119 fclose(f);
3120
3121 /* open fd of index file */
3122 #ifdef O_BINARY
3123 handle_ -> pidx = open(handle_ -> indexFilename, O_RDWR | O_BINARY );
3124 #else
3125 handle_ -> pidx = open(handle_ -> indexFilename, O_RDWR );
3126 #endif
3127 if ( handle_ -> pidx == (-1) )
3128 {
3129 result = QR_EC_IndexDatabaseError;
3130 return;
3131 }
3132 else
3133 {
3134 result = DB_lock(OFTrue);
3135 if ( result.bad() )
3136 return;
3137
3138 // test whether the file contains more than zero bytes
3139 if ( DB_lseek( handle_ -> pidx, 0L, SEEK_END ) > 0 )
3140 {
3141 DB_lseek( handle_ -> pidx, 0L, SEEK_SET );
3142 // allocate HEADERSIZE + 1 bytes and fill it with zeros,
3143 // ensuring whatever is read is terminated with a NUL byte
3144 char header[DBHEADERSIZE+1] = {};
3145 // 0 is an invalid version, no matter what
3146 unsigned int version = 0;
3147 if
3148 (
3149 read( handle_ -> pidx, header, DBHEADERSIZE ) != DBHEADERSIZE ||
3150 strncmp( header, DBMAGIC, strlen(DBMAGIC) ) != 0 ||
3151 sscanf( header + strlen(DBMAGIC), "%x", &version ) != 1 ||
3152 version != DBVERSION
3153 )
3154 {
3155 DB_unlock();
3156 if ( version )
3157 DCMQRDB_ERROR(handle_->indexFilename << ": invalid/unsupported QRDB database version " << version);
3158 else
3159 DCMQRDB_ERROR(handle_->indexFilename << ": unknown/legacy QRDB database file format");
3160 result = QR_EC_IndexDatabaseError;
3161 return;
3162 }
3163 }
3164 else
3165 {
3166 // write magic word and version number to the buffer
3167 // then write it to the file
3168 char header[DBHEADERSIZE + 1];
3169 sprintf( header, DBMAGIC "%.2X", DBVERSION );
3170 if ( write( handle_ -> pidx, header, DBHEADERSIZE ) != DBHEADERSIZE )
3171 {
3172 DCMQRDB_ERROR(handle_->indexFilename << ": " << OFStandard::getLastSystemErrorCode().message());
3173 DB_unlock();
3174 result = QR_EC_IndexDatabaseError;
3175 return;
3176 }
3177 }
3178
3179 DB_unlock();
3180
3181 handle_ -> idxCounter = -1;
3182 handle_ -> findRequestList = NULL;
3183 handle_ -> findResponseList = NULL;
3184 handle_ -> maxBytesPerStudy = maxBytesPerStudy;
3185 handle_ -> maxStudiesAllowed = maxStudiesPerStorageArea;
3186 handle_ -> uidList = NULL;
3187 result = EC_Normal;
3188 return;
3189 }
3190 }
3191 else
3192 {
3193 result = QR_EC_IndexDatabaseError;
3194 return;
3195 }
3196 }
3197
3198 /***********************
3199 * Destroys a handle
3200 */
3201
~DcmQueryRetrieveIndexDatabaseHandle()3202 DcmQueryRetrieveIndexDatabaseHandle::~DcmQueryRetrieveIndexDatabaseHandle()
3203 {
3204 if (handle_)
3205 {
3206 #ifndef _WIN32
3207 /* should not be necessary because we are closing the file handle anyway.
3208 * On Unix systems this does no harm, but on Windows the unlock fails
3209 * if the file was not locked before
3210 * and this gives an unnecessary error message on stderr.
3211 */
3212 DB_unlock();
3213 #endif
3214 close( handle_ -> pidx);
3215
3216 /* Free lists */
3217 DB_FreeElementList (handle_ -> findRequestList);
3218 DB_FreeElementList (handle_ -> findResponseList);
3219 DB_FreeUidList (handle_ -> uidList);
3220
3221 delete handle_;
3222 }
3223 }
3224
3225 /**********************************
3226 * Provides a storage filename
3227 */
3228
makeNewStoreFileName(const char * SOPClassUID,const char *,char * newImageFileName,size_t newImageFileNameLen)3229 OFCondition DcmQueryRetrieveIndexDatabaseHandle::makeNewStoreFileName(
3230 const char *SOPClassUID,
3231 const char * /* SOPInstanceUID */ ,
3232 char *newImageFileName,
3233 size_t newImageFileNameLen)
3234 {
3235
3236 OFString filename;
3237 char prefix[12];
3238
3239 const char *m = dcmSOPClassUIDToModality(SOPClassUID);
3240 if (m==NULL) m = "XX";
3241 sprintf(prefix, "%s_", m);
3242 // unsigned int seed = fnamecreator.hashString(SOPInstanceUID);
3243 unsigned int seed = (unsigned int)time(NULL);
3244 newImageFileName[0]=0; // return empty string in case of error
3245 if (! fnamecreator.makeFilename(seed, handle_->storageArea, prefix, ".dcm", filename))
3246 return QR_EC_IndexDatabaseError;
3247
3248 OFStandard::strlcpy(newImageFileName, filename.c_str(), newImageFileNameLen);
3249 return EC_Normal;
3250 }
3251
3252
instanceReviewed(int idx)3253 OFCondition DcmQueryRetrieveIndexDatabaseHandle::instanceReviewed(int idx)
3254 {
3255 // acquire shared lock and read record at index position
3256 OFCondition result = DB_lock(OFFalse);
3257 if (result.bad()) return result;
3258 IdxRecord record;
3259 result = DB_IdxRead(idx, &record);
3260 DB_unlock();
3261
3262 if (result.good() && (record.hstat != DVIF_objectIsNotNew))
3263 {
3264 // acquire exclusive lock and update flag
3265 result = DB_lock(OFTrue);
3266 if (result.bad()) return result;
3267
3268 record.hstat = DVIF_objectIsNotNew;
3269 DB_lseek(handle_->pidx, OFstatic_cast(long, DBHEADERSIZE + SIZEOF_STUDYDESC + idx * SIZEOF_IDXRECORD), SEEK_SET);
3270 if (write(handle_->pidx, OFreinterpret_cast(char *, &record), SIZEOF_IDXRECORD) != SIZEOF_IDXRECORD)
3271 result = QR_EC_IndexDatabaseError;
3272 DB_lseek(handle_->pidx, OFstatic_cast(long, DBHEADERSIZE), SEEK_SET);
3273 DB_unlock();
3274 }
3275
3276 return result;
3277 }
3278
3279
3280 /***********************
3281 * Default constructors for struct IdxRecord and DB_SSmallDcmElmt
3282 */
3283
IdxRecord()3284 IdxRecord::IdxRecord()
3285 : RecordedDate(0.0)
3286 , ImageSize(0)
3287 , hstat(DVIF_objectIsNotNew)
3288 {
3289 }
3290
DB_SmallDcmElmt()3291 DB_SmallDcmElmt::DB_SmallDcmElmt()
3292 : PValueField(NULL)
3293 , ValueLength(0)
3294 , XTag()
3295 {
3296 }
3297
3298
DcmQueryRetrieveIndexDatabaseHandleFactory(const DcmQueryRetrieveConfig * config)3299 DcmQueryRetrieveIndexDatabaseHandleFactory::DcmQueryRetrieveIndexDatabaseHandleFactory(const DcmQueryRetrieveConfig *config)
3300 : DcmQueryRetrieveDatabaseHandleFactory()
3301 , config_(config)
3302 {
3303 }
3304
~DcmQueryRetrieveIndexDatabaseHandleFactory()3305 DcmQueryRetrieveIndexDatabaseHandleFactory::~DcmQueryRetrieveIndexDatabaseHandleFactory()
3306 {
3307 }
3308
createDBHandle(const char *,const char * calledAETitle,OFCondition & result) const3309 DcmQueryRetrieveDatabaseHandle *DcmQueryRetrieveIndexDatabaseHandleFactory::createDBHandle(
3310 const char * /* callingAETitle */,
3311 const char *calledAETitle,
3312 OFCondition& result) const
3313 {
3314 return new DcmQueryRetrieveIndexDatabaseHandle(
3315 config_->getStorageArea(calledAETitle),
3316 config_->getMaxStudies(calledAETitle),
3317 config_->getMaxBytesPerStudy(calledAETitle), result);
3318 }
3319