DicomServer/Desktop/Print SCP/PrintJob.cs
2024-12-13 10:06:20 +08:00

360 lines
12 KiB
C#

// Copyright (c) 2012-2021 fo-dicom contributors.
// Licensed under the Microsoft Public License (MS-PL).
using FellowOakDicom.Log;
using FellowOakDicom.Printing;
using System;
using System.Collections.Generic;
using System.Drawing.Printing;
using System.IO;
using System.Linq;
using System.Threading;
namespace FellowOakDicom.Samples.Printing
{
public class StatusUpdateEventArgs : EventArgs
{
public ushort EventTypeId { get; private set; }
public string ExecutionStatusInfo { get; private set; }
public string FilmSessionLabel { get; private set; }
public string PrinterName { get; private set; }
public StatusUpdateEventArgs(
ushort eventTypeId,
string executionStatusInfo,
string filmSessionLabel,
string printerName)
{
EventTypeId = eventTypeId;
ExecutionStatusInfo = executionStatusInfo;
FilmSessionLabel = filmSessionLabel;
PrinterName = printerName;
}
}
public enum PrintJobStatus : ushort
{
Pending = 1,
Printing = 2,
Done = 3,
Failure = 4
}
public class PrintJob : DicomDataset
{
#region Properties and Attributes
public bool SendNEventReport { get; set; }
public Guid PrintJobGuid { get; private set; }
public IList<string> FilmBoxFolderList { get; private set; }
public Printer Printer { get; private set; }
public PrintJobStatus Status { get; private set; }
public string PrintJobFolder { get; private set; }
public string FullPrintJobFolder { get; private set; }
public Exception Error { get; private set; }
public string FilmSessionLabel { get; private set; }
private int _currentPage;
private FilmBox _currentFilmBox;
/// <summary>
/// Print job SOP class UID
/// </summary>
public readonly DicomUID SOPClassUID = DicomUID.PrintJob;
/// <summary>
/// Print job SOP instance UID
/// </summary>
public DicomUID SOPInstanceUID { get; private set; }
/// <summary>
/// Execution status of print job.
/// </summary>
/// <remarks>
/// Enumerated Values:
/// <list type="bullet">
/// <item><description>PENDING</description></item>
/// <item><description>PRINTING</description></item>
/// <item><description>DONE</description></item>
/// <item><description>FAILURE</description></item>
/// </list>
/// </remarks>
public string ExecutionStatus
{
get => GetSingleValueOrDefault(DicomTag.ExecutionStatus, string.Empty);
set => AddOrUpdate(DicomTag.ExecutionStatus, value.ToUpperInvariant());
}
/// <summary>
/// Additional information about Execution Status (2100,0020).
/// </summary>
public string ExecutionStatusInfo
{
get => GetSingleValueOrDefault(DicomTag.ExecutionStatusInfo, string.Empty);
set => AddOrUpdate(DicomTag.ExecutionStatusInfo, value.ToUpperInvariant());
}
/// <summary>
/// Specifies the priority of the print job.
/// </summary>
/// <remarks>
/// Enumerated values:
/// <list type="bullet">
/// <item><description>HIGH</description></item>
/// <item><description>MED</description></item>
/// <item><description>LOW</description></item>
/// </list>
/// </remarks>
public string PrintPriority
{
get => GetSingleValueOrDefault(DicomTag.PrintPriority, "MED");
set => AddOrUpdate(DicomTag.PrintPriority, value);
}
/// <summary>
/// Date/Time of print job creation.
/// </summary>
public DateTime CreationDateTime
{
get => this.GetDateTime(DicomTag.CreationDate, DicomTag.CreationTime);
set
{
AddOrUpdate(DicomTag.CreationDate, value);
AddOrUpdate(DicomTag.CreationTime, value);
}
}
/// <summary>
/// User defined name identifying the printer.
/// </summary>
public string PrinterName
{
get => GetSingleValueOrDefault(DicomTag.PrinterName, string.Empty);
set => AddOrUpdate(DicomTag.PrinterName, value);
}
/// <summary>
/// DICOM Application Entity Title that issued the print operation.
/// </summary>
public string Originator
{
get => GetSingleValueOrDefault(DicomTag.Originator, string.Empty);
set => AddOrUpdate(DicomTag.Originator, value);
}
public ILogger Log { get; private set; }
public event EventHandler<StatusUpdateEventArgs> StatusUpdate;
#endregion
#region Constructors
/// <summary>
/// Construct new print job using specified SOP instance UID. If passed SOP instance UID is missing, new UID will
/// be generated
/// </summary>
/// <param name="sopInstance">New print job SOP instance uID</param>
public PrintJob(DicomUID sopInstance, Printer printer, string originator, ILogger log)
: base()
{
Log = log;
SOPInstanceUID = string.IsNullOrEmpty(sopInstance?.UID) ? DicomUID.Generate() : sopInstance;
Add(DicomTag.SOPClassUID, SOPClassUID);
Add(DicomTag.SOPInstanceUID, SOPInstanceUID);
Printer = printer ?? throw new ArgumentNullException(nameof(printer));
Status = PrintJobStatus.Pending;
PrinterName = Printer.PrinterAet;
Originator = originator;
if (CreationDateTime == DateTime.MinValue)
{
CreationDateTime = DateTime.Now;
}
PrintJobFolder = SOPInstanceUID.UID;
var receivingFolder = Path.Combine(Environment.CurrentDirectory, "PrintJobs");
FullPrintJobFolder = Path.Combine(receivingFolder, PrintJobFolder);
FilmBoxFolderList = new List<string>();
}
#endregion
#region Printing Methods
public void Print(IList<FilmBox> filmBoxList)
{
try
{
Status = PrintJobStatus.Pending;
OnStatusUpdate("QUEUED");
var printJobDir = new DirectoryInfo(FullPrintJobFolder);
if (!printJobDir.Exists)
{
printJobDir.Create();
}
DicomFile file;
int filmsCount = FilmBoxFolderList.Count;
for (int i = 0; i < filmBoxList.Count; i++)
{
var filmBox = filmBoxList[i];
var filmBoxDir = printJobDir.CreateSubdirectory(string.Format("F{0:000000}", i + 1 + filmsCount));
file = new DicomFile(filmBox.FilmSession);
file.Save(Path.Combine(filmBoxDir.FullName, "FilmSession.dcm"));
FilmBoxFolderList.Add(filmBoxDir.Name);
filmBox.Save(filmBoxDir.FullName);
}
FilmSessionLabel = filmBoxList.First().FilmSession.FilmSessionLabel;
var thread = new Thread(new ThreadStart(DoPrint))
{
Name = $"PrintJob {SOPInstanceUID.UID}",
IsBackground = true
};
thread.Start();
}
catch (Exception ex)
{
Error = ex;
Status = PrintJobStatus.Failure;
OnStatusUpdate("UNKNOWN"); // The exception may be analyzed and a more proper code as defined in "Table C.13.9.1-1. Defined Terms for Printer and Execution Status Info" may be used
DeletePrintFolder();
}
}
private void DoPrint()
{
PrintDocument printDocument = null;
try
{
Status = PrintJobStatus.Printing;
OnStatusUpdate("QUEUED");
var printerSettings = new PrinterSettings
{
PrinterName = "Microsoft XPS Document Writer",
PrintToFile = true,
PrintFileName = Path.Combine(FullPrintJobFolder, SOPInstanceUID.UID + ".xps")
};
printDocument = new PrintDocument
{
PrinterSettings = printerSettings,
DocumentName = Thread.CurrentThread.Name,
PrintController = new StandardPrintController()
};
printDocument.QueryPageSettings += OnQueryPageSettings;
printDocument.PrintPage += OnPrintPage;
printDocument.Print();
Status = PrintJobStatus.Done;
OnStatusUpdate("NORMAL");
}
catch (Exception)
{
Status = PrintJobStatus.Failure;
OnStatusUpdate("UNKNOWN"); // The exception may be analyzed and a more proper code as defined in "Table C.13.9.1-1. Defined Terms for Printer and Execution Status Info" may be used
}
finally
{
if (printDocument != null)
{
//dispose the print document and unregister events handlers to avoid memory leaks
printDocument.QueryPageSettings -= OnQueryPageSettings;
printDocument.PrintPage -= OnPrintPage;
printDocument.Dispose();
}
}
}
private void OnPrintPage(object sender, PrintPageEventArgs e)
{
_currentFilmBox.Print(e.Graphics, e.MarginBounds, 100);
_currentFilmBox = null;
_currentPage++;
e.HasMorePages = _currentPage < FilmBoxFolderList.Count;
}
private void OnQueryPageSettings(object sender, QueryPageSettingsEventArgs e)
{
OnStatusUpdate($"NORMAL");
var filmBoxFolder = Path.Combine(FullPrintJobFolder, FilmBoxFolderList[_currentPage]);
var filmSession = FilmSession.Load(Path.Combine(filmBoxFolder, "FilmSession.dcm"));
_currentFilmBox = FilmBox.Load(filmSession, filmBoxFolder);
e.PageSettings.Margins.Left = 25;
e.PageSettings.Margins.Right = 25;
e.PageSettings.Margins.Top = 25;
e.PageSettings.Margins.Bottom = 25;
e.PageSettings.Landscape = _currentFilmBox.FilmOrientation == "LANDSCAPE";
}
private void DeletePrintFolder()
{
var folderInfo = new DirectoryInfo(FullPrintJobFolder);
if (folderInfo.Exists)
{
folderInfo.Delete(true);
}
}
#endregion
#region Notification Methods
protected virtual void OnStatusUpdate(string info)
{
ExecutionStatus = Status.ToString();
ExecutionStatusInfo = info;
if (Status != PrintJobStatus.Failure)
{
Log.Info("Print Job {0} Status {1}: {2}", SOPInstanceUID.UID.Split('.').Last(), Status, info);
}
else
{
Log.Error("Print Job {0} Status {1}: {2}", SOPInstanceUID.UID.Split('.').Last(), Status, info);
}
StatusUpdate?.Invoke(this, new StatusUpdateEventArgs((ushort)Status, info, FilmSessionLabel, PrinterName));
}
#endregion
}
}