context: #region License
  
  // Copyright (c) 2013, ClearCanvas Inc.
  // All rights reserved.
  // http://www.ClearCanvas.ca
  //
  // This file is part of the ClearCanvas RIS/PACS open source project.
  //
  // The ClearCanvas RIS/PACS open source project is free software: you can
  // redistribute it and/or modify it under the terms of the GNU General Public
  // License as published by the Free Software Foundation, either version 3 of the
  // License, or (at your option) any later version.
  //
  // The ClearCanvas RIS/PACS open source project is distributed in the hope that it
  // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
  // Public License for more details.
  //
  // You should have received a copy of the GNU General Public License along with
  // the ClearCanvas RIS/PACS open source project.  If not, see
  // <http://www.gnu.org/licenses/>.
  
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.IO;
  using Macro.Common;
  using Macro.Common.Statistics;
  using Macro.Common.Utilities;
  using Macro.Dicom;
  using Macro.Dicom.Codec;
  using Macro.Dicom.Utilities;
  using Macro.Dicom.Utilities.Command;
  using Macro.Dicom.Utilities.Xml;
  using Macro.ImageServer.Common;
  using Macro.ImageServer.Common.Command;
  using Macro.ImageServer.Common.Exceptions;
  using Macro.ImageServer.Core;
  using Macro.ImageServer.Core.Command;
  using Macro.ImageServer.Core.Process;
  using Macro.ImageServer.Core.Reconcile;
  using Macro.ImageServer.Core.Validation;
  using Macro.ImageServer.Model;
  using Macro.ImageServer.Rules;
  using DeleteDirectoryCommand = Macro.ImageServer.Core.Command.DeleteDirectoryCommand;
  
  namespace Macro.ImageServer.Services.WorkQueue.StudyProcess
  {
      public enum DuplicateProcessResultAction
      {
          /// <summary>
          /// Duplicate is identical and has been deleted from the filesystem
          /// </summary>
          Delete,
  
          /// <summary>
          /// Duplicate is different from the existing copy and a SIQ entry has been created.
          /// </summary>
          Reconcile,
  
          /// <summary>
          /// Duplicate has overwritten the existing SOP Instance
          /// </summary>
          Accept
      }
  
      /// <summary>
      /// Processor for 'StudyProcess' <see cref="WorkQueue"/> entries.
      /// </summary>
      [StudyIntegrityValidation(ValidationTypes = StudyIntegrityValidationModes.Default, Recovery= RecoveryModes.Automatic)]
      public class StudyProcessItemProcessor : BaseItemProcessor, ICancelable
      {
          public class ProcessDuplicateResult
          {
              public DuplicateProcessResultAction ActionTaken { get; set; }
          }
  
          #region Private Members
          private ServerRulesEngine _sopProcessedRulesEngine;
  
          protected StudyProcessStatistics Statistics;
      	protected StudyProcessorContext Context;
          #endregion
  
          #region Public Properties
         
          #endregion
  
          #region Constructors
          public StudyProcessItemProcessor()
          {
              Statistics = new StudyProcessStatistics();            
          }
  
          #endregion Constructors
  
          #region Private Methods
  
          void SaveDuplicateReport(WorkQueueUid uid, string sourceFile, string destinationFile, DicomFile dupFile, StudyXml studyXml)
          {
              using (var processor = new ServerCommandProcessor("Save duplicate report"))
              {
                  processor.AddCommand(new RenameFileCommand(sourceFile, destinationFile, false));
  
                  // Update the StudyStream object
                  processor.AddCommand( new InsertStudyXmlCommand(dupFile, studyXml, Context.StorageLocation));
  
                  processor.AddCommand(new DeleteWorkQueueUidCommand(uid));
  
                  processor.Execute();
              }
          }
  
          private ProcessDuplicateResult ProcessDuplicateReport(DicomFile dupFile, DicomFile baseFile, WorkQueueUid uid, StudyXml studyXml)
          {
              var result = new ProcessDuplicateResult();
  
              DateTime? dupTime = DateTimeParser.ParseDateAndTime(dupFile.DataSet, 0, DicomTags.InstanceCreationDate,
                                                                  DicomTags.InstanceCreationTime);
  
              DateTime? baseTime = DateTimeParser.ParseDateAndTime(baseFile.DataSet, 0, DicomTags.InstanceCreationDate,
                                                                   DicomTags.InstanceCreationTime);
  
              if (dupTime.HasValue && baseTime.HasValue)
              {
                  if (dupTime.Value <= baseTime.Value)
                  {
                      RemoveWorkQueueUid(uid, dupFile.Filename);
                      result.ActionTaken = DuplicateProcessResultAction.Delete;
                      return result;
                  }
              }
  
              result.ActionTaken = DuplicateProcessResultAction.Accept;
              SaveDuplicateReport(uid, dupFile.Filename, baseFile.Filename, dupFile, studyXml);
              return result;
          }
  
          private ProcessDuplicateResult OverwriteDuplicate(DicomFile dupFile, WorkQueueUid uid, StudyXml studyXml)
          {
              Platform.Log(LogLevel.Info, "Overwriting duplicate SOP {0}", uid.SopInstanceUid);
  
              var result = new ProcessDuplicateResult();
              result.ActionTaken = DuplicateProcessResultAction.Accept;
  
              using (var processor = new ServerCommandProcessor("Overwrite duplicate instance"))
              {
                  var destination = Context.StorageLocation.GetSopInstancePath(uid.SeriesInstanceUid, uid.SopInstanceUid);
                  processor.AddCommand(new RenameFileCommand(dupFile.Filename, destination, false));
  
                  // Ideally we don't need to insert the instance into the database since it's a duplicate.
  				// However, we need to do so to ensure the Study record is recreated if we are dealing with an orphan study.
  				// For other cases, this will cause the instance count in the DB to be out of sync with the filesystem.
  				// But it will be corrected at the end of the processing when the study verification is executed.
                  processor.AddCommand(new InsertInstanceCommand(dupFile, Context.StorageLocation));
                  
                  // Update the StudyStream object
                  processor.AddCommand(new InsertStudyXmlCommand(dupFile, studyXml, Context.StorageLocation));
                  
                  processor.AddCommand(new DeleteWorkQueueUidCommand(uid));
  
                  if (!processor.Execute())
                  {
                      // cause the item to fail
                      throw new Exception(string.Format("Error occurred when trying to overwrite duplicate in the filesystem."), processor.FailureException);
                  }
              }
  
              return result;
          }
  
          private ProcessDuplicateResult ProcessDuplicate(DicomFile dupFile, WorkQueueUid uid, StudyXml studyXml)
          {
              var result = new ProcessDuplicateResult();
  
  
              string duplicateSopPath = ServerPlatform.GetDuplicateUidPath(StorageLocation, uid);
              string basePath = StorageLocation.GetSopInstancePath(uid.SeriesInstanceUid, uid.SopInstanceUid);
  			if (!File.Exists(basePath))
  			{
  				// NOTE: This is special case. The file which caused dicom service to think this sop is a duplicate
  				// no longer exists in the study folder. Perhaps it has been moved to another folder during auto reconciliation.
  				// We have nothing to compare against so let's just throw it into the SIQ queue.
  				CreateDuplicateSIQEntry(uid, dupFile, null);
  			    result.ActionTaken = DuplicateProcessResultAction.Reconcile;
  			}
  			else
  			{
                  // Check if system is configured to override the rule for this study
                  if (DuplicatePolicy.IsParitionDuplicatePolicyOverridden(this.StorageLocation))
                  {
                      return OverwriteDuplicate(dupFile, uid, studyXml);
                  }
                  else
                  {
                      var baseFile = new DicomFile(basePath);
                      baseFile.Load();
  
                      if (DuplicateSopProcessorHelper.SopClassIsReport(dupFile.SopClass.Uid) && ServerPartition.AcceptLatestReport)
                      {
                          return ProcessDuplicateReport(dupFile, baseFile, uid, studyXml);
                      }
  
  
                      if (!dupFile.TransferSyntax.Equals(baseFile.TransferSyntax))
                      {
                          // If they're compressed, and we have a codec, lets decompress and still do the comparison
                          if (dupFile.TransferSyntax.Encapsulated
                              && !dupFile.TransferSyntax.LossyCompressed
                              && DicomCodecRegistry.GetCodec(dupFile.TransferSyntax) != null)
                          {
                              dupFile.ChangeTransferSyntax(TransferSyntax.ExplicitVrLittleEndian);
                          }
  
                          if (baseFile.TransferSyntax.Encapsulated
                              && !baseFile.TransferSyntax.LossyCompressed
                              && DicomCodecRegistry.GetCodec(baseFile.TransferSyntax) != null)
                          {
                              baseFile.ChangeTransferSyntax(TransferSyntax.ExplicitVrLittleEndian);
                          }
  
                          if (dupFile.TransferSyntax.Encapsulated || baseFile.TransferSyntax.Encapsulated)
                          {
                              string failure = String.Format("Base file transfer syntax is '{0}' while duplicate file has '{1}'",
                                                             baseFile.TransferSyntax, dupFile.TransferSyntax);
  
                              var list = new List<DicomAttributeComparisonResult>();
                              var compareResult = new DicomAttributeComparisonResult
                              {
                                  ResultType = ComparisonResultType.DifferentValues,
                                  TagName = DicomTagDictionary.GetDicomTag(DicomTags.TransferSyntaxUid).Name,
                                  Details = failure
                              };
                              list.Add(compareResult);
                              CreateDuplicateSIQEntry(uid, dupFile, list);
                              result.ActionTaken = DuplicateProcessResultAction.Reconcile;
                              return result;
                          }
                      }
  
                      var failureReason = new List<DicomAttributeComparisonResult>();
                      if (baseFile.DataSet.Equals(dupFile.DataSet, ref failureReason))
                      {
                          Platform.Log(LogLevel.Info,
                                   "Duplicate SOP being processed is identical.  Removing SOP: {0}",
                                   baseFile.MediaStorageSopInstanceUid);
  
  
                          RemoveWorkQueueUid(uid, duplicateSopPath);
                          result.ActionTaken = DuplicateProcessResultAction.Delete;
  
                      }
                      else
                      {
                          CreateDuplicateSIQEntry(uid, dupFile, failureReason);
                          result.ActionTaken = DuplicateProcessResultAction.Reconcile;
                      }
                  }
  				
  			}
  
              return result;
          }
  
          void CreateDuplicateSIQEntry(WorkQueueUid uid, DicomFile file, List<DicomAttributeComparisonResult> differences)
          {
              Platform.Log(LogLevel.Info, "Duplicate SOP is different from existing copy. Creating duplicate SIQ entry. SOP: {0}", uid.SopInstanceUid);
  
              using (var processor = new ServerCommandProcessor("Create Duplicate SIQ Entry"))
              {
                  var insertCommand = new InsertOrUpdateEntryCommand(
                      uid.GroupID, StorageLocation, file,
                      ServerPlatform.GetDuplicateGroupPath(StorageLocation, uid),
                      string.IsNullOrEmpty(uid.RelativePath)
                          ? Path.Combine(StorageLocation.StudyInstanceUid, uid.SopInstanceUid + "." + uid.Extension)
                          : uid.RelativePath,
                      differences);
                  processor.AddCommand(insertCommand);
  
                  processor.AddCommand(new DeleteWorkQueueUidCommand(uid));
  
                  processor.Execute();
              }
              
          }
  
          /// <summary>
          /// Process a specific DICOM file related to a <see cref="WorkQueue"/> request.
          /// </summary>
          /// <param name="queueUid"></param>
          /// <param name="stream">The <see cref="StudyXml"/> file to update with information from the file.</param>
          /// <param name="file">The file being processed.</param>
          /// <param name="compare">Indicates whether to compare the DICOM file against the study in the system.</param>
          protected virtual void ProcessFile(WorkQueueUid queueUid, DicomFile file, StudyXml stream, bool compare)
          {
              var processor = new SopInstanceProcessor(Context) {EnforceNameRules = true};
  
          	var fileInfo = new FileInfo(file.Filename);
  			long fileSize = fileInfo.Length;
  
  			processor.InstanceStats.FileLoadTime.Start();
  			processor.InstanceStats.FileLoadTime.End();
  			processor.InstanceStats.FileSize = (ulong)fileSize;
  			string sopInstanceUid = file.DataSet[DicomTags.SopInstanceUid].GetString(0, "File:" + fileInfo.Name);
  			processor.InstanceStats.Description = sopInstanceUid;
  
              string group = queueUid.GroupID ?? ServerHelper.GetUidGroup(file, ServerPartition, WorkQueueItem.InsertTime);
  
              ProcessingResult result = processor.ProcessFile(group, file, stream, compare, true, queueUid, null);
  
              if (result.Status == ProcessingStatus.Reconciled)
              {
                  // file has been saved by SopInstanceProcessor in another place for reconcilation
                  // Note: SopInstanceProcessor has removed the WorkQueueUid so we
                  // only need to delete the file here.
                  FileUtils.Delete(fileInfo.FullName);
              }
  			
  			Statistics.StudyInstanceUid = StorageLocation.StudyInstanceUid;
  			if (String.IsNullOrEmpty(processor.Modality) == false)
  				Statistics.Modality = processor.Modality;
  
  			// Update the statistics
  			Statistics.NumInstances++;
          	Statistics.AddSubStats(processor.InstanceStats);
          }
          
  
          /// <summary>
          /// Process all of the SOP Instances associated with a <see cref="WorkQueue"/> item.
          /// </summary>
          /// <param name="item">The <see cref="WorkQueue"/> item.</param>
          /// <returns>Number of instances that have been processed successfully.</returns>
          private int ProcessUidList(Model.WorkQueue item)
          {
          	StudyXml studyXml = LoadStudyXml(StorageLocation);
  
              int successfulProcessCount = 0;
  
              foreach (WorkQueueUid sop in WorkQueueUidList)
              {
                  if (sop.Failed)
                      continue;
  
  				if (CancelPending)
  				{
  					Platform.Log(LogLevel.Info, "Processing of study canceled for shutdown: {0}", StorageLocation.StudyInstanceUid);
                      return successfulProcessCount;	
  				}
  
                  if (ProcessWorkQueueUid(item, sop, studyXml))
                      successfulProcessCount++;
              }
  
              return successfulProcessCount;
          }
  
  
          /// <summary>
          /// Process a specified <see cref="WorkQueueUid"/>
          /// </summary>
          /// <param name="item">The <see cref="WorkQueue"/> item being processed</param>
          /// <param name="sop">The <see cref="WorkQueueUid"/> being processed</param>
          /// <param name="studyXml">The <see cref="StudyXml"/> object for the study being processed</param>
          /// <returns>true if the <see cref="WorkQueueUid"/> is successfully processed. false otherwise</returns>
          protected virtual bool ProcessWorkQueueUid(Model.WorkQueue item, WorkQueueUid sop, StudyXml studyXml)
          {
              Platform.CheckForNullReference(item, "item");
              Platform.CheckForNullReference(sop, "sop");
              Platform.CheckForNullReference(studyXml, "studyXml");
  
              OnProcessUidBegin(item, sop);
  
              string path = null;
              
              try
              {
                  if (sop.Duplicate && sop.Extension != null)
                  {
                      path = ServerPlatform.GetDuplicateUidPath(StorageLocation, sop);
                      var file = new DicomFile(path);
                      file.Load();
  
                      InstancePreProcessingResult result = PreProcessFile(sop, file);
  
                      if (false ==file.DataSet[DicomTags.StudyInstanceUid].ToString().Equals(StorageLocation.StudyInstanceUid) 
                              || result.DiscardImage)
                      {
                          RemoveWorkQueueUid(sop, null);
                      }
                      else 
                      {
                      	var duplicateResult = ProcessDuplicate(file, sop, studyXml);
                          if (duplicateResult.ActionTaken == DuplicateProcessResultAction.Delete || duplicateResult.ActionTaken == DuplicateProcessResultAction.Accept)
                          {
                              // make sure the folder is also deleted if it's empty
                              string folder = Path.GetDirectoryName(path);
  
                              String reconcileRootFolder = ServerPlatform.GetDuplicateFolderRootPath(StorageLocation);
                              DirectoryUtility.DeleteIfEmpty(folder, reconcileRootFolder);
                          }
                      }
                  }
                  else
                  {
                      try
                      {
                          path = StorageLocation.GetSopInstancePath(sop.SeriesInstanceUid, sop.SopInstanceUid);
                          var file = new DicomFile(path);
                          file.Load();
  
                          InstancePreProcessingResult result = PreProcessFile(sop, file);
  
                          if (false == file.DataSet[DicomTags.StudyInstanceUid].ToString().Equals(StorageLocation.StudyInstanceUid) 
                              || result.DiscardImage)
                          {
                              RemoveWorkQueueUid(sop, path);
                          }
                          else
                          {
                              ProcessFile(sop, file, studyXml, !result.AutoReconciled);
                          }
                      }
                      catch (DicomException ex)
                      {
                          // bad file. Remove it from the filesystem and the queue
                          RemoveBadDicomFile(path, ex.Message);
                          DeleteWorkQueueUid(sop);
                          return false;
                      }
                      
                  }
                  
                  return true;
              }
              catch (StudyIsNearlineException)
              {
                  // handled by caller
                  throw;
              }
              catch (Exception e)
              {
                  Platform.Log(LogLevel.Error, e, "Unexpected exception when processing file: {0} SOP Instance: {1}", path, sop.SopInstanceUid);
  
gt:                 item.FailureDescription = e.InnerException != null ? 