using System; 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 /// /// 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; /// /// The process /// protected Process mProcess; /// /// The arguments to use for the process /// protected ProcessArguments mProcessArguments; /// /// 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 += Process_Exited; mProcess.Start(); if (mOutputStreamWriter != null) { var thread = new Thread(GetStandardOutput); thread.Start(); } var standardErrorThread = new Thread(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) { var 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. private 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; var sr = new StreamReader(inputStream, Encoding.Default); //using (StreamReader sr = new StreamReader(inputStream, Encoding.Default)) { mProgressCallback.Begin(0, 100); mProgressCallback.SetText(mProcessArguments.GetCommandLineArguments()); var 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 /// private bool mInputDone; /// /// Collects all the data from Process.StandardOutput and sends it to the outputstream writer /// private void GetStandardOutput() { int readTotal = 0; var 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) 0x1A, '\n'})); } } mProgressCallback.SetText("Dispose() with exit code " + mProcess.ExitCode); } #endregion } }