/// <summary> /// Main routine for processing C-MOVE-RQ messages. Called by the <see cref="DicomScp{DicomScpParameters}"/> component. /// </summary> /// <param name="server"></param> /// <param name="association"></param> /// <param name="presentationID"></param> /// <param name="message"></param> /// <returns></returns> public override bool OnReceiveRequest(DicomServer server, ServerAssociationParameters association, byte presentationID, DicomMessage message) { bool finalResponseSent = false; string errorComment; try { // Check for a Cancel message, and cancel the SCU. if (message.CommandField == DicomCommandField.CCancelRequest) { if (_theScu != null) { _theScu.Cancel(); } return(true); } // Get the level of the Move. String level = message.DataSet[DicomTags.QueryRetrieveLevel].GetString(0, ""); // Trim the remote AE, see extra spaces at the end before which has caused problems string remoteAe = message.MoveDestination.Trim(); // Open a DB Connection using (IReadContext read = _store.OpenReadContext()) { // Load remote device information fromt he database. Device device = LoadRemoteHost(read, Partition, remoteAe); if (device == null) { errorComment = string.Format( "Unknown move destination \"{0}\", failing C-MOVE-RQ from {1} to {2}", remoteAe, association.CallingAE, association.CalledAE); Platform.Log(LogLevel.Error, errorComment); server.SendCMoveResponse(presentationID, message.MessageId, new DicomMessage(), DicomStatuses.QueryRetrieveMoveDestinationUnknown, errorComment); finalResponseSent = true; return(true); } // If the remote node is a DHCP node, use its IP address from the connection information, else // use what is configured. Always use the configured port. if (device.Dhcp) { device.IpAddress = association.RemoteEndPoint.Address.ToString(); } // Now setup the StorageSCU component _theScu = new ImageServerStorageScu(Partition, device, association.CallingAE, message.MessageId); // Now create the list of SOPs to send bool bOnline; if (level.Equals("PATIENT")) { bOnline = GetSopListForPatient(read, message, out errorComment); } else if (level.Equals("STUDY")) { bOnline = GetSopListForStudy(message, out errorComment); } else if (level.Equals("SERIES")) { bOnline = GetSopListForSeries(read, message, out errorComment); } else if (level.Equals("IMAGE")) { bOnline = GetSopListForSop(message, out errorComment); } else { errorComment = string.Format("Unexpected Study Root Move Query/Retrieve level: {0}", level); Platform.Log(LogLevel.Error, errorComment); server.SendCMoveResponse(presentationID, message.MessageId, new DicomMessage(), DicomStatuses.QueryRetrieveIdentifierDoesNotMatchSOPClass, errorComment); finalResponseSent = true; return(true); } // Could not find an online/readable location for the requested objects to move. // Note that if the C-MOVE-RQ included a list of study instance uids, and some // were online and some offline, we don't fail now (ie, the check on the Count) if (!bOnline && _theScu.StorageInstanceList.Count == 0) { Platform.Log(LogLevel.Error, "Unable to find online storage location for C-MOVE-RQ: {0}", errorComment); server.SendCMoveResponse(presentationID, message.MessageId, new DicomMessage(), DicomStatuses.QueryRetrieveUnableToPerformSuboperations, string.IsNullOrEmpty(errorComment) ? string.Empty : errorComment); finalResponseSent = true; _theScu.Dispose(); _theScu = null; return(true); } // No files were eligible for transfer, just send success and return if (_theScu.StorageInstanceList.Count == 0) { server.SendCMoveResponse(presentationID, message.MessageId, new DicomMessage(), DicomStatuses.Success, 0, 0, 0, 0); finalResponseSent = true; _theScu.Dispose(); _theScu = null; return(true); } // set the preferred syntax lists _theScu.LoadPreferredSyntaxes(read); _theScu.ImageStoreCompleted += delegate(Object sender, StorageInstance instance) { var scu = (StorageScu)sender; var msg = new DicomMessage(); DicomStatus status; if (instance.SendStatus.Status == DicomState.Failure) { errorComment = string.IsNullOrEmpty(instance.ExtendedFailureDescription) ? instance.SendStatus.ToString() : instance.ExtendedFailureDescription; } if (scu.RemainingSubOperations == 0) { foreach (StorageInstance sop in _theScu.StorageInstanceList) { if ((sop.SendStatus.Status != DicomState.Success) && (sop.SendStatus.Status != DicomState.Warning)) { msg.DataSet[DicomTags.FailedSopInstanceUidList].AppendString(sop.SopInstanceUid); } } if (scu.Status == ScuOperationStatus.Canceled) { status = DicomStatuses.Cancel; } else if (scu.Status == ScuOperationStatus.ConnectFailed) { status = DicomStatuses.QueryRetrieveMoveDestinationUnknown; } else if (scu.FailureSubOperations > 0) { status = DicomStatuses.QueryRetrieveSubOpsOneOrMoreFailures; } else if (!bOnline) { status = DicomStatuses.QueryRetrieveUnableToPerformSuboperations; } else { status = DicomStatuses.Success; } } else { status = DicomStatuses.Pending; if ((scu.RemainingSubOperations % 5) != 0) { return; } // Only send a RSP every 5 to reduce network load } server.SendCMoveResponse(presentationID, message.MessageId, msg, status, (ushort)scu.SuccessSubOperations, (ushort)scu.RemainingSubOperations, (ushort)scu.FailureSubOperations, (ushort)scu.WarningSubOperations, status == DicomStatuses.QueryRetrieveSubOpsOneOrMoreFailures ? errorComment : string.Empty); if (scu.RemainingSubOperations == 0) { finalResponseSent = true; } }; _theScu.AssociationAccepted += (sender, parms) => AssociationAuditLogger.BeginInstancesTransferAuditLogger( _theScu.StorageInstanceList, parms); _theScu.BeginSend( delegate(IAsyncResult ar) { if (_theScu != null) { if (!finalResponseSent) { var msg = new DicomMessage(); server.SendCMoveResponse(presentationID, message.MessageId, msg, DicomStatuses.QueryRetrieveSubOpsOneOrMoreFailures, (ushort)_theScu.SuccessSubOperations, 0, (ushort)(_theScu.FailureSubOperations + _theScu.RemainingSubOperations), (ushort)_theScu.WarningSubOperations, errorComment); finalResponseSent = true; } _theScu.EndSend(ar); _theScu.Dispose(); _theScu = null; } }, _theScu); return(true); } // end using() } catch (Exception e) { Platform.Log(LogLevel.Error, e, "Unexpected exception when processing C-MOVE-RQ"); if (finalResponseSent == false) { try { server.SendCMoveResponse(presentationID, message.MessageId, new DicomMessage(), DicomStatuses.QueryRetrieveUnableToProcess, e.Message); finalResponseSent = true; } catch (Exception x) { Platform.Log(LogLevel.Error, x, "Unable to send final C-MOVE-RSP message on association from {0} to {1}", association.CallingAE, association.CalledAE); server.Abort(); } } } return(false); }
/// <summary> /// Main routine for processing C-MOVE-RQ messages. Called by the <see cref="DicomScp{TContext}" /> component. /// </summary> /// <param name="server"></param> /// <param name="association"></param> /// <param name="presentationID"></param> /// <param name="message"></param> /// <returns></returns> public override bool OnReceiveRequest(DicomServer server, ServerAssociationParameters association, byte presentationID, DicomMessage message) { var finalResponseSent = false; string errorComment; try { // check for a Cancel Message, and cance the scu if (message.CommandField == DicomCommandField.CCancelRequest) { if (_theScu != null) { _theScu.Cancel(); } return(true); } var level = message.DataSet[DicomTags.QueryRetrieveLevel].GetString(0, string.Empty); var remoteAe = message.MoveDestination.Trim(); // load remote device for move information var device = IoC.Get <IDeviceManager>().LookupDevice(Partition, remoteAe); if (device == null) { errorComment = string.Format( "Unknown move destination \"{0}\", failing C-MOVE-RQ from {1} to {2}", remoteAe, association.CallingAE, association.CalledAE); Platform.Log(LogLevel.Error, errorComment); server.SendCMoveResponse(presentationID, message.MessageId, new DicomMessage(), DicomStatuses.QueryRetrieveMoveDestinationUnknown, errorComment); finalResponseSent = true; return(true); } //If the remote node is a DHCP node, use its IP address from the connection information, else // use what is configured.Always use the configured port. if (device.Dhcp && association.CallingAE.Equals(remoteAe)) { device.Hostname = association.RemoteEndPoint.Address.ToString(); } // now setup the storage scu component _theScu = new PacsStorageScu(Partition, device, association.CallingAE, message.MessageId); bool bOnline; if (level.Equals("PATIENT")) { bOnline = GetSopListForPatient(message, out errorComment); } else if (level.Equals("STUDY")) { bOnline = GetSopListForStudy(message, out errorComment); } else if (level.Equals("SERIES")) { bOnline = GetSopListForSeries(message, out errorComment); } else if (level.Equals("IMAGE")) { bOnline = GetSopListForInstance(message, out errorComment); } else { errorComment = string.Format("Unexpected Study Root Move Query/Retrieve level: {0}", level); Platform.Log(LogLevel.Error, errorComment); server.SendCMoveResponse(presentationID, message.MessageId, new DicomMessage(), DicomStatuses.QueryRetrieveIdentifierDoesNotMatchSOPClass, errorComment); finalResponseSent = true; return(true); } // Could not find an online/readable location for the requested objects to move. // Note that if the C-MOVE-RQ included a list of study instance uids, and some // were online and some offline, we don't fail now (ie, the check on the Count) if (!bOnline || _theScu.StorageInstanceList.Count == 0) { Platform.Log(LogLevel.Error, "Unable to find online storage location for C-MOVE-RQ: {0}", errorComment); server.SendCMoveResponse(presentationID, message.MessageId, new DicomMessage(), DicomStatuses.QueryRetrieveUnableToPerformSuboperations, string.IsNullOrEmpty(errorComment) ? string.Empty : errorComment); finalResponseSent = true; _theScu.Dispose(); _theScu = null; return(true); } _theScu.ImageStoreCompleted += delegate(object sender, StorageInstance instance) { var scu = (StorageScu)sender; var msg = new DicomMessage(); DicomStatus status; if (instance.SendStatus.Status == DicomState.Failure) { errorComment = string.IsNullOrEmpty(instance.ExtendedFailureDescription) ? instance.SendStatus.ToString() : instance.ExtendedFailureDescription; } if (scu.RemainingSubOperations == 0) { foreach (var sop in _theScu.StorageInstanceList) { if ((sop.SendStatus.Status != DicomState.Success) && (sop.SendStatus.Status != DicomState.Warning)) { msg.DataSet[DicomTags.FailedSopInstanceUidList].AppendString(sop.SopInstanceUid); } } if (scu.Status == ScuOperationStatus.Canceled) { status = DicomStatuses.Cancel; } else if (scu.Status == ScuOperationStatus.ConnectFailed) { status = DicomStatuses.QueryRetrieveMoveDestinationUnknown; } else if (scu.FailureSubOperations > 0) { status = DicomStatuses.QueryRetrieveSubOpsOneOrMoreFailures; } else if (!bOnline) { status = DicomStatuses.QueryRetrieveUnableToPerformSuboperations; } else { status = DicomStatuses.Success; } } else { status = DicomStatuses.Pending; if (scu.RemainingSubOperations % 5 != 0) { return; } // Only send a RSP every 5 to reduce network load } server.SendCMoveResponse(presentationID, message.MessageId, msg, status, (ushort)scu.SuccessSubOperations, (ushort)scu.RemainingSubOperations, (ushort)scu.FailureSubOperations, (ushort)scu.WarningSubOperations, status == DicomStatuses.QueryRetrieveSubOpsOneOrMoreFailures ? errorComment : string.Empty); if (scu.RemainingSubOperations == 0) { finalResponseSent = true; } }; _theScu.BeginSend( delegate(IAsyncResult ar) { if (_theScu != null) { if (!finalResponseSent) { var msg = new DicomMessage(); server.SendCMoveResponse(presentationID, message.MessageId, msg, DicomStatuses.QueryRetrieveSubOpsOneOrMoreFailures, (ushort)_theScu.SuccessSubOperations, 0, (ushort) (_theScu.FailureSubOperations + _theScu.RemainingSubOperations), (ushort)_theScu.WarningSubOperations, errorComment); finalResponseSent = true; } _theScu.EndSend(ar); _theScu.Dispose(); _theScu = null; } }, _theScu); return(true); } catch (Exception e) { Platform.Log(LogLevel.Error, e, "Unexpected exception when processing C-MOVE-RQ"); if (finalResponseSent == false) { try { server.SendCMoveResponse(presentationID, message.MessageId, new DicomMessage(), DicomStatuses.QueryRetrieveUnableToProcess, e.Message); finalResponseSent = true; } catch (Exception x) { Platform.Log(LogLevel.Error, x, "Unable to send final C-MOVE-RSP message on association from {0} to {1}", association.CallingAE, association.CalledAE); server.Abort(); } } } return(false); }