/// <summary> /// Process a <see cref="WorkQueue"/> item of type AutoRoute. /// </summary> protected override void ProcessItem(Model.WorkQueue item) { if (WorkQueueItem.ScheduledTime >= WorkQueueItem.ExpirationTime && !HasPendingItems) { Platform.Log(LogLevel.Debug, "Removing Idle {0} entry : {1}", item.WorkQueueTypeEnum, item.GetKey().Key); base.PostProcessing(item, WorkQueueProcessorStatus.Complete, WorkQueueProcessorDatabaseUpdate.None); return; } if (!HasPendingItems) { // nothing to process, change to idle state PostProcessing(item, WorkQueueProcessorStatus.Idle, WorkQueueProcessorDatabaseUpdate.None); return; } Platform.Log(LogLevel.Info, "Moving study {0} for Patient {1} (PatientId:{2} A#:{3}) on Partition {4} to {5}...", Study.StudyInstanceUid, Study.PatientsName, Study.PatientId, Study.AccessionNumber, ServerPartition.Description, DestinationDevice.AeTitle); // Load remote device information from the database. Device device = DestinationDevice; if (device == null) { item.FailureDescription = String.Format("Unknown auto-route destination \"{0}\"", item.DeviceKey); Platform.Log(LogLevel.Error, item.FailureDescription); PostProcessingFailure(item, WorkQueueProcessorFailureType.Fatal); // Fatal Error return; } if (device.Dhcp && device.IpAddress.Length == 0) { item.FailureDescription = String.Format("Auto-route destination is a DHCP device with no known IP address: \"{0}\"", device.AeTitle); Platform.Log(LogLevel.Error, item.FailureDescription); PostProcessingFailure(item, WorkQueueProcessorFailureType.Fatal); // Fatal error return; } // Now setup the StorageSCU component int sendCounter = 0; using (ImageServerStorageScu scu = new ImageServerStorageScu(ServerPartition, device)) { using (ServerExecutionContext context = new ServerExecutionContext()) // set the preferred syntax lists scu.LoadPreferredSyntaxes(context.ReadContext); // Load the Instances to Send into the SCU component scu.AddStorageInstanceList(InstanceList); // Set an event to be called when each image is transferred scu.ImageStoreCompleted += delegate(Object sender, StorageInstance instance) { if (instance.SendStatus.Status == DicomState.Success || instance.SendStatus.Status == DicomState.Warning || instance.SendStatus.Equals(DicomStatuses.SOPClassNotSupported)) { sendCounter++; OnInstanceSent(instance); } if (instance.SendStatus.Status == DicomState.Failure) { scu.FailureDescription = instance.SendStatus.Description; if (false == String.IsNullOrEmpty(instance.ExtendedFailureDescription)) { scu.FailureDescription = String.Format("{0} [{1}]", scu.FailureDescription, instance.ExtendedFailureDescription); } } if (CancelPending && !(this is WebMoveStudyItemProcessor) && !scu.Canceled) { Platform.Log(LogLevel.Info, "Auto-route canceled due to shutdown for study: {0}", StorageLocation.StudyInstanceUid); item.FailureDescription = "Operation was canceled due to server shutdown request."; scu.Cancel(); } }; try { // Block until send is complete scu.Send(); // Join for the thread to exit scu.Join(); } catch (Exception ex) { Platform.Log(LogLevel.Error, ex, "Error occurs while sending images to {0} : {1}", device.AeTitle, ex.Message); } finally { if (scu.FailureDescription.Length > 0) { item.FailureDescription = scu.FailureDescription; scu.Status = ScuOperationStatus.Failed; } // Reset the WorkQueue entry status if ((InstanceList.Count > 0 && sendCounter != InstanceList.Count) // not all sop were sent || scu.Status == ScuOperationStatus.Failed || scu.Status == ScuOperationStatus.ConnectFailed) { PostProcessingFailure(item, WorkQueueProcessorFailureType.NonFatal); // failures occurred} } else { OnComplete(); } } } }
/// <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{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 += (sender, e) => { var scu = (StorageScu) sender; var msg = new DicomMessage(); var instance = e.StorageInstance; 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; }