SingleDocumentCalculationWorker.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO.Abstractions;
  5. using System.Threading.Tasks;
  6. using BO.AppServer.Metadata.Enums;
  7. using BO.ProcessServer.Business.Enums;
  8. using BO.ProcessServer.Business.StatisticCounters;
  9. using BO.ProcessServer.Business.Workers.Contexts;
  10. using BO.ProcessServer.Options;
  11. using Connector.PS;
  12. using Quadarax.Foundation.Core.IO.MimeTypes;
  13. using Quadarax.Foundation.Core.Logging;
  14. using Quadarax.Foundation.Core.Thread;
  15. namespace BO.ProcessServer.Business.Workers
  16. {
  17. internal class SingleDocumentCalculationWorker : ContinousWorker
  18. {
  19. #region *** Constants ***
  20. private const string CS_WK_NAME = "Document";
  21. #endregion
  22. #region *** Private fields ***
  23. private IFileSystem _fileSystem;
  24. #endregion
  25. #region *** Properties ***
  26. public string ShellCommand { get; set; }
  27. public ServerProcessStateEnum Status { get; set; }
  28. public DateTime Started { get; set; }
  29. public DateTime Finished { get; set; }
  30. #endregion
  31. #region *** Constructors ***
  32. public SingleDocumentCalculationWorker(IConnectionPS connection, IFileSystem fileSystem, long documentId, IList<string> files, Configuration configuration, Statistics statistics, Pool pool,
  33. ILogger logger) : base($"{CS_WK_NAME}#{documentId:D8}", new CalculationCtx(connection, documentId, files, configuration, statistics, pool),
  34. logger)
  35. {
  36. Status = ServerProcessStateEnum.Pending;
  37. _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
  38. }
  39. #endregion
  40. #region *** Iteration ***
  41. protected override async Task Do(IWorkerContext context)
  42. {
  43. var ctx = (CalculationCtx)context;
  44. Status = ServerProcessStateEnum.Running;
  45. Started = DateTime.Now;
  46. // get document metadata
  47. var document = await ctx.Connection.GetDocumentInfoAsync(ctx.DocumentId);
  48. // find document proper scenario
  49. var scenarioMan = new Scenarios.Scenarios(ctx.Configuration, GetLogger());
  50. var scenario = scenarioMan.FindScenario(document);
  51. if (scenario == null)
  52. {
  53. // scenario doesn't exists for document -> change status to pending and move files to Failed branch.
  54. await ctx.Connection.SetDocumentStateAsync(ctx.DocumentId, DocumentStatusEnum.Pending);
  55. ctx.Pool.MoveDocumentFiles(Pool.PoolTypeEnum.Working, Pool.PoolTypeEnum.Fail, ctx.DocumentId);
  56. Status = ServerProcessStateEnum.FailInternal;
  57. }
  58. else
  59. {
  60. // scenario exists
  61. var processInfo = new ProcessStartInfo();
  62. processInfo.FileName = scenario.ShellCommand;
  63. processInfo.WindowStyle = scenario.RunWindowsStyle;
  64. processInfo.WorkingDirectory = ctx.Configuration.System.ScenarioPathBase;
  65. processInfo.Arguments = TranslateArguments(scenario.ShellCommandArguments, ctx.InputFiles,
  66. document.PresetProcessProfile, document.PresetQuality, document.PresetWatermark,
  67. document.PresetTest);
  68. processInfo.RedirectStandardOutput = true;
  69. processInfo.RedirectStandardError = true;
  70. ShellCommand = processInfo.FileName + " " + processInfo.Arguments;
  71. using (ctx.Process = Process.Start(processInfo))
  72. {
  73. var errors = await ctx.Process.StandardError.ReadToEndAsync();
  74. var outputs = await ctx.Process.StandardOutput.ReadToEndAsync();
  75. Log(LogSeverityEnum.Info,
  76. $"Starting process '{Name}.{ctx.Process?.ProcessName}' [Id:{ctx.Process?.Id}]: {ShellCommand}");
  77. if (ctx.Process != null)
  78. {
  79. ctx.Process.WaitForExit();
  80. if (!string.IsNullOrEmpty(errors))
  81. Log(LogSeverityEnum.Error, errors);
  82. if (!string.IsNullOrEmpty(outputs))
  83. Log(LogSeverityEnum.Info, outputs);
  84. Log(LogSeverityEnum.Info,
  85. $"End process '{Name}.{ctx.Process?.ProcessName}' [Id:{ctx.Process?.Id}]: Exit code {ctx.Process.ExitCode}");
  86. if (ctx.Process.ExitCode == scenario.ExitCodeSucc)
  87. {
  88. Status = ServerProcessStateEnum.Done;
  89. }
  90. else if (ctx.Process.ExitCode == scenario.ExitCodeFail)
  91. {
  92. Status = ServerProcessStateEnum.FailExit;
  93. }
  94. else
  95. {
  96. Log(LogSeverityEnum.Warn,
  97. $"Process '{Name}.{ctx.Process?.ProcessName}' has unhandled exitcode {ctx.Process.ExitCode}! Goes to fail.");
  98. Status = ServerProcessStateEnum.FailInternal;
  99. }
  100. }
  101. else
  102. {
  103. Status = ServerProcessStateEnum.FailInternal;
  104. }
  105. }
  106. }
  107. Finished = DateTime.Now;
  108. if (Status == ServerProcessStateEnum.Done)
  109. {
  110. var files = ctx.Pool.GetDocumentFiles(Pool.PoolTypeEnum.Working, document.Id, Constants.CS_POOL_OUTFILE_EXT);
  111. foreach (var file in files)
  112. {
  113. await using (var fs = await ctx.Pool.PopDocumentAsync(file))
  114. {
  115. await ctx.Connection.SetDocumentContentAsync(document.Id,
  116. _fileSystem.Path.GetFileNameWithoutExtension(file),
  117. MimeTypeUtil.GetMimeType(_fileSystem.Path.GetExtension(file)), false, fs);
  118. Log(LogSeverityEnum.Debug, $"Process '{Name}.{ctx.Process?.ProcessName}' file '{file}' uploaded.");
  119. }
  120. }
  121. await ctx.Connection.SetDocumentStateAsync(document.Id, DocumentStatusEnum.DoneOk);
  122. ctx.Pool.MoveDocumentFiles(Pool.PoolTypeEnum.Working, Pool.PoolTypeEnum.Done, document.Id);
  123. }
  124. else if (Status == ServerProcessStateEnum.FailInternal || Status == ServerProcessStateEnum.FailExit ||
  125. Status == ServerProcessStateEnum.FailTimeout)
  126. {
  127. ctx.Pool.MoveDocumentFiles(Pool.PoolTypeEnum.Working, Pool.PoolTypeEnum.Fail, document.Id);
  128. }
  129. Log(LogSeverityEnum.Info, $"Process '{Name}.{ctx.Process?.ProcessName}' resolution state is '{Status}'.");
  130. }
  131. #endregion
  132. #region *** Overrides ***
  133. protected override void OnBeforeStart()
  134. {
  135. Status = ServerProcessStateEnum.Running;
  136. Started = DateTime.Now;
  137. base.OnBeforeStart();
  138. }
  139. protected override void OnTimeout(IWorkerContext context)
  140. {
  141. var ctx = (CalculationCtx)context;
  142. ctx.Process.Dispose();
  143. ctx.Process = null;
  144. Finished = DateTime.Now;
  145. Status = ServerProcessStateEnum.FailTimeout;
  146. }
  147. #endregion
  148. #region *** Private Operations ***
  149. private string TranslateArguments(string arguments,IEnumerable<string> files, string profile, int quality, bool isWatermark,bool isTest)
  150. {
  151. var result = arguments.Replace(Constants.CS_TAG_INPUT_FILES, string.Join(Constants.CS_TAG_INPUT_FILES_SEP, files),StringComparison.OrdinalIgnoreCase);
  152. result = result.Replace(Constants.CS_TAG_P_PROFILE, profile, StringComparison.OrdinalIgnoreCase);
  153. result = result.Replace(Constants.CS_TAG_P_TEST, isTest.ToString(), StringComparison.OrdinalIgnoreCase);
  154. result = result.Replace(Constants.CS_TAG_P_WATERMARK, isWatermark.ToString(), StringComparison.OrdinalIgnoreCase);
  155. result = result.Replace(Constants.CS_TAG_P_QUALITY, quality.ToString(), StringComparison.OrdinalIgnoreCase);
  156. result = result.Replace(Constants.CS_TAG_P_OUT_EXT, Constants.CS_POOL_OUTFILE_EXT, StringComparison.OrdinalIgnoreCase);
  157. return result;
  158. }
  159. #endregion
  160. }
  161. }