using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading;
namespace m3uTool
{
///
/// A generic encoder. Creates an external process, sends it argument options,
/// and acts as a wrapper for streaming reads and writes
///
public abstract class ProcessStreamWrapper : IDisposable
{
#region Protected Variables
///
/// The arguments to use for the process
///
protected ProcessArguments mProcessArguments;
///
/// The process
///
protected Process mProcess;
///
/// How much data to buffer when reading and writing
///
protected const int mBufferLength = 1000;
///
/// The output stream writer. If it is null, it is assumed
/// that the process will take care of output itself.
///
protected StreamWriter mOutputStreamWriter = null;
///
/// Progress call-back, for status updates outside this class.
///
protected IProgressCallback mProgressCallback = new DoNothingProgressCallback();
#endregion
#region Properties
///
/// Gets or sets the progress callback.
///
/// The progress callback.
public IProgressCallback ProgressCallback
{
get { return mProgressCallback; }
set { mProgressCallback = value; }
}
///
/// Gets the process executable filename, such as "LAME.EXE", "FAAC.EXE", etc.
///
/// The process executable filename.
protected abstract string ProcessExecutableFilename
{
get;
}
#endregion
#region Constructor
///
/// Initializes a new instance of the class.
///
/// The enc opt.
public ProcessStreamWrapper(ProcessArguments encOpt) : this (encOpt, null)
{
}
///
/// Initializes a new instance of the class.
///
/// The enc opt.
/// Output to this stream
public ProcessStreamWrapper(ProcessArguments encOpt, Stream outputStream)
{
mProcessArguments = encOpt;
mProcess = CreateProcess(mProcessArguments, ProcessExecutableFilename);
if (outputStream != null)
{
mOutputStreamWriter = new StreamWriter(outputStream, Encoding.Default);
mProcess.StartInfo.RedirectStandardOutput = true;
mProcess.StartInfo.StandardOutputEncoding = Encoding.Default;
}
mProcess.StartInfo.RedirectStandardError = true;
mProcess.Exited += new EventHandler(Process_Exited);
mProcess.Start();
if (mOutputStreamWriter != null)
{
Thread thread = new Thread(new ThreadStart(GetStandardOutput));
thread.Start();
}
Thread standardErrorThread = new Thread(new ThreadStart(GetStandardError));
standardErrorThread.Start();
}
#endregion
#region Static methods
///
/// Creates a process given an executable and some process arguments
///
/// The enc opt.
/// The executable filename.
///
private static Process CreateProcess(ProcessArguments processArguments, string executable)
{
Process process = new Process();
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = executable;
process.StartInfo.Arguments = processArguments.GetCommandLineArguments();
process.StartInfo.WorkingDirectory = Environment.CurrentDirectory;
process.StartInfo.CreateNoWindow = true;
process.EnableRaisingEvents = true;
return process;
}
#endregion
#region Event handlers
///
/// Handles the Exited event of the Process.
///
/// The source of the event.
/// The instance containing the event data.
void Process_Exited(object sender, EventArgs e)
{
mProgressCallback.SetText("Process_Exited");
}
#endregion
#region Utility methods
private static int GetPercentDone(long previousLeft, long currentLeft, long currentDone, int lastDone)
{
// if currentLeft > previousLeft - lastDone,
// addedLeft = previousLeft - lastDone - currentLeft
// totalLength = previousLeft + currentDone + addedLeft
long totalLength = currentLeft;
if (currentLeft != previousLeft)
{
long leftDelta = previousLeft - lastDone - currentLeft;
totalLength = previousLeft + currentDone + leftDelta;
}
try
{
return Convert.ToInt32((currentDone / (double)totalLength) * 100);
}
catch(Exception ex)
{
Debug.WriteLine(ex.ToString());
}
return 0;
}
#endregion
#region Public methods
///
/// Sends the data to the underlying process through its input stream.
///
/// The data to encode
/// How many characters to write
/// The index to begin writing from
public void ProcessInput(char[] buffer, int startIndex, int count)
{
mProcess.StandardInput.Write(buffer, startIndex, count);
}
///
/// Sends the data from the input stream to the process
///
///
public void ProcessInput(Stream inputStream)
{
if (!inputStream.CanRead)
throw new ArgumentException("Can't read from inputStream");
mProgressCallback.SetText("ProcessInput: StreamReader sr");
long inputCount = inputStream.Length;
long previousInputCount = inputStream.Length;
long wroteCount = 0;
int percentageComplete = 0;
StreamReader sr = new StreamReader(inputStream, Encoding.Default);
//using (StreamReader sr = new StreamReader(inputStream, Encoding.Default))
{
mProgressCallback.Begin(0, 100);
mProgressCallback.SetText(mProcessArguments.GetCommandLineArguments());
char[] buffer = new char[mBufferLength];
while (!sr.EndOfStream)
{
int readLength = sr.Read(buffer, 0, mBufferLength);
ProcessInput(buffer, 0, readLength);
// update progress
wroteCount += readLength;
previousInputCount = inputCount;
inputCount = inputStream.Length;
int currentPercentageComplete = GetPercentDone(previousInputCount, inputCount, wroteCount, readLength);
if (currentPercentageComplete != percentageComplete)
{
percentageComplete = currentPercentageComplete;
mProgressCallback.StepTo(percentageComplete);
}
}
}
mInputDone = true;
mProgressCallback.SetText("ProcessInput: Done. " + String.Format("Wrote: {0}", wroteCount));
}
///
/// Open this file and encode it.
///
///
public void ProcessInput(string filename)
{
using (FileStream inputStream = File.OpenRead(filename))
{
ProcessInput(inputStream);
}
}
#endregion
#region Threaded methods
///
/// TEMP: this should be removed once GetStandardOutput is working
///
bool mInputDone = false;
///
/// Collects all the data from Process.StandardOutput and sends it to the outputstream writer
///
private void GetStandardOutput()
{
int readTotal = 0;
char[] writeBuffer = new char[mBufferLength];
try
{
while (!mProcess.StandardOutput.EndOfStream)
//while (mProcess.StandardOutput.Peek() > -1)
{
mProgressCallback.SetText("- Before read");
int readLength = mProcess.StandardOutput.Read(writeBuffer, 0, writeBuffer.Length);
mProgressCallback.SetText("- After read: " + readLength);
mOutputStreamWriter.Write(writeBuffer, 0, readLength);
if (mInputDone)
mProgressCallback.SetText(" StandardOutput: " + new string(writeBuffer, 0, readLength));
readTotal += readLength;
mProgressCallback.SetText("Read total = " + readTotal);
//mProcess.StandardInput.Flush();
//mProcess.StandardOutput.BaseStream.Flush();
}
}
catch(Exception ex)
{
mProgressCallback.SetText(ex.ToString());
}
// mOutputStreamWriter.Flush();
mProgressCallback.SetText("GetStandardOutput() " + "Done!" + " StandardOutput.EndOfStream = " + mProcess.StandardOutput.EndOfStream);
}
/* Asynchronous implementation of GetStandardOutput
//int mReadTotal = 0;
//private byte[] mBuffer = new byte[mBufferLength];
//private void GetStandardOutputAsync()
//{
// mProcess.StandardOutput.BaseStream.BeginRead(mBuffer, 0, mBufferLength, GetStandardOutputCallback, 0);
//}
//private void GetStandardOutputCallback(IAsyncResult asyncResult)
//{
// mProgressCallback.SetText("- Before read:");
// int readLength = mProcess.StandardOutput.BaseStream.EndRead(asyncResult);
// mProgressCallback.SetText("- After read:" + readLength);
// mOutputStreamWriter.BaseStream.Write(mBuffer, 0, readLength);
// mReadTotal += readLength;
// mProgressCallback.SetText("Read total = " + mReadTotal);
// mProcess.StandardOutput.BaseStream.BeginRead(mBuffer, 0, mBufferLength, GetStandardOutputCallback, 0);
//}
*/
///
/// Collects all the data from Process.StandardError and sends it to the progress callback
///
private void GetStandardError()
{
try
{
while (!mProcess.StandardError.EndOfStream)
{
string line = mProcess.StandardError.ReadLine();
mProgressCallback.SetText("GetStandardError() " + line);
}
}
catch (Exception ex)
{
mProgressCallback.SetText(ex.ToString());
}
mProgressCallback.SetText("GetStandardError() " + "Done!");
}
#endregion
#region IDisposable Members
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
public void Dispose()
{
int count = 0;
mProgressCallback.SetText("Dispose()");
while (mProcess != null && !mProcess.HasExited)
{
mProcess.WaitForExit(2);
if (count++ > mBufferLength && !mProcess.HasExited)
{
mProcess.Kill();
mProgressCallback.SetText("!!! Process.Kill()");
}
else
{
// mProgressCallback.SetText("Writing EOF");
mProcess.StandardInput.Write(new string(new char[] { (char)0x1A, '\n' }));
}
}
mProgressCallback.SetText("Dispose() with exit code " + mProcess.ExitCode);
}
#endregion
}
}