SingleDocumentCalculationWorker.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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. var processName = string.Empty;
  47. var processId = 0;
  48. // get document metadata
  49. var document = await ctx.Connection.GetDocumentInfoAsync(ctx.DocumentId);
  50. // find document proper scenario
  51. var scenarioMan = new Scenarios.Scenarios(ctx.Configuration, GetLogger());
  52. var scenario = scenarioMan.FindScenario(document);
  53. if (scenario == null)
  54. {
  55. // scenario doesn't exists for document -> change status to pending and move files to Failed branch.
  56. await ctx.Connection.SetDocumentStateAsync(ctx.DocumentId, DocumentStatusEnum.Pending);
  57. ctx.Pool.MoveDocumentFiles(Pool.PoolTypeEnum.Working, Pool.PoolTypeEnum.Fail, ctx.DocumentId);
  58. Status = ServerProcessStateEnum.FailInternal;
  59. }
  60. else
  61. {
  62. // scenario exists
  63. var processInfo = new ProcessStartInfo();
  64. processInfo.WindowStyle = scenario.RunWindowsStyle;
  65. processInfo.WorkingDirectory = TranslatePath(ctx.Configuration.System.ScenarioPathBase);
  66. processInfo.FileName = _fileSystem.Path.Combine(processInfo.WorkingDirectory, scenario.ShellCommand);
  67. processInfo.Arguments = TranslateArguments(scenario.ShellCommandArguments, ctx.InputFiles,
  68. document.PresetProcessProfile, document.PresetQuality, document.PresetWatermark,
  69. document.PresetTest);
  70. processInfo.RedirectStandardOutput = true;
  71. processInfo.RedirectStandardError = true;
  72. ShellCommand = processInfo.FileName + " " + processInfo.Arguments;
  73. using (ctx.Process = Process.Start(processInfo))
  74. {
  75. processName = "process";
  76. processId = ctx.Process?.Id ?? 0;
  77. var errors = await ctx.Process?.StandardError?.ReadToEndAsync();
  78. var outputs = await ctx.Process?.StandardOutput?.ReadToEndAsync();
  79. Log(LogSeverityEnum.Info,
  80. $"Starting process '{Name}.{processName}' [Id:{processId}]: {ShellCommand}");
  81. if (ctx.Process != null)
  82. {
  83. ctx.Process.WaitForExit();
  84. if (!string.IsNullOrEmpty(errors))
  85. Log(LogSeverityEnum.Error, errors);
  86. if (!string.IsNullOrEmpty(outputs))
  87. Log(LogSeverityEnum.Info, outputs);
  88. Log(LogSeverityEnum.Info,
  89. $"End process '{Name}.{processName}' [Id:{processId}]: Exit code {ctx.Process.ExitCode}");
  90. if (ctx.Process.ExitCode == scenario.ExitCodeSucc)
  91. {
  92. Status = ServerProcessStateEnum.Done;
  93. }
  94. else if (ctx.Process.ExitCode == scenario.ExitCodeFail)
  95. {
  96. Status = ServerProcessStateEnum.FailExit;
  97. }
  98. else
  99. {
  100. Log(LogSeverityEnum.Warn,
  101. $"Process '{Name}.{processName}' has unhandled exitcode {ctx.Process.ExitCode}! Goes to fail.");
  102. Status = ServerProcessStateEnum.FailInternal;
  103. }
  104. }
  105. else
  106. {
  107. Status = ServerProcessStateEnum.FailInternal;
  108. }
  109. }
  110. }
  111. Finished = DateTime.Now;
  112. if (Status == ServerProcessStateEnum.Done)
  113. {
  114. var files = ctx.Pool.GetDocumentFiles(Pool.PoolTypeEnum.Working, document.Id, Constants.CS_POOL_OUTFILE_EXT);
  115. foreach (var file in files)
  116. {
  117. await using (var fs = await ctx.Pool.PopDocumentAsync(file))
  118. {
  119. await ctx.Connection.SetDocumentContentAsync(document.Id,
  120. _fileSystem.Path.GetFileNameWithoutExtension(file),
  121. MimeTypeUtil.GetMimeType(_fileSystem.Path.GetExtension(file)), false, fs);
  122. Log(LogSeverityEnum.Debug, $"Process '{Name}.{processName}' file '{file}' uploaded.");
  123. }
  124. }
  125. await ctx.Connection.SetDocumentStateAsync(document.Id, DocumentStatusEnum.DoneOk);
  126. ctx.Pool.MoveDocumentFiles(Pool.PoolTypeEnum.Working, Pool.PoolTypeEnum.Done, document.Id);
  127. }
  128. else if (Status == ServerProcessStateEnum.FailInternal || Status == ServerProcessStateEnum.FailExit ||
  129. Status == ServerProcessStateEnum.FailTimeout)
  130. {
  131. ctx.Pool.MoveDocumentFiles(Pool.PoolTypeEnum.Working, Pool.PoolTypeEnum.Fail, document.Id);
  132. }
  133. Log(LogSeverityEnum.Info, $"Process '{Name}.{processName}' resolution state is '{Status}'.");
  134. }
  135. #endregion
  136. #region *** Overrides ***
  137. protected override void OnBeforeStart()
  138. {
  139. Status = ServerProcessStateEnum.Running;
  140. Started = DateTime.Now;
  141. base.OnBeforeStart();
  142. }
  143. protected override void OnTimeout(IWorkerContext context)
  144. {
  145. var ctx = (CalculationCtx)context;
  146. ctx.Process.Dispose();
  147. ctx.Process = null;
  148. Finished = DateTime.Now;
  149. Status = ServerProcessStateEnum.FailTimeout;
  150. }
  151. #endregion
  152. #region *** Private Operations ***
  153. private string TranslateArguments(string arguments,IEnumerable<string> files, string profile, int quality, bool isWatermark,bool isTest)
  154. {
  155. var result = arguments.Replace(Constants.CS_TAG_INPUT_FILES, string.Join(Constants.CS_TAG_INPUT_FILES_SEP, files),StringComparison.OrdinalIgnoreCase);
  156. result = result.Replace(Constants.CS_TAG_P_PROFILE, profile, StringComparison.OrdinalIgnoreCase);
  157. result = result.Replace(Constants.CS_TAG_P_TEST, isTest.ToString(), StringComparison.OrdinalIgnoreCase);
  158. result = result.Replace(Constants.CS_TAG_P_WATERMARK, isWatermark.ToString(), StringComparison.OrdinalIgnoreCase);
  159. result = result.Replace(Constants.CS_TAG_P_QUALITY, quality.ToString(), StringComparison.OrdinalIgnoreCase);
  160. result = result.Replace(Constants.CS_TAG_P_OUT_EXT, Constants.CS_POOL_OUTFILE_EXT, StringComparison.OrdinalIgnoreCase);
  161. return result;
  162. }
  163. private string TranslatePath(string path)
  164. {
  165. if (string.IsNullOrEmpty(path))
  166. return path;
  167. return path.Replace(Constants.CS_TAG_CURRENT_DIR, _fileSystem.Directory.GetCurrentDirectory());
  168. }
  169. #endregion
  170. }
  171. }