/// <summary> /// Do the work. /// </summary> protected override void OnExecute(ServerCommandProcessor theProcessor) { using (ZipFile zip = new ZipFile(_zipFile)) { zip.ForceNoCompression = !NasSettings.Default.CompressZipFiles; zip.TempFileFolder = _tempFolder; zip.Comment = String.Format("Archive for study {0}", _studyXml.StudyInstanceUid); zip.UseZip64WhenSaving = Zip64Option.AsNecessary; // Add the studyXml file zip.AddFile(Path.Combine(_studyFolder,String.Format("{0}.xml",_studyXml.StudyInstanceUid)), String.Empty); // Add the studyXml.gz file zip.AddFile(Path.Combine(_studyFolder, String.Format("{0}.xml.gz", _studyXml.StudyInstanceUid)), String.Empty); string uidMapXmlPath = Path.Combine(_studyFolder, "UidMap.xml"); if (File.Exists(uidMapXmlPath)) zip.AddFile(uidMapXmlPath, String.Empty); // Add each sop from the StudyXmlFile foreach (SeriesXml seriesXml in _studyXml) foreach (InstanceXml instanceXml in seriesXml) { string filename = Path.Combine(_studyFolder, seriesXml.SeriesInstanceUid); filename = Path.Combine(filename, String.Format("{0}.dcm", instanceXml.SopInstanceUid)); zip.AddFile(filename, seriesXml.SeriesInstanceUid); } zip.Save(); } }
/// <summary> /// Do the unzip. /// </summary> protected override void OnExecute(ServerCommandProcessor theProcessor) { using (ZipFile zip = new ZipFile(_zipFile)) { zip.ExtractAll(_destinationFolder,_overwrite); } }
/// <summary> /// Apply the rules. /// </summary> /// <remarks> /// When rules are applied, we are simply adding new <see cref="ServerDatabaseCommand"/> instances /// for the rules to the currently executing <see cref="ServerCommandProcessor"/>. They will be /// executed after all other rules have been executed. /// </remarks> protected override void OnExecute(ServerCommandProcessor theProcessor) { string studyXmlFile = Path.Combine(_directory, String.Format("{0}.xml", _studyInstanceUid)); StudyXml theXml = new StudyXml(_studyInstanceUid); if (File.Exists(studyXmlFile)) { using (Stream fileStream = FileStreamOpener.OpenForRead(studyXmlFile, FileMode.Open)) { XmlDocument theDoc = new XmlDocument(); StudyXmlIo.Read(theDoc, fileStream); theXml.SetMemento(theDoc); fileStream.Close(); } } else { string errorMsg = String.Format("Unable to load study XML file of restored study: {0}", studyXmlFile); Platform.Log(LogLevel.Error, errorMsg); throw new ApplicationException(errorMsg); } DicomFile defaultFile = null; bool rulesExecuted = false; foreach (SeriesXml seriesXml in theXml) { foreach (InstanceXml instanceXml in seriesXml) { // Skip non-image objects if (instanceXml.SopClass.Equals(SopClass.KeyObjectSelectionDocumentStorage) || instanceXml.SopClass.Equals(SopClass.GrayscaleSoftcopyPresentationStateStorageSopClass) || instanceXml.SopClass.Equals(SopClass.BlendingSoftcopyPresentationStateStorageSopClass) || instanceXml.SopClass.Equals(SopClass.ColorSoftcopyPresentationStateStorageSopClass)) { // Save the first one encountered, just in case the whole study is non-image objects. if (defaultFile == null) defaultFile = new DicomFile("test", new DicomAttributeCollection(), instanceXml.Collection); continue; } DicomFile file = new DicomFile("test", new DicomAttributeCollection(), instanceXml.Collection); _context.Message = file; _engine.Execute(_context); rulesExecuted = true; break; } if (rulesExecuted) break; } if (!rulesExecuted && defaultFile != null) { _context.Message = defaultFile; _engine.Execute(_context); } }
protected override void OnExecute(ServerCommandProcessor theProcessor, IUpdateContext updateContext) { // Update StudyStatusEnum in the StudyStorageTable IStudyStorageEntityBroker studyStorageUpdate = updateContext.GetBroker<IStudyStorageEntityBroker>(); StudyStorageUpdateColumns studyStorageUpdateColumns = new StudyStorageUpdateColumns(); studyStorageUpdateColumns.StudyStatusEnum = _newStatus; studyStorageUpdate.Update(_location.Key, studyStorageUpdateColumns); // Update ServerTransferSyntaxGUID in FilesystemStudyStorage IFilesystemStudyStorageEntityBroker filesystemUpdate = updateContext.GetBroker<IFilesystemStudyStorageEntityBroker>(); FilesystemStudyStorageUpdateColumns filesystemUpdateColumns = new FilesystemStudyStorageUpdateColumns(); filesystemUpdateColumns.ServerTransferSyntaxKey = _newSyntax.Key; filesystemUpdate.Update(_location.FilesystemStudyStorageKey, filesystemUpdateColumns); }
protected override void OnExecute(ServerCommandProcessor theProcessor, IUpdateContext updateContext) { Study study = _location.Study ?? Study.Find(updateContext, _location.Key); if (study.StudySizeInKB != _studySizeInKB) { IStudyEntityBroker broker = updateContext.GetBroker<IStudyEntityBroker>(); StudyUpdateColumns parameters = new StudyUpdateColumns() { StudySizeInKB = _studySizeInKB }; if (!broker.Update(study.Key, parameters)) throw new ApplicationException("Unable to update study size in the database"); } }
protected override void OnExecute(ServerCommandProcessor theProcessor) { _storageFile = Path.Combine(_destinationFolder, _sourceFile); _backupFile = Path.Combine(ExecutionContext.TempDirectory, _sourceFile); string baseDirectory = _backupFile.Substring(0, _backupFile.LastIndexOfAny(new char[] { Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar })); if (!Directory.Exists(baseDirectory)) Directory.CreateDirectory(baseDirectory); if (File.Exists(_storageFile)) { File.Move(_storageFile, _backupFile); _fileBackedup = true; } using (ZipFile zip = new ZipFile(_zipFile)) { zip.Extract(_sourceFile, _destinationFolder, true); } }
private void RemoveExistingImage(WorkQueueUid uid) { string path = StorageLocation.GetSopInstancePath(uid.SeriesInstanceUid, uid.SopInstanceUid); if (!File.Exists(path)) { return; } StudyXml studyXml = StorageLocation.LoadStudyXml(); DicomFile file = new DicomFile(path); file.Load(DicomReadOptions.DoNotStorePixelDataInDataSet | DicomReadOptions.Default); // don't need to load pixel data cause we will delete it #if DEBUG int originalInstanceCountInXml = studyXml.NumberOfStudyRelatedInstances; int originalStudyInstanceCount = Study.NumberOfStudyRelatedInstances; int originalSeriesInstanceCount = Study.Series[uid.SeriesInstanceUid].NumberOfSeriesRelatedInstances; #endif using (ServerCommandProcessor processor = new ServerCommandProcessor("Delete Existing Image")) { processor.AddCommand(new FileDeleteCommand(path, true)); processor.AddCommand(new RemoveInstanceFromStudyXmlCommand(StorageLocation, studyXml, file)); processor.AddCommand(new UpdateInstanceCountCommand(StorageLocation, file)); if (!processor.Execute()) { throw new ApplicationException(String.Format("Unable to remove existing image {0}", file.Filename), processor.FailureException); } } #if DEBUG Debug.Assert(!File.Exists(path)); Debug.Assert(studyXml.NumberOfStudyRelatedInstances == originalInstanceCountInXml - 1); Debug.Assert(Study.Load(Study.Key).NumberOfStudyRelatedInstances == originalStudyInstanceCount - 1); Debug.Assert(Study.Load(Study.Key).Series[uid.SeriesInstanceUid].NumberOfSeriesRelatedInstances == originalSeriesInstanceCount - 1); #endif }
private bool UpdateNameBasedOnTheStudy(DicomFile file) { bool updated = false; string orginalPatientsNameInFile = file.DataSet[DicomTags.PatientsName].ToString(); if (_theStudy == null) { return(false); } StudyComparer comparer = new StudyComparer(); ServerPartition partition = ServerPartitionMonitor.Instance.FindPartition(_theStudy.ServerPartitionKey); DifferenceCollection list = comparer.Compare(file, _theStudy, partition.GetComparisonOptions()); if (list.Count == 1) { ComparisionDifference different = list[0]; if (different.DicomTag.TagValue == DicomTags.PatientsName) { if (DicomNameUtils.LookLikeSameNames(orginalPatientsNameInFile, _theStudy.PatientsName)) { using (ServerCommandProcessor processor = new ServerCommandProcessor("Update Patient's Name")) { SetTagCommand command = new SetTagCommand(file, DicomTags.PatientsName, orginalPatientsNameInFile, _theStudy.PatientsName); processor.AddCommand(command); if (!processor.Execute()) { throw new ApplicationException(String.Format("AUTO-CORRECTION Failed: Unable to correct the patient's name in the image. Reason: {0}", processor.FailureReason), processor.FailureException); } updated = true; } } } } return(updated); }
/// <summary> /// Inserts a <see cref="StudyIntegrityQueue"/> entry for manual reconciliation. /// </summary> /// <param name="file">The DICOM file that needs to be reconciled.</param> /// <param name="reason">The type of <see cref="StudyIntegrityQueue"/> entry to be inserted.</param> /// <param name="uid">A UID to delete on insert.</param> /// <remarks> /// A copy of the DICOM file will be stored in a special folder allocated for /// reconciliation purpose. The caller is responsible for managing the original copy. /// </remarks> public void ScheduleReconcile(DicomFile file, StudyIntegrityReasonEnum reason, WorkQueueUid uid) { Platform.CheckForNullReference(_context.StudyLocation, "_context.StudyLocation"); Platform.Log(LogLevel.Info, "Scheduling new manual reconciliation for SOP {0}", file.MediaStorageSopInstanceUid); ServerFilesystemInfo fs = FilesystemMonitor.Instance.GetFilesystemInfo(_context.StudyLocation.FilesystemKey); Platform.CheckForNullReference(fs, "fs"); ReconcileStorage reconcileStorage = new ReconcileStorage(_context.StudyLocation, _context.Group); using (ServerCommandProcessor processor = new ServerCommandProcessor("Schedule Manual Reconciliation")) { string path = reconcileStorage.GetSopInstancePath(file.DataSet[DicomTags.SopInstanceUid].ToString()); DirectoryInfo dir = new DirectoryInfo(path); if (dir.Parent != null) { CreateDirectoryCommand mkdir = new CreateDirectoryCommand(dir.Parent.FullName); processor.AddCommand(mkdir); } SaveDicomFileCommand saveFileCommand = new SaveDicomFileCommand(path, file, true); processor.AddCommand(saveFileCommand); InsertSIQCommand updateStudyCommand = new InsertSIQCommand(_context.StudyLocation, reason, file, _context.Group, reconcileStorage); processor.AddCommand(updateStudyCommand); if (uid != null) { processor.AddCommand(new DeleteWorkQueueUidCommand(uid)); } if (processor.Execute() == false) { throw new ApplicationException(String.Format("Unable to schedule image reconcilation : {0}", processor.FailureReason), processor.FailureException); } } }
protected override void OnExecute(ServerCommandProcessor theProcessor) { _storageFile = Path.Combine(_destinationFolder, _sourceFile); _backupFile = Path.Combine(ExecutionContext.TempDirectory, _sourceFile); string baseDirectory = _backupFile.Substring(0, _backupFile.LastIndexOfAny(new char[] { Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar })); if (!Directory.Exists(baseDirectory)) { Directory.CreateDirectory(baseDirectory); } if (File.Exists(_storageFile)) { File.Move(_storageFile, _backupFile); _fileBackedup = true; } using (ZipFile zip = new ZipFile(_zipFile)) { zip.Extract(_sourceFile, _destinationFolder, true); } }
/// <summary> /// Do the work. /// </summary> protected override void OnExecute(ServerCommandProcessor theProcessor) { using (ZipFile zip = new ZipFile(_zipFile)) { zip.ForceNoCompression = !NasSettings.Default.CompressZipFiles; zip.TempFileFolder = _tempFolder; zip.Comment = String.Format("Archive for study {0}", _studyXml.StudyInstanceUid); zip.UseZip64WhenSaving = Zip64Option.AsNecessary; // Add the studyXml file zip.AddFile(Path.Combine(_studyFolder, String.Format("{0}.xml", _studyXml.StudyInstanceUid)), String.Empty); // Add the studyXml.gz file zip.AddFile(Path.Combine(_studyFolder, String.Format("{0}.xml.gz", _studyXml.StudyInstanceUid)), String.Empty); string uidMapXmlPath = Path.Combine(_studyFolder, "UidMap.xml"); if (File.Exists(uidMapXmlPath)) { zip.AddFile(uidMapXmlPath, String.Empty); } // Add each sop from the StudyXmlFile foreach (SeriesXml seriesXml in _studyXml) { foreach (InstanceXml instanceXml in seriesXml) { string filename = Path.Combine(_studyFolder, seriesXml.SeriesInstanceUid); filename = Path.Combine(filename, String.Format("{0}.dcm", instanceXml.SopInstanceUid)); zip.AddFile(filename, seriesXml.SeriesInstanceUid); } } zip.Save(); } }
public void RestoreNearlineStudy(RestoreQueue queueItem, string zipFile, string studyFolder) { ServerFilesystemInfo fs = _hsmArchive.Selector.SelectFilesystem(); if (fs == null) { DateTime scheduleTime = Platform.Time.AddMinutes(5); Platform.Log(LogLevel.Error, "No writeable filesystem for restore, rescheduling restore request to {0}", scheduleTime); queueItem.FailureDescription = "No writeable filesystem for restore, rescheduling restore request"; _hsmArchive.UpdateRestoreQueue(queueItem, RestoreQueueStatusEnum.Pending, scheduleTime); return; } string destinationFolder = Path.Combine(fs.Filesystem.FilesystemPath, _hsmArchive.ServerPartition.PartitionFolder); StudyStorageLocation restoredLocation = null; try { using (var processor = new ServerCommandProcessor("HSM Restore Offline Study")) { processor.AddCommand(new CreateDirectoryCommand(destinationFolder)); destinationFolder = Path.Combine(destinationFolder, studyFolder); processor.AddCommand(new CreateDirectoryCommand(destinationFolder)); destinationFolder = Path.Combine(destinationFolder, _studyStorage.StudyInstanceUid); processor.AddCommand(new CreateDirectoryCommand(destinationFolder)); processor.AddCommand(new ExtractZipCommand(zipFile, destinationFolder)); // We rebuild the StudyXml, in case any settings or issues have happened since archival processor.AddCommand(new RebuildStudyXmlCommand(_studyStorage.StudyInstanceUid, destinationFolder)); // Apply the rules engine. var context = new ServerActionContext(null, fs.Filesystem.GetKey(), _hsmArchive.ServerPartition, queueItem.StudyStorageKey) { CommandProcessor = processor }; processor.AddCommand( new ApplyRulesCommand(destinationFolder, _studyStorage.StudyInstanceUid, context)); // Do the actual insert into the DB var insertStorageCommand = new InsertFilesystemStudyStorageCommand( _hsmArchive.PartitionArchive.ServerPartitionKey, _studyStorage.StudyInstanceUid, studyFolder, fs.Filesystem.GetKey(), _syntax); processor.AddCommand(insertStorageCommand); if (!processor.Execute()) { Platform.Log(LogLevel.Error, "Unexpected error processing restore request for {0} on archive {1}", _studyStorage.StudyInstanceUid, _hsmArchive.PartitionArchive.Description); queueItem.FailureDescription = processor.FailureReason; _hsmArchive.UpdateRestoreQueue(queueItem, RestoreQueueStatusEnum.Failed, Platform.Time); } else { restoredLocation = insertStorageCommand.Location; // Unlock the Queue Entry using ( IUpdateContext update = PersistentStoreRegistry.GetDefaultStore().OpenUpdateContext(UpdateContextSyncMode.Flush)) { bool retVal = _hsmArchive.UpdateRestoreQueue(update, queueItem, RestoreQueueStatusEnum.Completed, Platform.Time.AddSeconds(60)); var studyLock = update.GetBroker <ILockStudy>(); var parms = new LockStudyParameters { StudyStorageKey = queueItem.StudyStorageKey, QueueStudyStateEnum = QueueStudyStateEnum.Idle }; retVal = retVal && studyLock.Execute(parms); if (!parms.Successful || !retVal) { string message = String.Format("Study {0} on partition {1} failed to unlock.", _studyStorage.StudyInstanceUid, _hsmArchive.ServerPartition.Description); Platform.Log(LogLevel.Info, message); throw new ApplicationException(message); } update.Commit(); Platform.Log(LogLevel.Info, "Successfully restored study: {0} on archive {1}", _studyStorage.StudyInstanceUid, _hsmArchive.PartitionArchive.Description); OnStudyRestored(restoredLocation); } } } } catch (StudyIntegrityValidationFailure ex) { Debug.Assert(restoredLocation != null); // study has been restored but it seems corrupted. Need to reprocess it. ReprocessStudy(restoredLocation, ex.Message); } catch (Exception e) { Platform.Log(LogLevel.Error, e, "Unexpected exception processing restore request for {0} on archive {1}", _studyStorage.StudyInstanceUid, _hsmArchive.PartitionArchive.Description); _hsmArchive.UpdateRestoreQueue(queueItem, RestoreQueueStatusEnum.Failed, Platform.Time); } }
private void RestoreOnlineStudy(RestoreQueue queueItem, string zipFile, string destinationFolder) { try { using (var processor = new ServerCommandProcessor("HSM Restore Online Study")) { var zipService = Platform.GetService <IZipService>(); using (var zipWriter = zipService.OpenWrite(zipFile)) { foreach (string file in zipWriter.EntryFileNames) { processor.AddCommand(new ExtractZipFileAndReplaceCommand(zipFile, file, destinationFolder)); } } // We rebuild the StudyXml, in case any settings or issues have happened since archival processor.AddCommand(new RebuildStudyXmlCommand(_location.StudyInstanceUid, destinationFolder)); StudyStatusEnum status; if (_syntax.Encapsulated && _syntax.LosslessCompressed) { status = StudyStatusEnum.OnlineLossless; } else if (_syntax.Encapsulated && _syntax.LossyCompressed) { status = StudyStatusEnum.OnlineLossy; } else { status = StudyStatusEnum.Online; } processor.AddCommand(new UpdateStudyStateCommand(_location, status, _serverSyntax)); // Apply the rules engine. var context = new ServerActionContext(null, _location.FilesystemKey, _hsmArchive.ServerPartition, queueItem.StudyStorageKey) { CommandProcessor = processor }; processor.AddCommand( new ApplyRulesCommand(destinationFolder, _location.StudyInstanceUid, context)); if (!processor.Execute()) { Platform.Log(LogLevel.Error, "Unexpected error processing restore request for {0} on archive {1}", _location.StudyInstanceUid, _hsmArchive.PartitionArchive.Description); queueItem.FailureDescription = processor.FailureReason; _hsmArchive.UpdateRestoreQueue(queueItem, RestoreQueueStatusEnum.Failed, Platform.Time); } else { // Unlock the Queue Entry and set to complete using (IUpdateContext update = PersistentStoreRegistry.GetDefaultStore().OpenUpdateContext(UpdateContextSyncMode.Flush)) { _hsmArchive.UpdateRestoreQueue(update, queueItem, RestoreQueueStatusEnum.Completed, Platform.Time.AddSeconds(60)); var studyLock = update.GetBroker <ILockStudy>(); var parms = new LockStudyParameters { StudyStorageKey = queueItem.StudyStorageKey, QueueStudyStateEnum = QueueStudyStateEnum.Idle }; bool retVal = studyLock.Execute(parms); if (!parms.Successful || !retVal) { Platform.Log(LogLevel.Info, "Study {0} on partition {1} failed to unlock.", _location.StudyInstanceUid, _hsmArchive.ServerPartition.Description); } update.Commit(); Platform.Log(LogLevel.Info, "Successfully restored study: {0} on archive {1}", _location.StudyInstanceUid, _hsmArchive.PartitionArchive.Description); _location = ReloadStorageLocation(); OnStudyRestored(_location); } } } } catch (StudyIntegrityValidationFailure ex) { // study has been restored but it seems corrupted. Need to reprocess it. ReprocessStudy(_location, ex.Message); } catch (Exception e) { Platform.Log(LogLevel.Error, e, "Unexpected exception processing restore request for {0} on archive {1}", _location.StudyInstanceUid, _hsmArchive.PartitionArchive.Description); queueItem.FailureDescription = e.Message; _hsmArchive.UpdateRestoreQueue(queueItem, RestoreQueueStatusEnum.Failed, Platform.Time); } }
private DicomProcessingResult HandleDuplicateFile(string sopInstanceUid, StudyStorageLocation studyLocation, ServerCommandProcessor commandProcessor, DicomMessageBase message, string sourceFilename, StudyProcessWorkQueueData data) { Study study = studyLocation.Study ?? studyLocation.LoadStudy(ServerExecutionContext.Current.PersistenceContext); if (study != null) { Platform.Log(LogLevel.Info, "Received duplicate SOP {0} (A#:{1} StudyUid:{2} Patient: {3} ID:{4})", sopInstanceUid, study.AccessionNumber, study.StudyInstanceUid, study.PatientsName, study.PatientId); } else { Platform.Log(LogLevel.Info, "Received duplicate SOP {0} (StudyUid:{1}). Existing files haven't been processed.", sopInstanceUid, studyLocation.StudyInstanceUid); } var sopProcessingContext = new SopInstanceProcessorContext(commandProcessor, studyLocation, _context.ContextID, _context.Request) { DuplicateProcessing = _context.DuplicateProcessing }; DicomProcessingResult result = DuplicateSopProcessorHelper.Process(sopProcessingContext, message, data, sourceFilename); return(result); }
/// <summary> /// Apply the rules. /// </summary> /// <remarks> /// When rules are applied, we are simply adding new <see cref="ServerDatabaseCommand"/> instances /// for the rules to the currently executing <see cref="ServerCommandProcessor"/>. They will be /// executed after all other rules have been executed. /// </remarks> protected override void OnExecute(ServerCommandProcessor theProcessor) { string studyXmlFile = Path.Combine(_directory, String.Format("{0}.xml", _studyInstanceUid)); StudyXml theXml = new StudyXml(_studyInstanceUid); if (File.Exists(studyXmlFile)) { using (Stream fileStream = FileStreamOpener.OpenForRead(studyXmlFile, FileMode.Open)) { XmlDocument theDoc = new XmlDocument(); StudyXmlIo.Read(theDoc, fileStream); theXml.SetMemento(theDoc); fileStream.Close(); } } else { string errorMsg = String.Format("Unable to load study XML file of restored study: {0}", studyXmlFile); Platform.Log(LogLevel.Error, errorMsg); throw new ApplicationException(errorMsg); } DicomFile defaultFile = null; bool rulesExecuted = false; foreach (SeriesXml seriesXml in theXml) { foreach (InstanceXml instanceXml in seriesXml) { // Skip non-image objects if (instanceXml.SopClass.Equals(SopClass.KeyObjectSelectionDocumentStorage) || instanceXml.SopClass.Equals(SopClass.GrayscaleSoftcopyPresentationStateStorageSopClass) || instanceXml.SopClass.Equals(SopClass.BlendingSoftcopyPresentationStateStorageSopClass) || instanceXml.SopClass.Equals(SopClass.ColorSoftcopyPresentationStateStorageSopClass)) { // Save the first one encountered, just in case the whole study is non-image objects. if (defaultFile == null) { defaultFile = new DicomFile("test", new DicomAttributeCollection(), instanceXml.Collection); } continue; } DicomFile file = new DicomFile("test", new DicomAttributeCollection(), instanceXml.Collection); _context.Message = file; _engine.Execute(_context); rulesExecuted = true; break; } if (rulesExecuted) { break; } } if (!rulesExecuted && defaultFile != null) { _context.Message = defaultFile; _engine.Execute(_context); } }
/// <summary> /// Archive the specified <see cref="ArchiveQueue"/> item. /// </summary> /// <param name="queueItem">The ArchiveQueue item to archive.</param> public void Run(ArchiveQueue queueItem) { using (ArchiveProcessorContext executionContext = new ArchiveProcessorContext(queueItem)) { try { if (!_nasArchive.CheckStudyDiskSpace()) { queueItem.FailureDescription = "Not enough free disk space."; _nasArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Pending, Platform.Time.AddMinutes(60)); return; } if (!GetStudyStorageLocation(queueItem)) { Platform.Log(LogLevel.Error, "Unable to find readable study storage location for archival queue request {0}. Delaying request.", queueItem.Key); queueItem.FailureDescription = "Unable to find readable study storage location for archival queue request."; _nasArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Pending, Platform.Time.AddMinutes(2)); return; } // First, check to see if we can lock the study, if not just reschedule the queue entry. if (!_storageLocation.QueueStudyStateEnum.Equals(QueueStudyStateEnum.Idle)) { Platform.Log(LogLevel.Info, "Study {0} on partition {1} is currently locked, delaying archival.", _storageLocation.StudyInstanceUid, _nasArchive.ServerPartition.Description); queueItem.FailureDescription = "Study is currently locked, delaying archival."; _nasArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Pending, Platform.Time.AddMinutes(2)); return; } StudyIntegrityValidator validator = new StudyIntegrityValidator(); validator.ValidateStudyState("Archive", _storageLocation, StudyIntegrityValidationModes.Default); using (IUpdateContext update = PersistentStoreRegistry.GetDefaultStore().OpenUpdateContext(UpdateContextSyncMode.Flush)) { ILockStudy studyLock = update.GetBroker<ILockStudy>(); LockStudyParameters parms = new LockStudyParameters { StudyStorageKey = queueItem.StudyStorageKey, QueueStudyStateEnum = QueueStudyStateEnum.ArchiveScheduled }; bool retVal = studyLock.Execute(parms); if (!parms.Successful || !retVal) { Platform.Log(LogLevel.Info, "Study {0} on partition {1} failed to lock, delaying archival.", _storageLocation.StudyInstanceUid, _nasArchive.ServerPartition.Description); queueItem.FailureDescription = "Study failed to lock, delaying archival."; _nasArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Pending, Platform.Time.AddMinutes(2)); return; } update.Commit(); } string studyFolder = _storageLocation.GetStudyPath(); string studyXmlFile = _storageLocation.GetStudyXmlPath(); // Load the study Xml file, this is used to generate the list of dicom files to archive. LoadStudyXml(studyXmlFile); DicomFile file = LoadFileFromStudyXml(); string patientsName = file.DataSet[DicomTags.PatientsName].GetString(0, string.Empty); string patientId = file.DataSet[DicomTags.PatientId].GetString(0, string.Empty); string accessionNumber = file.DataSet[DicomTags.AccessionNumber].GetString(0, string.Empty); Platform.Log(LogLevel.Info, "Starting archival of study {0} for Patient {1} (PatientId:{2} A#:{3}) on Partition {4} on archive {5}", _storageLocation.StudyInstanceUid, patientsName, patientId, accessionNumber, _nasArchive.ServerPartition.Description, _nasArchive.PartitionArchive.Description); // Use the command processor to do the archival. using (ServerCommandProcessor commandProcessor = new ServerCommandProcessor("Archive")) { _archiveXml = new XmlDocument(); // Create the study date folder string zipFilename = Path.Combine(_nasArchive.NasPath, _storageLocation.StudyFolder); commandProcessor.AddCommand(new CreateDirectoryCommand(zipFilename)); // Create a folder for the study zipFilename = Path.Combine(zipFilename, _storageLocation.StudyInstanceUid); commandProcessor.AddCommand(new CreateDirectoryCommand(zipFilename)); // Save the archive data in the study folder, based on a filename with a date / time stamp string filename = String.Format("{0}.zip", Platform.Time.ToString("yyyy-MM-dd-HHmm")); zipFilename = Path.Combine(zipFilename, filename); // Create the Xml data to store in the ArchiveStudyStorage table telling // where the archived study is located. XmlElement nasArchiveElement = _archiveXml.CreateElement("NasArchive"); _archiveXml.AppendChild(nasArchiveElement); XmlElement studyFolderElement = _archiveXml.CreateElement("StudyFolder"); nasArchiveElement.AppendChild(studyFolderElement); studyFolderElement.InnerText = _storageLocation.StudyFolder; XmlElement filenameElement = _archiveXml.CreateElement("Filename"); nasArchiveElement.AppendChild(filenameElement); filenameElement.InnerText = filename; XmlElement studyInstanceUidElement = _archiveXml.CreateElement("Uid"); nasArchiveElement.AppendChild(studyInstanceUidElement); studyInstanceUidElement.InnerText = _storageLocation.StudyInstanceUid; // Create the Zip file commandProcessor.AddCommand( new CreateStudyZipCommand(zipFilename, _studyXml, studyFolder, executionContext.TempDirectory)); // Update the database. commandProcessor.AddCommand(new InsertArchiveStudyStorageCommand(queueItem.StudyStorageKey, queueItem.PartitionArchiveKey, queueItem.GetKey(), _storageLocation.ServerTransferSyntaxKey, _archiveXml)); StudyRulesEngine studyEngine = new StudyRulesEngine(_storageLocation, _nasArchive.ServerPartition, _studyXml); studyEngine.Apply(ServerRuleApplyTimeEnum.StudyArchived, commandProcessor); if (!commandProcessor.Execute()) { Platform.Log(LogLevel.Error, "Unexpected failure archiving study: {0}", commandProcessor.FailureReason); queueItem.FailureDescription = commandProcessor.FailureReason; _nasArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Failed, Platform.Time); } else Platform.Log(LogLevel.Info, "Successfully archived study {0} on {1}", _storageLocation.StudyInstanceUid, _nasArchive.PartitionArchive.Description); // Log the current FilesystemQueue settings _storageLocation.LogFilesystemQueue(); } } catch (StudyIntegrityValidationFailure ex) { StringBuilder error = new StringBuilder(); error.AppendLine(String.Format("Partition : {0}", ex.ValidationStudyInfo.ServerAE)); error.AppendLine(String.Format("Patient : {0}", ex.ValidationStudyInfo.PatientsName)); error.AppendLine(String.Format("Study Uid : {0}", ex.ValidationStudyInfo.StudyInstaneUid)); error.AppendLine(String.Format("Accession# : {0}", ex.ValidationStudyInfo.AccessionNumber)); error.AppendLine(String.Format("Study Date : {0}", ex.ValidationStudyInfo.StudyDate)); queueItem.FailureDescription = error.ToString(); _nasArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Failed, Platform.Time); } catch (Exception e) { String msg = String.Format("Unexpected exception archiving study: {0} on {1}: {2}", _storageLocation.StudyInstanceUid, _nasArchive.PartitionArchive.Description, e.Message); Platform.Log(LogLevel.Error, e, msg); queueItem.FailureDescription = msg; _nasArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Failed, Platform.Time); } finally { // Unlock the Queue Entry using (IUpdateContext update = PersistentStoreRegistry.GetDefaultStore().OpenUpdateContext(UpdateContextSyncMode.Flush)) { ILockStudy studyLock = update.GetBroker<ILockStudy>(); LockStudyParameters parms = new LockStudyParameters { StudyStorageKey = queueItem.StudyStorageKey, QueueStudyStateEnum = QueueStudyStateEnum.Idle }; bool retVal = studyLock.Execute(parms); if (!parms.Successful || !retVal) { Platform.Log(LogLevel.Info, "Study {0} on partition {1} is failed to unlock.", _storageLocation.StudyInstanceUid, _nasArchive.ServerPartition.Description); } update.Commit(); } } } }
/// <summary> /// Creates an instance of <see cref="SopProcessingContext"/> /// </summary> /// <param name="commandProcessor">The <see cref="ServerCommandProcessor"/> used in the context</param> /// <param name="studyLocation">The <see cref="StudyStorageLocation"/> of the study being processed</param> /// <param name="uidGroup">A String value respresenting the group of SOP instances which are being processed.</param> public SopProcessingContext(ServerCommandProcessor commandProcessor, StudyStorageLocation studyLocation, string uidGroup) { _commandProcessor = commandProcessor; _studyLocation = studyLocation; _group = uidGroup; }
private void Purge(StudyItem study) { Task.Factory.StartNew(() => { var archive = SelectPartitionArchive(study); if (archive == null) { MessageBox.Show("Please add an archive in the partition where this study is located"); return; } if (study.IsArchivingScheduled()) { if ( MessageBox.Show( "This study is scheduled in the Archive Queue. Do you want to remove it from the queue before purging?", "Study is scheduled for archiving", MessageBoxButtons.YesNo) == System.Windows.Forms.DialogResult.Yes) { using (var processor = new ServerCommandProcessor("archive")) { processor.AddCommand(new DeleteAllArchiveQueueItemCommand(study.StudyStorage, archive)); processor.Execute(); } } } if (study.StudyStorageLocation.ArchiveLocations == null || !study.StudyStorageLocation.ArchiveLocations.Any()) { var i = 0; using (var processor = new ServerCommandProcessor("archive")) { var archiveCommand = new ArchiveStudyCommand(study.StudyStorageLocation, GetArchivePath(archive), @"C:\temp", archive); archiveCommand.ProgressUpdated += (s, e) => { study.OperationProgress = new OperationProgress() { Status = e.Percentage == 100 ? "Archived" : e.Status, Percentage = (int)e.Percentage }; }; processor.AddCommand(archiveCommand); if (!processor.Execute()) { MessageBox.Show(string.Format("Unable to archive study: {0}", processor.FailureException.Message)); } } } using (var processor = new ServerCommandProcessor("archive")) { processor.AddCommand(new PurgeStudyCommand(study.StudyStorage)); if (!processor.Execute()) { MessageBox.Show(string.Format("Unable to purge study: {0}", processor.FailureException.Message)); } else { MessageBox.Show("Study has been succesfully purged"); study.Status = "Nearline"; } } }); }
private void RestoreOnlineStudy(RestoreQueue queueItem, string zipFile, string destinationFolder) { try { using (ServerCommandProcessor processor = new ServerCommandProcessor("NAS Restore Online Study")) { using (ZipFile zip = new ZipFile(zipFile)) { foreach (string file in zip.EntryFileNames) { processor.AddCommand(new ExtractZipFileAndReplaceCommand(zipFile, file, destinationFolder)); } } // We rebuild the StudyXml, in case any settings or issues have happened since archival processor.AddCommand(new RebuildStudyXmlCommand(_location.StudyInstanceUid, destinationFolder)); StudyStatusEnum status; if (_syntax.Encapsulated && _syntax.LosslessCompressed) status = StudyStatusEnum.OnlineLossless; else if (_syntax.Encapsulated && _syntax.LossyCompressed) status = StudyStatusEnum.OnlineLossy; else status = StudyStatusEnum.Online; processor.AddCommand(new UpdateStudyStateCommand(_location, status, _serverSyntax)); // Apply the rules engine. ServerActionContext context = new ServerActionContext(null, _location.FilesystemKey, _nasArchive.ServerPartition, queueItem.StudyStorageKey) {CommandProcessor = processor}; processor.AddCommand( new ApplyRulesCommand(destinationFolder, _location.StudyInstanceUid, context)); if (!processor.Execute()) { Platform.Log(LogLevel.Error, "Unexpected error processing restore request for {0} on archive {1}", _location.StudyInstanceUid, _nasArchive.PartitionArchive.Description); queueItem.FailureDescription = processor.FailureReason; _nasArchive.UpdateRestoreQueue(queueItem, RestoreQueueStatusEnum.Failed, Platform.Time); } else { // Unlock the Queue Entry and set to complete using (IUpdateContext update = PersistentStoreRegistry.GetDefaultStore().OpenUpdateContext(UpdateContextSyncMode.Flush)) { _nasArchive.UpdateRestoreQueue(update, queueItem, RestoreQueueStatusEnum.Completed, Platform.Time.AddSeconds(60)); ILockStudy studyLock = update.GetBroker<ILockStudy>(); LockStudyParameters parms = new LockStudyParameters { StudyStorageKey = queueItem.StudyStorageKey, QueueStudyStateEnum = QueueStudyStateEnum.Idle }; bool retVal = studyLock.Execute(parms); if (!parms.Successful || !retVal) { Platform.Log(LogLevel.Info, "Study {0} on partition {1} failed to unlock.", _location.StudyInstanceUid, _nasArchive.ServerPartition.Description); } update.Commit(); Platform.Log(LogLevel.Info, "Successfully restored study: {0} on archive {1}", _location.StudyInstanceUid, _nasArchive.PartitionArchive.Description); OnStudyRestored(_location); } } } } catch (Exception e) { Platform.Log(LogLevel.Error, e, "Unexpected exception processing restore request for {0} on archive {1}", _location.StudyInstanceUid, _nasArchive.PartitionArchive.Description); queueItem.FailureDescription = e.Message; _nasArchive.UpdateRestoreQueue(queueItem, RestoreQueueStatusEnum.Failed, Platform.Time); } }
protected void ProcessFile(Model.WorkQueue item, WorkQueueUid sop, string path, StudyXml studyXml, IDicomCodecFactory theCodecFactory) { DicomFile file = null; _instanceStats = new CompressInstanceStatistics(); _instanceStats.ProcessTime.Start(); // Use the command processor for rollback capabilities. using (ServerCommandProcessor processor = new ServerCommandProcessor("Processing WorkQueue Compress DICOM File")) { string modality = String.Empty; try { file = new DicomFile(path); _instanceStats.FileLoadTime.Start(); file.Load(DicomReadOptions.StorePixelDataReferences | DicomReadOptions.Default); _instanceStats.FileLoadTime.End(); modality = file.DataSet[DicomTags.Modality].GetString(0, String.Empty); FileInfo fileInfo = new FileInfo(path); _instanceStats.FileSize = (ulong)fileInfo.Length; // Get the Patients Name for processing purposes. String patientsName = file.DataSet[DicomTags.PatientsName].GetString(0, ""); if (file.TransferSyntax.Equals(theCodecFactory.CodecTransferSyntax)) { // Delete the WorkQueueUid item processor.AddCommand(new DeleteWorkQueueUidCommand(sop)); // Do the actual processing if (!processor.Execute()) { Platform.Log(LogLevel.Warn, "Failure deleteing WorkQueueUid: {0} for SOP: {1}", processor.Description, file.MediaStorageSopInstanceUid); Platform.Log(LogLevel.Warn, "Compression file that failed: {0}", file.Filename); } else { Platform.Log(LogLevel.Warn, "Skip compressing SOP {0}. Its current transfer syntax is {1}", file.MediaStorageSopInstanceUid, file.TransferSyntax.Name); } } else { IDicomCodec codec = theCodecFactory.GetDicomCodec(); // Create a context for applying actions from the rules engine var context = new ServerActionContext(file, StorageLocation.FilesystemKey, ServerPartition, item.StudyStorageKey); context.CommandProcessor = processor; var parms = theCodecFactory.GetCodecParameters(item.Data); var compressCommand = new DicomCompressCommand(context.Message, theCodecFactory.CodecTransferSyntax, codec, parms); processor.AddCommand(compressCommand); var save = new SaveDicomFileCommand(file.Filename, file, false); processor.AddCommand(save); // Update the StudyStream object, must be done after compression // and after the compressed image has been successfully saved var insertStudyXmlCommand = new UpdateStudyXmlCommand(file, studyXml, StorageLocation); processor.AddCommand(insertStudyXmlCommand); // Delete the WorkQueueUid item processor.AddCommand(new DeleteWorkQueueUidCommand(sop)); // Do the actual processing if (!processor.Execute()) { EventManager.FireEvent(this, new FailedUpdateSopEventArgs { File = file, ServerPartitionEntry = context.ServerPartition, WorkQueueUidEntry = sop, WorkQueueEntry = WorkQueueItem, FileLength = (ulong)insertStudyXmlCommand.FileSize, FailureMessage = processor.FailureReason }); _instanceStats.CompressTime.Add(compressCommand.CompressTime); Platform.Log(LogLevel.Error, "Failure compressing command {0} for SOP: {1}", processor.Description, file.MediaStorageSopInstanceUid); Platform.Log(LogLevel.Error, "Compression file that failed: {0}", file.Filename); throw new ApplicationException("Unexpected failure (" + processor.FailureReason + ") executing command for SOP: " + file.MediaStorageSopInstanceUid, processor.FailureException); } _instanceStats.CompressTime.Add(compressCommand.CompressTime); Platform.Log(ServerPlatform.InstanceLogLevel, "Compress SOP: {0} for Patient {1}", file.MediaStorageSopInstanceUid, patientsName); EventManager.FireEvent(this, new UpdateSopEventArgs { File = file, ServerPartitionEntry = context.ServerPartition, WorkQueueUidEntry = sop, WorkQueueEntry = WorkQueueItem, FileLength = (ulong)insertStudyXmlCommand.FileSize }); } } catch (Exception e) { EventManager.FireEvent(this, new FailedUpdateSopEventArgs { File = file, ServerPartitionEntry = ServerPartition, WorkQueueUidEntry = sop, WorkQueueEntry = WorkQueueItem, FileLength = (ulong)new FileInfo(path).Length, FailureMessage = processor.FailureReason }); Platform.Log(LogLevel.Error, e, "Unexpected exception when {0}. Rolling back operation.", processor.Description); processor.Rollback(); throw; } finally { _instanceStats.ProcessTime.End(); _studyStats.AddSubStats(_instanceStats); _studyStats.StudyInstanceUid = StorageLocation.StudyInstanceUid; if (String.IsNullOrEmpty(modality) == false) { _studyStats.Modality = modality; } // Update the statistics _studyStats.NumInstances++; } } }
/// <summary> /// Process a specific DICOM file related to a <see cref="WorkQueue"/> request. /// </summary> /// <remarks> /// <para> /// On success and if <see cref="uid"/> is set, the <see cref="WorkQueueUid"/> field is deleted. /// </para> /// </remarks> /// <param name="stream">The <see cref="StudyXml"/> file to update with information from the file.</param> /// <param name="group">A group the sop is associated with.</param> /// <param name="file">The file to process.</param> /// <param name="compare">Flag to compare the demographics of <see cref="file"/> with the demographics in the database</param> /// <param name="retry">Flag telling if the item should be retried on failure. Note that if the item is a duplicate, the WorkQueueUid item is not failed. </param> /// <param name="uid">An optional WorkQueueUid associated with the entry, that will be deleted upon success or failed on failure.</param> /// <param name="deleteFile">An option file to delete as part of the process</param> /// <exception cref="Exception"/> /// <exception cref="DicomDataException"/> public ProcessingResult ProcessFile(string group, DicomFile file, StudyXml stream, bool compare, bool retry, WorkQueueUid uid, string deleteFile) { Platform.CheckForNullReference(file, "file"); try { CheckDataLength(file); _instanceStats.ProcessTime.Start(); ProcessingResult result = new ProcessingResult { Status = ProcessingStatus.Success }; using (ServerCommandProcessor processor = new ServerCommandProcessor("Process File")) { SopProcessingContext processingContext = new SopProcessingContext(processor, _context.StorageLocation, group); if (EnforceNameRules) { _patientNameRules.Apply(file); } if (compare && ShouldReconcile(_context.StorageLocation, file)) { ScheduleReconcile(processingContext, file, uid); result.Status = ProcessingStatus.Reconciled; } else { InsertInstance(file, stream, uid, deleteFile); result.Status = ProcessingStatus.Success; } } _instanceStats.ProcessTime.End(); if (_context.SopProcessedRulesEngine.Statistics.LoadTime.IsSet) { _instanceStats.SopRulesLoadTime.Add(_context.SopProcessedRulesEngine.Statistics.LoadTime); } if (_context.SopProcessedRulesEngine.Statistics.ExecutionTime.IsSet) { _instanceStats.SopEngineExecutionTime.Add(_context.SopProcessedRulesEngine.Statistics.ExecutionTime); } _context.SopProcessedRulesEngine.Statistics.Reset(); //TODO: Should throw exception if result is failed? return(result); } catch (Exception e) { // If its a duplicate, ignore the exception, and just throw it if (deleteFile != null && (e is InstanceAlreadyExistsException || e.InnerException != null && e.InnerException is InstanceAlreadyExistsException)) { throw; } if (uid != null) { FailUid(uid, retry); } throw; } }
/// <summary> /// Perform the edit. /// </summary> /// <param name="actionXml">A serialized XML representation of <see cref="SetTagCommand"/> objects</param> /// <returns></returns> public bool Edit(XmlElement actionXml) { Platform.Log(LogLevel.Info, "Starting Edit of study {0} for Patient {1} (PatientId:{2} A#:{3}) on Partition {4}", Study.StudyInstanceUid, Study.PatientsName, Study.PatientId, Study.AccessionNumber, ServerPartition.Description); LoadExtensions(); EditStudyWorkQueueDataParser parser = new EditStudyWorkQueueDataParser(); EditStudyWorkQueueData data = parser.Parse(actionXml); using (ServerCommandProcessor processor = new ServerCommandProcessor("Web Edit Study")) { // Convert UpdateItem in the request into BaseImageLevelUpdateCommand List <BaseImageLevelUpdateCommand> updateCommands = null; if (data != null) { updateCommands = CollectionUtils.Map <Edit.UpdateItem, BaseImageLevelUpdateCommand>( data.EditRequest.UpdateEntries, delegate(Edit.UpdateItem item) { // Note: For edit, we assume each UpdateItem is equivalent to SetTagCommand return(new SetTagCommand(item.DicomTag.TagValue, item.OriginalValue, item.Value)); } ); } UpdateStudyCommand updateStudyCommand = new UpdateStudyCommand(ServerPartition, StorageLocation, updateCommands, ServerRuleApplyTimeEnum.SopEdited); processor.AddCommand(updateStudyCommand); // Note, this command will only insert the ArchiveQueue command if a delete doesn't exist processor.AddCommand(new InsertArchiveQueueCommand(ServerPartition.Key, StorageLocation.Key)); var context = new WebEditStudyContext { CommandProcessor = processor, EditType = data.EditRequest.EditType, OriginalStudyStorageLocation = StorageLocation, EditCommands = updateCommands, OriginalStudy = Study, OrginalPatient = Patient, UserId = data.EditRequest.UserId, Reason = data.EditRequest.Reason }; OnStudyUpdating(context); if (!processor.Execute()) { Platform.Log(LogLevel.Error, processor.FailureException, "Unexpected failure editing study: {0}", processor.FailureReason); FailureReason = processor.FailureReason; return(false); } // reload the StudyStorageLocation NewStorageLocation = StudyStorageLocation.FindStorageLocations(StorageLocation.StudyStorage)[0]; context.NewStudystorageLocation = NewStorageLocation; OnStudyUpdated(context); if (updateStudyCommand.Statistics != null) { StatisticsLogger.Log(LogLevel.Info, updateStudyCommand.Statistics); } return(true); } }
/// <summary> /// Do the actual rebuild. On error, will attempt to reprocess the study. /// </summary> public void RebuildXml() { string rootStudyPath = _location.GetStudyPath(); try { using (ServerCommandProcessor processor = new ServerCommandProcessor("Rebuild XML")) { var command = new RebuildStudyXmlCommand(_location.StudyInstanceUid, rootStudyPath); processor.AddCommand(command); var updateCommand = new UpdateStudySizeInDBCommand(_location, command); processor.AddCommand(updateCommand); if (!processor.Execute()) { throw new ApplicationException(processor.FailureReason, processor.FailureException); } Study theStudy = _location.Study; if (theStudy.NumberOfStudyRelatedInstances != command.StudyXml.NumberOfStudyRelatedInstances) { // We rebuilt, but the counts don't match. throw new StudyIntegrityValidationFailure(ValidationErrors.InconsistentObjectCount, new ValidationStudyInfo(theStudy, _location.ServerPartition), string.Format( "Database study count {0} does not match study xml {1}", theStudy.NumberOfStudyRelatedInstances, command.StudyXml.NumberOfStudyRelatedInstances)); } Platform.Log(LogLevel.Info, "Completed reprocessing Study XML file for study {0}", _location.StudyInstanceUid); } } catch (Exception e) { Platform.Log(LogLevel.Error, e, "Unexpected error when rebuilding study XML for directory: {0}", _location.FilesystemPath); StudyReprocessor reprocessor = new StudyReprocessor(); try { WorkQueue reprocessEntry = reprocessor.ReprocessStudy("Rebuild StudyXml", _location, Platform.Time); if (reprocessEntry != null) { Platform.Log(LogLevel.Error, "Failure attempting to reprocess study: {0}", _location.StudyInstanceUid); } else { Platform.Log(LogLevel.Error, "Inserted reprocess request for study: {0}", _location.StudyInstanceUid); } } catch (InvalidStudyStateOperationException ex) { Platform.Log(LogLevel.Error, "Failure attempting to reprocess study {0}: {1}", _location.StudyInstanceUid, ex.Message); } } }
/// <summary> /// Archive the specified <see cref="ArchiveQueue"/> item. /// </summary> /// <param name="queueItem">The ArchiveQueue item to archive.</param> public void Run(ArchiveQueue queueItem) { using (ArchiveProcessorContext executionContext = new ArchiveProcessorContext(queueItem)) { try { if (!GetStudyStorageLocation(queueItem)) { Platform.Log(LogLevel.Error, "Unable to find readable study storage location for archival queue request {0}. Delaying request.", queueItem.Key); queueItem.FailureDescription = "Unable to find readable study storage location for archival queue request."; _hsmArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Pending, Platform.Time.AddMinutes(2)); return; } // First, check to see if we can lock the study, if not just reschedule the queue entry. if (!_storageLocation.QueueStudyStateEnum.Equals(QueueStudyStateEnum.Idle)) { Platform.Log(LogLevel.Info, "Study {0} on partition {1} is currently locked, delaying archival.", _storageLocation.StudyInstanceUid, _hsmArchive.ServerPartition.Description); queueItem.FailureDescription = "Study is currently locked, delaying archival."; _hsmArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Pending, Platform.Time.AddMinutes(2)); return; } StudyIntegrityValidator validator = new StudyIntegrityValidator(); validator.ValidateStudyState("Archive", _storageLocation, StudyIntegrityValidationModes.Default); using (IUpdateContext update = PersistentStoreRegistry.GetDefaultStore().OpenUpdateContext(UpdateContextSyncMode.Flush)) { ILockStudy studyLock = update.GetBroker <ILockStudy>(); LockStudyParameters parms = new LockStudyParameters { StudyStorageKey = queueItem.StudyStorageKey, QueueStudyStateEnum = QueueStudyStateEnum.ArchiveScheduled }; bool retVal = studyLock.Execute(parms); if (!parms.Successful || !retVal) { Platform.Log(LogLevel.Info, "Study {0} on partition {1} failed to lock, delaying archival.", _storageLocation.StudyInstanceUid, _hsmArchive.ServerPartition.Description); queueItem.FailureDescription = "Study failed to lock, delaying archival."; _hsmArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Pending, Platform.Time.AddMinutes(2)); return; } update.Commit(); } string studyFolder = _storageLocation.GetStudyPath(); string studyXmlFile = _storageLocation.GetStudyXmlPath(); // Load the study Xml file, this is used to generate the list of dicom files to archive. LoadStudyXml(studyXmlFile); DicomFile file = LoadFileFromStudyXml(); string patientsName = file.DataSet[DicomTags.PatientsName].GetString(0, string.Empty); string patientId = file.DataSet[DicomTags.PatientId].GetString(0, string.Empty); string accessionNumber = file.DataSet[DicomTags.AccessionNumber].GetString(0, string.Empty); Platform.Log(LogLevel.Info, "Starting archival of study {0} for Patient {1} (PatientId:{2} A#:{3}) on Partition {4} on archive {5}", _storageLocation.StudyInstanceUid, patientsName, patientId, accessionNumber, _hsmArchive.ServerPartition.Description, _hsmArchive.PartitionArchive.Description); // Use the command processor to do the archival. using (ServerCommandProcessor commandProcessor = new ServerCommandProcessor("Archive")) { _archiveXml = new XmlDocument(); // Create the study date folder string zipFilename = Path.Combine(_hsmArchive.HsmPath, _storageLocation.StudyFolder); commandProcessor.AddCommand(new CreateDirectoryCommand(zipFilename)); // Create a folder for the study zipFilename = Path.Combine(zipFilename, _storageLocation.StudyInstanceUid); commandProcessor.AddCommand(new CreateDirectoryCommand(zipFilename)); // Save the archive data in the study folder, based on a filename with a date / time stamp string filename = String.Format("{0}.zip", Platform.Time.ToString("yyyy-MM-dd-HHmm")); zipFilename = Path.Combine(zipFilename, filename); // Create the Xml data to store in the ArchiveStudyStorage table telling // where the archived study is located. XmlElement hsmArchiveElement = _archiveXml.CreateElement("HsmArchive"); _archiveXml.AppendChild(hsmArchiveElement); XmlElement studyFolderElement = _archiveXml.CreateElement("StudyFolder"); hsmArchiveElement.AppendChild(studyFolderElement); studyFolderElement.InnerText = _storageLocation.StudyFolder; XmlElement filenameElement = _archiveXml.CreateElement("Filename"); hsmArchiveElement.AppendChild(filenameElement); filenameElement.InnerText = filename; XmlElement studyInstanceUidElement = _archiveXml.CreateElement("Uid"); hsmArchiveElement.AppendChild(studyInstanceUidElement); studyInstanceUidElement.InnerText = _storageLocation.StudyInstanceUid; // Create the Zip file commandProcessor.AddCommand( new CreateStudyZipCommand(zipFilename, _studyXml, studyFolder, executionContext.TempDirectory)); // Update the database. commandProcessor.AddCommand(new InsertArchiveStudyStorageCommand(queueItem.StudyStorageKey, queueItem.PartitionArchiveKey, queueItem.GetKey(), _storageLocation.ServerTransferSyntaxKey, _archiveXml)); StudyRulesEngine studyEngine = new StudyRulesEngine(_storageLocation, _hsmArchive.ServerPartition, _studyXml); studyEngine.Apply(ServerRuleApplyTimeEnum.StudyArchived, commandProcessor); if (!commandProcessor.Execute()) { Platform.Log(LogLevel.Error, "Unexpected failure archiving study ({0}) to archive {1}: {2}, zip filename: {3}", _storageLocation.StudyInstanceUid, _hsmArchive.PartitionArchive.Description, commandProcessor.FailureReason, zipFilename); queueItem.FailureDescription = commandProcessor.FailureReason; _hsmArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Failed, Platform.Time); } else { Platform.Log(LogLevel.Info, "Successfully archived study {0} on {1} to zip {2}", _storageLocation.StudyInstanceUid, _hsmArchive.PartitionArchive.Description, zipFilename); } // Log the current FilesystemQueue settings _storageLocation.LogFilesystemQueue(); } } catch (StudyIntegrityValidationFailure ex) { StringBuilder error = new StringBuilder(); error.AppendLine(String.Format("Partition : {0}", ex.ValidationStudyInfo.ServerAE)); error.AppendLine(String.Format("Patient : {0}", ex.ValidationStudyInfo.PatientsName)); error.AppendLine(String.Format("Study Uid : {0}", ex.ValidationStudyInfo.StudyInstaneUid)); error.AppendLine(String.Format("Accession# : {0}", ex.ValidationStudyInfo.AccessionNumber)); error.AppendLine(String.Format("Study Date : {0}", ex.ValidationStudyInfo.StudyDate)); queueItem.FailureDescription = error.ToString(); _hsmArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Failed, Platform.Time); } catch (Exception e) { String msg = String.Format("Unexpected exception archiving study: {0} on {1}: {2}", _storageLocation.StudyInstanceUid, _hsmArchive.PartitionArchive.Description, e.Message); Platform.Log(LogLevel.Error, e, msg); queueItem.FailureDescription = msg; _hsmArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Failed, Platform.Time); } finally { // Unlock the Queue Entry using (IUpdateContext update = PersistentStoreRegistry.GetDefaultStore().OpenUpdateContext(UpdateContextSyncMode.Flush)) { ILockStudy studyLock = update.GetBroker <ILockStudy>(); LockStudyParameters parms = new LockStudyParameters { StudyStorageKey = queueItem.StudyStorageKey, QueueStudyStateEnum = QueueStudyStateEnum.Idle }; bool retVal = studyLock.Execute(parms); if (!parms.Successful || !retVal) { Platform.Log(LogLevel.Info, "Study {0} on partition {1} is failed to unlock.", _storageLocation.StudyInstanceUid, _hsmArchive.ServerPartition.Description); } update.Commit(); } } } }
public void RestoreNearlineStudy(RestoreQueue queueItem, string zipFile, string destinationFolder, string studyFolder) { ServerFilesystemInfo fs = _nasArchive.Selector.SelectFilesystem(); if (fs == null) { DateTime scheduleTime = Platform.Time.AddMinutes(5); Platform.Log(LogLevel.Error, "No writeable filesystem for restore, rescheduling restore request to {0}", scheduleTime); queueItem.FailureDescription = "No writeable filesystem for restore, rescheduling restore request"; _nasArchive.UpdateRestoreQueue(queueItem, RestoreQueueStatusEnum.Pending, scheduleTime); return; } StudyStorageLocation restoredLocation = null; try { using (ServerCommandProcessor processor = new ServerCommandProcessor("NAS Restore Offline Study")) { processor.AddCommand(new CreateDirectoryCommand(destinationFolder)); destinationFolder = Path.Combine(destinationFolder, studyFolder); processor.AddCommand(new CreateDirectoryCommand(destinationFolder)); destinationFolder = Path.Combine(destinationFolder, _studyStorage.StudyInstanceUid); processor.AddCommand(new CreateDirectoryCommand(destinationFolder)); processor.AddCommand(new ExtractZipCommand(zipFile, destinationFolder)); // We rebuild the StudyXml, in case any settings or issues have happened since archival processor.AddCommand(new RebuildStudyXmlCommand(_studyStorage.StudyInstanceUid, destinationFolder)); // Apply the rules engine. ServerActionContext context = new ServerActionContext(null, fs.Filesystem.GetKey(), _nasArchive.ServerPartition, queueItem.StudyStorageKey) {CommandProcessor = processor}; processor.AddCommand( new ApplyRulesCommand(destinationFolder, _studyStorage.StudyInstanceUid, context)); // Do the actual insert into the DB InsertFilesystemStudyStorageCommand insertStorageCommand = new InsertFilesystemStudyStorageCommand( _nasArchive.PartitionArchive.ServerPartitionKey, _studyStorage.StudyInstanceUid, studyFolder, fs.Filesystem.GetKey(), _syntax); processor.AddCommand(insertStorageCommand); if (!processor.Execute()) { Platform.Log(LogLevel.Error, "Unexpected error processing restore request for {0} on archive {1}", _studyStorage.StudyInstanceUid, _nasArchive.PartitionArchive.Description); queueItem.FailureDescription = processor.FailureReason; _nasArchive.UpdateRestoreQueue(queueItem, RestoreQueueStatusEnum.Failed, Platform.Time); } else { restoredLocation = insertStorageCommand.Location; // Unlock the Queue Entry using ( IUpdateContext update = PersistentStoreRegistry.GetDefaultStore().OpenUpdateContext(UpdateContextSyncMode.Flush)) { bool retVal = _nasArchive.UpdateRestoreQueue(update, queueItem, RestoreQueueStatusEnum.Completed, Platform.Time.AddSeconds(60)); ILockStudy studyLock = update.GetBroker<ILockStudy>(); LockStudyParameters parms = new LockStudyParameters { StudyStorageKey = queueItem.StudyStorageKey, QueueStudyStateEnum = QueueStudyStateEnum.Idle }; retVal = retVal && studyLock.Execute(parms); if (!parms.Successful || !retVal) { string message = String.Format("Study {0} on partition {1} failed to unlock.", _studyStorage.StudyInstanceUid, _nasArchive.ServerPartition.Description); Platform.Log(LogLevel.Info, message); throw new ApplicationException(message); } update.Commit(); Platform.Log(LogLevel.Info, "Successfully restored study: {0} on archive {1}", _studyStorage.StudyInstanceUid, _nasArchive.PartitionArchive.Description); OnStudyRestored(restoredLocation); } } } } catch(StudyIntegrityValidationFailure ex) { Debug.Assert(restoredLocation != null); // study has been restored but it seems corrupted. Need to reprocess it. ReprocessStudy(restoredLocation, ex.Message); } catch (Exception e) { Platform.Log(LogLevel.Error, e, "Unexpected exception processing restore request for {0} on archive {1}", _studyStorage.StudyInstanceUid, _nasArchive.PartitionArchive.Description); _nasArchive.UpdateRestoreQueue(queueItem, RestoreQueueStatusEnum.Failed, Platform.Time); } }
private DicomProcessingResult HandleDuplicate(string sopInstanceUid, StudyStorageLocation studyLocation, ServerCommandProcessor commandProcessor, DicomFile file) { Study study = studyLocation.Study ?? studyLocation.LoadStudy(ServerExecutionContext.Current.PersistenceContext); if (study != null) { Platform.Log(LogLevel.Info, "Received duplicate SOP {0} (A#:{1} StudyUid:{2} Patient: {3} ID:{4})", sopInstanceUid, study.AccessionNumber, study.StudyInstanceUid, study.PatientsName, study.PatientId); } else { Platform.Log(LogLevel.Info, "Received duplicate SOP {0} (StudyUid:{1}). Existing files haven't been processed.", sopInstanceUid, studyLocation.StudyInstanceUid); } SopProcessingContext sopProcessingContext = new SopProcessingContext(commandProcessor, studyLocation, _context.ContextID); DicomProcessingResult result = DuplicateSopProcessorHelper.Process(sopProcessingContext, file); return(result); }
private static void OnStudyRestored(StudyStorageLocation location) { ValidateStudy(location); using(ServerCommandProcessor processor = new ServerCommandProcessor("Update Study Size In DB")) { processor.AddCommand(new UpdateStudySizeInDBCommand(location)); if (!processor.Execute()) { Platform.Log(LogLevel.Error, "Unexpected error when trying to update the study size in DB:", processor.FailureReason); } } }
/// <summary> /// Imports the specified <see cref="DicomMessageBase"/> object into the system. /// The object will be inserted into the <see cref="WorkQueue"/> for processing and /// if it's a duplicate, proper checks will be done and depending on the policy, it will be /// ignored, rejected or inserted into the <see cref="StudyIntegrityQueue"/> for manual intervention. /// </summary> /// <param name="message">The DICOM object to be imported.</param> /// <returns>An instance of <see cref="DicomProcessingResult"/> that describes the result of the processing.</returns> /// <exception cref="DicomDataException">Thrown when the DICOM object contains invalid data</exception> public DicomProcessingResult Import(DicomMessageBase message) { Platform.CheckForNullReference(message, "message"); String studyInstanceUid; String seriesInstanceUid; String sopInstanceUid; String accessionNumber; String patientsName; LoadMessageUids(message, out studyInstanceUid, out seriesInstanceUid, out sopInstanceUid, out accessionNumber, out patientsName); DicomFile file = null; // Scrub the name for invalid characters. string newName = XmlUtils.XmlCharacterScrub(patientsName); if (!newName.Equals(patientsName)) { message.DataSet[DicomTags.PatientsName].SetStringValue(newName); } var result = new DicomProcessingResult { Successful = true, StudyInstanceUid = studyInstanceUid, SeriesInstanceUid = seriesInstanceUid, SopInstanceUid = sopInstanceUid, AccessionNumber = accessionNumber }; try { Validate(message); } catch (DicomDataException e) { result.SetError(DicomStatuses.ProcessingFailure, e.Message); return(result); } // Use the command processor for rollback capabilities. using (var commandProcessor = new ServerCommandProcessor(String.Format("Processing Sop Instance {0}", sopInstanceUid))) { try { string failureMessage; StudyStorageLocation studyLocation = GetWritableOnlineStorage(message); // GetWritableOnlineStorage should throw an exception if the study location cannot be found. Platform.CheckForNullReference(studyLocation, "studyLocation"); if (!studyLocation.QueueStudyStateEnum.Equals(QueueStudyStateEnum.Idle) && (!studyLocation.QueueStudyStateEnum.Equals(QueueStudyStateEnum.ProcessingScheduled))) { failureMessage = String.Format("Study {0} on partition {1} is being processed: {2}, can't accept new images.", studyLocation.StudyInstanceUid, _context.Partition.Description, studyLocation.QueueStudyStateEnum.Description); result.SetError(DicomStatuses.StorageStorageOutOfResources, failureMessage); return(result); } if (studyLocation.StudyStatusEnum.Equals(StudyStatusEnum.OnlineLossy)) { if (studyLocation.IsLatestArchiveLossless) { result.DicomStatus = DicomStatuses.StorageStorageOutOfResources; failureMessage = String.Format("Study {0} on partition {1} can't accept new images due to lossy compression of the study. Restoring study.", studyLocation.StudyInstanceUid, _context.Partition.Description); Platform.Log(LogLevel.Error, failureMessage); if (ServerHelper.InsertRestoreRequest(studyLocation) == null) { Platform.Log(LogLevel.Warn, "Unable to insert Restore Request for Study"); } result.SetError(DicomStatuses.StorageStorageOutOfResources, failureMessage); result.RestoreRequested = true; return(result); } } String path = studyLocation.FilesystemPath; String finalDest = studyLocation.GetSopInstancePath(seriesInstanceUid, sopInstanceUid); file = ConvertToDicomFile(message, finalDest, _context.SourceAE); if (HasUnprocessedCopy(studyLocation.Key, seriesInstanceUid, sopInstanceUid)) { var accept = false; // This is a special case: #10569 // Allow user to revive an orphaned study by reprocessing the files found in the filesystem if (File.Exists(finalDest)) { accept = DuplicatePolicy.IsParitionDuplicatePolicyOverridden(studyLocation); } if (!accept) { failureMessage = string.Format("Another copy of the SOP Instance was received but has not been processed: {0}", sopInstanceUid); result.SetError(DicomStatuses.DuplicateSOPInstance, failureMessage); return(result); } } var data = new StudyProcessWorkQueueData { ReceivingAeTitle = _context.AlternateAe == null ? _context.Partition.AeTitle : _context.AlternateAe.AeTitle }; if (File.Exists(finalDest)) { result = HandleDuplicate(sopInstanceUid, studyLocation, commandProcessor, file, data); if (!result.Successful) { return(result); } } else { HandleNonDuplicate(seriesInstanceUid, sopInstanceUid, studyLocation, commandProcessor, file, path, false, data); } if (commandProcessor.Execute()) { result.DicomStatus = DicomStatuses.Success; } else { failureMessage = String.Format("Failure processing message: {0}. Sending failure status.", commandProcessor.FailureReason); result.SetError(DicomStatuses.ProcessingFailure, failureMessage); // processor already rolled back return(result); } } catch (NoWritableFilesystemException) { String failureMessage = String.Format("Unable to process image, no writable filesystem found for Study UID {0}.", sopInstanceUid); commandProcessor.Rollback(); result.SetError(DicomStatuses.StorageStorageOutOfResources, failureMessage); } catch (StudyIsNearlineException e) { String failureMessage = e.RestoreRequested ? String.Format("{0}. Restore has been requested.", e.Message) : e.Message; Platform.Log(LogLevel.Error, failureMessage); commandProcessor.Rollback(); result.SetError(DicomStatuses.ProcessingFailure, failureMessage); } catch (FilesystemNotWritableException) { commandProcessor.Rollback(); string folder; if (!FilesystemMonitor.Instance.GetWriteableIncomingFolder(_context.Partition, out folder)) { String failureMessage = String.Format("Unable to process image, study storage location is missing or not writeable: {0}.", sopInstanceUid); result.SetError(DicomStatuses.StorageStorageOutOfResources, failureMessage); return(result); } if (file == null) { file = ConvertToDicomFile(message, string.Empty, _context.SourceAE); } if (!SaveToFolder(folder, sopInstanceUid, studyInstanceUid, file)) { String failureMessage = String.Format("Study storage location not writeable and no writeable incoming folder: {0}.", sopInstanceUid); result.SetError(DicomStatuses.StorageStorageOutOfResources, failureMessage); return(result); } Platform.Log(LogLevel.Info, "Saved existing SOP without writeable storage location to {0} folder: {1}", FilesystemMonitor.ImportDirectorySuffix, sopInstanceUid); result.DicomStatus = DicomStatuses.Success; return(result); } catch (Exception e) { Platform.Log(LogLevel.Error, e, "Unexpected exception when {0}. Rolling back operation.", commandProcessor.Description); commandProcessor.Rollback(); result.SetError(result.DicomStatus ?? DicomStatuses.ProcessingFailure, e.Message); } } return(result); }
private void ProcessSeriesLevelDelete(Model.WorkQueue item) { // ensure the Study is loaded. Study study = StorageLocation.Study; Platform.CheckForNullReference(study, "Study record doesn't exist"); Platform.Log(LogLevel.Info, "Processing Series Level Deletion for Study {0}, A#: {1}", study.StudyInstanceUid, study.AccessionNumber); _seriesToDelete = new List <Series>(); bool completed = false; try { // Load the list of Series to be deleted from the WorkQueueUid LoadUids(item); // Go through the list of series and add commands // to delete each of them. It's all or nothing. using (ServerCommandProcessor processor = new ServerCommandProcessor(String.Format("Deleting Series from study {0}, A#:{1}, Patient: {2}, ID:{3}", study.StudyInstanceUid, study.AccessionNumber, study.PatientsName, study.PatientId))) { StudyXml studyXml = StorageLocation.LoadStudyXml(); IDictionary <string, Series> existingSeries = StorageLocation.Study.Series; // Add commands to delete the folders and update the xml foreach (WorkQueueUid uid in WorkQueueUidList) { // Delete from study XML if (studyXml.Contains(uid.SeriesInstanceUid)) { //Note: DeleteDirectoryCommand doesn't throw exception if the folder doesn't exist var xmlUpdate = new RemoveSeriesFromStudyXml(studyXml, uid.SeriesInstanceUid); processor.AddCommand(xmlUpdate); } // Delete from filesystem string path = StorageLocation.GetSeriesPath(uid.SeriesInstanceUid); if (Directory.Exists(path)) { var delDir = new DeleteDirectoryCommand(path, true); processor.AddCommand(delDir); } } // flush the updated xml to disk processor.AddCommand(new SaveXmlCommand(studyXml, StorageLocation)); // Update the db.. NOTE: these commands are executed at the end. foreach (WorkQueueUid uid in WorkQueueUidList) { // Delete from DB WorkQueueUid queueUid = uid; Series theSeries = existingSeries[queueUid.SeriesInstanceUid]; if (theSeries != null) { _seriesToDelete.Add(theSeries); var delSeries = new DeleteSeriesFromDBCommand(StorageLocation, theSeries); processor.AddCommand(delSeries); delSeries.Executing += DeleteSeriesFromDbExecuting; } else { // Series doesn't exist Platform.Log(LogLevel.Info, "Series {0} is invalid or no longer exists", uid.SeriesInstanceUid); } // The WorkQueueUid must be cleared before the entry can be removed from the queue var deleteUid = new DeleteWorkQueueUidCommand(uid); processor.AddCommand(deleteUid); // Force a re-archival if necessary processor.AddCommand(new InsertArchiveQueueCommand(item.ServerPartitionKey, item.StudyStorageKey)); } if (!processor.Execute()) { throw new ApplicationException( String.Format("Error occurred when series from Study {0}, A#: {1}", study.StudyInstanceUid, study.AccessionNumber), processor.FailureException); } else { foreach (Series series in _seriesToDelete) { OnSeriesDeleted(series); } } } completed = true; } finally { if (completed) { OnCompleted(); PostProcessing(item, WorkQueueProcessorStatus.Complete, WorkQueueProcessorDatabaseUpdate.ResetQueueState); } else { PostProcessing(item, WorkQueueProcessorStatus.Pending, WorkQueueProcessorDatabaseUpdate.None); } } }
private void HandleNonDuplicateFile(string seriesInstanceUid, string sopInstanceUid, StudyStorageLocation studyLocation, ServerCommandProcessor commandProcessor, DicomMessageBase message, string sourcePath, string path, bool dupImage, StudyProcessWorkQueueData data) { commandProcessor.AddCommand(new CreateDirectoryCommand(path)); path = Path.Combine(path, studyLocation.PartitionFolder); commandProcessor.AddCommand(new CreateDirectoryCommand(path)); path = Path.Combine(path, studyLocation.StudyFolder); commandProcessor.AddCommand(new CreateDirectoryCommand(path)); path = Path.Combine(path, studyLocation.StudyInstanceUid); commandProcessor.AddCommand(new CreateDirectoryCommand(path)); path = Path.Combine(path, seriesInstanceUid); commandProcessor.AddCommand(new CreateDirectoryCommand(path)); path = Path.Combine(path, sopInstanceUid); path += ServerPlatform.DicomFileExtension; commandProcessor.AddCommand(new RenameFileCommand(sourcePath, path, true)); WorkQueueUidData uidData = null; if (_context.Request != null && !string.IsNullOrEmpty(_context.Request.OperationToken)) { uidData = new WorkQueueUidData { OperationToken = _context.Request.OperationToken }; } commandProcessor.AddCommand( new UpdateWorkQueueCommand(message, studyLocation, dupImage, data, uidData, _context.Request)); #region SPECIAL CODE FOR TESTING if (Diagnostics.Settings.SimulateFileCorruption) { commandProcessor.AddCommand(new CorruptDicomFileCommand(path)); } #endregion }
/// <summary> /// Reprocess a specific study. /// </summary> /// <param name="partition">The ServerPartition the study is on.</param> /// <param name="location">The storage location of the study to process.</param> /// <param name="engine">The rules engine to use when processing the study.</param> /// <param name="postArchivalEngine">The rules engine used for studies that have been archived.</param> /// <param name="dataAccessEngine">The rules engine strictly used for setting data acess.</param> protected static void ProcessStudy(ServerPartition partition, StudyStorageLocation location, ServerRulesEngine engine, ServerRulesEngine postArchivalEngine, ServerRulesEngine dataAccessEngine) { if (!location.QueueStudyStateEnum.Equals(QueueStudyStateEnum.Idle) || !location.AcquireWriteLock()) { Platform.Log(LogLevel.Error, "Unable to lock study {0}. The study is being processed. (Queue State: {1})", location.StudyInstanceUid, location.QueueStudyStateEnum.Description); } else { try { DicomFile msg = LoadInstance(location); if (msg == null) { Platform.Log(LogLevel.Error, "Unable to load file for study {0}", location.StudyInstanceUid); return; } bool archiveQueueExists; bool archiveStudyStorageExists; bool filesystemDeleteExists; using (IReadContext read = PersistentStoreRegistry.GetDefaultStore().OpenReadContext()) { // Check for existing archive queue entries var archiveQueueBroker = read.GetBroker <IArchiveQueueEntityBroker>(); var archiveQueueCriteria = new ArchiveQueueSelectCriteria(); archiveQueueCriteria.StudyStorageKey.EqualTo(location.Key); archiveQueueExists = archiveQueueBroker.Count(archiveQueueCriteria) > 0; var archiveStorageBroker = read.GetBroker <IArchiveStudyStorageEntityBroker>(); var archiveStudyStorageCriteria = new ArchiveStudyStorageSelectCriteria(); archiveStudyStorageCriteria.StudyStorageKey.EqualTo(location.Key); archiveStudyStorageExists = archiveStorageBroker.Count(archiveStudyStorageCriteria) > 0; var filesystemQueueBroker = read.GetBroker <IFilesystemQueueEntityBroker>(); var filesystemQueueCriteria = new FilesystemQueueSelectCriteria(); filesystemQueueCriteria.StudyStorageKey.EqualTo(location.Key); filesystemQueueCriteria.FilesystemQueueTypeEnum.EqualTo(FilesystemQueueTypeEnum.DeleteStudy); filesystemDeleteExists = filesystemQueueBroker.Count(filesystemQueueCriteria) > 0; } using (var commandProcessor = new ServerCommandProcessor("Study Rule Processor") { PrimaryServerPartitionKey = partition.GetKey(), PrimaryStudyKey = location.Study.GetKey() }) { var context = new ServerActionContext(msg, location.FilesystemKey, partition, location.Key, commandProcessor); // Check if the Study has been archived if (archiveStudyStorageExists && !archiveQueueExists && !filesystemDeleteExists) { // Add a command to delete the current filesystemQueue entries, so that they can // be reinserted by the rules engine. context.CommandProcessor.AddCommand(new DeleteFilesystemQueueCommand(location.Key, ServerRuleApplyTimeEnum.StudyArchived)); // How to deal with exiting FilesystemQueue entries is problematic here. If the study // has been migrated off tier 1, we probably don't want to modify the tier migration // entries. Compression entries may have been entered when the Study was initially // processed, we don't want to delete them, because they might still be valid. // We just re-run the rules engine at this point, and delete only the StudyPurge entries, // since those we know at least would only be applied for archived studies. var studyRulesEngine = new StudyRulesEngine(postArchivalEngine, location, location.ServerPartition, location.LoadStudyXml()); studyRulesEngine.Apply(ServerRuleApplyTimeEnum.StudyArchived, commandProcessor); // Post Archive doesn't allow data access rules. Force Data Access rules to be reapplied // to these studies also. dataAccessEngine.Execute(context); } else { // Add a command to delete the current filesystemQueue entries, so that they can // be reinserted by the rules engine. context.CommandProcessor.AddCommand(new DeleteFilesystemQueueCommand(location.Key, ServerRuleApplyTimeEnum.StudyProcessed)); // Execute the rules engine, insert commands to update the database into the command processor. // Due to ticket #11673, we create a new rules engine instance for each study, since the Study QC rules // don't work right now with a single rules engine. //TODO CR (Jan 2014) - Check if we can go back to caching the rules engine to reduce database hits on the rules var studyRulesEngine = new StudyRulesEngine(location, location.ServerPartition, location.LoadStudyXml()); studyRulesEngine.Apply(ServerRuleApplyTimeEnum.StudyProcessed, commandProcessor); } // Do the actual database updates. if (false == context.CommandProcessor.Execute()) { Platform.Log(LogLevel.Error, "Unexpected failure processing Study level rules for study {0}", location.StudyInstanceUid); } // Log the FilesystemQueue related entries location.LogFilesystemQueue(); } } finally { location.ReleaseWriteLock(); } } }
/// <summary> /// Migrates the study to new tier /// </summary> /// <param name="storage"></param> /// <param name="newFilesystem"></param> private void DoMigrateStudy(StudyStorageLocation storage, ServerFilesystemInfo newFilesystem) { Platform.CheckForNullReference(storage, "storage"); Platform.CheckForNullReference(newFilesystem, "newFilesystem"); TierMigrationStatistics stat = new TierMigrationStatistics { StudyInstanceUid = storage.StudyInstanceUid }; stat.ProcessSpeed.Start(); StudyXml studyXml = storage.LoadStudyXml(); stat.StudySize = (ulong)studyXml.GetStudySize(); Platform.Log(LogLevel.Info, "About to migrate study {0} from {1} to {2}", storage.StudyInstanceUid, storage.FilesystemTierEnum, newFilesystem.Filesystem.Description); string newPath = Path.Combine(newFilesystem.Filesystem.FilesystemPath, storage.PartitionFolder); DateTime startTime = Platform.Time; DateTime lastLog = Platform.Time; int fileCounter = 0; ulong bytesCopied = 0; long instanceCountInXml = studyXml.NumberOfStudyRelatedInstances; using (ServerCommandProcessor processor = new ServerCommandProcessor("Migrate Study")) { TierMigrationContext context = new TierMigrationContext { OriginalStudyLocation = storage, Destination = newFilesystem }; // The multiple CreateDirectoryCommands are done so that rollback of the directories being created happens properly if either of the directories already exist. var origFolder = context.OriginalStudyLocation.GetStudyPath(); processor.AddCommand(new CreateDirectoryCommand(newPath)); newPath = Path.Combine(newPath, context.OriginalStudyLocation.StudyFolder); processor.AddCommand(new CreateDirectoryCommand(newPath)); newPath = Path.Combine(newPath, context.OriginalStudyLocation.StudyInstanceUid); // don't create this directory so that it won't be backed up by MoveDirectoryCommand var copyDirCommand = new CopyDirectoryCommand(origFolder, newPath, delegate(string path) { // Update the progress. This is useful if the migration takes long time to complete. FileInfo file = new FileInfo(path); bytesCopied += (ulong)file.Length; fileCounter++; if (file.Extension != null && file.Extension.Equals(ServerPlatform.DicomFileExtension, StringComparison.InvariantCultureIgnoreCase)) { TimeSpan elapsed = Platform.Time - lastLog; TimeSpan totalElapsed = Platform.Time - startTime; double speedInMBPerSecond = 0; if (totalElapsed.TotalSeconds > 0) { speedInMBPerSecond = (bytesCopied / 1024f / 1024f) / totalElapsed.TotalSeconds; } if (elapsed > TimeSpan.FromSeconds(WorkQueueSettings.Instance.TierMigrationProgressUpdateInSeconds)) { #region Log Progress StringBuilder stats = new StringBuilder(); if (instanceCountInXml != 0) { float pct = (float)fileCounter / instanceCountInXml; stats.AppendFormat("{0} files moved [{1:0.0}MB] since {2} ({3:0}% completed). Speed={4:0.00}MB/s", fileCounter, bytesCopied / 1024f / 1024f, startTime, pct * 100, speedInMBPerSecond); } else { stats.AppendFormat("{0} files moved [{1:0.0}MB] since {2}. Speed={3:0.00}MB/s", fileCounter, bytesCopied / 1024f / 1024f, startTime, speedInMBPerSecond); } Platform.Log(LogLevel.Info, "Tier migration for study {0}: {1}", storage.StudyInstanceUid, stats.ToString()); try { using (IUpdateContext ctx = PersistentStoreRegistry.GetDefaultStore().OpenUpdateContext(UpdateContextSyncMode.Flush)) { IWorkQueueEntityBroker broker = ctx.GetBroker <IWorkQueueEntityBroker>(); WorkQueueUpdateColumns parameters = new WorkQueueUpdateColumns { FailureDescription = stats.ToString() }; broker.Update(WorkQueueItem.GetKey(), parameters); ctx.Commit(); } } catch { // can't log the progress so far... just ignore it } finally { lastLog = DateTime.Now; } #endregion } } }); processor.AddCommand(copyDirCommand); DeleteDirectoryCommand delDirCommand = new DeleteDirectoryCommand(origFolder, false) { RequiresRollback = false }; processor.AddCommand(delDirCommand); TierMigrateDatabaseUpdateCommand updateDbCommand = new TierMigrateDatabaseUpdateCommand(context); processor.AddCommand(updateDbCommand); Platform.Log(LogLevel.Info, "Start migrating study {0}.. expecting {1} to be moved", storage.StudyInstanceUid, ByteCountFormatter.Format(stat.StudySize)); if (!processor.Execute()) { if (processor.FailureException != null) { throw processor.FailureException; } throw new ApplicationException(processor.FailureReason); } stat.DBUpdate = updateDbCommand.Statistics; stat.CopyFiles = copyDirCommand.CopySpeed; stat.DeleteDirTime = delDirCommand.Statistics; } stat.ProcessSpeed.SetData(bytesCopied); stat.ProcessSpeed.End(); Platform.Log(LogLevel.Info, "Successfully migrated study {0} from {1} to {2} in {3} [ {4} files, {5} @ {6}, DB Update={7}, Remove Dir={8}]", storage.StudyInstanceUid, storage.FilesystemTierEnum, newFilesystem.Filesystem.FilesystemTierEnum, TimeSpanFormatter.Format(stat.ProcessSpeed.ElapsedTime), fileCounter, ByteCountFormatter.Format(bytesCopied), stat.CopyFiles.FormattedValue, stat.DBUpdate.FormattedValue, stat.DeleteDirTime.FormattedValue); string originalPath = storage.GetStudyPath(); if (Directory.Exists(storage.GetStudyPath())) { Platform.Log(LogLevel.Info, "Original study folder could not be deleted. It must be cleaned up manually: {0}", originalPath); ServerPlatform.Alert(AlertCategory.Application, AlertLevel.Warning, WorkQueueItem.WorkQueueTypeEnum.ToString(), 1000, GetWorkQueueContextData(WorkQueueItem), TimeSpan.Zero, "Study has been migrated to a new tier. Original study folder must be cleaned up manually: {0}", originalPath); } UpdateAverageStatistics(stat); }
/// <summary> /// Removes all WorkQueueUids from the database and delete the corresponding DICOM files from the filesystem. /// </summary> private void ProcessWorkQueueUids() { if (Study == null) { Platform.Log(LogLevel.Info, "Begin StudyProcess Cleanup (Study has not been created): Attempt #{0}. {1} unprocessed files will be removed", WorkQueueItem.FailureCount + 1, WorkQueueUidList.Count); } else { Platform.Log(LogLevel.Info, "Begin StudyProcess Cleanup for study {0}, Patient {1} (PatientId:{2} A#:{3}) on Partition {4}. Attempt #{5}. {6} unprocessed files will be removed", Study.StudyInstanceUid, Study.PatientsName, Study.PatientId, Study.AccessionNumber, ServerPartition.Description, WorkQueueItem.FailureCount + 1, WorkQueueUidList.Count ); } foreach (WorkQueueUid sop in WorkQueueUidList) { string path = GetFileStoredPath(sop); Platform.Log(LogLevel.Info, "Cleaning up {0}", path); using (ServerCommandProcessor processor = new ServerCommandProcessor(String.Format("Deleting {0}", sop.SopInstanceUid))) { // delete the file FileDeleteCommand deleteFile = new FileDeleteCommand(path, true); processor.AddCommand(deleteFile); // delete the WorkQueueUID from the database DeleteWorkQueueUidCommand deleteUid = new DeleteWorkQueueUidCommand(sop); processor.AddCommand(deleteUid); try { // delete the directory (if empty) var fileInfo = new FileInfo(path); ClearCanvas.ImageServer.Core.Command.DeleteDirectoryCommand deleteDir = new ClearCanvas.ImageServer.Core.Command.DeleteDirectoryCommand(fileInfo.Directory.FullName, false, true); processor.AddCommand(deleteDir); } catch (DirectoryNotFoundException ex) { // ignore } if (!processor.Execute()) { throw new Exception(String.Format("Unable to delete SOP {0}", sop.SopInstanceUid), processor.FailureException); } } // Delete the base directory if it's empty var baseDir = GetBaseDirectory(sop); if (Directory.Exists(baseDir)) { using (ServerCommandProcessor processor = new ServerCommandProcessor(String.Format("Deleting {0}", sop.SopInstanceUid))) { ClearCanvas.ImageServer.Core.Command.DeleteDirectoryCommand deleteDir = new ClearCanvas.ImageServer.Core.Command.DeleteDirectoryCommand(baseDir, false, true); processor.AddCommand(deleteDir); if (!processor.Execute()) { throw new Exception(String.Format("Unable to delete {0}", baseDir), processor.FailureException); } } } } }
/// <summary> /// Archive the specified <see cref="ArchiveQueue"/> item. /// </summary> /// <param name="queueItem">The ArchiveQueue item to archive.</param> public void Run(ArchiveQueue queueItem) { using (ArchiveProcessorContext executionContext = new ArchiveProcessorContext(queueItem)) { try { if (!GetStudyStorageLocation(queueItem)) { Platform.Log(LogLevel.Error, "Unable to find readable study storage location for archival queue request {0}. Delaying request.", queueItem.Key); queueItem.FailureDescription = "Unable to find readable study storage location for archival queue request."; _hsmArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Pending, Platform.Time.AddMinutes(2)); return; } // First, check to see if we can lock the study, if not just reschedule the queue entry. if (!_storageLocation.QueueStudyStateEnum.Equals(QueueStudyStateEnum.Idle)) { Platform.Log(LogLevel.Info, "Study {0} on partition {1} is currently locked, delaying archival.", _storageLocation.StudyInstanceUid, _hsmArchive.ServerPartition.Description); queueItem.FailureDescription = "Study is currently locked, delaying archival."; _hsmArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Pending, Platform.Time.AddMinutes(2)); return; } StudyIntegrityValidator validator = new StudyIntegrityValidator(); validator.ValidateStudyState("Archive", _storageLocation, StudyIntegrityValidationModes.Default); using (IUpdateContext update = PersistentStoreRegistry.GetDefaultStore().OpenUpdateContext(UpdateContextSyncMode.Flush)) { ILockStudy studyLock = update.GetBroker <ILockStudy>(); LockStudyParameters parms = new LockStudyParameters { StudyStorageKey = queueItem.StudyStorageKey, QueueStudyStateEnum = QueueStudyStateEnum.ArchiveScheduled }; bool retVal = studyLock.Execute(parms); if (!parms.Successful || !retVal) { Platform.Log(LogLevel.Info, "Study {0} on partition {1} failed to lock, delaying archival.", _storageLocation.StudyInstanceUid, _hsmArchive.ServerPartition.Description); queueItem.FailureDescription = "Study failed to lock, delaying archival."; _hsmArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Pending, Platform.Time.AddMinutes(2)); return; } update.Commit(); } string studyXmlFile = _storageLocation.GetStudyXmlPath(); // Load the study Xml file, this is used to generate the list of dicom files to archive. LoadStudyXml(studyXmlFile); DicomFile file = LoadFileFromStudyXml(); string patientsName = file.DataSet[DicomTags.PatientsName].GetString(0, string.Empty); string patientId = file.DataSet[DicomTags.PatientId].GetString(0, string.Empty); string accessionNumber = file.DataSet[DicomTags.AccessionNumber].GetString(0, string.Empty); Platform.Log(LogLevel.Info, "Starting archival of study {0} for Patient {1} (PatientId:{2} A#:{3}) on Partition {4} on archive {5}", _storageLocation.StudyInstanceUid, patientsName, patientId, accessionNumber, _hsmArchive.ServerPartition.Description, _hsmArchive.PartitionArchive.Description); // Use the command processor to do the archival. using (ServerCommandProcessor commandProcessor = new ServerCommandProcessor("Archive")) { var archiveStudyCmd = new ArchiveStudyCommand(_storageLocation, _hsmArchive.HsmPath, executionContext.TempDirectory, _hsmArchive.PartitionArchive) { ForceCompress = HsmSettings.Default.CompressZipFiles }; commandProcessor.AddCommand(archiveStudyCmd); commandProcessor.AddCommand(new UpdateArchiveQueueItemCommand(queueItem.GetKey(), _storageLocation.GetKey(), ArchiveQueueStatusEnum.Completed)); StudyRulesEngine studyEngine = new StudyRulesEngine(_storageLocation, _hsmArchive.ServerPartition, _studyXml); studyEngine.Apply(ServerRuleApplyTimeEnum.StudyArchived, commandProcessor); if (!commandProcessor.Execute()) { Platform.Log(LogLevel.Error, "Unexpected failure archiving study ({0}) to archive {1}: {2}, zip filename: {3}", _storageLocation.StudyInstanceUid, _hsmArchive.PartitionArchive.Description, commandProcessor.FailureReason, archiveStudyCmd.OutputZipFilePath); queueItem.FailureDescription = commandProcessor.FailureReason; _hsmArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Failed, Platform.Time); } else { Platform.Log(LogLevel.Info, "Successfully archived study {0} on {1} to zip {2}", _storageLocation.StudyInstanceUid, _hsmArchive.PartitionArchive.Description, archiveStudyCmd.OutputZipFilePath); } // Log the current FilesystemQueue settings _storageLocation.LogFilesystemQueue(); } } catch (StudyIntegrityValidationFailure ex) { StringBuilder error = new StringBuilder(); error.AppendLine(String.Format("Partition : {0}", ex.ValidationStudyInfo.ServerAE)); error.AppendLine(String.Format("Patient : {0}", ex.ValidationStudyInfo.PatientsName)); error.AppendLine(String.Format("Study Uid : {0}", ex.ValidationStudyInfo.StudyInstaneUid)); error.AppendLine(String.Format("Accession# : {0}", ex.ValidationStudyInfo.AccessionNumber)); error.AppendLine(String.Format("Study Date : {0}", ex.ValidationStudyInfo.StudyDate)); queueItem.FailureDescription = error.ToString(); _hsmArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Failed, Platform.Time); } catch (Exception e) { String msg = String.Format("Unexpected exception archiving study: {0} on {1}: {2}", _storageLocation.StudyInstanceUid, _hsmArchive.PartitionArchive.Description, e.Message); Platform.Log(LogLevel.Error, e, msg); queueItem.FailureDescription = msg; _hsmArchive.UpdateArchiveQueue(queueItem, ArchiveQueueStatusEnum.Failed, Platform.Time); } finally { // Unlock the Queue Entry using (IUpdateContext update = PersistentStoreRegistry.GetDefaultStore().OpenUpdateContext(UpdateContextSyncMode.Flush)) { ILockStudy studyLock = update.GetBroker <ILockStudy>(); LockStudyParameters parms = new LockStudyParameters { StudyStorageKey = queueItem.StudyStorageKey, QueueStudyStateEnum = QueueStudyStateEnum.Idle }; bool retVal = studyLock.Execute(parms); if (!parms.Successful || !retVal) { Platform.Log(LogLevel.Info, "Study {0} on partition {1} is failed to unlock.", _storageLocation.StudyInstanceUid, _hsmArchive.ServerPartition.Description); } update.Commit(); } } } }
private void InsertInstance(DicomFile file, StudyXml stream, WorkQueueUid uid, string deleteFile) { using (ServerCommandProcessor processor = new ServerCommandProcessor("Processing WorkQueue DICOM file")) { EventsHelper.Fire(OnInsertingSop, this, new SopInsertingEventArgs { Processor = processor }); InsertInstanceCommand insertInstanceCommand = null; InsertStudyXmlCommand insertStudyXmlCommand = null; String patientsName = file.DataSet[DicomTags.PatientsName].GetString(0, String.Empty); _modality = file.DataSet[DicomTags.Modality].GetString(0, String.Empty); if (_context.UpdateCommands.Count > 0) { foreach (BaseImageLevelUpdateCommand command in _context.UpdateCommands) { command.File = file; processor.AddCommand(command); } } try { // Create a context for applying actions from the rules engine ServerActionContext context = new ServerActionContext(file, _context.StorageLocation.FilesystemKey, _context.Partition, _context.StorageLocation.Key); context.CommandProcessor = processor; _context.SopCompressionRulesEngine.Execute(context); String seriesUid = file.DataSet[DicomTags.SeriesInstanceUid].GetString(0, String.Empty); String sopUid = file.DataSet[DicomTags.SopInstanceUid].GetString(0, String.Empty); String finalDest = _context.StorageLocation.GetSopInstancePath(seriesUid, sopUid); if (_context.UpdateCommands.Count > 0) { processor.AddCommand(new SaveDicomFileCommand(_context.StorageLocation, file, file.Filename != finalDest)); } else if (file.Filename != finalDest || processor.CommandCount > 0) { // Have to be careful here about failure on exists vs. not failing on exists // because of the different use cases of the importer. // save the file in the study folder, or if its been compressed processor.AddCommand(new SaveDicomFileCommand(finalDest, file, file.Filename != finalDest)); } // Update the StudyStream object insertStudyXmlCommand = new InsertStudyXmlCommand(file, stream, _context.StorageLocation); processor.AddCommand(insertStudyXmlCommand); // Have the rules applied during the command processor, and add the objects. processor.AddCommand(new ApplySopRulesCommand(context, _context.SopProcessedRulesEngine)); // If specified, delete the file if (deleteFile != null) { processor.AddCommand(new FileDeleteCommand(deleteFile, true)); } // Insert into the database, but only if its not a duplicate so the counts don't get off insertInstanceCommand = new InsertInstanceCommand(file, _context.StorageLocation); processor.AddCommand(insertInstanceCommand); // Do a check if the StudyStatus value should be changed in the StorageLocation. This // should only occur if the object has been compressed in the previous steps. processor.AddCommand(new UpdateStudyStatusCommand(_context.StorageLocation, file)); if (uid != null) { processor.AddCommand(new DeleteWorkQueueUidCommand(uid)); } // Do the actual processing if (!processor.Execute()) { Platform.Log(LogLevel.Error, "Failure processing command {0} for SOP: {1}", processor.Description, file.MediaStorageSopInstanceUid); Platform.Log(LogLevel.Error, "File that failed processing: {0}", file.Filename); throw new ApplicationException("Unexpected failure (" + processor.FailureReason + ") executing command for SOP: " + file.MediaStorageSopInstanceUid, processor.FailureException); } Platform.Log(ServerPlatform.InstanceLogLevel, "Processed SOP: {0} for Patient {1}", file.MediaStorageSopInstanceUid, patientsName); } catch (Exception e) { Platform.Log(LogLevel.Error, e, "Unexpected exception when {0}. Rolling back operation.", processor.Description); processor.Rollback(); throw new ApplicationException("Unexpected exception when processing file.", e); } finally { if (insertInstanceCommand != null && insertInstanceCommand.Statistics.IsSet) { _instanceStats.InsertDBTime.Add(insertInstanceCommand.Statistics); } if (insertStudyXmlCommand != null && insertStudyXmlCommand.Statistics.IsSet) { _instanceStats.InsertStreamTime.Add(insertStudyXmlCommand.Statistics); } } } }
private static void HandleNonDuplicate(string seriesInstanceUid, string sopInstanceUid, StudyStorageLocation studyLocation, ServerCommandProcessor commandProcessor, DicomFile file, string path, bool dupImage, string extension) { commandProcessor.AddCommand(new CreateDirectoryCommand(path)); path = Path.Combine(path, studyLocation.PartitionFolder); commandProcessor.AddCommand(new CreateDirectoryCommand(path)); path = Path.Combine(path, studyLocation.StudyFolder); commandProcessor.AddCommand(new CreateDirectoryCommand(path)); path = Path.Combine(path, studyLocation.StudyInstanceUid); commandProcessor.AddCommand(new CreateDirectoryCommand(path)); path = Path.Combine(path, seriesInstanceUid); commandProcessor.AddCommand(new CreateDirectoryCommand(path)); path = Path.Combine(path, sopInstanceUid); path += ServerPlatform.DicomFileExtension; commandProcessor.AddCommand(new SaveDicomFileCommand(path, file, true)); commandProcessor.AddCommand( new UpdateWorkQueueCommand(file, studyLocation, dupImage)); #region SPECIAL CODE FOR TESTING if (Diagnostics.Settings.SimulateFileCorruption) { commandProcessor.AddCommand(new CorruptDicomFileCommand(path)); } #endregion }