/// <summary> /// Post processes the image. /// </summary> /// <param name="stream">The source image stream.</param> /// <param name="extension">The image extension.</param> /// <returns> /// The <see cref="MemoryStream"/>. /// </returns> public static MemoryStream PostProcessImage(MemoryStream stream, string extension) { // Create a source temporary file with the correct extension. long length = stream.Length; string tempFile = Path.GetTempFileName(); string sourceFile = Path.ChangeExtension(tempFile, extension); File.Move(tempFile, sourceFile); // Save the input stream to a temp file for post processing. using (FileStream fileStream = File.Create(sourceFile)) { stream.CopyTo(fileStream); } PostProcessingResultEventArgs result = RunProcess(sourceFile, length); if (result != null && result.Saving > 0) { using (FileStream fileStream = File.OpenRead(sourceFile)) { // Replace stream contents. stream.SetLength(0); fileStream.CopyTo(stream); } } // Cleanup File.Delete(sourceFile); stream.Position = 0; return(stream); }
/// <summary> /// Post processes the image. /// </summary> /// <param name="context">The current context.</param> /// <param name="stream">The source image stream.</param> /// <param name="extension">The image extension.</param> /// <returns> /// The <see cref="MemoryStream"/>. /// </returns> public static MemoryStream PostProcessImage(HttpContext context, MemoryStream stream, string extension) { if (!PostProcessorBootstrapper.Instance.IsInstalled) { return(stream); } // Create a temporary source file with the correct extension. long length = stream.Length; string tempSourceFile = Path.GetTempFileName(); string sourceFile = Path.ChangeExtension(tempSourceFile, extension); File.Move(tempSourceFile, sourceFile); // Give our destination file a unique name. string destinationFile = sourceFile.Replace(extension, "-out" + extension); // Save the input stream to our source temp file for post processing. using (FileStream fileStream = File.Create(sourceFile)) { stream.CopyTo(fileStream); } PostProcessingResultEventArgs result = RunProcess(context.Request.Unvalidated.Url, sourceFile, destinationFile, length); // If our result is good and a saving is made we replace our original stream contents with our new compressed file. if (result != null && result.ResultFileSize > 0 && result.Saving > 0) { using (FileStream fileStream = File.OpenRead(destinationFile)) { stream.SetLength(0); fileStream.CopyTo(stream); } } // Cleanup the temp files. try { // Ensure files exist, are not read only, and delete if (File.Exists(sourceFile)) { File.SetAttributes(sourceFile, FileAttributes.Normal); File.Delete(sourceFile); } if (File.Exists(destinationFile)) { File.SetAttributes(destinationFile, FileAttributes.Normal); File.Delete(destinationFile); } } catch { // Normally a No no, but logging would be excessive + temp files get cleaned up eventually. } stream.Position = 0; return(stream); }
/// <summary> /// Runs the process to optimize the images. /// </summary> /// <param name="sourceFile">The source file.</param> /// <param name="length">The source file length in bytes.</param> /// <returns> /// The <see cref="Task{PostProcessingResultEventArgs}"/> containing post-processing information. /// </returns> private static PostProcessingResultEventArgs RunProcess(string sourceFile, long length) { PostProcessingResultEventArgs result = null; ProcessStartInfo start = new ProcessStartInfo("cmd") { WindowStyle = ProcessWindowStyle.Hidden, WorkingDirectory = PostProcessorBootstrapper.Instance.WorkingPath, Arguments = GetArguments(sourceFile, length), UseShellExecute = false, CreateNoWindow = true }; if (string.IsNullOrWhiteSpace(start.Arguments)) { return(null); } int elapsedTime = 0; bool eventHandled = false; try { Process process = new Process { StartInfo = start, EnableRaisingEvents = true }; process.Exited += (sender, args) => { result = new PostProcessingResultEventArgs(sourceFile, length); process.Dispose(); eventHandled = true; }; process.Start(); } catch (System.ComponentModel.Win32Exception) { // Some security policies don't allow execution of programs in this way return(null); } // Wait for Exited event, but not more than 5 seconds. const int SleepAmount = 100; while (!eventHandled) { elapsedTime += SleepAmount; if (elapsedTime > 5000) { break; } Thread.Sleep(SleepAmount); } return(result); }
/// <summary> /// Runs the process to optimize the images. /// </summary> /// <param name="sourceFile">The source file.</param> /// <param name="length">The source file length in bytes.</param> /// <returns> /// The <see cref="Task{PostProcessingResultEventArgs}"/> containing post-processing information. /// </returns> private static PostProcessingResultEventArgs RunProcess(string sourceFile, long length) { PostProcessingResultEventArgs result = null; ProcessStartInfo start = new ProcessStartInfo("cmd") { WindowStyle = ProcessWindowStyle.Hidden, WorkingDirectory = PostProcessorBootstrapper.Instance.WorkingPath, Arguments = GetArguments(sourceFile, length), UseShellExecute = false, CreateNoWindow = true }; if (string.IsNullOrWhiteSpace(start.Arguments)) { return null; } int elapsedTime = 0; bool eventHandled = false; try { Process process = new Process { StartInfo = start, EnableRaisingEvents = true }; process.Exited += (sender, args) => { result = new PostProcessingResultEventArgs(sourceFile, length); process.Dispose(); eventHandled = true; }; process.Start(); } catch (System.ComponentModel.Win32Exception) { // Some security policies don't allow execution of programs in this way return null; } // Wait for Exited event, but not more than 5 seconds. const int SleepAmount = 100; while (!eventHandled) { elapsedTime += SleepAmount; if (elapsedTime > 5000) { break; } Thread.Sleep(SleepAmount); } return result; }
/// <summary> /// Runs the process to optimize the images. /// </summary> /// <param name="url">The current request url.</param> /// <param name="sourceFile">The source file.</param> /// <param name="destinationFile">The destination file.</param> /// <param name="length">The source file length in bytes.</param> /// <returns> /// The <see cref="System.Threading.Tasks.Task"/> containing post-processing information. /// </returns> private static PostProcessingResultEventArgs RunProcess(Uri url, string sourceFile, string destinationFile, long length) { // Create a new, hidden process to run our postprocessor command. // We allow no more than the set timeout (default 5 seconds) for the process to run before killing it to prevent blocking the app. int timeout = PostProcessorBootstrapper.Instance.Timout; PostProcessingResultEventArgs result = null; string arguments = GetArguments(sourceFile, destinationFile, length); if (string.IsNullOrWhiteSpace(arguments)) { // Not a file we can post process. return(null); } ProcessStartInfo start = new ProcessStartInfo("cmd") { WindowStyle = ProcessWindowStyle.Hidden, WorkingDirectory = PostProcessorBootstrapper.Instance.WorkingPath, Arguments = arguments, UseShellExecute = false, CreateNoWindow = true }; Process process = null; try { process = new Process { StartInfo = start, EnableRaisingEvents = true }; // Process has completed successfully within the time limit. process.Exited += (sender, args) => { result = new PostProcessingResultEventArgs(destinationFile, length); }; process.Start(); // Wait for processing to finish, but not more than our timeout. if (!process.WaitForExit(timeout)) { process.Kill(); ImageProcessorBootstrapper.Instance.Logger.Log( typeof(PostProcessor), $"Unable to post process image for request {url} within {timeout}ms. Original image returned."); } } catch (Exception ex) { // Some security policies don't allow execution of programs in this way ImageProcessorBootstrapper.Instance.Logger.Log(typeof(PostProcessor), ex.Message); } finally { // Make sure we always dispose and release process?.Dispose(); } return(result); }
/// <summary> /// Post processes the image. /// </summary> /// <param name="context">The current context.</param> /// <param name="stream">The source image stream.</param> /// <param name="extension">The image extension.</param> /// <returns> /// The <see cref="MemoryStream" />. /// </returns> public static async Task <MemoryStream> PostProcessImageAsync(HttpContext context, MemoryStream stream, string extension) { if (!PostProcessorBootstrapper.Instance.IsInstalled) { return(stream); } string sourceFile = null, destinationFile = null; var timeout = PostProcessorBootstrapper.Instance.Timout; try { // Save source file length var length = stream.Length; // Create a temporary source file with the correct extension var tempSourceFile = Path.GetTempFileName(); sourceFile = Path.ChangeExtension(tempSourceFile, extension); File.Move(tempSourceFile, sourceFile); // Give our destination file a unique name destinationFile = sourceFile.Replace(extension, "-out" + extension); // Get processes to start var processStartInfos = GetProcessStartInfos(extension, length, sourceFile, destinationFile).ToList(); if (processStartInfos.Count > 0) { // Save the input stream to our source temp file for post processing using (var fileStream = File.Create(sourceFile)) { await stream.CopyToAsync(fileStream).ConfigureAwait(false); } // Create cancellation token with timeout using (var cancellationTokenSource = new CancellationTokenSource(timeout)) { foreach (var processStartInfo in processStartInfos) { // Use destination file as new source (if previous process created one). if (File.Exists(destinationFile)) { File.Copy(destinationFile, sourceFile, true); } // Set default properties processStartInfo.FileName = Path.Combine(PostProcessorBootstrapper.Instance.WorkingPath, processStartInfo.FileName); processStartInfo.CreateNoWindow = true; processStartInfo.WindowStyle = ProcessWindowStyle.Hidden; // Run process using (var processResults = await ProcessEx.RunAsync(processStartInfo, cancellationTokenSource.Token).ConfigureAwait(false)) { if (processResults.ExitCode == 1) { ImageProcessorBootstrapper.Instance.Logger.Log(typeof(PostProcessor), $"Unable to post process image for request {context.Request.Unvalidated.Url}, {processStartInfo.FileName} {processStartInfo.Arguments} exited with error code 1. Original image returned."); break; } } } } // Save result if (!File.Exists(destinationFile)) { File.Copy(sourceFile, destinationFile, true); } var result = new PostProcessingResultEventArgs(destinationFile, length); if (result.ResultFileSize > 0 && result.Saving > 0) { using (var fileStream = File.OpenRead(destinationFile)) { stream.SetLength(0); await fileStream.CopyToAsync(stream).ConfigureAwait(false); } } } } catch (OperationCanceledException) { ImageProcessorBootstrapper.Instance.Logger.Log(typeof(PostProcessor), $"Unable to post process image for request {context.Request.Unvalidated.Url} within {timeout}ms. Original image returned."); } catch (Exception ex) { // Some security policies don't allow execution of programs in this way ImageProcessorBootstrapper.Instance.Logger.Log(typeof(PostProcessor), ex.Message); } finally { // Always cleanup files try { // Ensure files exist, are not read only, and delete if (sourceFile != null && File.Exists(sourceFile)) { File.SetAttributes(sourceFile, FileAttributes.Normal); File.Delete(sourceFile); } if (destinationFile != null && File.Exists(destinationFile)) { File.SetAttributes(destinationFile, FileAttributes.Normal); File.Delete(destinationFile); } } catch { // Normally a no no, but logging would be excessive + temp files get cleaned up eventually. } } // ALways return stream (even if it's not optimized) stream.Position = 0; return(stream); }