using System; using System.Collections.Generic; using System.Diagnostics; using System.IO.Abstractions; using System.Threading.Tasks; using BO.AppServer.Metadata.Enums; using BO.ProcessServer.Business.Enums; using BO.ProcessServer.Business.StatisticCounters; using BO.ProcessServer.Business.Workers.Contexts; using BO.ProcessServer.Options; using Connector.PS; using Quadarax.Foundation.Core.IO.MimeTypes; using Quadarax.Foundation.Core.Logging; using Quadarax.Foundation.Core.Thread; namespace BO.ProcessServer.Business.Workers { internal class SingleDocumentCalculationWorker : ContinousWorker { #region *** Constants *** private const string CS_WK_NAME = "Document"; #endregion #region *** Private fields *** private IFileSystem _fileSystem; #endregion #region *** Properties *** public string ShellCommand { get; set; } public ServerProcessStateEnum Status { get; set; } public DateTime Started { get; set; } public DateTime Finished { get; set; } #endregion #region *** Constructors *** public SingleDocumentCalculationWorker(IConnectionPS connection, IFileSystem fileSystem, long documentId, IList files, Configuration configuration, Statistics statistics, Pool pool, ILogger logger) : base($"{CS_WK_NAME}#{documentId:D8}", new CalculationCtx(connection, documentId, files, configuration, statistics, pool), logger) { Status = ServerProcessStateEnum.Pending; _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem)); } #endregion #region *** Iteration *** protected override async Task Do(IWorkerContext context) { var ctx = (CalculationCtx)context; Status = ServerProcessStateEnum.Running; Started = DateTime.Now; // get document metadata var document = await ctx.Connection.GetDocumentInfoAsync(ctx.DocumentId); // find document proper scenario var scenarioMan = new Scenarios.Scenarios(ctx.Configuration, GetLogger()); var scenario = scenarioMan.FindScenario(document); if (scenario == null) { // scenario doesn't exists for document -> change status to pending and move files to Failed branch. await ctx.Connection.SetDocumentStateAsync(ctx.DocumentId, DocumentStatusEnum.Pending); ctx.Pool.MoveDocumentFiles(Pool.PoolTypeEnum.Working, Pool.PoolTypeEnum.Fail, ctx.DocumentId); Status = ServerProcessStateEnum.FailInternal; } else { // scenario exists var processInfo = new ProcessStartInfo(); processInfo.FileName = scenario.ShellCommand; processInfo.WindowStyle = scenario.RunWindowsStyle; processInfo.WorkingDirectory = ctx.Configuration.System.ScenarioPathBase; processInfo.Arguments = TranslateArguments(scenario.ShellCommandArguments, ctx.InputFiles, document.PresetProcessProfile, document.PresetQuality, document.PresetWatermark, document.PresetTest); processInfo.RedirectStandardOutput = true; processInfo.RedirectStandardError = true; ShellCommand = processInfo.FileName + " " + processInfo.Arguments; using (ctx.Process = Process.Start(processInfo)) { var errors = await ctx.Process.StandardError.ReadToEndAsync(); var outputs = await ctx.Process.StandardOutput.ReadToEndAsync(); Log(LogSeverityEnum.Info, $"Starting process '{Name}.{ctx.Process?.ProcessName}' [Id:{ctx.Process?.Id}]: {ShellCommand}"); if (ctx.Process != null) { ctx.Process.WaitForExit(); if (!string.IsNullOrEmpty(errors)) Log(LogSeverityEnum.Error, errors); if (!string.IsNullOrEmpty(outputs)) Log(LogSeverityEnum.Info, outputs); Log(LogSeverityEnum.Info, $"End process '{Name}.{ctx.Process?.ProcessName}' [Id:{ctx.Process?.Id}]: Exit code {ctx.Process.ExitCode}"); if (ctx.Process.ExitCode == scenario.ExitCodeSucc) { Status = ServerProcessStateEnum.Done; } else if (ctx.Process.ExitCode == scenario.ExitCodeFail) { Status = ServerProcessStateEnum.FailExit; } else { Log(LogSeverityEnum.Warn, $"Process '{Name}.{ctx.Process?.ProcessName}' has unhandled exitcode {ctx.Process.ExitCode}! Goes to fail."); Status = ServerProcessStateEnum.FailInternal; } } else { Status = ServerProcessStateEnum.FailInternal; } } } Finished = DateTime.Now; if (Status == ServerProcessStateEnum.Done) { var files = ctx.Pool.GetDocumentFiles(Pool.PoolTypeEnum.Working, document.Id, Constants.CS_POOL_OUTFILE_EXT); foreach (var file in files) { await using (var fs = await ctx.Pool.PopDocumentAsync(file)) { await ctx.Connection.SetDocumentContentAsync(document.Id, _fileSystem.Path.GetFileNameWithoutExtension(file), MimeTypeUtil.GetMimeType(_fileSystem.Path.GetExtension(file)), false, fs); Log(LogSeverityEnum.Debug, $"Process '{Name}.{ctx.Process?.ProcessName}' file '{file}' uploaded."); } } await ctx.Connection.SetDocumentStateAsync(document.Id, DocumentStatusEnum.DoneOk); ctx.Pool.MoveDocumentFiles(Pool.PoolTypeEnum.Working, Pool.PoolTypeEnum.Done, document.Id); } else if (Status == ServerProcessStateEnum.FailInternal || Status == ServerProcessStateEnum.FailExit || Status == ServerProcessStateEnum.FailTimeout) { ctx.Pool.MoveDocumentFiles(Pool.PoolTypeEnum.Working, Pool.PoolTypeEnum.Fail, document.Id); } Log(LogSeverityEnum.Info, $"Process '{Name}.{ctx.Process?.ProcessName}' resolution state is '{Status}'."); } #endregion #region *** Overrides *** protected override void OnBeforeStart() { Status = ServerProcessStateEnum.Running; Started = DateTime.Now; base.OnBeforeStart(); } protected override void OnTimeout(IWorkerContext context) { var ctx = (CalculationCtx)context; ctx.Process.Dispose(); ctx.Process = null; Finished = DateTime.Now; Status = ServerProcessStateEnum.FailTimeout; } #endregion #region *** Private Operations *** private string TranslateArguments(string arguments,IEnumerable files, string profile, int quality, bool isWatermark,bool isTest) { var result = arguments.Replace(Constants.CS_TAG_INPUT_FILES, string.Join(Constants.CS_TAG_INPUT_FILES_SEP, files),StringComparison.OrdinalIgnoreCase); result = result.Replace(Constants.CS_TAG_P_PROFILE, profile, StringComparison.OrdinalIgnoreCase); result = result.Replace(Constants.CS_TAG_P_TEST, isTest.ToString(), StringComparison.OrdinalIgnoreCase); result = result.Replace(Constants.CS_TAG_P_WATERMARK, isWatermark.ToString(), StringComparison.OrdinalIgnoreCase); result = result.Replace(Constants.CS_TAG_P_QUALITY, quality.ToString(), StringComparison.OrdinalIgnoreCase); result = result.Replace(Constants.CS_TAG_P_OUT_EXT, Constants.CS_POOL_OUTFILE_EXT, StringComparison.OrdinalIgnoreCase); return result; } #endregion } }