using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Tango.BL;
using Tango.BL.Builders;
using Tango.BL.DTO;
using Tango.BL.Entities;
using Tango.BL.Enumerations;
using Tango.Core;
using Tango.Core.ExtensionMethods;
using Tango.Integration.Operation;
using Tango.Logging;
using Tango.PMR.MachineStatus;
using Tango.PMR.Printing;
namespace Tango.Integration.JobRuns
{
///
/// Represents a basic database job runs logger.
///
///
public class BasicJobRunsLogger : ExtendedObject, IJobRunsLogger
{
private Job _job;
private Machine _defaultMachine;
private List _currentJobEvents;
private MachineStatus _startMachineStatus;
private JobTicket _jobTicket;
public event EventHandler JobRunAvailable;
#region Properties
///
/// Gets the machine operator.
///
public IMachineOperator MachineOperator { get; private set; }
///
/// Gets a value indicating whether this instance is started.
///
public bool IsStarted { get; private set; }
///
/// Gets or sets the job designations of which the logger should log (supports multiple flags).
///
public JobDesignations JobDesignationFilter { get; set; }
///
/// Gets or sets the job run source when logging job runs.
///
public JobSource JobSource { get; set; }
///
/// Gets or sets a value indicating whether create job run files with job ticket information and more.
///
public bool CreateJobRunsFiles { get; set; }
///
/// Gets or sets the job runs files folder.
///
public String JobRunsFolder { get; set; }
#endregion
#region Constructors
///
/// Initializes a new instance of the class.
///
/// The machine operator.
public BasicJobRunsLogger(IMachineOperator machineOperator)
{
JobDesignationFilter = JobDesignations.Default | JobDesignations.SampleDye | JobDesignations.FineTuning;
MachineOperator = machineOperator;
Init();
}
#endregion
#region Private Methods
///
/// Initializes this instance.
///
private void Init()
{
MachineOperator.PrintingStarted -= Machine_PrintingStarted;
MachineOperator.PrintingStarted += Machine_PrintingStarted;
MachineOperator.PrintingCompleted -= Machine_PrintingCompleted;
MachineOperator.PrintingCompleted += Machine_PrintingCompleted;
MachineOperator.PrintingAborted -= Machine_PrintingAborted;
MachineOperator.PrintingAborted += Machine_PrintingAborted;
MachineOperator.PrintingFailed -= Machine_PrintingFailed;
MachineOperator.PrintingFailed += Machine_PrintingFailed;
MachineOperator.HeadCleaningEnded += MachineOperator_HeadCleaningEnded;
MachineOperator.MachineEventsStateProvider.EventsReceived -= MachineEventsStateProvider_EventsReceived;
MachineOperator.MachineEventsStateProvider.EventsReceived += MachineEventsStateProvider_EventsReceived;
_currentJobEvents = new List();
JobRunsFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"Twine", "Tango", "JobRuns Extended Info");
}
private void MachineEventsStateProvider_EventsReceived(object sender, IEnumerable events)
{
foreach (var item in events.ToList())
{
_currentJobEvents.Add(item);
}
}
private bool ShouldLog()
{
return IsStarted && _job != null && JobDesignationFilter.HasFlag(_job.Designation);
}
private void InsertJobRun(PrintingEventArgs e, JobRunStatus status, Exception exception)
{
if (!e.IsResumingJob)
{
if (ShouldLog())
{
if (e.Job.Guid == _job.Guid)
{
Task.Factory.StartNew(() =>
{
try
{
using (var db = ObservablesContext.CreateDefault())
{
var colorSpaces = db.ColorSpaces.ToList();
JobRun run = new JobRun();
run.UserGuid = _job.UserGuid;
run.StartDate = e.StartDate;
run.UploadingStartDate = e.UploadingStartTime;
run.HeatingStartDate = e.HeatingStartTime;
run.ActualStartDate = e.ActualStartTime;
run.EndDate = DateTime.UtcNow;
run.JobName = _job.Name;
run.Source = JobSource;
run.Designation = _job.Designation;
run.JobGuid = _job.Guid;
run.RmlGuid = _job.RmlGuid;
run.MachineGuid = _job.MachineGuid;
run.JobRunStatus = status;
run.EndPosition = e.JobHandler.Status.Progress;
run.NumberOfUnits = _job.NumberOfUnits;
run.JobLength = e.JobHandler.Status.TotalProgress;
run.JobLogicalLength = e.Job.Length;
run.LiquidQuantities = e.LiquidQuantities;
run.IsGradient = _job.Segments.Any(x => x.BrushStops.Count > 1);
run.GradientResolutionCm = MachineOperator.GradientGenerationConfiguration.ResolutionCM;
run.ActualStartPosition = e.Job.ResumeStartPosition;
run.ActualEndPosition = e.JobHandler.Status.ProgressMinusSettingUp;
if (_defaultMachine != null)
{
run.MachineType = _defaultMachine.MachineType;
}
else if (_job.Machine != null)
{
run.MachineType = _job.Machine.MachineType;
}
var jobFile = e.Job.ToJobFileWhenLoaded();
try
{
if (_job.Designation == JobDesignations.FineTuning)
{
jobFile.Segments.First().BrushStops.First().ColorSpaceGuid = colorSpaces.First(x => x.Code == (int)ColorSpaces.LAB).Guid;
}
}
catch (Exception ex)
{
LogManager.Log(ex, "Error setting brush stop color space to LAB on fine tuning job run (JobFileString).");
}
run.JobString = jobFile.ToString();
run.ApplicationVersion = Assembly.GetEntryAssembly().GetName().Version.ToString();
run.FirmwareVersion = MachineOperator.DeviceInformation?.Version;
run.CeVersion = _job.Rml.ColorConversionVersion.ToString();
run.ProcessParametersTableGuid = MachineOperator.CurrentProcessParameters?.Guid;
//Set liquid quantities
SetJobRunLiquidQuantities(run, run.LiquidQuantities);
if (exception != null)
{
run.FailedMessage = exception.FlattenMessage();
}
if (_job.Designation == JobDesignations.FineTuning)
{
try
{
run.FineTuningString = JsonConvert.SerializeObject(_job.VectorFineTuningRunModel);
}
catch (Exception ex)
{
LogManager.Log(ex, "Error serializing fine tuning model for job run.");
}
}
db.JobRuns.Add(run);
e.Job.LastRun = DateTime.UtcNow;
_job.LastRun = DateTime.UtcNow;
var job = db.Jobs.SingleOrDefault(x => x.Guid == _job.Guid);
if (job != null)
{
job.LastRun = DateTime.UtcNow;
}
LogManager.Log($"Inserting job run for '{run.JobName}'...\n{run.ToJsonString(nameof(JobRun.JobString), nameof(JobRun.LiquidQuantityString))}");
db.SaveChanges();
JobRunInfo jobRunInfo = new JobRunInfo();
jobRunInfo.JobRunID = run.ID;
jobRunInfo.JobTicket = _jobTicket;
jobRunInfo.Events = _currentJobEvents.Select(x => MachinesEventDTO.FromObservable(x)).ToList();
jobRunInfo.StartMachineStatus = _startMachineStatus;
jobRunInfo.EndMachineStatus = MachineOperator.MachineStatus?.Clone();
if (CreateJobRunsFiles)
{
try
{
Directory.CreateDirectory(JobRunsFolder);
String json = jobRunInfo.ToJsonString();
File.WriteAllText(Path.Combine(JobRunsFolder, $"{run.ID}.run"), json);
LogManager.Log($"JobRun extended info file '{run.ID}' created.");
}
catch (Exception ex)
{
LogManager.Log(ex, "Error creating job run extended info file.");
}
}
JobRunAvailable?.Invoke(this, new JobRunAvailableEventArgs() { JobRunInfo = jobRunInfo, JobRun = run });
}
}
catch (Exception ex)
{
LogManager.Log(ex, "Error logging the last job run to the database.");
}
});
}
}
}
else
{
using (ObservablesContext db = ObservablesContext.CreateDefault())
{
try
{
var run = db.JobRuns.OrderByDescending(x => x.LastUpdated).FirstOrDefault(x => x.JobGuid == _job.Guid);
if (run == null)
{
LogManager.Log($"Error updating job run by resumed job. Could not locate the existing job run by job guid '{_job.Guid}'.");
return;
}
run.LastUpdated = DateTime.UtcNow;
run.EndDate = DateTime.UtcNow;
run.JobRunStatus = status;
run.EndPosition = e.JobHandler.Status.Progress;
run.JobLength = e.JobHandler.Status.TotalProgress;
run.ActualEndPosition = e.JobHandler.Status.ProgressMinusSettingUp;
if (exception != null)
{
run.FailedMessage = exception.FlattenMessage();
}
else
{
run.FailedMessage = null;
}
var newQuantities = e.LiquidQuantities.ToList();
var oldQuantities = run.LiquidQuantities.ToList();
SetJobRunLiquidQuantities(run, newQuantities, update: true);
//Append liquid quantities string.
foreach (var newQuantity in newQuantities)
{
var oldQuantity = oldQuantities.FirstOrDefault(x => x.LiquidType == newQuantity.LiquidType);
if (oldQuantity != null)
{
oldQuantity.Quantity += newQuantity.Quantity;
}
else
{
oldQuantities.Add(newQuantity);
}
}
run.LiquidQuantities = oldQuantities;
db.SaveChanges();
//TODO: Needs to resubmit this to Azure! Then in Azure Distinct by job run ID, by job guid, by latest!
JobRunInfo jobRunInfo = null;
if (CreateJobRunsFiles)
{
try
{
String jobInfoPath = Path.Combine(JobRunsFolder, $"{run.ID}.run");
if (File.Exists(jobInfoPath))
{
String json = File.ReadAllText(jobInfoPath);
jobRunInfo = JsonConvert.DeserializeObject(json);
jobRunInfo.Events.AddRange(_currentJobEvents.Select(x => MachinesEventDTO.FromObservable(x)).ToList());
jobRunInfo.EndMachineStatus = MachineOperator.MachineStatus?.Clone();
File.WriteAllText(jobInfoPath, jobRunInfo.ToJsonString());
}
}
catch (Exception ex)
{
LogManager.Log(ex, "Error while trying to update jobrun info file after job resume.");
}
}
JobRunAvailable?.Invoke(this, new JobRunAvailableEventArgs() { JobRunInfo = jobRunInfo, JobRun = run });
}
catch (Exception ex)
{
LogManager.Log(ex, "Error while trying to update jobrun by resumed job.");
}
}
}
}
private void InsertHeadCleaningJobRun(HeadCleaningEndedEventArgs e)
{
if (IsStarted && _defaultMachine != null)
{
Task.Factory.StartNew(() =>
{
try
{
using (var db = ObservablesContext.CreateDefault())
{
JobRun run = new JobRun();
run.IsHeadCleaning = true;
run.StartDate = e.StartDate;
run.UploadingStartDate = e.StartDate;
run.HeatingStartDate = e.StartDate;
run.ActualStartDate = e.StartDate;
run.EndDate = DateTime.UtcNow;
run.JobName = "HEAD CLEANING";
run.Source = JobSource;
run.MachineGuid = _defaultMachine.Guid;
run.JobRunStatus = e.Status;
run.EndPosition = e.EndPosition;
run.JobLength = e.Length;
run.LiquidQuantities = e.LiquidQuantities;
SetJobRunLiquidQuantities(run, run.LiquidQuantities);
//if (exception != null)
//{
// run.FailedMessage = exception.FlattenMessage();
//}
db.JobRuns.Add(run);
LogManager.Log($"Inserting head cleaning job run...\n{run.ToJsonString(nameof(JobRun.JobString), nameof(JobRun.LiquidQuantityString))}");
db.SaveChanges();
}
}
catch (Exception ex)
{
LogManager.Log(ex, "Error logging the last head cleaning job run to the database.");
}
});
}
}
public void SetJobRunLiquidQuantities(JobRun run, IEnumerable quantities, bool update = false)
{
if (run == null || quantities == null) return;
var props = run.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.CanRead && p.CanWrite && p.PropertyType == typeof(long))
.Where(p => p.Name.EndsWith("Quantity", StringComparison.OrdinalIgnoreCase))
.ToDictionary(p => p.Name.ToLower(), p => p);
foreach (var q in quantities)
{
var key = (q.LiquidType.ToString() + "Quantity").ToLower();
if (!props.TryGetValue(key, out var prop))
{
LogManager.Log($"Could not locate liquid quantity field for {q.LiquidType} on JobRun object.", LogCategory.Error);
continue;
}
var currentVal = (long)(prop.GetValue(run) ?? 0L);
var newVal = update ? currentVal + q.Quantity : q.Quantity;
prop.SetValue(run, newVal);
}
}
#endregion
#region Public Methods
///
/// Starts the logger.
///
public void Start()
{
IsStarted = true;
}
///
/// Stops the logger.
///
public void Stop()
{
IsStarted = false;
}
///
/// Sets the head cleaning parameters.
///
/// The machine.
public void SetDefaultMachine(Machine machine)
{
_defaultMachine = machine;
}
#endregion
#region Event Handlers
private void Machine_PrintingFailed(object sender, PrintingFailedEventArgs e)
{
InsertJobRun(e, JobRunStatus.Failed, e.Exception);
}
private void Machine_PrintingAborted(object sender, PrintingEventArgs e)
{
InsertJobRun(e, JobRunStatus.Aborted, null);
}
private void Machine_PrintingCompleted(object sender, PrintingEventArgs e)
{
InsertJobRun(e, JobRunStatus.Completed, null);
}
private void Machine_PrintingStarted(object sender, PrintingEventArgs e)
{
_job = e.Job;
_currentJobEvents = new List();
_startMachineStatus = MachineOperator.MachineStatus?.Clone();
_jobTicket = e.JobHandler.JobTicket;
}
private void MachineOperator_HeadCleaningEnded(object sender, HeadCleaningEndedEventArgs e)
{
InsertHeadCleaningJobRun(e);
}
#endregion
}
}