using Google.Protobuf;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using Tango.BL.Entities;
using Tango.Core;
using Tango.Core.Helpers;
using Tango.Core.IO;
using Tango.Logging;
using Tango.PMR.Diagnostics;
using Tango.Serialization;
namespace Tango.Integration.Diagnostics
{
///
/// Represents a Tango machine diagnostics file recorder capable of recording diagnostics data coming from the machine to a file.
/// This file can then be played using the .
///
///
///
public class DiagnosticsFileRecorder : ExtendedObject, IDisposable
{
private ConcurrentQueue _frames; //Holds the queue of data frames ready to be written to file.
private Thread _recordingThread; //Holds the recording thread.
private TemporaryFile _tempDataFile; //Holds the temporary recording file name.
private FileStream _dataFileStream; //Holds the temporary recording file stream.
private TaskCompletionSource _stopCompletionSource; //Holds the "Stop" async method completion source.
private DiagnosticsTimeCodeChannel _timeCodeChannel; //Holds the diagnostics time code channel.
private List _events;
private Stopwatch _stopWatch; //Holds the stop watch for keeping tracks over frames time stamps.
private DateTime _lastVideoPush;
private List _videoFrames;
#region Constructors
public DiagnosticsFileRecorder()
{
_lastVideoPush = DateTime.Now;
VideoFPS = 10;
VideoQuality = 30;
}
#endregion
#region Properties
private bool _isRecording;
///
/// Gets a value indicating whether the recorder is currently recording.
///
public bool IsRecording
{
get { return _isRecording; }
private set { _isRecording = value; RaisePropertyChangedAuto(); }
}
private long _totalFramesRecorded;
///
/// Gets the total frames recorded.
///
public long TotalFramesRecorded
{
get { return _totalFramesRecorded; }
private set { _totalFramesRecorded = value; RaisePropertyChanged(nameof(TotalFramesRecorded)); }
}
private long _totalVideoFramesRecorded;
///
/// Gets or sets the total video frames recorded.
///
public long TotalVideoFramesRecorded
{
get { return _totalVideoFramesRecorded; }
private set { _totalVideoFramesRecorded = value; RaisePropertyChangedAuto(); }
}
private int _videoQuality;
///
/// Gets or sets the video quality.
///
public int VideoQuality
{
get { return _videoQuality; }
set { _videoQuality = value; RaisePropertyChangedAuto(); }
}
private long _totalBytesDataRecorded;
///
/// Gets the total bytes recorded.
///
public long TotalDataBytesRecorded
{
get { return _totalBytesDataRecorded; }
private set { _totalBytesDataRecorded = value; RaisePropertyChanged(nameof(TotalDataBytesRecorded)); }
}
private long _totalBytesRecorded;
///
/// Gets or sets the total bytes recorded.
///
public long TotalBytesRecorded
{
get { return _totalBytesRecorded; }
set { _totalBytesRecorded = value; RaisePropertyChangedAuto(); }
}
///
/// Gets the recording time.
///
public TimeSpan RecordingTime
{
get { return _stopWatch != null ? _stopWatch.Elapsed : TimeSpan.Zero; }
}
///
/// Gets or sets the video recording frame rate.
///
public int VideoFPS { get; set; }
#endregion
#region Public Methods
///
/// Start recording. The actual recording will start only on the next call.
///
public void Start()
{
if (!IsRecording)
{
if (_stopWatch != null)
{
_stopWatch.Stop();
_stopWatch = null;
}
_tempDataFile = TemporaryManager.CreateFile();
_frames = new ConcurrentQueue();
_events = new List();
_timeCodeChannel = new DiagnosticsTimeCodeChannel();
TotalDataBytesRecorded = 0;
TotalFramesRecorded = 0;
TotalVideoFramesRecorded = 0;
TotalBytesRecorded = 0;
_dataFileStream = _tempDataFile.CreateStream();
IsRecording = true;
_recordingThread = new Thread(RecordingThreadMethod);
_recordingThread.IsBackground = true;
_recordingThread.Start();
}
}
///
/// Stop recording.
///
///
public Task Stop()
{
if (IsRecording)
{
IsRecording = false;
_stopCompletionSource = new TaskCompletionSource();
return _stopCompletionSource.Task;
}
else
{
return Task.FromResult(new object());
}
}
///
/// Writes a machine event to the diagnostics file.
///
/// The data.
public void Write(MachinesEvent ev)
{
_events.Add(new DiagnosticsFileEvent(ev));
}
///
/// Writes a diagnostics packet to the recording.
///
/// The data.
public void Write(StartDiagnosticsResponse data)
{
DataFileFrame frame = new DataFileFrame();
frame.StartDiagnosticsResponse = data;
Write(frame);
}
///
/// Writes the specified video frames. Each video frame is a different camera!
///
/// The video frames.
public void Write(IEnumerable videoFrames)
{
if (!IsRecording) throw LogManager.Log(new InvalidOperationException("Please start the recorder before attempting to push data."));
int milli = 1000 / VideoFPS;
if (DateTime.Now >= _lastVideoPush.AddMilliseconds(milli))
{
_videoFrames = videoFrames.ToList();
_lastVideoPush = DateTime.Now;
}
}
///
/// Writes the specified data frame to the recording.
///
/// The frame.
/// Please start the recorder before attempting to push data.
public void Write(DataFileFrame frame)
{
if (!IsRecording) throw LogManager.Log(new InvalidOperationException("Please start the recorder before attempting to push data."));
if (_stopWatch == null)
{
_stopWatch = new Stopwatch();
_stopWatch.Start();
}
frame.Milliseconds = (int)_stopWatch.Elapsed.TotalMilliseconds;
_frames.Enqueue(frame);
RaisePropertyChanged(nameof(RecordingTime));
}
///
/// Saves the current recording to the specified file. (Use only after calling "Stop").
///
/// Name of the file.
///
///
/// Cannot save a recording file while recording.
/// or
/// No data was captured during recording time. Cannot save recording
///
public Task Save(String fileName)
{
if (IsRecording)
{
throw LogManager.Log(new InvalidOperationException("Cannot save a recording file while recording."));
}
if (_timeCodeChannel.Frames.Count == 0)
{
throw LogManager.Log(new InvalidOperationException("No data was captured during recording time. Cannot save recording"));
}
return Task.Factory.StartNew(() =>
{
BinaryDataSerializer serializer = new BinaryDataSerializer();
_timeCodeChannel.Events = _events;
byte[] timeCodeData = serializer.SerializeToBytes(_timeCodeChannel);
using (FileStream fs = new FileStream(fileName, FileMode.Create))
{
BinaryWriter writer = new BinaryWriter(fs);
writer.Write(timeCodeData.Length);
writer.Write(timeCodeData, 0, timeCodeData.Length);
using (Stream dataFileStream = _tempDataFile.CreateStream())
{
dataFileStream.CopyTo(fs);
}
}
});
}
#endregion
#region Recording Thread
///
/// Handles the recording thread.
///
private void RecordingThreadMethod()
{
while (IsRecording)
{
while (_frames.Count > 0)
{
DataFileFrame frame = null;
if (_frames.TryDequeue(out frame))
{
DiagnosticsTimeCodeChannelFrame timeCodeFrame = new DiagnosticsTimeCodeChannelFrame();
timeCodeFrame.Position = TotalDataBytesRecorded;
if (_videoFrames != null && _videoFrames.Count > 0)
{
foreach (var videoFrame in _videoFrames)
{
if (videoFrame != null)
{
var bytes = ByteString.CopyFrom(videoFrame.ToJpegBytes(VideoQuality));
frame.VideoFrames.Add(bytes);
TotalBytesRecorded += bytes.Length;
}
}
_videoFrames = null;
TotalVideoFramesRecorded++;
}
byte[] data = frame.ToBytes();
_dataFileStream.Write(data, 0, data.Length);
TotalFramesRecorded++;
TotalDataBytesRecorded += data.Length;
TotalBytesRecorded += data.Length;
timeCodeFrame.Milliseconds = frame.Milliseconds;
timeCodeFrame.FrameLength = data.Length;
_timeCodeChannel.Frames.Add(timeCodeFrame);
}
}
Thread.Sleep(10);
}
_dataFileStream.Dispose();
if (_stopCompletionSource != null)
{
_stopCompletionSource.SetResult(new object());
}
}
#endregion
#region IDisposable
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose()
{
_tempDataFile.Delete();
}
#endregion
}
}