1 /*
2  *
3  *  Copyright (C) 1993-2017, 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:  Andrew Hewett, Marco Eichelberg
17  *
18  *  Purpose: class DcmQueryRetrieveIndexDatabaseHandle
19  *
20  */
21 
22 #ifndef DCMQRDBI_H
23 #define DCMQRDBI_H
24 
25 #include "dcmtk/config/osconfig.h"     /* make sure OS specific configuration is included first */
26 #include "dcmtk/dcmqrdb/dcmqrdba.h"    /* for class DcmQueryRetrieveDatabaseHandle */
27 
28 #include "dcmtk/dcmnet/dicom.h"
29 #include "dcmtk/dcmnet/dimse.h"
30 #include "dcmtk/ofstd/offname.h"
31 
32 struct StudyDescRecord;
33 struct DB_Private_Handle;
34 struct DB_SmallDcmElmt;
35 struct IdxRecord;
36 struct DB_ElementList;
37 class DcmQueryRetrieveConfig;
38 
39 /* ENSURE THAT DBVERSION IS INCREMENTED WHENEVER ONE OF THE INDEX FILE STRUCTS IS MODIFIED */
40 
41 #define DBINDEXFILE  "index.dat"
42 #define DBMAGIC      "QRDB"
43 #define DBVERSION    5
44 #define DBHEADERSIZE 6
45 
46 #if DBVERSION > 0xFF
47 #error maximum database version reached, you have to invent a new mechanism
48 #endif
49 
50 #ifndef _WIN32
51 /* we lock image files on all platforms except Win32 where it does not work
52  * due to the different semantics of LockFile/LockFileEx compared to flock.
53  */
54 #define LOCK_IMAGE_FILES
55 #endif
56 
57 /** enumeration describing the levels of the DICOM Q/R information model
58  */
59 enum DB_LEVEL
60 {
61   /// DICOM Q/R patient level
62   PATIENT_LEVEL,
63   /// DICOM Q/R study level
64   STUDY_LEVEL,
65   /// DICOM Q/R series level
66   SERIE_LEVEL,
67   /// DICOM Q/R instance level
68   IMAGE_LEVEL
69 };
70 
71 /** This enum describes the status of one entry in the database hierarchy. An
72  *  entry can describe a study, a series or an instance. A study or series is
73  *  new exactly if all subobjects (series and instances) are new. A study or
74  *  series contains new subobjecs as long as any subobject (series or instance)
75  *  has the status objectIsNew. Instances can never have the status
76  *  DVIF_objectContainsNewSubobjects.
77  */
78 enum DVIFhierarchyStatus
79 {
80   /// object (study, series or instance) in the database is not new
81   DVIF_objectIsNotNew,
82   /// object (study, series or instance) in the database is new
83   DVIF_objectIsNew,
84   /// object (study or series) in the database is not new but contains new subobjects
85   DVIF_objectContainsNewSubobjects
86 };
87 
88 /// upper limit for the number of studies per storage area
89 #define DB_UpperMaxStudies              500
90 
91 /// upper limit for the number bytes per study
92 #define DB_UpperMaxBytesPerStudy        0x40000000L
93 
94 
95 /** This class maintains database handles based on the classical "index.dat" file.
96  *  A database handle maintains a connection to a database and encapsulates database support for
97  *  store, find and move/get operations.
98  */
99 class DCMTK_DCMQRDB_EXPORT DcmQueryRetrieveIndexDatabaseHandle: public DcmQueryRetrieveDatabaseHandle
100 {
101 private:
102   /// private undefined copy constructor
103   DcmQueryRetrieveIndexDatabaseHandle(const DcmQueryRetrieveIndexDatabaseHandle& other);
104 
105   /// private undefined assignment operator
106   DcmQueryRetrieveIndexDatabaseHandle& operator=(const DcmQueryRetrieveIndexDatabaseHandle& other);
107 
108 public:
109 
110   /** Constructor. Creates and initializes a index file handle for the given
111    *  database storage area (storageArea).
112    *  @param storageArea name of storage area, must not be NULL
113    *  @param maxStudiesPerStorageArea maximum number of studies for this storage area,
114    *    needed to correctly parse the index file.
115    *  @param maxBytesPerStudy maximum number of bytes per study, for quota mechanism
116    *  @param result upon successful initialization of the database handle,
117    *    EC_Normal is returned in this parameter, otherwise an error code is returned.
118    */
119   DcmQueryRetrieveIndexDatabaseHandle(
120     const char *storageArea,
121     long maxStudiesPerStorageArea,
122     long maxBytesPerStudy,
123     OFCondition& result);
124 
125   /** Destructor. Destroys handle, cancels any ongoing
126    *  request if necessary, deletes temporary files used for C-STORE and
127    *  sub-operations of C-MOVE.
128    */
129    ~DcmQueryRetrieveIndexDatabaseHandle();
130 
131   /** Configure the DB module to perform (or not perform) checking
132    *  of FIND and MOVE request identifiers. Default is no checking.
133    *  @param checkFind checking for C-FIND parameters
134    *  @param checkMove checking for C-MOVE parameters
135    */
136   void setIdentifierChecking(OFBool checkFind, OFBool checkMove);
137 
138   /** create a filename under which a DICOM object that is currently
139    *  being received through a C-STORE operation can be stored.
140    *  @param SOPClassUID SOP class UID of DICOM instance
141    *  @param SOPInstanceUID SOP instance UID of DICOM instance
142    *  @param newImageFileName file name is returned in this parameter.
143    *    Memory must be provided by the caller and should be at least MAXPATHLEN+1
144    *    characters. The file name generated should be an absolute file name.
145    *  @param newImageFileNameLen length of buffer pointed to by newImageFileName
146    *  @return EC_Normal upon normal completion, or some other OFCondition code upon failure.
147    */
148   OFCondition makeNewStoreFileName(
149       const char *SOPClassUID,
150       const char *SOPInstanceUID,
151       char       *newImageFileName,
152       size_t      newImageFileNameLen);
153 
154   /** register the given DICOM object, which has been received through a C-STORE
155    *  operation and stored in a file, in the database.
156    *  @param SOPClassUID SOP class UID of DICOM instance
157    *  @param SOPInstanceUID SOP instance UID of DICOM instance
158    *  @param imageFileName file name (full path) of DICOM instance
159    *  @param status pointer to DB status object in which a DIMSE status code
160         suitable for use with the C-STORE-RSP message is set.
161    *  @param isNew if true, the instance is marked as "new" in the database,
162    *    if such a flag is maintained in the database.
163    *  @return EC_Normal upon normal completion, or some other OFCondition code upon failure.
164    */
165   OFCondition storeRequest(
166       const char *SOPClassUID,
167       const char *SOPInstanceUID,
168       const char *imageFileName,
169       DcmQueryRetrieveDatabaseStatus  *status,
170       OFBool     isNew = OFTrue );
171 
172   /** @copydoc DcmQueryRetrieveDatabaseHandle::startFindRequest()
173    */
174   OFCondition startFindRequest(
175       const char *SOPClassUID,
176       DcmDataset *findRequestIdentifiers,
177       DcmQueryRetrieveDatabaseStatus *status);
178 
179   /** @copydoc DcmQueryRetrieveDatabaseHandle::nextFindResponse()
180    */
181   OFCondition nextFindResponse(
182       DcmDataset **findResponseIdentifiers,
183       DcmQueryRetrieveDatabaseStatus *status,
184       const DcmQueryRetrieveCharacterSetOptions& characterSetOptions);
185 
186   /** cancel the ongoing FIND request, stop and reset every running operation
187    *  associated with this request, delete existing temporary files.
188    *  @param status pointer to DB status object in which a DIMSE status code
189    *    suitable for use with the C-FIND-RSP message is set.
190    *  @return EC_Normal upon normal completion, or some other OFCondition code upon failure.
191    */
192   OFCondition cancelFindRequest(DcmQueryRetrieveDatabaseStatus *status);
193 
194   /** initiate MOVE operation using the given SOP class UID (which identifies
195    *  the retrieve model) and DICOM dataset containing move request identifiers.
196    *  @param SOPClassUID SOP class UID of retrieve service, identifies Q/R model
197    *  @param moveRequestIdentifiers dataset containing request identifiers (i.e., the query)
198    *    The caller retains responsibility for destroying the
199    *    moveRequestIdentifiers when no longer needed.
200    *  @param status pointer to DB status object in which a DIMSE status code
201    *    suitable for use with the C-MOVE-RSP message is set. Status will be
202    *    PENDING if any MOVE responses will be generated or SUCCESS if no MOVE responses will
203    *    be generated (SUCCESS indicates the completion of a operation), or
204    *    another status code upon failure.
205    *  @return EC_Normal upon normal completion, or some other OFCondition code upon failure.
206    */
207   OFCondition startMoveRequest(
208       const char *SOPClassUID,
209       DcmDataset *moveRequestIdentifiers,
210       DcmQueryRetrieveDatabaseStatus *status);
211 
212   /** Constructs the information required for the next available C-MOVE
213    *  sub-operation (the image SOP class UID, SOP Instance UID and an
214    *  imageFileName containing the requested data).
215    *  @param SOPClassUID pointer to string of at least 65 characters into
216    *    which the SOP class UID for the next DICOM object to be transferred is copied.
217    *  @param SOPClassUIDSize size of SOPClassUID element
218    *  @param SOPInstanceUID pointer to string of at least 65 characters into
219    *    which the SOP instance UID for the next DICOM object to be transferred is copied.
220    *  @param SOPInstanceUIDSize size of SOPInstanceUID element
221    *  @param imageFileName pointer to string of at least MAXPATHLEN+1 characters into
222    *    which the file path for the next DICOM object to be transferred is copied.
223    *  @param imageFileNameSize size of imageFileName element
224    *  @param numberOfRemainingSubOperations On return, this parameter will contain
225    *     the number of suboperations still remaining for the request
226    *     (this number is needed by move responses with PENDING status).
227    *  @param status pointer to DB status object in which a DIMSE status code
228    *    suitable for use with the C-MOVE-RSP message is set. Status will be
229    *    PENDING if more MOVE responses will be generated or SUCCESS if no more
230    *    MOVE responses will be generated (SUCCESS indicates the completion of
231    *    a operation), or another status code upon failure.
232    *  @return EC_Normal upon normal completion, or some other OFCondition code upon failure.
233    */
234   OFCondition nextMoveResponse(
235       char *SOPClassUID,
236       size_t SOPClassUIDSize,
237       char *SOPInstanceUID,
238       size_t SOPInstanceUIDSize,
239       char *imageFileName,
240       size_t imageFileNameSize,
241       unsigned short *numberOfRemainingSubOperations,
242       DcmQueryRetrieveDatabaseStatus *status);
243 
244   /** cancel the ongoing MOVE request, stop and reset every running operation
245    *  associated with this request, delete existing temporary files.
246    *  @param status pointer to DB status object in which a DIMSE status code
247    *    suitable for use with the C-MOVE-RSP message is set.
248    *  @return EC_Normal upon normal completion, or some other OFCondition code upon failure.
249    */
250   OFCondition cancelMoveRequest(DcmQueryRetrieveDatabaseStatus *status);
251 
252   /** Prune invalid records from the database.
253    *  Records referring to non-existant image files are invalid.
254    */
255   OFCondition pruneInvalidRecords();
256 
257   // methods not inherited from the base class
258 
259   /** enable/disable the DB quota system (default: enabled) which causes images
260    *  to be deleted if certain boundaries (number of studies, bytes per study) are exceeded.
261    */
262   void enableQuotaSystem(OFBool enable);
263 
264   /** dump database index file to stdout.
265    *  @param storeArea name of storage area, must not be NULL
266    */
267   static void printIndexFile (char *storeArea);
268 
269   /** search for a SOP class and SOP instance UIDs in index file.
270   *  @param storeArea name of storage area, must not be NULL
271   *  @param sopClassUID SOP Class UID to search for
272   *  @param sopInstanceUID SOP Instance UID to search for
273   *  @return OFTrue if SOP Class and SOP Instance UIDs are found. otherwise return OFFalse.
274   */
275   OFBool findSOPInstance(const char *storeArea, const OFString &sopClassUID,const OFString &sopInstanceUID);
276 
277   /** deletes the given file only if the quota mechanism is enabled.
278    *  The image is not de-registered from the database by this routine.
279    *  @param imgFile file name (path) to the file to be deleted.
280    *  @return EC_Normal upon normal completion, or some other OFCondition code upon failure.
281    */
282   OFCondition deleteImageFile(char* imgFile);
283 
284   /** create lock on database
285    *  @param exclusive exclusive/shared lock flag
286    *  @return EC_Normal upon success, an error code otherwise
287    */
288   OFCondition DB_lock(OFBool exclusive);
289 
290   /** release lock on database
291    */
292   OFCondition DB_unlock();
293 
294   /** Get next Index record that is in use (i.e. references a non-empty a filename)
295    *  @param idx pointer to index number, updated upon successful return
296    *  @param idxRec pointer to index record structure
297    *  @return EC_Normal upon success, an error code otherwise
298    */
299   OFCondition DB_IdxGetNext(int *idx, IdxRecord *idxRec);
300 
301   /** seek to beginning of image records in index file
302    *  @param idx initialized to -1
303    *  @return EC_Normal upon success, an error code otherwise
304    */
305   OFCondition DB_IdxInitLoop(int *idx);
306 
307   /** read index record at given index
308    *  @param idx index
309    *  @param idxRec pointer to index record
310    *  @return EC_Normal upon success, an error code otherwise
311    */
312   OFCondition DB_IdxRead(int idx, IdxRecord *idxRec);
313 
314   /** get study descriptor record from start of index file
315    *  @param pStudyDesc pointer to study record descriptor structure
316    *  @return EC_Normal upon success, an error code otherwise
317    */
318   OFCondition DB_GetStudyDesc(StudyDescRecord *pStudyDesc);
319 
320   /** write study descriptor record to start of index file
321    *  @param pStudyDesc pointer to study record descriptor structure
322    *  @return EC_Normal upon success, an error code otherwise
323    */
324   OFCondition DB_StudyDescChange(StudyDescRecord *pStudyDesc);
325 
326   /** deactivate index record at given index by setting an empty filename
327    *  @param idx index
328    *  @return EC_Normal upon success, an error code otherwise
329    */
330   OFCondition DB_IdxRemove(int idx);
331 
332   /** clear the "is new" flag for the instance with the given index
333    *  @param idx index
334    *  @return EC_Normal upon success, an error code otherwise
335    */
336   OFCondition instanceReviewed(int idx);
337 
338   /// return name of storage area
339   const char *getStorageArea() const;
340 
341   /// return path to index file
342   const char *getIndexFilename() const;
343 
344 
345 private:
346 
347   /** a private helper class that performs character set conversions on the fly
348    *  (if necessary) before matching.
349    */
350   class CharsetConsideringMatcher;
351 
352   /** Determine if a character set is not compatible to UTF-8, i.e.\ if it is
353    *  not UTF-8 or ASCII.
354    *  @param characterSet the character set to inspect.
355    *  @return OFTrue if the character set is neither ASCII nor UTF-8, OFFalse
356    *    otherwise.
357    */
358   static OFBool isConversionToUTF8Necessary(const OFString& characterSet);
359 
360   /** Determine if data in the source character set must be converted to
361    *  be compatible to the given destination character set.
362    *  @param sourceCharacterSet the character set the data is encoded in.
363    *  @param destinationCharacterSet the character set that is requested,
364    *    e.g. the character set that the SCU understands.
365    *  @return OFTrue if the source character set is not equal to and not a
366    *    subset of the destination character set, OFFalse otherwise.
367    */
368   static OFBool isConversionNecessary(const OFString& sourceCharacterSet,
369                                       const OFString& destinationCharacterSet);
370 
371   OFCondition removeDuplicateImage(
372       const char *SOPInstanceUID, const char *StudyInstanceUID,
373       StudyDescRecord *pStudyDesc, const char *newImageFileName);
374   int deleteOldestStudy(StudyDescRecord *pStudyDesc);
375   OFCondition deleteOldestImages(StudyDescRecord *pStudyDesc, int StudyNum, char *StudyUID, long RequiredSize);
376   void makeResponseList(DB_Private_Handle *phandle, IdxRecord *idxRec);
377   int matchStudyUIDInStudyDesc (StudyDescRecord *pStudyDesc, char *StudyUID, int maxStudiesAllowed);
378   OFCondition checkupinStudyDesc(StudyDescRecord *pStudyDesc, char *StudyUID, long imageSize);
379 
380   OFCondition hierarchicalCompare (
381       DB_Private_Handle *phandle,
382       IdxRecord         *idxRec,
383       DB_LEVEL          level,
384       DB_LEVEL          infLevel,
385       int               *match,
386       CharsetConsideringMatcher& dbmatch);
387 
388   OFCondition testFindRequestList (
389       DB_ElementList  *findRequestList,
390       DB_LEVEL        queryLevel,
391       DB_LEVEL        infLevel,
392       DB_LEVEL        lowestLevel);
393 
394   OFCondition testMoveRequestList (
395       DB_ElementList  *findRequestList,
396       DB_LEVEL        queryLevel,
397       DB_LEVEL        infLevel,
398       DB_LEVEL        lowestLevel);
399 
400   /// database handle
401   DB_Private_Handle *handle_;
402 
403   /// flag indicating whether or not the quota system is enabled
404   OFBool quotaSystemEnabled;
405 
406   /// flag indicating whether or not the check function for FIND requests is enabled
407   OFBool doCheckFindIdentifier;
408 
409   /// flag indicating whether or not the check function for MOVE requests is enabled
410   OFBool doCheckMoveIdentifier;
411 
412   /// helper object for file name creation
413   OFFilenameCreator fnamecreator;
414 
415 };
416 
417 
418 /** Index database factory class. Instances of this class are able to create database
419  *  handles for a given called application entity title.
420  */
421 class DCMTK_DCMQRDB_EXPORT DcmQueryRetrieveIndexDatabaseHandleFactory: public DcmQueryRetrieveDatabaseHandleFactory
422 {
423 private:
424   /// private undefined copy constructor
425   DcmQueryRetrieveIndexDatabaseHandleFactory(const DcmQueryRetrieveIndexDatabaseHandleFactory& other);
426 
427   /// private undefined assignment operator
428   DcmQueryRetrieveIndexDatabaseHandleFactory& operator=(const DcmQueryRetrieveIndexDatabaseHandleFactory& other);
429 
430 public:
431 
432   /** constructor
433    *  @param config system configuration object, must not be NULL.
434    */
435   DcmQueryRetrieveIndexDatabaseHandleFactory(const DcmQueryRetrieveConfig *config);
436 
437   /// destructor
438   virtual ~DcmQueryRetrieveIndexDatabaseHandleFactory();
439 
440   /** this method creates a new database handle instance on the heap and returns
441    *  a pointer to it, along with a result that indicates if the instance was
442    *  successfully initialized, i.e. connected to the database
443    *  @param callingAETitle calling aetitle
444    *  @param calledAETitle called aetitle
445    *  @param result result returned in this variable
446    *  @return pointer to database object, must not be NULL if result is EC_Normal.
447    */
448   virtual DcmQueryRetrieveDatabaseHandle *createDBHandle(
449     const char *callingAETitle,
450     const char *calledAETitle,
451     OFCondition& result) const;
452 
453 private:
454 
455   /// pointer to system configuration
456   const DcmQueryRetrieveConfig *config_;
457 };
458 
459 #endif
460