OSDN Git Service

Refactored the ProcessRunner and PasswordGen classes.
authorLoRd_MuldeR <mulder2@gmx.de>
Thu, 10 Feb 2022 21:55:52 +0000 (22:55 +0100)
committerLoRd_MuldeR <mulder2@gmx.de>
Thu, 10 Feb 2022 21:55:52 +0000 (22:55 +0100)
gui/Process/ExecutableHelper.cs
gui/Process/PasswordGen.cs
gui/Process/SlunkCryptRunner.cs
gui/SlunkCryptGUI.xaml.cs
gui/Utilities/ProcessRunner.cs

index 31facdc..11e4d2b 100644 (file)
@@ -59,21 +59,31 @@ namespace com.muldersoft.slunkcrypt.gui.process
 
         private static bool CheckExecutableFile(ref FileStream executableFile, string appBaseDirectory, string suffix)
         {
+            bool success = false;
             try
             {
                 executableFile = new FileStream(Path.Combine(appBaseDirectory, String.Format(FILENAME_FORMAT, suffix)), FileMode.Open, FileAccess.Read, FileShare.Read);
-                Version appVersion = VersionInfo.Version;
-                FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(executableFile.Name);
-                if (string.Equals(fileVersion.FileDescription, "SlunkCrypt", StringComparison.OrdinalIgnoreCase) &&
-                    string.Equals(fileVersion.CompanyName, "Muldersoft", StringComparison.OrdinalIgnoreCase) &&
-                    (fileVersion.FileMajorPart == appVersion.Major) && (fileVersion.FileMinorPart == appVersion.Minor))
+                try
                 {
-                    return true;
+                    Version appVersion = VersionInfo.Version;
+                    FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(executableFile.Name);
+                    if (string.Equals(fileVersion.FileDescription, "SlunkCrypt", StringComparison.OrdinalIgnoreCase) &&
+                        string.Equals(fileVersion.CompanyName, "Muldersoft", StringComparison.OrdinalIgnoreCase) &&
+                        (fileVersion.FileMajorPart == appVersion.Major) && (fileVersion.FileMinorPart == appVersion.Minor))
+                    {
+                        success = true;
+                    }
+                }
+                finally
+                {
+                    if (!success)
+                    {
+                        executableFile.Dispose(); /*clean-up*/
+                    }
                 }
-                executableFile.Dispose(); /*clean-up*/
             }
             catch { }
-            return false;
+            return success;
         }
     }
 }
index 9592b0f..cb13037 100644 (file)
@@ -4,19 +4,19 @@
 /******************************************************************************/
 
 using System;
-using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
-using System.Text;
 using System.Threading.Tasks;
 
+using com.muldersoft.slunkcrypt.gui.utils;
+
 namespace com.muldersoft.slunkcrypt.gui.process
 {
     public static class PasswordGen
     {
-        private static readonly TimeSpan TIMEOUT_MSEC = TimeSpan.FromSeconds(90);
+        private static readonly TimeSpan TIMEOUT = TimeSpan.FromSeconds(180);
 
-        private const string COMMAND_PASSWRD = "-p {0:D}";
+        private const string COMMAND_PASSWRD = "-p";
 
         // =============================================================================
         // Exception classes
@@ -24,7 +24,7 @@ namespace com.muldersoft.slunkcrypt.gui.process
 
         public class GenerationFailedException : IOException
         {
-            public GenerationFailedException(string message, Exception innerException) : base(message, innerException)
+            public GenerationFailedException(string message) : base(message)
             {
             }
         }
@@ -35,21 +35,25 @@ namespace com.muldersoft.slunkcrypt.gui.process
 
         public static async Task<string> GeneratePassword(int length)
         {
+            if (length <= 0)
+            {
+                throw new ArgumentOutOfRangeException("Password length must be a positive value!");
+            }
             using (FileStream executableFile = ExecutableHelper.GetExecutableFile())
             {
-                try
+                string[] arguments = new string[] { COMMAND_PASSWRD, string.Format("{0:D}", length) };
+                Tuple<int, string[]> result = await ProcessRunner.ExecAsnyc(executableFile.Name, arguments, null, ProcessPriorityClass.BelowNormal, TIMEOUT);
+                if (result.Item1 == 0)
                 {
-                    string password = await StartProcess(executableFile, length);
-                    if (IsWeakPassword(password))
+                    foreach (string password in result.Item2)
                     {
-                        throw new InvalidDataException("The generated password string is invalid!");
+                        if ((password.Length >= length) && (!IsWeakPassword(password)))
+                        {
+                            return password;
+                        }
                     }
-                    return password;
-                }
-                catch (Exception e)
-                {
-                    throw new GenerationFailedException("Failed to generate password string!", e);
                 }
+                throw new GenerationFailedException("Failed to generate password string!");
             }
         }
 
@@ -72,68 +76,5 @@ namespace com.muldersoft.slunkcrypt.gui.process
             }
             return (flags != 0xF);
         }
-
-        // =============================================================================
-        // Internal methods
-        // =============================================================================
-
-        private static async Task<string> StartProcess(FileStream executableFile, int length)
-        {
-            using (Process process = new Process())
-            {
-                process.StartInfo.UseShellExecute = false;
-                process.StartInfo.CreateNoWindow = true;
-                process.StartInfo.RedirectStandardOutput = true;
-                process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
-                process.StartInfo.FileName = executableFile.Name;
-                process.StartInfo.Arguments = string.Format(COMMAND_PASSWRD, length);
-                process.Start();
-
-                Stack<string> outputLines = await WaitForExit(process, TIMEOUT_MSEC);
-
-                if (process.ExitCode == 0)
-                {
-                    while (outputLines.Count > 0)
-                    {
-                        string line = outputLines.Pop();
-                        if (line.Length >= length)
-                        {
-                            return line;
-                        }
-                    }
-                }
-                return string.Empty;
-            }
-        }
-
-        private static async Task<Stack<string>> WaitForExit(Process process, TimeSpan timeout)
-        {
-            Task<Stack<string>> readTask;
-            await Task.WhenAny(readTask = Task.Run(() => ReadOutput(process)), Task.Delay(timeout));
-            if (!process.WaitForExit(125))
-            {
-                process.Kill();
-                process.WaitForExit();
-            }
-            return await readTask;
-        }
-
-        private static Stack<string> ReadOutput(Process process)
-        {
-            Stack<string> result = new Stack<string>();
-            using (StreamReader reader = process.StandardOutput)
-            {
-                string line;
-                while ((line = reader.ReadLine()) != null)
-                {
-                    line = line.Trim();
-                    if (line.Length != 0)
-                    {
-                        result.Push(line.Trim());
-                    }
-                }
-            }
-            return result;
-        }
     }
 }
index fabd8b0..af78ccd 100644 (file)
@@ -7,7 +7,6 @@ using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
-using System.Text;
 using System.Text.RegularExpressions;
 using System.Threading.Tasks;
 using System.Windows.Threading;
@@ -62,34 +61,37 @@ namespace com.muldersoft.slunkcrypt.gui.process
         // Internal methods
         // =============================================================================
 
-        protected override double ParseProgressString(StringBuilder currentLine)
+        protected override double ParseProgressString(ref string currentLine, bool stream)
         {
-            Match match;
-            double progress, result = double.NaN;
-            do
+            double temp, result = double.NaN;
+            int index = int.MaxValue;
+            Match match = RX_PROGRESS.Match(currentLine);
+            while (match.Success)
             {
-                match = RX_PROGRESS.Match(currentLine.ToString());
-                if (match.Success)
+                if (TryParseProgressValue(match, out temp))
                 {
-                    if (!double.IsNaN(progress = ParseProgressValue(match)))
-                    {
-                        result = progress;
-                    }
-                    currentLine.Remove(match.Index, match.Length);
+                    result = temp;
                 }
+                index = Math.Min(index, match.Index);
+                match = RX_PROGRESS.Match(currentLine, match.Index + match.Length);
+            }
+            if (index != int.MaxValue)
+            {
+                currentLine = (index > 0) ? currentLine.Substring(0, index - 1).TrimEnd() : string.Empty;
             }
-            while (match.Success);
             return result;
         }
 
-        private static double ParseProgressValue(Match match)
+        private static bool TryParseProgressValue(Match match, out double progress)
         {
             uint intPart, fractPart;
             if (uint.TryParse(match.Groups[1].Value, out intPart) && uint.TryParse(match.Groups[2].Value, out fractPart))
             {
-                return ((intPart * 10) + fractPart) / 1000.0;
+                progress = ((intPart * 10) + fractPart) / 1000.0;
+                return true;
             }
-            return double.NaN;
+            progress = double.NaN;
+            return false;
         }
 
         private static string GetCommandString(Mode mode)
index 1c0a5a3..5936348 100644 (file)
@@ -660,9 +660,13 @@ namespace com.muldersoft.slunkcrypt.gui
             try
             {
                 Task<string> passwordTask = Task.Run(() => PasswordGen.GeneratePassword(GEN_PASSWD_LENGTH));
-                await Task.WhenAll(passwordTask, Task.Delay(1000));
+                await Task.WhenAll(passwordTask, Task.Delay(333));
                 password = passwordTask.Result;
             }
+            catch (ProcessRunner.ProcessStartException err)
+            {
+                MessageBox.Show(this, "Failed to create SlunkCrypt process:\n\n" + err.InnerException?.Message ?? err.Message, "Process Creation Error", MessageBoxButton.OK, MessageBoxImage.Error);
+            }
             catch (ExecutableHelper.ExecutableNotFoundException)
             {
                 MessageBox.Show(this, "The SlunkCrypt executable file could not be found.\n\nPlease make sure that the SlunkCrypt CLI executable file is located in the same directory as the GUI program!", "Executable Not Found", MessageBoxButton.OK, MessageBoxImage.Error);
@@ -847,13 +851,10 @@ namespace com.muldersoft.slunkcrypt.gui
                 .AppendLine(VersionInfo.VersionStr)
                 .AppendLine("This work has been released under the \u201CCC0 1.0\u201D license!")
                 .AppendLine()
-                .AppendLine("Source-code repository:")
-                .AppendLine("https://gitlab.com/lord_mulder/slunkcrypt/")
-                .AppendLine()
-                .AppendLine("Operating System: " + Environment.OSVersion.VersionString)
+                .AppendLine(Environment.OSVersion.VersionString)
                 .AppendLine(string.Format("Operating System Bitness: {0:D}, Process Bitness: {1:D}", Environment.Is64BitOperatingSystem ? 64 : 32, Environment.Is64BitProcess ? 64 : 32))
                 .AppendLine(".NET Runtime Version: " + Environment.Version)
-                .AppendLine(string.Format("CPU Count: {0:D}, Architecture: {1}, SSE2 Supported: {2}", Environment.ProcessorCount, cpuFeatures.x64 ? "x64" : "x86", cpuFeatures.sse2 ? "Yes" : "No"))
+                .AppendLine(string.Format("CPU Count: {0:D}, Architecture: {1}, SSE2 Support: {2}", Environment.ProcessorCount, cpuFeatures.x64 ? "x64" : "x86", cpuFeatures.sse2 ? "Yes" : "No"))
                 .AppendLine()
                 .AppendLine("Using “Silk” icons, by Mark James")
                 .ToString();
index 75f9c5e..b5de147 100644 (file)
@@ -4,6 +4,7 @@
 /******************************************************************************/
 
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Diagnostics;
@@ -29,7 +30,7 @@ namespace com.muldersoft.slunkcrypt.gui.utils
         private readonly Dispatcher m_dispatcher;
         private readonly ProcessPriorityClass? m_priorityClass;
 
-        private volatile bool m_running = false,  m_aborted = false, m_disposed = false;
+        private volatile bool m_running = false, m_finished = false, m_aborted = false, m_disposed = false;
 
         // =============================================================================
         // Exception classes
@@ -63,14 +64,7 @@ namespace com.muldersoft.slunkcrypt.gui.utils
             {
                 throw new ArgumentException("The given ProcessPriorityClass is undefined!");
             }
-            m_process.StartInfo.UseShellExecute = false;
-            m_process.StartInfo.CreateNoWindow  = true;
-            m_process.StartInfo.RedirectStandardOutput = true;
-            m_process.StartInfo.RedirectStandardError  = true;
-            m_process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
-            m_process.StartInfo.StandardErrorEncoding  = Encoding.UTF8;
-            m_process.EnableRaisingEvents = true;
-            m_process.Exited += ProcessExitedEventHandler;
+            InitializeProcess(m_process, m_hasExited, true);
         }
 
         ~ProcessRunner()
@@ -89,9 +83,9 @@ namespace com.muldersoft.slunkcrypt.gui.utils
             {
                 throw new ObjectDisposedException("ProcessRunner");
             }
-            if (m_running)
+            if (m_running || m_finished)
             {
-                throw new InvalidOperationException("Process is already running!");
+                throw new InvalidOperationException("Process is still running or has already finished!");
             }
             m_running = true;
             try
@@ -100,10 +94,21 @@ namespace com.muldersoft.slunkcrypt.gui.utils
             }
             finally
             {
+                m_finished = true;
                 m_running = false;
             }
         }
 
+        public static async Task<Tuple<int, string[]>> ExecAsnyc(string executableFile, string[] arguments = null, IReadOnlyDictionary<string, string> environmentVariables = null, ProcessPriorityClass? priorityClass = null, TimeSpan? timeout = null)
+        {
+            TaskCompletionSource<int> completionSource = new TaskCompletionSource<int>();
+            using (Process process = new Process())
+            {
+                InitializeProcess(process, completionSource);
+                return await DoExecAsnyc(process, completionSource.Task, executableFile, arguments, environmentVariables, priorityClass, timeout);
+            }
+        }
+
         public void AbortProcess()
         {
             if ((!m_disposed) && m_running)
@@ -132,32 +137,38 @@ namespace com.muldersoft.slunkcrypt.gui.utils
         // Protected methods
         // =============================================================================
 
-        protected virtual double ParseProgressString(StringBuilder currentLine)
+        protected virtual double ParseProgressString(ref string currentLine, bool stream)
         {
             return double.NaN;
         }
 
         // =============================================================================
-        // Event handlers
+        // Internal methods
         // =============================================================================
 
-        private void ProcessExitedEventHandler(object sender, EventArgs e)
+        private static void InitializeProcess(Process process, TaskCompletionSource<int> completionSource = null, bool redirStdErr = false)
         {
-            if (m_process.HasExited)
+            process.StartInfo.UseShellExecute = false;
+            process.StartInfo.CreateNoWindow = true;
+            process.StartInfo.RedirectStandardOutput = true;
+            process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
+            if (redirStdErr)
             {
-                m_hasExited.TrySetResult(m_process.ExitCode);
+                process.StartInfo.RedirectStandardError = true;
+                process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
+            }
+            if (!ReferenceEquals(completionSource, null))
+            {
+                process.EnableRaisingEvents = true;
+                process.Exited += (sndr, e) => completionSource.TrySetResult(process.ExitCode);
             }
         }
 
-        // =============================================================================
-        // Internal methods
-        // =============================================================================
-
         private async Task<int> DoExecAsnyc(string executablePath, string[] arguments, IReadOnlyDictionary<string, string> environmentVariables)
         {
             try
             {
-                StartProcess(executablePath, arguments, environmentVariables);
+                StartProcess(m_process, executablePath, arguments, environmentVariables, m_priorityClass);
             }
             catch (Exception err)
             {
@@ -166,20 +177,34 @@ namespace com.muldersoft.slunkcrypt.gui.utils
             return await WaitForExit();
         }
 
-        private void StartProcess(string executablePath, string[] arguments, IReadOnlyDictionary<string, string> environmentVariables)
+        private static async Task<Tuple<int,string[]>> DoExecAsnyc(Process process, Task<int> hasExited, string executablePath, string[] arguments, IReadOnlyDictionary<string, string> environmentVariables, ProcessPriorityClass? priorityClass, TimeSpan? timeout)
+        {
+            try
+            {
+                StartProcess(process, executablePath, arguments, environmentVariables, priorityClass);
+            }
+            catch (Exception err)
+            {
+                throw new ProcessStartException("Failed to create the process!", err);
+            }
+            string[] outputLines = await WaitForExit(process, hasExited, timeout.GetValueOrDefault(TimeSpan.MaxValue));
+            return Tuple.Create(hasExited.Result, outputLines);
+        }
+
+        private static void StartProcess(Process process, string executablePath, string[] arguments, IReadOnlyDictionary<string, string> environmentVariables, ProcessPriorityClass? priorityClass)
         {
-            m_process.StartInfo.FileName = executablePath;
-            m_process.StartInfo.Arguments = CreateArgumentList(arguments);
-            SetupEnvironment(m_process.StartInfo.EnvironmentVariables, environmentVariables);
-            m_process.StartInfo.WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory;
-            m_process.Start();
-            SetProcessPriority(m_priorityClass);
+            process.StartInfo.FileName = executablePath;
+            process.StartInfo.Arguments = CreateArgumentList(arguments);
+            SetupEnvironment(process.StartInfo.EnvironmentVariables, environmentVariables);
+            process.StartInfo.WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory;
+            process.Start();
+            SetProcessPriority(process, priorityClass);
         }
 
         private async Task<int> WaitForExit()
         {
-            Task readStdOutTask = Task.Run(() => ReadProcessOutput(m_process.StandardOutput, STDOUT));
-            Task readStdErrTask = Task.Run(() => ReadProcessOutput(m_process.StandardError,  STDERR));
+            Task readStdOutTask = Task.Run(() => ReadProcessOutput(m_process.StandardOutput, (line) => HandleOutput(line, STDOUT)));
+            Task readStdErrTask = Task.Run(() => ReadProcessOutput(m_process.StandardError,  (line) => HandleOutput(line, STDERR)));
             Task<int> hasExited = m_hasExited.Task;
             await Task.WhenAll(readStdOutTask, readStdErrTask, hasExited);
             if (m_aborted || m_disposed)
@@ -191,21 +216,33 @@ namespace com.muldersoft.slunkcrypt.gui.utils
             return hasExited.Result;
         }
 
-        private void ReadProcessOutput(StreamReader reader, bool stream)
+        private static async Task<string[]> WaitForExit(Process process, Task<int> hasExited, TimeSpan timeout)
         {
-            using(reader)
+            ConcurrentStack<string> outputLines = new ConcurrentStack<string>();
+            Task readerTask = Task.Run(() => ReadProcessOutput(process.StandardOutput, (line) => outputLines.Push(line)));
+            if (await Task.WhenAny(hasExited, Task.Delay(timeout)) != hasExited)
+            {
+                KillProcess(process);
+            }
+            await Task.WhenAll(readerTask, hasExited);
+            return outputLines.ToArray();
+        }
+
+        private static void ReadProcessOutput(StreamReader reader, Action<string> outputHandler)
+        {
+            using (reader)
             {
                 char[] buffer = new char[1024];
-                StringBuilder currentLine = new StringBuilder();
+                StringBuilder lineBuffer = new StringBuilder();
                 while (!reader.EndOfStream)
                 {
-                    ReadNextChunk(reader, buffer, currentLine, stream);
+                    ReadNextChunk(reader, buffer, lineBuffer, outputHandler);
                 }
-                NotifyOutputAvailable(currentLine, stream);
+                ProcessLine(lineBuffer, outputHandler);
             }
         }
 
-        private void ReadNextChunk(StreamReader reader, char[] buffer, StringBuilder currentLine, bool stderr)
+        private static void ReadNextChunk(StreamReader reader, char[] buffer, StringBuilder lineBuffer, Action<string> outputHandler)
         {
             int count = reader.Read(buffer, 0, buffer.Length);
             if (count > 0)
@@ -215,40 +252,44 @@ namespace com.muldersoft.slunkcrypt.gui.utils
                     char c = buffer[i];
                     if ((c != '\0') && (c != '\n') && (c != '\r') && (c != '\b'))
                     {
-                        currentLine.Append(c);
+                        lineBuffer.Append(c);
                     }
                     else
                     {
-                        CheckForProgressUpdate(currentLine);
-                        NotifyOutputAvailable(currentLine, stderr);
-                        currentLine.Clear();
+                        ProcessLine(lineBuffer, outputHandler);
+                        lineBuffer.Clear();
                     }
                 }
-                CheckForProgressUpdate(currentLine);
             }
         }
 
-        private void CheckForProgressUpdate(StringBuilder currentLine)
+        private static void ProcessLine(StringBuilder lineBuffer, Action<string> outputHandler)
         {
-            if (currentLine.Length > 0)
+            if (lineBuffer.Length > 0)
             {
-                double progress = ParseProgressString(currentLine);
-                if (!(double.IsNaN(progress) || double.IsInfinity(progress)))
+                String currentLine;
+                if (!string.IsNullOrWhiteSpace(currentLine = lineBuffer.ToString()))
                 {
-                    NotifyProgressChanged(progress);
+                    outputHandler.Invoke(currentLine.Trim());
                 }
             }
         }
 
-        private void NotifyOutputAvailable(StringBuilder currentLine, bool stream)
+        private void HandleOutput(string currentLine, bool stream)
         {
-            if (currentLine.Length > 0)
+            CheckForProgressUpdate(ref currentLine, stream);
+            if (!String.IsNullOrEmpty(currentLine))
             {
-                String line = currentLine.ToString().Trim();
-                if (!string.IsNullOrEmpty(line))
-                {
-                    NotifyOutputAvailable(line, stream);
-                }
+                NotifyOutputAvailable(currentLine, stream);
+            }
+        }
+
+        private void CheckForProgressUpdate(ref string currentLine, bool stream)
+        {
+            double progress = ParseProgressString(ref currentLine, stream);
+            if (!double.IsNaN(progress))
+            {
+                NotifyProgressChanged(progress);
             }
         }
 
@@ -338,13 +379,22 @@ namespace com.muldersoft.slunkcrypt.gui.utils
             catch { }
         }
 
-        private void SetProcessPriority(ProcessPriorityClass? priorityClass)
+        private static void KillProcess(Process process)
+        {
+            try
+            {
+                process.Kill();
+            }
+            catch { }
+        }
+
+        private static void SetProcessPriority(Process process, ProcessPriorityClass? priorityClass)
         {
             try
             {
                 if (priorityClass.HasValue)
                 {
-                    m_process.PriorityClass = priorityClass.Value;
+                    process.PriorityClass = priorityClass.Value;
                 }
             }
             catch { }