366 lines
11 KiB
C#
366 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace m3uTool
|
|
{
|
|
/// <summary>
|
|
/// A generic encoder. Creates an external process, sends it argument options,
|
|
/// and acts as a wrapper for streaming reads and writes
|
|
/// </summary>
|
|
public abstract class ProcessStreamWrapper : IDisposable
|
|
{
|
|
#region Protected Variables
|
|
/// <summary>
|
|
/// The arguments to use for the process
|
|
/// </summary>
|
|
protected ProcessArguments mProcessArguments;
|
|
|
|
/// <summary>
|
|
/// The process
|
|
/// </summary>
|
|
protected Process mProcess;
|
|
|
|
/// <summary>
|
|
/// How much data to buffer when reading and writing
|
|
/// </summary>
|
|
protected const int mBufferLength = 1000;
|
|
|
|
/// <summary>
|
|
/// The output stream writer. If it is null, it is assumed
|
|
/// that the process will take care of output itself.
|
|
/// </summary>
|
|
protected StreamWriter mOutputStreamWriter = null;
|
|
|
|
/// <summary>
|
|
/// Progress call-back, for status updates outside this class.
|
|
/// </summary>
|
|
protected IProgressCallback mProgressCallback = new DoNothingProgressCallback();
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Gets or sets the progress callback.
|
|
/// </summary>
|
|
/// <value>The progress callback.</value>
|
|
public IProgressCallback ProgressCallback
|
|
{
|
|
get { return mProgressCallback; }
|
|
set { mProgressCallback = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the process executable filename, such as "LAME.EXE", "FAAC.EXE", etc.
|
|
/// </summary>
|
|
/// <value>The process executable filename.</value>
|
|
protected abstract string ProcessExecutableFilename
|
|
{
|
|
get;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ProcessStreamWrapper"/> class.
|
|
/// </summary>
|
|
/// <param name="encOpt">The enc opt.</param>
|
|
public ProcessStreamWrapper(ProcessArguments encOpt) : this (encOpt, null)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="ProcessStreamWrapper"/> class.
|
|
/// </summary>
|
|
/// <param name="encOpt">The enc opt.</param>
|
|
/// <param name="outputStream">Output to this stream</param>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Creates a process given an executable and some process arguments
|
|
/// </summary>
|
|
/// <param name="processArguments">The enc opt.</param>
|
|
/// <param name="executable">The executable filename.</param>
|
|
/// <returns></returns>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Handles the Exited event of the Process.
|
|
/// </summary>
|
|
/// <param name="sender">The source of the event.</param>
|
|
/// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Sends the data to the underlying process through its input stream.
|
|
/// </summary>
|
|
/// <param name="buffer">The data to encode</param>
|
|
/// <param name="count">How many characters to write</param>
|
|
/// <param name="startIndex">The index to begin writing from</param>
|
|
public void ProcessInput(char[] buffer, int startIndex, int count)
|
|
{
|
|
mProcess.StandardInput.Write(buffer, startIndex, count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends the data from the input stream to the process
|
|
/// </summary>
|
|
/// <param name="inputStream"></param>
|
|
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));
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Open this file and encode it.
|
|
/// </summary>
|
|
/// <param name="filename"></param>
|
|
public void ProcessInput(string filename)
|
|
{
|
|
using (FileStream inputStream = File.OpenRead(filename))
|
|
{
|
|
ProcessInput(inputStream);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Threaded methods
|
|
|
|
|
|
/// <summary>
|
|
/// TEMP: this should be removed once GetStandardOutput is working
|
|
/// </summary>
|
|
bool mInputDone = false;
|
|
|
|
/// <summary>
|
|
/// Collects all the data from Process.StandardOutput and sends it to the outputstream writer
|
|
/// </summary>
|
|
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);
|
|
//}
|
|
*/
|
|
|
|
/// <summary>
|
|
/// Collects all the data from Process.StandardError and sends it to the progress callback
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
|
|
/// </summary>
|
|
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
|
|
}
|
|
}
|