PrintJob.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. // Copyright (c) 2012-2020 fo-dicom contributors.
  2. // Licensed under the Microsoft Public License (MS-PL).
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Drawing.Printing;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Threading;
  9. namespace Dicom.Printing
  10. {
  11. public class StatusUpdateEventArgs : EventArgs
  12. {
  13. public ushort EventTypeId { get; private set; }
  14. public string ExecutionStatusInfo { get; private set; }
  15. public string FilmSessionLabel { get; private set; }
  16. public string PrinterName { get; private set; }
  17. public StatusUpdateEventArgs(
  18. ushort eventTypeId,
  19. string executionStatusInfo,
  20. string filmSessionLabel,
  21. string printerName)
  22. {
  23. EventTypeId = eventTypeId;
  24. ExecutionStatusInfo = executionStatusInfo;
  25. FilmSessionLabel = filmSessionLabel;
  26. PrinterName = printerName;
  27. }
  28. }
  29. public enum PrintJobStatus : ushort
  30. {
  31. Pending = 1,
  32. Printing = 2,
  33. Done = 3,
  34. Failure = 4
  35. }
  36. public class PrintJob : DicomDataset
  37. {
  38. #region Properties and Attributes
  39. public bool SendNEventReport { get; set; }
  40. public Guid PrintJobGuid { get; private set; }
  41. public IList<string> FilmBoxFolderList { get; private set; }
  42. public Printer Printer { get; private set; }
  43. public PrintJobStatus Status { get; private set; }
  44. public string PrintJobFolder { get; private set; }
  45. public string FullPrintJobFolder { get; private set; }
  46. public Exception Error { get; private set; }
  47. public string FilmSessionLabel { get; private set; }
  48. private int _currentPage;
  49. private FilmBox _currentFilmBox;
  50. /// <summary>
  51. /// Print job SOP class UID
  52. /// </summary>
  53. public readonly DicomUID SOPClassUID = DicomUID.PrintJobSOPClass;
  54. /// <summary>
  55. /// Print job SOP instance UID
  56. /// </summary>
  57. public DicomUID SOPInstanceUID { get; private set; }
  58. /// <summary>
  59. /// Execution status of print job.
  60. /// </summary>
  61. /// <remarks>
  62. /// Enumerated Values:
  63. /// <list type="bullet">
  64. /// <item><description>PENDING</description></item>
  65. /// <item><description>PRINTING</description></item>
  66. /// <item><description>DONE</description></item>
  67. /// <item><description>FAILURE</description></item>
  68. /// </list>
  69. /// </remarks>
  70. public string ExecutionStatus
  71. {
  72. get => GetSingleValueOrDefault(DicomTag.ExecutionStatus, string.Empty);
  73. set => AddOrUpdate(DicomTag.ExecutionStatus, value.ToUpperInvariant());
  74. }
  75. /// <summary>
  76. /// Additional information about Execution Status (2100,0020).
  77. /// </summary>
  78. public string ExecutionStatusInfo
  79. {
  80. get => GetSingleValueOrDefault(DicomTag.ExecutionStatusInfo, string.Empty);
  81. set => AddOrUpdate(DicomTag.ExecutionStatusInfo, value.ToUpperInvariant());
  82. }
  83. /// <summary>
  84. /// Specifies the priority of the print job.
  85. /// </summary>
  86. /// <remarks>
  87. /// Enumerated values:
  88. /// <list type="bullet">
  89. /// <item><description>HIGH</description></item>
  90. /// <item><description>MED</description></item>
  91. /// <item><description>LOW</description></item>
  92. /// </list>
  93. /// </remarks>
  94. public string PrintPriority
  95. {
  96. get => GetSingleValueOrDefault(DicomTag.PrintPriority, "MED");
  97. set => AddOrUpdate(DicomTag.PrintPriority, value);
  98. }
  99. /// <summary>
  100. /// Date/Time of print job creation.
  101. /// </summary>
  102. public DateTime CreationDateTime
  103. {
  104. get => this.GetDateTime(DicomTag.CreationDate, DicomTag.CreationTime);
  105. set
  106. {
  107. AddOrUpdate(DicomTag.CreationDate, value);
  108. AddOrUpdate(DicomTag.CreationTime, value);
  109. }
  110. }
  111. /// <summary>
  112. /// User defined name identifying the printer.
  113. /// </summary>
  114. public string PrinterName
  115. {
  116. get => GetSingleValueOrDefault(DicomTag.PrinterName, string.Empty);
  117. set => AddOrUpdate(DicomTag.PrinterName, value);
  118. }
  119. /// <summary>
  120. /// DICOM Application Entity Title that issued the print operation.
  121. /// </summary>
  122. public string Originator
  123. {
  124. get => GetSingleValueOrDefault(DicomTag.Originator, string.Empty);
  125. set => AddOrUpdate(DicomTag.Originator, value);
  126. }
  127. public Log.Logger Log { get; private set; }
  128. public event EventHandler<StatusUpdateEventArgs> StatusUpdate;
  129. #endregion
  130. #region Constructors
  131. /// <summary>
  132. /// Construct new print job using specified SOP instance UID. If passed SOP instance UID is missing, new UID will
  133. /// be generated
  134. /// </summary>
  135. /// <param name="sopInstance">New print job SOP instance uID</param>
  136. public PrintJob(DicomUID sopInstance, Printer printer, string originator, Log.Logger log)
  137. : base()
  138. {
  139. Log = log;
  140. if (sopInstance == null || sopInstance.UID == string.Empty)
  141. {
  142. SOPInstanceUID = DicomUID.Generate();
  143. }
  144. else
  145. {
  146. SOPInstanceUID = sopInstance;
  147. }
  148. Add(DicomTag.SOPClassUID, SOPClassUID);
  149. Add(DicomTag.SOPInstanceUID, SOPInstanceUID);
  150. Printer = printer ?? throw new ArgumentNullException("printer");
  151. Status = PrintJobStatus.Pending;
  152. PrinterName = Printer.PrinterAet;
  153. Originator = originator;
  154. if (CreationDateTime == DateTime.MinValue)
  155. {
  156. CreationDateTime = DateTime.Now;
  157. }
  158. PrintJobFolder = SOPInstanceUID.UID;
  159. var receivingFolder = Path.Combine(Environment.CurrentDirectory, "PrintJobs");
  160. FullPrintJobFolder = Path.Combine(receivingFolder, PrintJobFolder);
  161. FilmBoxFolderList = new List<string>();
  162. }
  163. #endregion
  164. #region Printing Methods
  165. public void Print(IList<FilmBox> filmBoxList)
  166. {
  167. try
  168. {
  169. Status = PrintJobStatus.Pending;
  170. OnStatusUpdate("QUEUED");
  171. var printJobDir = new DirectoryInfo(FullPrintJobFolder);
  172. if (!printJobDir.Exists)
  173. {
  174. printJobDir.Create();
  175. }
  176. DicomFile file;
  177. int filmsCount = FilmBoxFolderList.Count;
  178. for (int i = 0; i < filmBoxList.Count; i++)
  179. {
  180. var filmBox = filmBoxList[i];
  181. var filmBoxDir = printJobDir.CreateSubdirectory(string.Format("F{0:000000}", i + 1 + filmsCount));
  182. file = new DicomFile(filmBox.FilmSession);
  183. file.Save(Path.Combine(filmBoxDir.FullName, "FilmSession.dcm"));
  184. FilmBoxFolderList.Add(filmBoxDir.Name);
  185. filmBox.Save(filmBoxDir.FullName);
  186. }
  187. FilmSessionLabel = filmBoxList.First().FilmSession.FilmSessionLabel;
  188. var thread = new Thread(new ThreadStart(DoPrint))
  189. {
  190. Name = $"PrintJob {SOPInstanceUID.UID}",
  191. IsBackground = true
  192. };
  193. thread.Start();
  194. }
  195. catch (Exception ex)
  196. {
  197. Error = ex;
  198. Status = PrintJobStatus.Failure;
  199. 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
  200. DeletePrintFolder();
  201. }
  202. }
  203. private void DoPrint()
  204. {
  205. PrintDocument printDocument = null;
  206. try
  207. {
  208. Status = PrintJobStatus.Printing;
  209. OnStatusUpdate("QUEUED");
  210. var printerSettings = new PrinterSettings
  211. {
  212. PrinterName = "Microsoft XPS Document Writer",
  213. PrintToFile = true,
  214. PrintFileName = Path.Combine(FullPrintJobFolder, SOPInstanceUID.UID + ".xps")
  215. };
  216. printDocument = new PrintDocument
  217. {
  218. PrinterSettings = printerSettings,
  219. DocumentName = Thread.CurrentThread.Name,
  220. PrintController = new StandardPrintController()
  221. };
  222. printDocument.QueryPageSettings += OnQueryPageSettings;
  223. printDocument.PrintPage += OnPrintPage;
  224. printDocument.Print();
  225. Status = PrintJobStatus.Done;
  226. OnStatusUpdate("NORMAL");
  227. }
  228. catch (Exception)
  229. {
  230. Status = PrintJobStatus.Failure;
  231. 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
  232. }
  233. finally
  234. {
  235. if (printDocument != null)
  236. {
  237. //dispose the print document and unregister events handlers to avoid memory leaks
  238. printDocument.QueryPageSettings -= OnQueryPageSettings;
  239. printDocument.PrintPage -= OnPrintPage;
  240. printDocument.Dispose();
  241. }
  242. }
  243. }
  244. private void OnPrintPage(object sender, PrintPageEventArgs e)
  245. {
  246. _currentFilmBox.Print(e.Graphics, e.MarginBounds, 100);
  247. _currentFilmBox = null;
  248. _currentPage++;
  249. e.HasMorePages = _currentPage < FilmBoxFolderList.Count;
  250. }
  251. private void OnQueryPageSettings(object sender, QueryPageSettingsEventArgs e)
  252. {
  253. OnStatusUpdate($"NORMAL");
  254. var filmBoxFolder = Path.Combine(FullPrintJobFolder, FilmBoxFolderList[_currentPage]);
  255. var filmSession = FilmSession.Load(Path.Combine(filmBoxFolder, "FilmSession.dcm"));
  256. _currentFilmBox = FilmBox.Load(filmSession, filmBoxFolder);
  257. e.PageSettings.Margins.Left = 25;
  258. e.PageSettings.Margins.Right = 25;
  259. e.PageSettings.Margins.Top = 25;
  260. e.PageSettings.Margins.Bottom = 25;
  261. e.PageSettings.Landscape = _currentFilmBox.FilmOrientation == "LANDSCAPE";
  262. }
  263. private void DeletePrintFolder()
  264. {
  265. var folderInfo = new DirectoryInfo(FullPrintJobFolder);
  266. if (folderInfo.Exists)
  267. {
  268. folderInfo.Delete(true);
  269. }
  270. }
  271. #endregion
  272. #region Notification Methods
  273. protected virtual void OnStatusUpdate(string info)
  274. {
  275. ExecutionStatus = Status.ToString();
  276. ExecutionStatusInfo = info;
  277. if (Status != PrintJobStatus.Failure)
  278. {
  279. Log.Info("Print Job {0} Status {1}: {2}", SOPInstanceUID.UID.Split('.').Last(), Status, info);
  280. }
  281. else
  282. {
  283. Log.Error("Print Job {0} Status {1}: {2}", SOPInstanceUID.UID.Split('.').Last(), Status, info);
  284. }
  285. StatusUpdate?.Invoke(this, new StatusUpdateEventArgs((ushort)Status, info, FilmSessionLabel, PrinterName));
  286. }
  287. #endregion
  288. }
  289. }