/* * * Copyright (C) 1993-2018, 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: dcmqrdb * * Author: Marco Eichelberg * * Purpose: class DcmQueryRetrieveOptions * */ #include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ #include "dcmtk/dcmqrdb/dcmqrtis.h" #include "dcmtk/dcmnet/diutil.h" #include "dcmtk/dcmdata/dcdeftag.h" #include "dcmtk/dcmqrdb/dcmqrdbi.h" #include "dcmtk/dcmqrdb/dcmqrdbs.h" #include "dcmtk/dcmdata/dcfilefo.h" #include "dcmtk/dcmqrdb/dcmqropt.h" BEGIN_EXTERN_C #ifdef HAVE_SYS_STAT_H #include /* needed for stat() */ #endif #ifdef HAVE_FCNTL_H #include /* needed on Solaris for O_RDONLY */ #endif END_EXTERN_C /* ========================================== helper functions ======================================== */ OFBool TI_addStudyEntry(TI_DBEntry *db, DcmDataset *reply); OFBool TI_addSeriesEntry(TI_StudyEntry *study, DcmDataset *reply); OFBool TI_addImageEntry(TI_SeriesEntry *series, DcmDataset *reply); extern "C" int TI_seriesCompare(const void *a, const void *b); extern "C" int TI_imageCompare(const void *a, const void *b); static void TI_getInfoFromDataset(DcmDataset *dset, DIC_PN patientName, DIC_CS studyId, DIC_IS seriesNumber, DIC_CS modality, DIC_IS imageNumber) { DU_getStringDOElement(dset, DCM_PatientName, patientName, sizeof(DIC_PN)); DU_stripLeadingAndTrailingSpaces(patientName); DU_getStringDOElement(dset, DCM_StudyID, studyId, sizeof(DIC_CS)); DU_stripLeadingAndTrailingSpaces(studyId); DU_getStringDOElement(dset, DCM_SeriesNumber, seriesNumber, sizeof(DIC_IS)); DU_stripLeadingAndTrailingSpaces(seriesNumber); DU_getStringDOElement(dset, DCM_Modality, modality, sizeof(DIC_CS)); DU_stripLeadingAndTrailingSpaces(modality); DU_getStringDOElement(dset, DCM_InstanceNumber, imageNumber, sizeof(DIC_IS)); DU_stripLeadingAndTrailingSpaces(imageNumber); } static void TI_getInfoFromImage(char *imgFile, DIC_PN patientName, DIC_CS studyId, DIC_IS seriesNumber, DIC_CS modality, DIC_IS imageNumber) { DcmFileFormat dcmff; if (dcmff.loadFile(imgFile).bad()) { DCMQRDB_ERROR("Help!, cannot open image file: " << imgFile); return; } DcmDataset *obj = dcmff.getDataset(); TI_getInfoFromDataset(obj, patientName, studyId, seriesNumber, modality, imageNumber); } static void TI_destroyImageEntries(TI_SeriesEntry *series) { int i; if (series == NULL) return; for (i=0; iimageCount; i++) { free(series->images[i]); series->images[i] = NULL; } series->imageCount = 0; } static void TI_destroySeriesEntries(TI_StudyEntry *study) { int i; if (study == NULL) return; for (i=0; iseriesCount; i++) { TI_destroyImageEntries(study->series[i]); free(study->series[i]); study->series[i] = NULL; } study->seriesCount = 0; } static void TI_destroyStudyEntries(TI_DBEntry *db) { int i; if (db == NULL) return; for (i=0; istudyCount; i++) { TI_destroySeriesEntries(db->studies[i]); free(db->studies[i]); db->studies[i] = NULL; } db->studyCount = 0; } static void storeProgressCallback(void * /*callbackData*/, T_DIMSE_StoreProgress *progress, T_DIMSE_C_StoreRQ * /*req*/) { int percent; static int dotsSoFar = 0; int dotsToPrint; int i; switch (progress->state) { case DIMSE_StoreBegin: printf(" 0%%________25%%_________50%%__________75%%________100%%\n"); printf(" "); dotsSoFar = 0; break; case DIMSE_StoreEnd: printf("\n"); break; default: if (progress->totalBytes == 0) { percent = 100; } else { percent = (int)(((float)progress->progressBytes / (float)progress->totalBytes) * 100.0); } dotsToPrint = (percent/2) - dotsSoFar; for (i=0; icbf(cbd->cbs, responseIdentifiers); /* responseIdentifers will be deleted in DIMSE_findUser() */ } static OFBool TI_welcome() { printf("\n"); printf("Welcome to the Image CTN Telnet Initiator\n"); printf("\n"); printf("This program allows you to list the contents of the CTN databases, send\n"); printf("images to peer Application Entities (AEs), and to verify connectivity with\n"); printf("peer AEs.\n"); printf("The databases can only be viewed using a Study/Series/Image\n"); printf("information model.\n"); printf("\n"); printf("Network associations will be started when you try to send a\n"); printf("study/series/image or perform an echo.\n"); printf("\n"); printf("The prompt shows the current database title and the current peer AE title.\n"); printf("\n"); printf("Type help for help\n"); return OFTrue; } static OFBool TI_detachDB(TI_DBEntry *db) { if (db == NULL) return OFTrue; TI_destroyStudyEntries(db); if (!db->isRemoteDB && db->dbHandle != NULL) { delete db->dbHandle; db->dbHandle = NULL; } else { } return OFTrue; } #define STUDYFORMAT "%-30s %-12s %-12s\n" static void printStudyEntry(TI_StudyEntry *study) { printf(STUDYFORMAT, study->patientName, study->patientID, study->studyID); } #define SERIESFORMAT "%-6s %-8s %-s\n" static void printSeriesEntry(TI_SeriesEntry *series) { printf(SERIESFORMAT, series->seriesNumber, series->modality, series->seriesInstanceUID); } #define IMAGEFORMAT "%-5s %-s\n" static void printImageEntry(TI_ImageEntry *image) { printf(IMAGEFORMAT, image->imageNumber, image->sopInstanceUID); } static void TI_buildStudyQuery(DcmDataset **query) { if (*query != NULL) delete *query; *query = new DcmDataset; if (*query == NULL) { DCMQRDB_ERROR("Help, out of memory"); return; } DU_putStringDOElement(*query, DCM_QueryRetrieveLevel, "STUDY"); DU_putStringDOElement(*query, DCM_StudyInstanceUID, NULL); DU_putStringDOElement(*query, DCM_StudyID, NULL); DU_putStringDOElement(*query, DCM_PatientName, NULL); DU_putStringDOElement(*query, DCM_PatientID, NULL); } static OFBool TI_genericEntryCallback(TI_GenericCallbackStruct *cbs, DcmDataset *reply) { if (cbs->db) return TI_addStudyEntry(cbs->db, reply); if (cbs->study) return TI_addSeriesEntry(cbs->study, reply); if (cbs->series) return TI_addImageEntry(cbs->series, reply); return OFFalse; } OFBool TI_addSeriesEntry(TI_StudyEntry *study, DcmDataset *reply) { TI_SeriesEntry *series; OFBool ok = OFTrue; if (study->seriesCount >= TI_MAXSERIES) { DCMQRDB_ERROR("TI_addSeriesEntry: too many series"); return OFFalse; } series = (TI_SeriesEntry*) malloc(sizeof(TI_SeriesEntry)); if (series == NULL) return OFFalse; bzero((char*)series, sizeof(TI_SeriesEntry)); /* make sure its clean */ /* extract info from reply */ ok = DU_getStringDOElement(reply, DCM_SeriesInstanceUID, series->seriesInstanceUID, sizeof(series->seriesInstanceUID)); if (ok) ok = DU_getStringDOElement(reply, DCM_SeriesNumber, series->seriesNumber, sizeof(series->seriesNumber)); if (ok) ok = DU_getStringDOElement(reply, DCM_Modality, series->modality, sizeof(series->modality)); if (!ok) { DCMQRDB_ERROR("TI_addSeriesEntry: missing data in DB reply"); return OFFalse; } DU_stripLeadingAndTrailingSpaces(series->seriesInstanceUID); DU_stripLeadingAndTrailingSpaces(series->seriesNumber); DU_stripLeadingAndTrailingSpaces(series->modality); series->intSeriesNumber = atoi(series->seriesNumber); /* add to array */ study->series[study->seriesCount] = series; study->seriesCount++; return OFTrue; } int TI_seriesCompare(const void *a, const void *b) { TI_SeriesEntry **sa, **sb; int cmp = 0; /* compare function for qsort, a and b are pointers to * the images array elements. The routine must return an * integer less than, equal to, or greater than 0 according as * the first argument is to be considered less than, equal to, * or greater than the second. */ sa = (TI_SeriesEntry **)a; sb = (TI_SeriesEntry **)b; cmp = (*sa)->intSeriesNumber - (*sb)->intSeriesNumber; return cmp; } static void TI_buildSeriesQuery(DcmDataset **query, TI_StudyEntry *study) { if (*query != NULL) delete *query; *query = new DcmDataset; if (*query == NULL) { DCMQRDB_ERROR("Help, out of memory"); return; } DU_putStringDOElement(*query, DCM_QueryRetrieveLevel, "SERIES"); DU_putStringDOElement(*query, DCM_StudyInstanceUID, study->studyInstanceUID); DU_putStringDOElement(*query, DCM_SeriesInstanceUID, NULL); DU_putStringDOElement(*query, DCM_Modality, NULL); DU_putStringDOElement(*query, DCM_SeriesNumber, NULL); } OFBool TI_addImageEntry(TI_SeriesEntry *series, DcmDataset *reply) { TI_ImageEntry *image; OFBool ok = OFTrue; DIC_CS studyID; if (series->imageCount >= TI_MAXIMAGES) { DCMQRDB_ERROR("TI_addImageEntry: too many images"); return OFFalse; } image = (TI_ImageEntry*) malloc(sizeof(TI_ImageEntry)); if (image == NULL) return OFFalse; bzero((char*)image, sizeof(TI_ImageEntry)); /* make sure its clean */ bzero((char*)studyID, sizeof(DIC_CS)); /* extract info from reply */ ok = DU_getStringDOElement(reply, DCM_SOPInstanceUID, image->sopInstanceUID, sizeof(image->sopInstanceUID)); if (ok) ok = DU_getStringDOElement(reply, DCM_InstanceNumber, image->imageNumber, sizeof(image->imageNumber)); if (!ok) { DCMQRDB_ERROR("TI_addImageEntry: missing data in DB reply"); return OFFalse; } DU_stripLeadingAndTrailingSpaces(image->sopInstanceUID); DU_stripLeadingAndTrailingSpaces(image->imageNumber); image->intImageNumber = atoi(image->imageNumber); /* add to array */ series->images[series->imageCount] = image; series->imageCount++; return OFTrue; } int TI_imageCompare(const void *a, const void *b) { TI_ImageEntry **ia, **ib; int cmp = 0; /* compare function for qsort, a and b are pointers to * the images array elements. The routine must return an * integer less than, equal to, or greater than 0 according as * the first argument is to be considered less than, equal to, * or greater than the second. */ ia = (TI_ImageEntry **)a; ib = (TI_ImageEntry **)b; /* compare image numbers */ cmp = (*ia)->intImageNumber - (*ib)->intImageNumber; return cmp; } static void TI_buildImageQuery(DcmDataset **query, TI_StudyEntry *study, TI_SeriesEntry *series) { if (*query != NULL) delete *query; *query = new DcmDataset; if (*query == NULL) { DCMQRDB_ERROR("Help, out of memory!"); return; } DU_putStringDOElement(*query, DCM_QueryRetrieveLevel, "IMAGE"); DU_putStringDOElement(*query, DCM_StudyInstanceUID, study->studyInstanceUID); DU_putStringDOElement(*query, DCM_SeriesInstanceUID, series->seriesInstanceUID); DU_putStringDOElement(*query, DCM_InstanceNumber, NULL); DU_putStringDOElement(*query, DCM_SOPInstanceUID, NULL); } OFBool TI_addStudyEntry(TI_DBEntry *db, DcmDataset *reply) { TI_StudyEntry *se; OFBool ok = OFTrue; if (db->studyCount >= TI_MAXSTUDIES) { DCMQRDB_ERROR("TI_addStudyEntry: too many studies"); return OFFalse; } se = (TI_StudyEntry*) malloc(sizeof(TI_StudyEntry)); if (se == NULL) return OFFalse; bzero((char*)se, sizeof(TI_StudyEntry)); /* make sure its clean */ /* extract info from reply */ ok = DU_getStringDOElement(reply, DCM_StudyInstanceUID, se->studyInstanceUID, sizeof(se->studyInstanceUID)); if (ok) ok = DU_getStringDOElement(reply, DCM_StudyID, se->studyID, sizeof(se->studyID)); if (ok) ok = DU_getStringDOElement(reply, DCM_PatientName, se->patientName, sizeof(se->patientName)); if (ok) ok = DU_getStringDOElement(reply, DCM_PatientID, se->patientID, sizeof(se->patientID)); if (!ok) { DCMQRDB_ERROR("TI_addStudyEntry: missing data in DB reply"); return OFFalse; } DU_stripLeadingAndTrailingSpaces(se->studyInstanceUID); DU_stripLeadingAndTrailingSpaces(se->studyID); DU_stripLeadingAndTrailingSpaces(se->patientName); DU_stripLeadingAndTrailingSpaces(se->patientID); /* add to array */ db->studies[db->studyCount] = se; db->studyCount++; return OFTrue; } /* ========================================== DcmQueryRetrieveTelnetInitiator ======================================== */ DcmQueryRetrieveTelnetInitiator::DcmQueryRetrieveTelnetInitiator( DcmQueryRetrieveConfig &cfg) : dbEntries(NULL) , dbCount(0) , peerHostName(NULL) , peerNamesCount(0) , myAETitle(NULL) , net(NULL) , assoc(NULL) , maxReceivePDULength(0) , currentdb(0) , currentPeerTitle(NULL) , config(cfg) , networkTransferSyntax(EXS_Unknown) , blockMode_(DIMSE_BLOCKING) , dimse_timeout_(0) { bzero((char*)peerNames, sizeof(peerNames)); } OFBool DcmQueryRetrieveTelnetInitiator::TI_detachAssociation(OFBool abortFlag) { OFCondition cond = EC_Normal; DIC_NODENAME presentationAddress; DIC_AE peerTitle; OFString temp_str; if (assoc == NULL) { return OFTrue; /* nothing to do */ } ASC_getPresentationAddresses(assoc->params, NULL, 0, presentationAddress, sizeof(presentationAddress)); ASC_getAPTitles(assoc->params, NULL, 0, peerTitle, sizeof(peerTitle), NULL, 0); if (abortFlag) { /* abort association */ DCMQRDB_INFO("Aborting Association (" << peerTitle << ")"); cond = ASC_abortAssociation(assoc); if (cond.bad()) { DCMQRDB_ERROR("Association Abort Failed: " << DimseCondition::dump(temp_str, cond)); } } else { /* release association */ DCMQRDB_INFO("Releasing Association (" << peerTitle << ")"); cond = ASC_releaseAssociation(assoc); if (cond.bad()) { DCMQRDB_ERROR("Association Release Failed: " << DimseCondition::dump(temp_str, cond)); } } ASC_dropAssociation(assoc); ASC_destroyAssociation(&assoc); if (abortFlag) { printf("Aborted Association (%s,%s)\n", presentationAddress, peerTitle); } else { printf("Released Association (%s,%s)\n", presentationAddress, peerTitle); } return OFTrue; } OFCondition DcmQueryRetrieveTelnetInitiator::addPresentationContexts(T_ASC_Parameters *params) { OFCondition cond = EC_Normal; /* abstract syntaxes for storage SOP classes are taken from dcmdata */ const char *abstractSyntaxes[] = { UID_VerificationSOPClass, UID_FINDStudyRootQueryRetrieveInformationModel }; int i; int pid = 1; /* ** We prefer to accept Explicitly encoded transfer syntaxes. ** If we are running on a Little Endian machine we prefer ** LittleEndianExplicitTransferSyntax to BigEndianTransferSyntax. ** Some SCP implementations will just select the first transfer ** syntax they support (this is not part of the standard) so ** organise the proposed transfer syntaxes to take advantage ** of such behaviour. */ unsigned int numTransferSyntaxes = 0; const char* transferSyntaxes[] = { NULL, NULL, NULL }; if (networkTransferSyntax == EXS_LittleEndianImplicit) { transferSyntaxes[0] = UID_LittleEndianImplicitTransferSyntax; numTransferSyntaxes = 1; } else { /* gLocalByteOrder is defined in dcxfer.h */ if (gLocalByteOrder == EBO_LittleEndian) { /* we are on a little endian machine */ transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax; transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax; transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax; numTransferSyntaxes = 3; } else { /* we are on a big endian machine */ transferSyntaxes[0] = UID_BigEndianExplicitTransferSyntax; transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax; transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax; numTransferSyntaxes = 3; } } /* first add presentation contexts for find and verification */ for (i=0; i<(int)DIM_OF(abstractSyntaxes) && cond.good(); i++) { cond = ASC_addPresentationContext( params, pid, abstractSyntaxes[i], transferSyntaxes, numTransferSyntaxes); pid += 2; /* only odd presentation context id's */ } /* and then for all storage SOP classes */ for (i=0; iisRemoteDB) { OFStandard::strlcpy(currentAETitle, myAETitle, sizeof(currentAETitle)); } else { OFStandard::strlcpy(currentAETitle, dbEntries[currentdb]->title, sizeof(currentAETitle)); } cond = ASC_createAssociationParameters(¶ms, maxReceivePDULength); if (cond.bad()) { DCMQRDB_ERROR("Help, cannot create association parameters: " << DimseCondition::dump(temp_str, cond)); return OFFalse; } ASC_setAPTitles(params, currentAETitle, currentPeerTitle, NULL); if (!config.peerForAETitle(currentPeerTitle, &peer, &port)) { DCMQRDB_ERROR("Help, AE title (" << currentPeerTitle << ") no longer in config"); ASC_destroyAssociationParameters(¶ms); return OFFalse; } sprintf(presentationAddress, "%s:%d", peer, port); ASC_setPresentationAddresses(params, OFStandard::getHostName().c_str(), presentationAddress); cond = addPresentationContexts(params); if (cond.bad()) { DCMQRDB_ERROR("Help, cannot add presentation contexts: " << DimseCondition::dump(temp_str, cond)); ASC_destroyAssociationParameters(¶ms); return OFFalse; } DCMQRDB_DEBUG("Request Parameters:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_RQ)); /* create association */ DCMQRDB_INFO("Requesting Association"); cond = ASC_requestAssociation(net, params, &assoc); if (cond.bad()) { if (cond == DUL_ASSOCIATIONREJECTED) { T_ASC_RejectParameters rej; ASC_getRejectParameters(params, &rej); DCMQRDB_ERROR("Association Rejected:" << OFendl << ASC_printRejectParameters(temp_str, &rej)); ASC_dropAssociation(assoc); ASC_destroyAssociation(&assoc); return OFFalse; } else { DCMQRDB_ERROR("Association Request Failed: Peer (" << presentationAddress << ", " << currentPeerTitle << "): " << DimseCondition::dump(temp_str, cond)); ASC_dropAssociation(assoc); ASC_destroyAssociation(&assoc); return OFFalse; } } /* what has been accepted/refused ? */ DCMQRDB_DEBUG("Association Parameters Negotiated:" << OFendl << ASC_dumpParameters(temp_str, params, ASC_ASSOC_AC)); if (ASC_countAcceptedPresentationContexts(params) == 0) { DCMQRDB_ERROR("All Presentation Contexts Refused: Peer (" << presentationAddress << "," << currentPeerTitle << ")"); ASC_abortAssociation(assoc); ASC_dropAssociation(assoc); ASC_destroyAssociation(&assoc); return OFFalse; } DCMQRDB_INFO("Association Accepted (Max Send PDV: " << assoc->sendPDVLength << ")"); printf("New Association Started (%s,%s)\n", presentationAddress, currentPeerTitle); return OFTrue; } /* * Change Association */ OFBool DcmQueryRetrieveTelnetInitiator::TI_changeAssociation() { DIC_AE actualPeerAETitle; OFBool ok = OFTrue; if (assoc != NULL) { /* do we really need to change the association */ ASC_getAPTitles(assoc->params, NULL, 0, actualPeerAETitle, sizeof(actualPeerAETitle), NULL, 0); if (strcmp(actualPeerAETitle, currentPeerTitle) == 0) { /* no need to change */ return OFTrue; } } ok = TI_detachAssociation(OFFalse); if (!ok) return ok; ok = TI_attachAssociation(); return ok; } OFBool DcmQueryRetrieveTelnetInitiator::TI_sendEcho() { OFCondition cond = EC_Normal; DIC_US msgId; DIC_US status; DcmDataset *stDetail = NULL; msgId = assoc->nextMsgID++; printf("[MsgID %d] Echo, ", msgId); fflush(stdout); cond = DIMSE_echoUser(assoc, msgId, blockMode_, dimse_timeout_, &status, &stDetail); if (cond.good()) { printf("Complete [Status: %s]\n", DU_cstoreStatusString(status)); } else { OFString temp_str; DCMQRDB_ERROR("Failed: " << DimseCondition::dump(temp_str, cond)); ASC_abortAssociation(assoc); ASC_dropAssociation(assoc); ASC_destroyAssociation(&assoc); } if (stDetail != NULL) { printf(" Status Detail (should never be any):\n"); stDetail->print(COUT); delete stDetail; } return (cond.good()); } OFBool DcmQueryRetrieveTelnetInitiator::TI_storeImage(char *sopClass, char *sopInstance, char * imgFile) { OFCondition cond = EC_Normal; DIC_US msgId; DcmDataset *stDetail = NULL; T_ASC_PresentationContextID presId; T_DIMSE_C_StoreRQ req; T_DIMSE_C_StoreRSP rsp; DIC_PN patientName; DIC_CS studyId; DIC_IS seriesNumber; DIC_CS modality; DIC_IS imageNumber; if (strlen(sopClass) == 0) { DCMQRDB_WARN("CTN has deleted image, giving up (no sopClass): " << ((imgFile)?(imgFile):("(nil)"))); /* give up because if this image is gone, then others are also * very likely to have disappeared. The user should restart * the operation when other activities have finished. */ return OFFalse; } #ifdef LOCK_IMAGE_FILES /* shared lock image file */ int lockfd; #ifdef O_BINARY lockfd = open(imgFile, O_RDONLY | O_BINARY, 0666); #else lockfd = open(imgFile, O_RDONLY, 0666); #endif if (lockfd < 0) { DCMQRDB_WARN("CTN has deleted image, giving up (no imgFile): " << ((imgFile)?(imgFile):("(nil)"))); /* give up because if this image is gone, then others are also * very likely to have disappeared. The user should restart * the operation when other activities have finished. */ return OFFalse; } dcmtk_flock(lockfd, LOCK_SH); #endif /* which presentation context should be used */ presId = ASC_findAcceptedPresentationContextID(assoc, sopClass); if (presId == 0) { DCMQRDB_ERROR("No presentation context for: (" << dcmSOPClassUIDToModality(sopClass, "OT") << ") " << sopClass); return OFFalse; } TI_getInfoFromImage(imgFile, patientName, studyId, seriesNumber, modality, imageNumber); /* start store */ msgId = assoc->nextMsgID++; printf("[MsgID %d] Store,\n", msgId); printf(" PatientName: %s, StudyID: %s,\n", patientName, studyId); printf(" Series: %s, Modality: %s, Image: %s,\n", seriesNumber, modality, imageNumber); printf(" Image UID: %s\n", sopInstance); fflush(stdout); bzero((char*)&req, sizeof(req)); req.MessageID = msgId; OFStandard::strlcpy(req.AffectedSOPClassUID, sopClass, sizeof(req.AffectedSOPClassUID)); OFStandard::strlcpy(req.AffectedSOPInstanceUID, sopInstance, sizeof(req.AffectedSOPInstanceUID)); req.DataSetType = DIMSE_DATASET_PRESENT; req.Priority = DIMSE_PRIORITY_MEDIUM; cond = DIMSE_storeUser(assoc, presId, &req, imgFile, NULL, storeProgressCallback, NULL, blockMode_, dimse_timeout_, &rsp, &stDetail); #ifdef LOCK_IMAGE_FILES /* unlock image file */ dcmtk_flock(lockfd, LOCK_UN); close(lockfd); #endif if (cond.good()) { printf("[MsgID %d] Complete [Status: %s]\n", msgId, DU_cstoreStatusString(rsp.DimseStatus)); } else { OFString temp_str; DCMQRDB_ERROR("[MsgID " << msgId << "] Failed: " << DimseCondition::dump(temp_str, cond)); ASC_abortAssociation(assoc); ASC_dropAssociation(assoc); ASC_destroyAssociation(&assoc); } if (stDetail != NULL) { printf(" Status Detail:\n"); stDetail->print(COUT); delete stDetail; } return (cond.good()); } /* * Find for remote DBs */ OFBool DcmQueryRetrieveTelnetInitiator::TI_remoteFindQuery(TI_DBEntry *db, DcmDataset *query, TI_GenericEntryCallbackFunction callbackFunction, TI_GenericCallbackStruct *callbackData) { OFBool ok = OFTrue; TI_LocalFindCallbackData cbd; OFCondition cond = EC_Normal; T_ASC_PresentationContextID presId; DIC_US msgId; T_DIMSE_C_FindRQ req; T_DIMSE_C_FindRSP rsp; DcmDataset *stDetail = NULL; int responseCount = 0; currentPeerTitle = db->title; /* make sure we have an association */ ok = TI_changeAssociation(); if (!ok) return OFFalse; cbd.cbf = callbackFunction; cbd.cbs = callbackData; /* which presentation context should be used */ presId = ASC_findAcceptedPresentationContextID(assoc, UID_FINDStudyRootQueryRetrieveInformationModel); if (presId == 0) { DCMQRDB_ERROR("No Presentation Context for Find Operation"); return OFFalse; } msgId = assoc->nextMsgID++; DCMQRDB_INFO("Sending Find SCU RQ: MsgID " << msgId << ":" << OFendl << DcmObject::PrintHelper(*query)); req.MessageID = msgId; OFStandard::strlcpy(req.AffectedSOPClassUID, UID_FINDStudyRootQueryRetrieveInformationModel, sizeof(req.AffectedSOPClassUID)); req.Priority = DIMSE_PRIORITY_MEDIUM; cond = DIMSE_findUser(assoc, presId, &req, query, responseCount, findCallback, &cbd, blockMode_, dimse_timeout_, &rsp, &stDetail); if (cond.good()) { OFString temp_str; DCMQRDB_INFO(DIMSE_dumpMessage(temp_str, rsp, DIMSE_INCOMING)); } else { OFString temp_str; DCMQRDB_ERROR("Find Failed: " << DimseCondition::dump(temp_str, cond)); } if (stDetail != NULL) { printf(" Status Detail:\n"); stDetail->print(COUT); delete stDetail; } return cond.good(); } /* ====================================== TI USER INTERFACE ===================================== */ OFBool DcmQueryRetrieveTelnetInitiator::TI_shortHelp(int /*arg*/ , const char * /*cmdbuf*/ ) { printf("h)elp, t)itle, da)tabase, st)udy, ser)ies i)mage, di)splay, sen)d, e)cho, q)uit\n"); return OFTrue; } OFBool DcmQueryRetrieveTelnetInitiator::TI_help(int arg, const char * /*cmdbuf*/ ) { if (DCM_dcmqrdbLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL)) { printf("TI_help: arg=%d\n", arg); } printf("Command Summary:\n"); printf("help list this summary\n"); printf("? short help\n"); printf("title [#] list [set] current peer AE title\n"); printf("database [#] list [set] current database\n"); printf("study [#] list [set] current study\n"); printf("series [#] list [set] current series\n"); printf("image [#] list [set] current image\n"); printf("send study [#] send current [specific] study\n"); printf("send series [#] send current [specific] series\n"); printf("send image [#] send current [specific] image\n"); printf("echo [#] verify connectivity [# times]\n"); printf("quit quit program\n"); printf("exit synonym for quit\n"); return OFTrue; } OFBool DcmQueryRetrieveTelnetInitiator::TI_title(int arg, const char * /*cmdbuf*/ ) { int i; TI_DBEntry *db; const char *peer; int port; DIC_AE peerTitle; if (DCM_dcmqrdbLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL)) { printf("TI_title: arg=%d\n", arg); } bzero(peerTitle, sizeof(peerTitle)); if (assoc) { ASC_getAPTitles(assoc->params, NULL, 0, peerTitle, sizeof(peerTitle), NULL, 0); } db = dbEntries[currentdb]; if (arg < 0) { /* print list of peer AE titles we know */ printf("Peer AE Titles:\n"); printf(" %-16s %s\n", "Peer AE", "HostName:PortNumber"); for (i=0; ipeerTitleCount; i++) { if (strcmp(currentPeerTitle, db->peerTitles[i]) == 0) { printf("*"); } else { printf(" "); } /* active = (strcmp(peerTitle, db->peerTitles[i]) == 0); */ config.peerForAETitle(db->peerTitles[i], &peer, &port); printf(" %d) %-16s (%s:%d)\n", i, db->peerTitles[i], peer, port); } } else { /* choosing new peer AE title */ if (arg >= db->peerTitleCount) { printf("ERROR: Peer AE Title Choice: 0 - %d\n", db->peerTitleCount - 1); } else { currentPeerTitle = db->peerTitles[arg]; } } return OFTrue; } OFBool DcmQueryRetrieveTelnetInitiator::TI_attachDB(TI_DBEntry *db) { OFCondition dbcond = EC_Normal; db->studyCount = 0; db->currentStudy = 0; db->currentImage = 0; if (!db->isRemoteDB && db->dbHandle == NULL) { /* Create a database handle */ db->dbHandle = new DcmQueryRetrieveIndexDatabaseHandle( config.getStorageArea(db->title), config.getMaxStudies(db->title), config.getMaxBytesPerStudy(db->title), dbcond); if (dbcond.bad()) { DCMQRDB_ERROR("TI_attachDB: cannot create DB Handle"); return OFFalse; } } else { } return OFTrue; } OFBool DcmQueryRetrieveTelnetInitiator::TI_database(int arg, const char * /*cmdbuf*/ ) { int i; TI_DBEntry *db = NULL; OFBool found = OFFalse; if (DCM_dcmqrdbLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL)) { printf("TI_database: arg=%d\n", arg); } if (arg < 0) { /* print list of database titles we know */ printf("Database Titles:\n"); printf(" %s\n", "Database"); for (i=0; iisRemoteDB) { printf("!"); } else { printf(" "); } printf("%2d) %s\n", i, dbEntries[i]->title); } } else { /* choosing new title */ if (arg >= dbCount) { printf("ERROR: Database Title Choice: 0 - %d\n", dbCount - 1); } else { /* release old dbHandle */ TI_detachDB(dbEntries[currentdb]); currentdb = arg; /* check to make sure that current peer AE title is * available for this database, if not must choose * another and tell user about the change. */ db = dbEntries[currentdb]; for (i=0; !found && ipeerTitleCount; i++) { found = (strcmp(currentPeerTitle, db->peerTitles[i]) == 0); } if (!found) { printf("WARNING: Actual Peer AE Title (%s) has no access to database: %s\n", currentPeerTitle, db->title); printf(" Setting Default Peer AE Title: %s\n", db->peerTitles[0]); currentPeerTitle = db->peerTitles[0]; } if (!TI_attachDB(dbEntries[currentdb])) { DCMQRDB_FATAL("unable to open database, bailing out"); exit(10); } } } return OFTrue; } OFBool DcmQueryRetrieveTelnetInitiator::TI_echo(int arg, const char * /*cmdbuf*/ ) { OFBool ok = OFTrue; if (DCM_dcmqrdbLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL)) { printf("TI_echo: arg=%d\n", arg); } ok = TI_changeAssociation(); if (!ok) return OFFalse; if (arg <= 0) arg = 1; /* send 1 echo message */ /* send echo message to peer AE title */ while ( arg-- > 0 && ok) { ok = TI_sendEcho(); } ok = TI_detachAssociation(OFFalse); return ok; } OFBool DcmQueryRetrieveTelnetInitiator::TI_quit(int arg, const char * /*cmdbuf*/ ) { if (DCM_dcmqrdbLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL)) { printf("TI_quit: arg=%d\n", arg); } TI_detachAssociation(OFFalse); printf("Good Bye, Auf Wiedersehen, Au Revoir\n"); exit(0); return OFTrue; } OFBool DcmQueryRetrieveTelnetInitiator::TI_actualizeStudies() { TI_DBEntry *db; db = dbEntries[currentdb]; /* get a list of all the available studies in the current database */ if (!TI_buildStudies(db)) return OFFalse; if (db->studyCount == 0) { printf("No Studies in Database: %s\n", db->title); return OFFalse; } if (db->currentStudy < 0 || db->currentStudy >= db->studyCount) db->currentStudy = 0; return OFTrue; } OFBool DcmQueryRetrieveTelnetInitiator::TI_study(int arg, const char * /*cmdbuf*/ ) { TI_DBEntry *db; TI_StudyEntry *se; int i; if (DCM_dcmqrdbLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL)) { printf("TI_study: arg=%d\n", arg); } db = dbEntries[currentdb]; if (db->isRemoteDB) { currentPeerTitle = db->title; /* make sure we have an association */ OFBool ok = TI_changeAssociation(); if (!ok) return OFFalse; } if (!TI_actualizeStudies()) return OFFalse; #ifndef RETAIN_ASSOCIATION if (dbEntries[currentdb]->isRemoteDB) { TI_detachAssociation(OFFalse); } #endif if (arg >= 0) { /* set current study */ if (arg >= db->studyCount) { printf("ERROR: Study Choice: 0 - %d\n", db->studyCount - 1); return OFFalse; } db->currentStudy = arg; return OFTrue; } /* list studies to user */ printf(" "); printf(STUDYFORMAT, "Patient", "PatientID", "StudyID"); for (i=0; istudyCount; i++) { if (db->currentStudy == i) { printf("*"); } else { printf(" "); } printf(" %2d) ", i); se = db->studies[i]; printStudyEntry(se); } printf("\n"); printf("%d Studies in Database: %s\n", db->studyCount, db->title); return OFTrue; } OFBool DcmQueryRetrieveTelnetInitiator::TI_actualizeSeries() { TI_DBEntry *db; TI_StudyEntry *study; db = dbEntries[currentdb]; if (db->studyCount == 0) if (!TI_actualizeStudies()) return OFFalse; study = db->studies[db->currentStudy]; /* get a list of all the available series in the current study */ if (!TI_buildSeries(db, study)) return OFFalse; if (study->seriesCount == 0) { printf("No Series in Study %s (Database: %s)\n", study->studyID, db->title); return OFFalse; } if (db->currentSeries < 0 || db->currentSeries >= study->seriesCount) db->currentSeries = 0; return OFTrue; } OFBool DcmQueryRetrieveTelnetInitiator::TI_series(int arg, const char * /*cmdbuf*/ ) { TI_DBEntry *db; TI_StudyEntry *study; TI_SeriesEntry *series; int i; if (DCM_dcmqrdbLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL)) { printf("TI_sseries: arg=%d\n", arg); } db = dbEntries[currentdb]; if (db->isRemoteDB) { currentPeerTitle = db->title; /* make sure we have an association */ OFBool ok = TI_changeAssociation(); if (!ok) return OFFalse; } if (!TI_actualizeSeries()) return OFFalse; #ifndef RETAIN_ASSOCIATION if (dbEntries[currentdb]->isRemoteDB) { TI_detachAssociation(OFFalse); } #endif study = db->studies[db->currentStudy]; if (arg >= 0) { /* set current series */ if (arg >= study->seriesCount) { printf("ERROR: Series Choice: 0 - %d\n", study->seriesCount - 1); return OFFalse; } db->currentSeries = arg; return OFTrue; } /* list series to user */ printf(" "); printf(SERIESFORMAT, "Series", "Modality", "SeriesInstanceUID"); for (i=0; iseriesCount; i++) { if (db->currentSeries == i) { printf("*"); } else { printf(" "); } printf(" %2d) ", i); series = study->series[i]; printSeriesEntry(series); } printf("\n"); printf("%d Series in StudyID %s,\n", study->seriesCount, study->studyID); printf(" Patient: %s (Database: %s)\n", study->patientName, db->title); return OFTrue; } OFBool DcmQueryRetrieveTelnetInitiator::TI_actualizeImages() { TI_DBEntry *db; TI_StudyEntry *study; TI_SeriesEntry *series; db = dbEntries[currentdb]; if (db->studyCount == 0) { if (!TI_actualizeStudies()) return OFFalse; } study = db->studies[db->currentStudy]; if (study->seriesCount == 0) { if (!TI_actualizeSeries()) return OFFalse; } series = study->series[db->currentSeries]; /* get a list of all the available images in the current series */ if (!TI_buildImages(db, study, series)) return OFFalse; if (series->imageCount == 0) { printf("No Images in Series %s, Study %s (Database: %s)\n", series->seriesNumber, study->studyID, db->title); return OFFalse; } return OFTrue; } OFBool DcmQueryRetrieveTelnetInitiator::TI_image(int arg, const char * /*cmdbuf*/ ) { TI_DBEntry *db; TI_StudyEntry *study; TI_SeriesEntry *series; TI_ImageEntry *image; int i; if (DCM_dcmqrdbLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL)) { printf("TI_image: arg=%d\n", arg); } db = dbEntries[currentdb]; if (db->isRemoteDB) { currentPeerTitle = db->title; /* make sure we have an association */ OFBool ok = TI_changeAssociation(); if (!ok) return OFFalse; } if (!TI_actualizeImages()) return OFFalse; #ifndef RETAIN_ASSOCIATION if (dbEntries[currentdb]->isRemoteDB) { TI_detachAssociation(OFFalse); } #endif study = db->studies[db->currentStudy]; series = study->series[db->currentSeries]; if (arg >= 0) { /* set current image */ if (arg >= series->imageCount) { printf("ERROR: Image Choice: 0 - %d\n", series->imageCount - 1); return OFFalse; } db->currentImage = arg; return OFTrue; } /* list images to user */ printf(" "); printf(IMAGEFORMAT, "Image", "ImageInstanceUID"); for (i=0; iimageCount; i++) { if (db->currentImage == i) { printf("*"); } else { printf(" "); } printf(" %2d) ", i); image = series->images[i]; printImageEntry(image); } printf("\n"); printf("%d Images in %s Series, StudyID %s,\n", series->imageCount, series->modality, study->studyID); printf(" Patient: %s (Database: %s)\n", study->patientName, db->title); return OFTrue; } OFBool DcmQueryRetrieveTelnetInitiator::TI_sendStudy(int arg, const char * /*cmdbuf*/ ) { OFBool ok = OFTrue; DcmDataset *query = NULL; TI_DBEntry *db; TI_StudyEntry *study; OFCondition dbcond = EC_Normal; DcmQueryRetrieveDatabaseStatus dbStatus(STATUS_Pending); DIC_UI sopClass; DIC_UI sopInstance; char imgFile[MAXPATHLEN+1]; DIC_US nRemaining = 0; if (DCM_dcmqrdbLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL)) { printf("TI_sendStudy: arg=%d\n", arg); } db = dbEntries[currentdb]; /* ** We cannot read images from a DB and send images to the same DB ** over the network because of deadlock. The DB move routines ** lock the index file. When we send over the network to the same ** DB it tries to lock the index file exclusively to insert the image ** in the database. We end up waiting for a response from the remote ** peer which never comes. */ if (strcmp(db->title, currentPeerTitle) == 0) { printf("Sorry, cannot send images from a DB to itself, possible deadlock\n"); return OFFalse; } /* make sure study info is actual */ ok = TI_actualizeStudies(); if (!ok) return OFFalse; /* set arg as current study */ if (arg < 0) { arg = db->currentStudy; } if (arg >= db->studyCount) { printf("ERROR: Study Choice: 0 - %d\n", db->studyCount - 1); return OFFalse; } study = db->studies[arg]; /* make sure we have an association */ ok = TI_changeAssociation(); if (!ok) return OFFalse; /* fabricate query */ query = new DcmDataset; if (query == NULL) { DCMQRDB_ERROR("Help, out of memory"); return OFFalse; } DU_putStringDOElement(query, DCM_QueryRetrieveLevel, "STUDY"); DU_putStringDOElement(query, DCM_StudyInstanceUID, study->studyInstanceUID); dbcond = db->dbHandle->startMoveRequest( UID_MOVEStudyRootQueryRetrieveInformationModel, query, &dbStatus); delete query; query = NULL; if (dbcond.bad()) { DCMQRDB_ERROR("TI_sendStudy: cannot query database"); return OFFalse; } while (ok && dbStatus.status() == STATUS_Pending) { dbcond = db->dbHandle->nextMoveResponse(sopClass, sizeof(sopClass), sopInstance, sizeof(sopInstance), imgFile, sizeof(imgFile), &nRemaining, &dbStatus); if (dbcond.bad()) { DCMQRDB_ERROR("TI_sendStudy: database error"); return OFFalse; } if (dbStatus.status() == STATUS_Pending) { ok = TI_storeImage(sopClass, sopInstance, imgFile); if (!ok) { db->dbHandle->cancelMoveRequest(&dbStatus); } } } ok = TI_detachAssociation(OFFalse); return ok; } OFBool DcmQueryRetrieveTelnetInitiator::TI_sendSeries(int arg, const char * /*cmdbuf*/ ) { OFBool ok = OFTrue; DcmDataset *query = NULL; TI_DBEntry *db; TI_StudyEntry *study; TI_SeriesEntry *series; OFCondition dbcond = EC_Normal; DcmQueryRetrieveDatabaseStatus dbStatus(STATUS_Pending); DIC_UI sopClass; DIC_UI sopInstance; char imgFile[MAXPATHLEN+1]; DIC_US nRemaining = 0; if (DCM_dcmqrdbLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL)) { printf("TI_sendSeries: arg=%d\n", arg); } db = dbEntries[currentdb]; /* make sure study/series info is actual */ ok = TI_actualizeSeries(); if (!ok) return OFFalse; study = db->studies[db->currentStudy]; /* set arg as current series */ if (arg < 0) { arg = db->currentSeries; } if (arg >= study->seriesCount) { printf("ERROR: Series Choice: 0 - %d\n", study->seriesCount - 1); return OFFalse; } series = study->series[arg]; /* make sure we have an association */ ok = TI_changeAssociation(); if (!ok) return OFFalse; /* fabricate query */ query = new DcmDataset; if (query == NULL) { DCMQRDB_ERROR("Help, out of memory"); return OFFalse; } DU_putStringDOElement(query, DCM_QueryRetrieveLevel, "SERIES"); DU_putStringDOElement(query, DCM_StudyInstanceUID, study->studyInstanceUID); DU_putStringDOElement(query, DCM_SeriesInstanceUID, series->seriesInstanceUID); dbcond = db->dbHandle->startMoveRequest( UID_MOVEStudyRootQueryRetrieveInformationModel, query, &dbStatus); delete query; query = NULL; if (dbcond.bad()) { DCMQRDB_ERROR("TI_sendSeries: cannot query database"); return OFFalse; } while (ok && dbStatus.status() == STATUS_Pending) { dbcond = db->dbHandle->nextMoveResponse(sopClass, sizeof(sopClass), sopInstance, sizeof(sopInstance), imgFile, sizeof(imgFile), &nRemaining, &dbStatus); if (dbcond.bad()) { DCMQRDB_ERROR("TI_sendSeries: database error"); return OFFalse; } if (dbStatus.status() == STATUS_Pending) { ok = TI_storeImage(sopClass, sopInstance, imgFile); if (!ok) { db->dbHandle->cancelMoveRequest(&dbStatus); } } } ok = TI_detachAssociation(OFFalse); return ok; } OFBool DcmQueryRetrieveTelnetInitiator::TI_sendImage(int arg, const char * /*cmdbuf*/ ) { OFBool ok = OFTrue; DcmDataset *query = NULL; TI_DBEntry *db; TI_StudyEntry *study; TI_SeriesEntry *series; TI_ImageEntry *image; OFCondition dbcond = EC_Normal; DcmQueryRetrieveDatabaseStatus dbStatus(STATUS_Pending); DIC_UI sopClass; DIC_UI sopInstance; char imgFile[MAXPATHLEN+1]; DIC_US nRemaining = 0; if (DCM_dcmqrdbLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL)) { printf("TI_sendImage: arg=%d\n", arg); } db = dbEntries[currentdb]; /* make sure study/series/image info is actual */ ok = TI_actualizeImages(); if (!ok) return OFFalse; study = db->studies[db->currentStudy]; series = study->series[db->currentSeries]; /* set arg as current image */ if (arg < 0) { arg = db->currentImage; } if (arg >= series->imageCount) { printf("ERROR: Image Choice: 0 - %d\n", series->imageCount - 1); return OFFalse; } image = series->images[arg]; /* make sure we have an association */ ok = TI_changeAssociation(); if (!ok) return OFFalse; /* fabricate query */ query = new DcmDataset; if (query == NULL) { DCMQRDB_ERROR("Help, out of memory"); return OFFalse; } DU_putStringDOElement(query, DCM_QueryRetrieveLevel, "IMAGE"); DU_putStringDOElement(query, DCM_StudyInstanceUID, study->studyInstanceUID); DU_putStringDOElement(query, DCM_SeriesInstanceUID, series->seriesInstanceUID); DU_putStringDOElement(query, DCM_SOPInstanceUID, image->sopInstanceUID); dbcond = db->dbHandle->startMoveRequest( UID_MOVEStudyRootQueryRetrieveInformationModel, query, &dbStatus); delete query; query = NULL; if (dbcond.bad()) { DCMQRDB_ERROR("TI_sendImage: cannot query database"); return OFFalse; } /* * We should only ever get 1 response to the above query, * but you never know (there could be non-unique UIDs in * the database). */ while (ok && dbStatus.status() == STATUS_Pending) { dbcond = db->dbHandle->nextMoveResponse(sopClass, sizeof(sopClass), sopInstance, sizeof(sopInstance), imgFile, sizeof(imgFile), &nRemaining, &dbStatus); if (dbcond.bad()) { DCMQRDB_ERROR("TI_sendImage: database error"); return OFFalse; } if (dbStatus.status() == STATUS_Pending) { ok = TI_storeImage(sopClass, sopInstance, imgFile); if (!ok) { db->dbHandle->cancelMoveRequest(&dbStatus); } } } ok = TI_detachAssociation(OFFalse); return ok; } OFBool DcmQueryRetrieveTelnetInitiator::TI_send(int /*arg*/, const char *cmdbuf) { OFBool ok = OFTrue; char cmdarg[1024]; int iarg; int narg; if (dbEntries[currentdb]->isRemoteDB) { printf("Sorry, cannot send from remote DB\n"); return OFTrue; } bzero(cmdarg, sizeof(cmdarg)); narg = sscanf(cmdbuf, "send %s %d", cmdarg, &iarg); if (narg == 1) iarg = -1; if (strncmp("st", cmdarg, strlen("st")) == 0) TI_sendStudy(iarg, cmdbuf); else if (strncmp("se", cmdarg, strlen("se")) == 0) TI_sendSeries(iarg, cmdbuf); else if (strncmp("i", cmdarg, strlen("i")) == 0) TI_sendImage(iarg, cmdbuf); else { printf("What do you want to send? Type help for help\n"); } return ok; } void DcmQueryRetrieveTelnetInitiator::TI_userInput() { char cmdBuf[1024]; /* can't have lines longer than this */ int arg; /* make the first database current */ currentdb = 0; /* make the first peer title for this database current */ currentPeerTitle = dbEntries[currentdb]->peerTitles[0]; /* open db */ TI_database(currentdb, cmdBuf); TI_welcome(); printf("\n"); while (1) { printf("%s->%s> ", dbEntries[currentdb]->title, currentPeerTitle); if (fgets(cmdBuf, 1024, stdin) == NULL) { DCMQRDB_ERROR("unexpected end of input\n"); return; /* give up */ } DU_stripLeadingSpaces(cmdBuf); if (strlen(cmdBuf) == 0) continue; /* no input */ if (sscanf(cmdBuf, "%*s %d", &arg) != 1) { arg = -1; } /* find command parser */ if (strncmp("h", cmdBuf, strlen("h")) == 0) TI_help(arg, cmdBuf); else if (strncmp("?", cmdBuf, strlen("?")) == 0) TI_shortHelp(arg, cmdBuf); else if (strncmp("t", cmdBuf, strlen("t")) == 0) TI_title(arg, cmdBuf); else if (strncmp("da", cmdBuf, strlen("da")) == 0) TI_database(arg, cmdBuf); else if (strncmp("st", cmdBuf, strlen("st")) == 0) TI_study(arg, cmdBuf); else if (strncmp("ser", cmdBuf, strlen("ser"))== 0) TI_series(arg, cmdBuf); else if (strncmp("i", cmdBuf, strlen("i")) == 0) TI_image(arg, cmdBuf); else if (strncmp("send", cmdBuf, strlen("send")) == 0) TI_send(arg, cmdBuf); else if (strncmp("ec", cmdBuf, strlen("ec")) == 0) TI_echo(arg, cmdBuf); else if (strncmp("q", cmdBuf, strlen("q")) == 0) TI_quit(arg, cmdBuf); else if (strncmp("exit", cmdBuf, strlen("exit")) == 0) TI_quit(arg, cmdBuf); else { printf("What do you want to do? Type help for help\n"); } } } /* ========================================== TI QUERY ========================================== */ OFBool DcmQueryRetrieveTelnetInitiator::TI_dbReadable(const char *dbTitle) { char path[MAXPATHLEN+1]; sprintf(path, "%s%c%s", config.getStorageArea(dbTitle), PATH_SEPARATOR, DBINDEXFILE); return (access(path, R_OK) == 0); } time_t DcmQueryRetrieveTelnetInitiator::TI_dbModifyTime(const char *dbTitle) { char path[MAXPATHLEN+1]; struct stat s; sprintf(path, "%s%c%s", config.getStorageArea(dbTitle), PATH_SEPARATOR, DBINDEXFILE); if (stat(path, &s) < 0) { DCMQRDB_ERROR("cannot stat: " << path); return 0; } return s.st_mtime; } /* * Study Level */ OFBool DcmQueryRetrieveTelnetInitiator::TI_buildRemoteStudies(TI_DBEntry *db) { TI_GenericCallbackStruct cbs; DcmDataset *query = NULL; OFBool ok = OFTrue; cbs.db = db; cbs.study = NULL; cbs.series = NULL; TI_destroyStudyEntries(db); /* get all known studies */ TI_buildStudyQuery(&query); ok = TI_remoteFindQuery(db, query, TI_genericEntryCallback, &cbs); delete query; return ok; } OFBool DcmQueryRetrieveTelnetInitiator::TI_buildStudies(TI_DBEntry *db) { OFCondition dbcond = EC_Normal; DcmQueryRetrieveDatabaseStatus dbStatus(STATUS_Pending); DcmDataset *query = NULL; DcmDataset *reply = NULL; if (db->isRemoteDB) { return TI_buildRemoteStudies(db); } if (db->studyCount != 0 && TI_dbModifyTime(db->title) < db->lastQueryTime) { /* nothing has changed */ return OFTrue; } TI_destroyStudyEntries(db); /* get all known studies */ TI_buildStudyQuery(&query); printf("Querying Database for Studies ...\n"); db->lastQueryTime = time(NULL); dbcond = db->dbHandle->startFindRequest( UID_FINDStudyRootQueryRetrieveInformationModel, query, &dbStatus); if (dbcond.bad()) { DCMQRDB_ERROR("TI_buildStudies: cannot query database"); delete query; return OFFalse; } dbStatus.deleteStatusDetail(); while (dbStatus.status() == STATUS_Pending) { dbcond = db->dbHandle->nextFindResponse(&reply, &dbStatus, config.getCharacterSetOptions()); if (dbcond.bad()) { DCMQRDB_ERROR("TI_buildStudies: database error"); return OFFalse; } if (dbStatus.status() == STATUS_Pending) { TI_addStudyEntry(db, reply); delete reply; reply = NULL; } } delete query; return OFTrue; } /* * Series Level */ OFBool DcmQueryRetrieveTelnetInitiator::TI_buildRemoteSeries(TI_DBEntry *db, TI_StudyEntry *study) { TI_GenericCallbackStruct cbs; DcmDataset *query = NULL; OFBool ok = OFTrue; cbs.db = NULL; cbs.study = study; cbs.series = NULL; TI_destroySeriesEntries(study); /* get all known studies */ TI_buildSeriesQuery(&query, study); ok = TI_remoteFindQuery(db, query, TI_genericEntryCallback, &cbs); delete query; return ok; } OFBool DcmQueryRetrieveTelnetInitiator::TI_buildSeries(TI_DBEntry *db, TI_StudyEntry *study) { OFCondition dbcond = EC_Normal; DcmQueryRetrieveDatabaseStatus dbStatus(STATUS_Pending); DcmDataset *query = NULL; DcmDataset *reply = NULL; if (db->isRemoteDB) { return TI_buildRemoteSeries(db, study); } if (study->seriesCount != 0 && TI_dbModifyTime(db->title) < db->lastQueryTime) { /* nothing has changed */ return OFTrue; } TI_destroySeriesEntries(study); /* get all known series for this study */ TI_buildSeriesQuery(&query, study); printf("Querying Database for Series ...\n"); study->lastQueryTime = time(NULL); dbcond = db->dbHandle->startFindRequest( UID_FINDStudyRootQueryRetrieveInformationModel, query, &dbStatus); if (dbcond.bad()) { DCMQRDB_ERROR("TI_buildSeries: cannot query database"); delete query; query = NULL; return OFFalse; } dbStatus.deleteStatusDetail(); while (dbStatus.status() == STATUS_Pending) { dbcond = db->dbHandle->nextFindResponse(&reply, &dbStatus, config.getCharacterSetOptions()); if (dbcond.bad()) { DCMQRDB_ERROR("TI_buildSeries: database error"); return OFFalse; } if (dbStatus.status() == STATUS_Pending) { TI_addSeriesEntry(study, reply); delete reply; reply = NULL; } } delete query; query = NULL; if (study->seriesCount > 0) { /* sort the seriesinto assending series number order */ qsort(study->series, study->seriesCount, sizeof(study->series[0]), TI_seriesCompare); } return OFTrue; } /* * Image Level */ OFBool DcmQueryRetrieveTelnetInitiator::TI_buildRemoteImages(TI_DBEntry *db, TI_StudyEntry *study, TI_SeriesEntry *series) { TI_GenericCallbackStruct cbs; DcmDataset *query = NULL; OFBool ok = OFTrue; cbs.db = NULL; cbs.study = NULL; cbs.series = series; TI_destroyImageEntries(series); /* get all known studies */ TI_buildImageQuery(&query, study, series); ok = TI_remoteFindQuery(db, query, TI_genericEntryCallback, &cbs); delete query; return ok; } OFBool DcmQueryRetrieveTelnetInitiator::TI_buildImages(TI_DBEntry *db, TI_StudyEntry *study, TI_SeriesEntry *series) { OFCondition dbcond = EC_Normal; DcmQueryRetrieveDatabaseStatus dbStatus(STATUS_Pending); DcmDataset *query = NULL; DcmDataset *reply = NULL; if (db->isRemoteDB) { return TI_buildRemoteImages(db, study, series); } if (series->imageCount != 0 && TI_dbModifyTime(db->title) < study->lastQueryTime) { /* nothing has changed */ return OFTrue; } TI_destroyImageEntries(series); /* get all known images in current series */ TI_buildImageQuery(&query, study, series); if (DCM_dcmqrdbLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL)) { printf("QUERY OBJECT:\n"); query->print(COUT); } printf("Querying Database for Images ...\n"); series->lastQueryTime = time(NULL); dbcond = db->dbHandle->startFindRequest( UID_FINDStudyRootQueryRetrieveInformationModel, query, &dbStatus); delete query; query = NULL; if (dbcond.bad()) { DCMQRDB_ERROR("TI_buildImages: cannot query database"); return OFFalse; } while (dbStatus.status() == STATUS_Pending) { dbcond = db->dbHandle->nextFindResponse(&reply, &dbStatus, config.getCharacterSetOptions()); if (dbcond.bad()) { DCMQRDB_ERROR("TI_buildImages: database error"); return OFFalse; } if (dbStatus.status() == STATUS_Pending) { if (DCM_dcmqrdbLogger.isEnabledFor(OFLogger::INFO_LOG_LEVEL)) { printf("REPLY OBJECT:\n"); reply->print(COUT); } TI_addImageEntry(series, reply); delete reply; reply = NULL; } } if (series->imageCount > 0) { /* sort the images into assending image number order */ qsort(series->images, series->imageCount, sizeof(series->images[0]), TI_imageCompare); } return OFTrue; } OFBool DcmQueryRetrieveTelnetInitiator::addPeerName(const char *peerName, const char *configFileName) { int k; OFBool found = OFFalse; const char **aeTitles; if (peerNamesCount == TI_MAXPEERS) return OFFalse; for( k=0; !found && ktitle; for( j=0 ; jpeerTitleCount ; j++ ) { tmpString += dbEntries[i]->peerTitles[j]; tmpString += ' '; } DCMQRDB_INFO(tmpString); } } OFBool DcmQueryRetrieveTelnetInitiator::findDBPeerTitles( const char *configFileName, TI_DBEntry *dbEntry, const char *peer) { const char **peerTitles = NULL; int peerTitleCount = 0; int i; // discover all known AETitles for peer peerTitleCount = config.aeTitlesForPeer( peer,&peerTitles ); if( peerTitleCount <= 0 ) { DCMQRDB_ERROR("no AE titles defined (in: " << configFileName << ") for peer: " << peer); return( OFFalse ); } // check to make sure peer+AEtitle has access to database areas for( i=0 ; ititle, peerTitles[i], peer ) ) { // add peer title to database's peer title list if( dbEntry->peerTitles == NULL ) dbEntry->peerTitles = (const char**) malloc( sizeof( const char* ) ); else dbEntry->peerTitles = (const char**) realloc( dbEntry->peerTitles, (dbEntry->peerTitleCount + 1) * sizeof(const char*) ); dbEntry->peerTitles[ dbEntry->peerTitleCount ] = peerTitles[i]; dbEntry->peerTitleCount++; } } // throw away the old list free( peerTitles ); return( dbEntry->peerTitleCount > 0 ); } void DcmQueryRetrieveTelnetInitiator::createConfigEntries( const char *configFileName, int remoteDBTitlesCount, const char **remoteDBTitles) { const char **ctnTitles = NULL; int ctnTitleCount = 0; int i, j; TI_DBEntry *dbEntry = NULL; dbCount = 0; // discover all the known CTN AE titles ctnTitleCount = config.ctnTitles(&ctnTitles); for( i=0 ; ititle = ctnTitles[i]; for( j=0 ; jpeerTitleCount > 0 ) { // add database to list, it is accessible by something if( dbEntries == NULL ) dbEntries = (TI_DBEntry**) malloc( sizeof( TI_DBEntry* ) ); else dbEntries = (TI_DBEntry**) realloc( dbEntries, (dbCount + 1) * sizeof(TI_DBEntry*) ); dbEntries[ dbCount ] = dbEntry; dbCount++; } else free( dbEntry ); } } // throw away the old lists free( ctnTitles ); // add any remote DB entries for( i=0 ; ititle = remoteDBTitles[i]; dbEntry->isRemoteDB = OFTrue; if( dbEntries == NULL ) dbEntries = (TI_DBEntry**) malloc( sizeof( TI_DBEntry* ) ); else dbEntries = (TI_DBEntry**) realloc( dbEntries, (dbCount + 1) * sizeof(TI_DBEntry*) ); dbEntries[ dbCount ] = dbEntry; dbCount++; for( j=0 ; jpeerTitles == NULL ) dbEntry->peerTitles = (const char**) malloc( sizeof( const char* ) ); else dbEntry->peerTitles = (const char**) realloc( dbEntry->peerTitles, (dbEntry->peerTitleCount + 1) * sizeof(const char*) ); dbEntry->peerTitles[ dbEntry->peerTitleCount ] = peerTitles[k]; dbEntry->peerTitleCount++; } // throw away the old list free( peerTitles ); } } } }