/* * * Copyright (C) 1994-2021, OFFIS e.V. * All rights reserved. See COPYRIGHT file for details. * * This software and supporting documentation were developed by * * OFFIS e.V. * R&D Division Health * Escherweg 2 * D-26121 Oldenburg, Germany * * * Module: dcmdata * * Author: Gerd Ehlers, Andreas Barth * * Purpose: class DcmDicomDir * */ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ #ifdef HAVE_UNIX_H #if defined(macintosh) && defined (HAVE_WINSOCK_H) /* unix.h defines timeval incompatible with winsock.h */ #define timeval _UNWANTED_timeval #endif #include /* for unlink() under Metrowerks C++ (Macintosh) */ #undef timeval #endif #include "dcmtk/ofstd/ofstream.h" #include "dcmtk/ofstd/ofdefine.h" #include "dcmtk/dcmdata/dcdicdir.h" #include "dcmtk/dcmdata/dcuid.h" #include "dcmtk/dcmdata/dcdirrec.h" #include "dcmtk/dcmdata/dcxfer.h" #include "dcmtk/dcmdata/dcdeftag.h" #include "dcmtk/dcmdata/dcostrma.h" /* for class DcmOutputStream */ #include "dcmtk/dcmdata/dcostrmf.h" /* for class DcmOutputFileStream */ #include "dcmtk/dcmdata/dcistrmf.h" /* for class DcmInputFileStream */ #include "dcmtk/dcmdata/dcvrcs.h" #include "dcmtk/dcmdata/dcvrus.h" #include "dcmtk/dcmdata/dcmetinf.h" #include "dcmtk/ofstd/ofstd.h" #include "dcmtk/dcmdata/dcwcache.h" /* for class DcmWriteCache */ #include "dcmtk/dcmdata/dcvrui.h" /* for class DcmUniqueIdentifier */ #ifndef O_BINARY #define O_BINARY 0 /* only Windows has O_BINARY */ #endif // ******************************** DcmDicomDir::DcmDicomDir() : errorFlag(EC_Normal), dicomDirFileName(), modified(OFFalse), mustCreateNewDir(OFFalse), DirFile(new DcmFileFormat()), RootRec(NULL), MRDRSeq(NULL) { dicomDirFileName.set(DEFAULT_DICOMDIR_NAME); OFCondition cond = DirFile->loadFile(dicomDirFileName); if (cond.bad()) { delete DirFile; // clean up file format object DirFile = new DcmFileFormat(); mustCreateNewDir = OFTrue; } createNewElements( "" ); // create missing data elements RootRec = new DcmDirectoryRecord( ERT_root, NULL, OFFilename()); DcmTag mrdrSeqTag( DCM_DirectoryRecordSequence ); MRDRSeq = new DcmSequenceOfItems( mrdrSeqTag ); errorFlag = convertLinearToTree(); } // ******************************** DcmDicomDir::DcmDicomDir(const OFFilename &fileName, const char *fileSetID) : errorFlag(EC_Normal), dicomDirFileName(), modified(OFFalse), mustCreateNewDir(OFFalse), DirFile(new DcmFileFormat()), RootRec(NULL), MRDRSeq(NULL) { if ( fileName.isEmpty() ) dicomDirFileName.set(DEFAULT_DICOMDIR_NAME); else dicomDirFileName = fileName; OFCondition cond = DirFile->loadFile(dicomDirFileName); if (cond.bad()) { delete DirFile; // clean up file format object DirFile = new DcmFileFormat(); mustCreateNewDir = OFTrue; } createNewElements( fileSetID ); // create missing data elements RootRec = new DcmDirectoryRecord( ERT_root, NULL, OFFilename()); DcmTag mrdrSeqTag( DCM_DirectoryRecordSequence ); MRDRSeq = new DcmSequenceOfItems( mrdrSeqTag ); errorFlag = convertLinearToTree(); } // ******************************** /* This copy constructor implementation is untested */ DcmDicomDir::DcmDicomDir( const DcmDicomDir & old ) : errorFlag(old.errorFlag), dicomDirFileName(old.dicomDirFileName), modified(old.modified), mustCreateNewDir(old.mustCreateNewDir), DirFile(new DcmFileFormat(*old.DirFile)), RootRec(new DcmDirectoryRecord(*old.RootRec)), MRDRSeq(new DcmSequenceOfItems(*old.MRDRSeq)) { } // ******************************** DcmDicomDir::~DcmDicomDir() { if (modified) write(); delete DirFile; delete RootRec; delete MRDRSeq; } // ******************************** /* creates required data elements. Only called by the constructors. */ OFCondition DcmDicomDir::createNewElements( const char* fileSetID ) { OFCondition l_error = EC_Normal; DcmUnsignedLongOffset *uloP; DcmUnsignedShort *usP; DcmCodeString *csP; DcmDataset &dset = getDataset(); // guaranteed to exist DcmTag fileIDTag( DCM_FileSetID ); csP = new DcmCodeString( fileIDTag ); // (0004,1130) if ( fileSetID != NULL && *fileSetID != '\0' ) csP->putString( fileSetID ); if ( dset.insert( csP, OFFalse ) != EC_Normal ) delete csP; // not created or inserted: // (0004,1141) // (0004,1142) DcmTag firstRecTag( DCM_OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntity ); uloP = new DcmUnsignedLongOffset( firstRecTag ); // (0004,1200) uloP->putUint32(Uint32(0)); if ( dset.insert( uloP, OFFalse ) != EC_Normal ) delete uloP; DcmTag lastRecTag( DCM_OffsetOfTheLastDirectoryRecordOfTheRootDirectoryEntity ); uloP = new DcmUnsignedLongOffset( lastRecTag ); // (0004,1202) uloP->putUint32(Uint32(0)); if ( dset.insert( uloP, OFFalse ) != EC_Normal ) delete uloP; DcmTag fileConsTag( DCM_FileSetConsistencyFlag ); usP = new DcmUnsignedShort( fileConsTag ); // (0004,1212) usP->putUint16(Uint16(0x0000)); dset.insert( usP, OFTrue ); return l_error; } // ******************************** DcmDataset& DcmDicomDir::getDataset() { if ( DirFile == NULL ) DirFile = new DcmFileFormat(); DcmDataset *localDataset = DirFile->getDataset(); if ( localDataset == NULL ) { errorFlag = EC_CorruptedData; DCMDATA_ERROR("DcmDicomDir::getDataset() Missing Dataset in DICOMDIR File. Must create new DICOMDIR file."); if ( DirFile != NULL ) delete DirFile; DirFile = new DcmFileFormat(); localDataset = DirFile->getDataset(); } return *localDataset; // must exist, otherwise error in DcmFileFormat } // ******************************** DcmSequenceOfItems& DcmDicomDir::getDirRecSeq( DcmDataset &dset ) { DcmSequenceOfItems *localDirRecSeq = NULL; DcmStack stack; if ( dset.search( DCM_DirectoryRecordSequence, stack, ESM_fromHere, OFFalse ) == EC_Normal ) { if ( stack.top()->ident() == EVR_SQ ) localDirRecSeq = OFstatic_cast(DcmSequenceOfItems *, stack.top()); } if ( localDirRecSeq == NULL ) { errorFlag = EC_CorruptedData; if ( !mustCreateNewDir ) { DCMDATA_WARN("DcmDicomDir::getDirRecSeq() Missing Directory Record Sequence. Must create new one."); } DcmTag dirSeqTag( DCM_DirectoryRecordSequence ); // (0004,1220) localDirRecSeq = new DcmSequenceOfItems( dirSeqTag ); dset.insert( localDirRecSeq, OFTrue ); } return *localDirRecSeq; // must exist, otherwise memory exhausted } // ******************************** DcmUnsignedLongOffset* DcmDicomDir::lookForOffsetElem( DcmObject *obj, const DcmTagKey &offsetTag ) { DcmUnsignedLongOffset *offElem = NULL; if ( obj != NULL ) { DcmStack stack; if ( obj->search( offsetTag, stack, ESM_fromHere, OFFalse ) == EC_Normal ) { if ( stack.top()->ident() == EVR_up ) { offElem = OFstatic_cast(DcmUnsignedLongOffset *, stack.top()); #ifdef DEBUG Uint32 l_uint = 0; offElem->getUint32(l_uint); DCMDATA_TRACE("DcmDicomDir::lookForOffsetElem() Offset Element " << offElem->getTag() << " offs=0x" << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(8) << l_uint << " p=" << OFstatic_cast(void *, offElem) << " l=" << offElem->getNextRecord()); #endif } } } return offElem; } // ******************************** OFCondition DcmDicomDir::resolveGivenOffsets( DcmObject *startPoint, const OFMap &itOffsets, const DcmTagKey &offsetTag ) { OFCondition l_error = EC_Normal; if ( startPoint != NULL ) { DcmStack stack; Uint32 offset; for (;;) { l_error = startPoint->nextObject(stack, OFTrue); if (l_error.bad()) break; DcmObject *cur = stack.top(); if (cur->ident() != EVR_up || cur->getTag() != offsetTag) continue; DcmUnsignedLongOffset *offElem = OFstatic_cast(DcmUnsignedLongOffset *, cur); l_error = offElem->getUint32(offset); /* an offset of 0 means that no directory record is referenced */ if (l_error.good() && (offset > 0)) { OFMap::const_iterator it = itOffsets.find(offset); if (it != itOffsets.end()) { offElem->setNextRecord(it->second); } else { DCMDATA_WARN("DcmDicomDir::resolveGivenOffsets() Cannot resolve offset " << offset); /* FIXME: obviously, this error code is never returned but always ignored!? */ l_error = EC_InvalidOffset; } } } } return l_error; } // ******************************** OFCondition DcmDicomDir::resolveAllOffsets( DcmDataset &dset ) // inout { OFCondition l_error = EC_Normal; DcmObject *obj = NULL; DcmDirectoryRecord *rec = NULL; DcmSequenceOfItems &localDirRecSeq = getDirRecSeq( dset ); unsigned long maxitems = localDirRecSeq.card(); OFMap itOffsets; for (unsigned long i = 0; i < maxitems; i++ ) { obj = localDirRecSeq.nextInContainer(obj); rec = OFstatic_cast(DcmDirectoryRecord *, obj); long filePos = rec->getFileOffset(); itOffsets[ OFstatic_cast(Uint32, filePos) ] = rec; DCMDATA_DEBUG("DcmDicomDir::resolveAllOffsets() Item Offset [" << i << "] = 0x" << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(8) << filePos); } resolveGivenOffsets( &dset, itOffsets, DCM_OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntity ); resolveGivenOffsets( &dset, itOffsets, DCM_OffsetOfTheLastDirectoryRecordOfTheRootDirectoryEntity ); resolveGivenOffsets( &localDirRecSeq, itOffsets, DCM_OffsetOfTheNextDirectoryRecord ); resolveGivenOffsets( &localDirRecSeq, itOffsets, DCM_OffsetOfReferencedLowerLevelDirectoryEntity ); resolveGivenOffsets( &localDirRecSeq, itOffsets, DCM_RETIRED_MRDRDirectoryRecordOffset ); return l_error; } // ******************************** OFCondition DcmDicomDir::linkMRDRtoRecord( DcmDirectoryRecord *dRec ) { OFCondition l_error = EC_Normal; if ( dRec != NULL ) { // enter referenced MRDR into protected attribute: // only works since DcmDicomDir is a friend class of DcmDirectoryRecord dRec->referencedMRDR = dRec->lookForReferencedMRDR(); } return l_error; } // ******************************** OFCondition DcmDicomDir::moveRecordToTree( DcmDirectoryRecord *startRec, DcmSequenceOfItems &fromDirSQ, DcmDirectoryRecord *toRecord ) { OFCondition l_error = EC_Normal; if (toRecord == NULL) l_error = EC_IllegalCall; else { while ( (startRec != NULL) && l_error.good() ) { DcmDirectoryRecord *lowerRec = NULL; DcmDirectoryRecord *nextRec = NULL; // check whether directory record is really part of the given sequence: if (&fromDirSQ != startRec->getParent()) { DCMDATA_ERROR("DcmDicomDir: Record with offset=" << startRec->getFileOffset() << " is referenced more than once, ignoring later reference"); l_error = EC_InvalidDICOMDIR; // exit the while loop break; } DcmUnsignedLongOffset *offElem; offElem = lookForOffsetElem( startRec, DCM_OffsetOfReferencedLowerLevelDirectoryEntity ); if ( offElem != NULL ) lowerRec = OFstatic_cast(DcmDirectoryRecord *, offElem->getNextRecord()); offElem = lookForOffsetElem( startRec, DCM_OffsetOfTheNextDirectoryRecord ); if ( offElem != NULL ) nextRec = OFstatic_cast(DcmDirectoryRecord *, offElem->getNextRecord()); DCMDATA_TRACE("DcmDicomDir::moveRecordToTree() Record with" << " offset=" << startRec->getFileOffset() << " p=" << OFstatic_cast(void *, startRec) << " has lower=" << OFstatic_cast(void *, lowerRec) << " and next=" << OFstatic_cast(void *, nextRec) << " Record"); linkMRDRtoRecord( startRec ); // use protected method for insertion without type check: if ( toRecord->masterInsertSub( startRec ) == EC_Normal ) { // only works since friend class DcmItem *dit = fromDirSQ.remove( startRec ); if ( dit == NULL ) { DCMDATA_ERROR("DcmDicomDir: Record with offset=" << startRec->getFileOffset() << " is part of unknown Sequence"); } } else { DCMDATA_ERROR("DcmDicomDir::moveRecordToTree() Cannot insert DirRecord (=NULL?)"); } // recursively call this method for next lower level: l_error = moveRecordToTree( lowerRec, fromDirSQ, startRec ); // We handled this record, now move on to the next one on this level. // The next while-loop iteration does the equivalent of the following: // moveRecordToTree( nextRec, fromDirSQ, toRecord ); startRec = nextRec; } } return l_error; } // ******************************** OFCondition DcmDicomDir::moveMRDRbetweenSQs( DcmSequenceOfItems &fromSQ, DcmSequenceOfItems &toSQ ) { OFCondition l_error = EC_Normal; unsigned long num = fromSQ.card(); for (unsigned long i = 0, j = 0; i < num; i++) { DcmDirectoryRecord *dRec; dRec = OFstatic_cast(DcmDirectoryRecord *, fromSQ.getItem( j )); if (dRec != NULL && dRec->getRecordType() == ERT_Mrdr) { toSQ.insert( dRec ); fromSQ.remove( j ); } else j++; } return l_error; } // ******************************** OFCondition DcmDicomDir::convertLinearToTree() { DcmDataset &dset = getDataset(); // guaranteed to exist DcmSequenceOfItems &localDirRecSeq = getDirRecSeq( dset ); // currently, always returns EC_Normal OFCondition l_error = resolveAllOffsets( dset ); // search for first directory record: DcmDirectoryRecord *firstRootRecord = NULL; DcmUnsignedLongOffset *offElem = lookForOffsetElem( &dset, DCM_OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntity ); if ( offElem != NULL ) firstRootRecord = OFstatic_cast(DcmDirectoryRecord *, offElem->getNextRecord()); // create tree structure from flat record list: l_error = moveRecordToTree( firstRootRecord, localDirRecSeq, &getRootRecord() ); if (l_error.good()) { // move MRDRs from localDirRecSeq to global MRDRSeq: moveMRDRbetweenSQs( localDirRecSeq, getMRDRSequence() ); // dissolve MRDR references for all remaining items for (unsigned long i = localDirRecSeq.card(); i > 0; i-- ) linkMRDRtoRecord( OFstatic_cast(DcmDirectoryRecord *, localDirRecSeq.getItem(i-1)) ); } return l_error; } // ******************************** // ******************************** Uint32 DcmDicomDir::lengthUntilSQ(DcmDataset &dset, E_TransferSyntax oxfer, E_EncodingType enctype ) { Uint32 templen = 0L; unsigned long num = dset.card(); for (unsigned long i = 0; i < num; i++ ) { DcmObject *dO = dset.getElement( i ); DcmXfer xf(oxfer); templen += xf.sizeofTagHeader(dO->getVR()); if ( dO->getTag().getXTag() == DCM_DirectoryRecordSequence ) break; Uint32 sublength = dO->getLength( oxfer, enctype ); templen += sublength; if ( sublength==DCM_UndefinedLength ) { DcmVR subvr( dO->getVR() ); DCMDATA_WARN("DcmDicomDir::lengthUntilSQ() Sub element \"" << subvr.getVRName() << "\" has undefined Length"); } if ( dO->getVR() == EVR_SQ && enctype == EET_UndefinedLength ) templen += 8; // for ItemDelimitationItem } DCMDATA_TRACE("DcmDicomDir::lengthUntilSQ() Length of Dataset until SQ=" << templen); return templen; } // ******************************** Uint32 DcmDicomDir::lengthOfRecord( DcmItem *item, E_TransferSyntax oxfer, E_EncodingType enctype ) { Uint32 templen = 0; if ( item != NULL ) { templen = item->getLength( oxfer, enctype ); templen += 8; // for Tag and Length if ( enctype == EET_UndefinedLength ) templen += 8; // for ItemDelimitationItem } return templen; } // ******************************** OFCondition DcmDicomDir::convertGivenPointer( DcmObject *startPoint, const DcmTagKey &offsetTag ) { OFCondition l_error = EC_Normal; if ( startPoint != NULL ) { DcmStack stack; for (;;) { l_error = startPoint->nextObject(stack, OFTrue); if (l_error.bad()) break; DcmObject *cur = stack.top(); if (cur->ident() != EVR_up || cur->getTag() != offsetTag) continue; DcmUnsignedLongOffset *offElem = OFstatic_cast(DcmUnsignedLongOffset *, cur); DcmObject *obj = offElem->getNextRecord(); if (obj != NULL) offElem->putUint32(OFstatic_cast(DcmDirectoryRecord *, obj)->getFileOffset()); else offElem->putUint32(0); } } return l_error; } // ******************************** OFCondition DcmDicomDir::convertAllPointer( DcmDataset &dset, // inout Uint32 beginOfDataSet, // in E_TransferSyntax oxfer, // in E_EncodingType enctype ) // in { OFCondition l_error = EC_Normal; DcmObject *obj = NULL; DcmDirectoryRecord *rec = NULL; DcmSequenceOfItems &localDirRecSeq = getDirRecSeq( dset ); Uint32 offs_Item1 = beginOfDataSet + lengthUntilSQ( dset, oxfer, enctype ); unsigned long num = localDirRecSeq.card(); Uint32 item_pos = offs_Item1; for (unsigned long i = 0; i < num; i++ ) { obj = localDirRecSeq.nextInContainer(obj); rec = OFstatic_cast(DcmDirectoryRecord *, obj); rec->setFileOffset( item_pos ); item_pos = lengthOfRecord( rec, oxfer, enctype ) + item_pos; } /* calling convertGivenPointer() requires that the above for-loop has been run through */ OFCondition e1 = convertGivenPointer( &dset, DCM_OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntity ); OFCondition e2 = convertGivenPointer( &dset, DCM_OffsetOfTheLastDirectoryRecordOfTheRootDirectoryEntity ); OFCondition e3 = convertGivenPointer( &localDirRecSeq, DCM_OffsetOfTheNextDirectoryRecord ); OFCondition e4 = convertGivenPointer( &localDirRecSeq, DCM_OffsetOfReferencedLowerLevelDirectoryEntity ); OFCondition e5 = convertGivenPointer( &localDirRecSeq, DCM_RETIRED_MRDRDirectoryRecordOffset ); if ( e1 == EC_InvalidVR || e2 == EC_InvalidVR || e3 == EC_InvalidVR || e4 == EC_InvalidVR || e5 == EC_InvalidVR ) l_error = EC_InvalidVR; return l_error; } // ******************************** OFCondition DcmDicomDir::copyRecordPtrToSQ( DcmDirectoryRecord *record, DcmSequenceOfItems &toDirSQ, DcmDirectoryRecord **firstRec, DcmDirectoryRecord **lastRec ) { DcmDirectoryRecord *nextRec = NULL; DcmDirectoryRecord *lastReturnItem = NULL; if ( record != NULL ) { unsigned long lastIndex = record->cardSub(); for (unsigned long i = lastIndex; i > 0; i-- ) { DCMDATA_DEBUG("DcmDicomDir::copyRecordPtrToSQ() Testing sub record no. " << i << " of " << lastIndex); DcmDirectoryRecord *subRecord = record->getSub( i-1 ); if ( subRecord != NULL ) { DcmUnsignedLongOffset *uloP; if ( i == lastIndex ) lastReturnItem = subRecord; // memorize last item // adjust nextPointer DcmTag nextRecTag( DCM_OffsetOfTheNextDirectoryRecord ); uloP = new DcmUnsignedLongOffset( nextRecTag ); uloP->putUint32(Uint32(0)); uloP->setNextRecord( nextRec ); subRecord->insert( uloP, OFTrue ); #ifdef DEBUG Uint32 l_uint = 0; uloP->getUint32(l_uint); DCMDATA_TRACE("DcmDicomDir::copyRecordPtrToSQ() Next Offset Element " << uloP->getTag() << " offs=0x" << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(8) << l_uint << " p=" << OFstatic_cast(void *, uloP) << " next=" << OFstatic_cast(void *, nextRec)); #endif copyRecordPtrToSQ( subRecord, toDirSQ, firstRec, lastRec ); // adjust lowerPointer DcmTag lowerRefTag( DCM_OffsetOfReferencedLowerLevelDirectoryEntity ); uloP = new DcmUnsignedLongOffset( lowerRefTag ); uloP->putUint32(Uint32(0)); uloP->setNextRecord( *firstRec ); subRecord->insert( uloP, OFTrue ); #ifdef DEBUG uloP->getUint32(l_uint); DCMDATA_TRACE("DcmDicomDir::copyRecordPtrToSQ() Lower Offset Element " << uloP->getTag() << " offs=0x" << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') << STD_NAMESPACE setw(8) << l_uint << " p=" << OFstatic_cast(void *, uloP) << " lower=" << OFstatic_cast(void *, *firstRec)); #endif /* insert at beginning */ toDirSQ.prepend( subRecord ); nextRec = subRecord; } } // for ( i ... } if ( lastRec != NULL ) *lastRec = lastReturnItem; // points to first record of subordinate level if ( firstRec != NULL ) *firstRec = nextRec; return EC_Normal; } // ******************************** OFCondition DcmDicomDir::convertTreeToLinear(Uint32 beginOfDataSet, E_TransferSyntax oxfer, E_EncodingType enctype, E_GrpLenEncoding glenc, DcmSequenceOfItems &unresRecs ) { OFCondition l_error = EC_Normal; DcmDataset &dset = getDataset(); // guaranteed to exist DcmSequenceOfItems &localDirRecSeq = getDirRecSeq( dset ); // copy items to which no pointer exists to a temporary list unsigned long numUnresItems = localDirRecSeq.card(); for (unsigned long i = numUnresItems; i > 0; i-- ) { DCMDATA_DEBUG("DcmDicomDir::convertTreeToLinear() Copy pointer of unresolved Record no. " << i << " of " << numUnresItems << " to unresRecsSeq:"); unresRecs.insert( localDirRecSeq.getItem(i-1), 0 ); } // convert items back into the root directory entity: DcmDirectoryRecord *firstRootRecord[1], *lastRootRecord[1]; copyRecordPtrToSQ( &getRootRecord(), localDirRecSeq, firstRootRecord, lastRootRecord ); // set pointer to first directory record: DcmUnsignedLongOffset *offElem = lookForOffsetElem( &dset, DCM_OffsetOfTheFirstDirectoryRecordOfTheRootDirectoryEntity ); if ( offElem != NULL ) offElem->setNextRecord( *firstRootRecord ); // set pointer to last directory record: offElem = lookForOffsetElem( &dset, DCM_OffsetOfTheLastDirectoryRecordOfTheRootDirectoryEntity ); if ( offElem != NULL ) offElem->setNextRecord( *lastRootRecord ); // copy MRDRs in localDirRecSeq unsigned long numMRDRItems = getMRDRSequence().card(); for (unsigned long j = numMRDRItems; j > 0; j-- ) { DCMDATA_DEBUG("DcmDicomDir::convertTreeToLinear() Copy pointer of MRDR no. " << j << " of " << numUnresItems << " to localDirRecSeq:"); localDirRecSeq.insert( getMRDRSequence().getItem(j-1), 0 ); } // compute group lengths before computing byte offsets dset.computeGroupLengthAndPadding(glenc, EPD_noChange, oxfer, enctype); // convert maximum twice if ( convertAllPointer( dset, beginOfDataSet, oxfer, enctype ) == EC_InvalidVR ) if ( convertAllPointer( dset, beginOfDataSet, oxfer, enctype ) == EC_InvalidVR ) { DCMDATA_ERROR("DcmDicomDir: There are some incorrect Offsets in file " << dicomDirFileName); l_error = EC_CorruptedData; } return l_error; } // ******************************** OFCondition DcmDicomDir::insertMediaSOPUID( DcmMetaInfo &metaInfo ) // inout { OFCondition l_error = EC_Normal; DcmTag medSOPClassTag( DCM_MediaStorageSOPClassUID ); DcmUniqueIdentifier *mediaStorageSOPClassUID = new DcmUniqueIdentifier( medSOPClassTag ); const char* valueUID = UID_MediaStorageDirectoryStorage; mediaStorageSOPClassUID->putString( valueUID ); metaInfo.insert( mediaStorageSOPClassUID, OFTrue ); return l_error; } // ******************************** void DcmDicomDir::print(STD_NAMESPACE ostream &out, const size_t flags, const int level, const char *pixelFileName, size_t *pixelCounter) { int i; for ( i=0; iprint(out, flags, 1, pixelFileName, pixelCounter); // friend class out << OFendl; for ( i=0; icardSub(); for (unsigned long i = 0; i < lastIndex; i++ ) { DcmDirectoryRecord *subRecord = startRec->getSub( i ); const char* subName = subRecord->lookForReferencedFileID(); // friend if ( subName != NULL && !strcmp( filename, subName ) ) { DCMDATA_DEBUG("DcmDicomDir::recurseMatchFile() Record p=" << OFstatic_cast(void *, subRecord) << " with matching filename [" << subName << "] found"); retRec = subRecord; break; } else retRec = recurseMatchFile( subRecord, filename ); } } return retRec; } // ******************************** DcmDirectoryRecord* DcmDicomDir::searchMatchFile( DcmSequenceOfItems& recSeq, const char *filename ) { DcmDirectoryRecord* retRec = NULL; if ( filename != NULL && *filename != '\0' ) { unsigned long lastIndex = recSeq.card(); for (unsigned long i = 0; i < lastIndex; i++ ) { DcmDirectoryRecord *record; record = OFstatic_cast(DcmDirectoryRecord *, recSeq.getItem( i )); const char* subName = record->lookForReferencedFileID(); // friend if ( subName != NULL && !strcmp( filename, subName ) ) { DCMDATA_DEBUG("DcmDicomDir::searchMatchFile() Record p=" << OFstatic_cast(void *, record) << " with matching filename [" << subName << "] found"); retRec = record; break; } } } return retRec; } // ******************************** DcmDirectoryRecord* DcmDicomDir::matchFilename( const char *filename ) { DcmDirectoryRecord* retRec = NULL; if ( filename != NULL && *filename != '\0' ) { retRec = recurseMatchFile( &getRootRecord(), filename ); if ( retRec == NULL ) { retRec = searchMatchFile( getMRDRSequence(), filename ); if ( retRec == NULL ) { DcmDataset &dset = getDataset(); retRec = searchMatchFile( getDirRecSeq(dset), filename ); } } } if (retRec == NULL) { DCMDATA_DEBUG("DcmDicomDir::matchFilename() No Record with matching filename [" << filename << "] found"); } return retRec; } // ******************************** DcmDirectoryRecord* DcmDicomDir::matchOrCreateMRDR( const char *filename ) { DcmDirectoryRecord* newMRDR = NULL; DcmDirectoryRecord* matchRec = matchFilename( filename ); if ( matchRec != NULL ) { if ( matchRec->getRecordType() == ERT_Mrdr ) newMRDR = matchRec; else if ( matchRec->getRecordType() != ERT_root ) { newMRDR = new DcmDirectoryRecord( ERT_Mrdr, filename, OFFilename()); if ( matchRec->assignToMRDR( newMRDR ) != EC_IllegalCall ) getMRDRSequence().insert( newMRDR ); else { delete newMRDR; newMRDR = NULL; DCMDATA_ERROR("DcmDicomDir: Internal ERROR: Can't Create MRDR"); } if (newMRDR != NULL) { DCMDATA_DEBUG("DcmDicomDir::matchOrCreateMRDR() New MRDR p=" << OFstatic_cast(void *, newMRDR) << " with matching filename [" << filename << "] created, original Record p=" << OFstatic_cast(void *, matchRec) << " with same filename modified"); } modified = OFTrue; } } if (newMRDR == NULL) DCMDATA_WARN("DcmDicomDir::matchOrCreateMRDR() No MRDR with matching filename [" << filename << "] found"); return newMRDR; } // ******************************** // ******************************** OFCondition DcmDicomDir::write(const E_TransferSyntax oxfer, const E_EncodingType enctype, const E_GrpLenEncoding glenc) { if (oxfer != DICOMDIR_DEFAULT_TRANSFERSYNTAX) { DCMDATA_ERROR("DcmDicomDir::write() Wrong TransferSyntax used, only LittleEndianExplicit allowed"); } errorFlag = EC_Normal; E_TransferSyntax outxfer = DICOMDIR_DEFAULT_TRANSFERSYNTAX; // create a temporary file based on the DICOMDIR filename OFFilename tempFilename; OFStandard::appendFilenameExtension(tempFilename, dicomDirFileName, DICOMDIR_TEMP_SUFFIX); DcmOutputFileStream *outStream = new DcmOutputFileStream(tempFilename); if (! outStream->good()) { DCMDATA_ERROR("DcmDicomDir: Cannot create DICOMDIR temporary file: " << tempFilename); errorFlag = outStream->status(); delete outStream; return errorFlag; } DcmDataset &dset = getDataset(); // guaranteed to exist DcmMetaInfo &metainfo = *(getDirFileFormat().getMetaInfo()); DcmSequenceOfItems &localDirRecSeq = getDirRecSeq(dset); DcmTag unresSeqTag(DCM_DirectoryRecordSequence); DcmSequenceOfItems localUnresRecs(unresSeqTag); // insert Media Storage SOP Class UID insertMediaSOPUID(metainfo); // add missing information such as Media Storage SOP Instance UID, // but do not overwrite the value of Media Storage SOP Class UID getDirFileFormat().validateMetaInfo(outxfer, EWM_fileformat); { // it is important that the cache object is destroyed before the file is renamed! // Therefore, the variable declaration is "encapsulated" in curly brackets. DcmWriteCache wcache; metainfo.transferInit(); metainfo.write(*outStream, META_HEADER_DEFAULT_TRANSFERSYNTAX, enctype, &wcache); metainfo.transferEnd(); Uint32 beginOfDataset = OFstatic_cast(Uint32, outStream->tell()); // convert to writable format errorFlag = convertTreeToLinear(beginOfDataset, outxfer, enctype, glenc, localUnresRecs); dset.transferInit(); // do not calculate GroupLength and Padding twice! dset.write(*outStream, outxfer, enctype, &wcache, EGL_noChange); dset.transferEnd(); } // outStream is closed here delete outStream; OFFilename backupFilename; if (!mustCreateNewDir) { #ifndef DICOMDIR_WITHOUT_BACKUP // create a temporary backup of the existing DICOMDIR OFStandard::appendFilenameExtension(backupFilename, dicomDirFileName, DICOMDIR_BACKUP_SUFFIX); OFStandard::deleteFile(backupFilename); if (errorFlag == EC_Normal) { if (!OFStandard::renameFile(dicomDirFileName, backupFilename)) { OFString buffer = OFStandard::getLastSystemErrorCode().message(); errorFlag = makeOFCondition(OFM_dcmdata, 19, OF_error, buffer.c_str()); } } #else if (!OFStandard::deleteFile(dicomDirFileName)) { OFString buffer = OFStandard::getLastSystemErrorCode().message(); errorFlag = makeOFCondition(OFM_dcmdata, 19, OF_error, buffer.c_str()); } #endif } if (errorFlag == EC_Normal && !OFStandard::renameFile(tempFilename, dicomDirFileName)) { OFString buffer = OFStandard::getLastSystemErrorCode().message(); errorFlag = makeOFCondition(OFM_dcmdata, 19, OF_error, buffer.c_str()); } modified = OFFalse; if (errorFlag == EC_Normal) { // remove temporary backup (if any) OFStandard::deleteFile(backupFilename); } // remove all records from sequence localDirRecSeq while (localDirRecSeq.card() > 0) localDirRecSeq.remove(OFstatic_cast(unsigned long, 0)); // move records to which no pointer exists back while (localUnresRecs.card() > 0) { DcmItem *unresRecord = localUnresRecs.remove(OFstatic_cast(unsigned long, 0)); localDirRecSeq.insert(unresRecord); } return errorFlag; } // ******************************** // ******************************** OFCondition DcmDicomDir::countMRDRRefs( DcmDirectoryRecord *startRec, ItemOffset *refCounter, const unsigned long numCounters ) { OFCondition l_error = EC_Normal; if ( refCounter == NULL ) l_error = EC_IllegalCall; else if ( startRec != NULL ) { unsigned long lastIndex = startRec->cardSub(); for (unsigned long i = 0; i < lastIndex; i++ ) { DcmDirectoryRecord *subRecord = startRec->getSub( i ); DcmDirectoryRecord *refMRDR = subRecord->lookForReferencedMRDR(); // friend class if ( refMRDR != NULL ) { unsigned long j; for ( j = 0; j < numCounters; j++ ) { if ( refMRDR == refCounter[ j ].item ) { ++refCounter[ j ].fileOffset; // Reference counter break; } } DCMDATA_DEBUG("DcmDicomDir::countMRDRRefs() MRDR p=" << OFstatic_cast(void *, refMRDR) << " found, which is " << refMRDR->numberOfReferences << " times referenced and " << j << " times counted"); } countMRDRRefs( subRecord, refCounter, numCounters ); } } return l_error; } // ******************************** OFCondition DcmDicomDir::checkMRDRRefCounter( DcmDirectoryRecord *startRec, ItemOffset *refCounter, const unsigned long numCounters ) { OFCondition l_error = EC_Normal; if ( refCounter == NULL ) l_error = EC_IllegalCall; else if ( startRec != NULL ) { unsigned long lastIndex = startRec->cardSub(); for (unsigned long i = 0; i < lastIndex; i++ ) { DcmDirectoryRecord *subRecord = startRec->getSub( i ); DcmDirectoryRecord *refMRDR = subRecord->lookForReferencedMRDR(); // friend class if ( refMRDR != NULL ) { unsigned long j; for ( j = 0; j < numCounters; j++ ) { if ( refMRDR == refCounter[ j ].item ) { ++refCounter[ j ].fileOffset; // reference counter break; } } DCMDATA_DEBUG("DcmDicomDir::checkMRDRRefCounter() MRDR p=" << OFstatic_cast(void *, refMRDR) << " found, which is " << refMRDR->numberOfReferences << " times referenced and " << j << " times counted"); } OFCondition err1 = checkMRDRRefCounter( subRecord, refCounter, numCounters ); if ( l_error == EC_Normal && err1 != EC_Normal ) l_error = err1; // the first error counts } } return l_error; } // ******************************** /* GERMAN COMMENT - PLEASE IGNORE Strategie fuer verify (mit autocorrect==OFTrue): - lege Tabelle an mit Zeigern auf MRDRs und Referenzzaehlern mit der Groesse getDirRecSeq( getDataset() ).card() + getMRDRSequence().card() - durchlaufe den Record-Baum und erhoehe bei jedem Auftreten eines MRDR-Verweises, den entsprechenden Zaehler in obiger Tabelle - setze in allen MRDRs, auf die laut Tabelle kein Verweis existiert, das activation flag auf INAKTIV PENDING: - ueberpruefe fuer alle inaktiven MRDRs, ob deren referenzierte Dateien von keinem anderen Record referenziert werden und loesche dann gegebenenfalls die Dateien - loesche alle inaktiven MRDRs aus der Sequenz getMRDRSequence() - uebertrage alle aktiven MRDRs aus der Sequenz getDirRecSeq( getDataset() ) in die Sequenz getMRDRSequence() - loesche die gesamte Sequenz getDirRecSeq( getDataset() ), unter Inkaufnahme eines Datenverlustes: es kann nicht automatisch entschieden werden, an welche Position innerhalb der Record-Hierearchie die zu loeschenden Records eingefuegt werden muessen */ OFCondition DcmDicomDir::verify( OFBool autocorrect ) { errorFlag = EC_Normal; DcmSequenceOfItems &localDirRecSeq = getDirRecSeq(getDataset()); unsigned long maxMRDRs = localDirRecSeq.card() + getMRDRSequence().card(); ItemOffset *refCounter = new ItemOffset[ maxMRDRs ]; // create MRDR table for MRDRs from MRDRSeq and from DirRecSeq: unsigned long i; for ( i = 0; i < getMRDRSequence().card(); i++ ) { DcmDirectoryRecord *rec; rec = OFstatic_cast(DcmDirectoryRecord *, getMRDRSequence().getItem( i )); refCounter[i].item = rec; refCounter[i].fileOffset = 0L; } for (unsigned long j = 0; j < localDirRecSeq.card() && i < maxMRDRs; j++ ) { DcmDirectoryRecord *rec; rec = OFstatic_cast(DcmDirectoryRecord *, localDirRecSeq.getItem( j )); if ( rec->getRecordType() == ERT_Mrdr ) { refCounter[i].item = rec; refCounter[i].fileOffset = 0L; i++; } } // maxMRDRs = i; // adjust table size to real value // count number of references for each MRDR countMRDRRefs( &getRootRecord(), refCounter, maxMRDRs ); // check stored reference counters for correctness OFCondition err3 = EC_Normal; for (unsigned long k = 0; k < maxMRDRs; k++ ) { DcmDirectoryRecord *refMRDR = OFstatic_cast(DcmDirectoryRecord *, refCounter[k].item); Uint32 refNum = refMRDR->lookForNumberOfReferences(); // friend if ( refCounter[k].fileOffset != refNum ) { DCMDATA_ERROR("DcmDicomDir::verify() Reference counter of MRDR p=" << OFstatic_cast(void *, refMRDR) << " has incorrect value=" << refNum << " (must be " << refCounter[k].fileOffset << ")"); if (refCounter[k].fileOffset==refMRDR->numberOfReferences) DCMDATA_ERROR("but internal record class value numberOfReferences is correct"); if ( autocorrect ) // correct reference counter, friend refMRDR->setNumberOfReferences( refCounter[k].fileOffset ); else err3 = EC_CorruptedData; } // set inactivation flag for MRDRs without reference if ( autocorrect && refCounter[k].fileOffset == 0L ) refMRDR->setRecordInUseFlag( 0xffff ); } delete[] refCounter; OFCondition err1 = getDirFileFormat().verify( autocorrect ); OFCondition err2 = getRootRecord().verify( OFFalse ); // no automatic correction if ( errorFlag == EC_Normal && ( err1 != EC_Normal || err2 != EC_Normal || err3 != EC_Normal ) ) errorFlag = EC_CorruptedData; return errorFlag; }