From d3186c6d45a84ea7e94702801028197820c93077 Mon Sep 17 00:00:00 2001 From: Violet Hansen Date: Sat, 21 Dec 2024 17:18:28 +0200 Subject: [PATCH] Improving file enumeration in AppControl Manager (#470) Separated wildcard detection from the rest of the extensions. This improves the extension checks in the tight loops and speeds up the overall enumeration process. Moved the default App Control supported extensions HashSet outside of the method as static var so it won't be redefined every time the method is called. --- AppControl Manager/Logic/GetFilesFast.cs | 146 +++++++++++++++++------ 1 file changed, 109 insertions(+), 37 deletions(-) diff --git a/AppControl Manager/Logic/GetFilesFast.cs b/AppControl Manager/Logic/GetFilesFast.cs index f694d7b17..100c72311 100644 --- a/AppControl Manager/Logic/GetFilesFast.cs +++ b/AppControl Manager/Logic/GetFilesFast.cs @@ -27,6 +27,14 @@ internal static class FileUtility }; + // The Default App Control supported extensions, case-insensitive + private static readonly HashSet appControlExtensions = new(StringComparer.OrdinalIgnoreCase) + { + ".sys", ".exe", ".com", ".dll", ".rll", ".ocx", ".msp", ".mst", ".msi", + ".js", ".vbs", ".ps1", ".appx", ".bin", ".bat", ".hxs", ".mui", ".lex", ".mof" + }; + + /// /// Custom HashSet comparer to compare two FileInfo objects based on their FullName (full path of file) /// @@ -62,24 +70,21 @@ internal static List GetFilesFast( FileInfo[]? files, string[]? extensionsToFilterBy) { - // Create a Stopwatch instance - Stopwatch stopwatch = new(); + // Create a Stopwatch instance and start measuring time + Stopwatch stopwatch = Stopwatch.StartNew(); - // Start measuring time - stopwatch.Start(); - - // Use the Default App Control supported extensions and make them case-insensitive - HashSet extensions = new(StringComparer.OrdinalIgnoreCase) - { - ".sys", ".exe", ".com", ".dll", ".rll", ".ocx", ".msp", ".mst", ".msi", - ".js", ".vbs", ".ps1", ".appx", ".bin", ".bat", ".hxs", ".mui", ".lex", ".mof" - }; + // A HashSet used to store extensions to filter files + HashSet extensions = new(StringComparer.OrdinalIgnoreCase); // If custom extensions are provided, use them and make them case-insensitive if (extensionsToFilterBy is { Length: > 0 }) { extensions = new HashSet(extensionsToFilterBy, StringComparer.OrdinalIgnoreCase); } + else + { + extensions = appControlExtensions; + } // Define a HashSet to store the final output HashSet output = new(new FileInfoComparer()); @@ -92,6 +97,9 @@ internal static List GetFilesFast( // To store all of the tasks List tasks = []; + + #region Directories + // Process directories if provided if (directories is { Length: > 0 }) { @@ -101,30 +109,53 @@ internal static List GetFilesFast( tasks.Add(Task.Run(() => { IEnumerator enumerator = directory.EnumerateFiles("*", options2).GetEnumerator(); - while (true) + + // If there is wildcard in extensions to filter by, then add all files without performing extension check + if (extensions.Contains("*")) { - try + while (true) { - // Move to the next file - if (!enumerator.MoveNext()) - { - // If we reach the end of the enumeration, we break out of the loop - break; - } - - // Check if the file extension is in the Extensions HashSet or Wildcard was used - if (extensions.Contains(enumerator.Current.Extension) || extensions.Contains("*")) + try { + // Move to the next file + // The reason we use MoveNext() instead of foreach loop is that protected/inaccessible files + // Would throw errors and this way we can catch them and move to the next file without terminating the entire loop + if (!enumerator.MoveNext()) + { + // If we reach the end of the enumeration, we break out of the loop + break; + } bc.Add(enumerator.Current); } + catch { } } - catch { } } + // Filter files by extensions if there is no wildcard character for filtering + else + { + while (true) + { + try + { + // Move to the next file + if (!enumerator.MoveNext()) + { + // If we reach the end of the enumeration, we break out of the loop + break; + } + // Check if the file extension is in the Extensions HashSet or Wildcard was used + if (extensions.Contains(enumerator.Current.Extension)) + { + bc.Add(enumerator.Current); + } + } + catch { } + } + } })); - // Check for immediate sub-directories and process them if present DirectoryInfo[] subDirectories = directory.GetDirectories(); @@ -136,24 +167,45 @@ internal static List GetFilesFast( tasks.Add(Task.Run(() => { IEnumerator subEnumerator = subDirectory.EnumerateFiles("*", options).GetEnumerator(); - while (true) + + if (extensions.Contains("*")) { - try + while (true) { - // Move to the next file - if (!subEnumerator.MoveNext()) + try { - // If we reach the end of the enumeration, we break out of the loop - break; + // Move to the next file + if (!subEnumerator.MoveNext()) + { + // If we reach the end of the enumeration, we break out of the loop + break; + } + bc.Add(subEnumerator.Current); } - - // Check if the file extension is in the Extensions HashSet or Wildcard was used - if (extensions.Contains(subEnumerator.Current.Extension) || extensions.Contains("*")) + catch { } + } + } + else + { + while (true) + { + try { - bc.Add(subEnumerator.Current); + // Move to the next file + if (!subEnumerator.MoveNext()) + { + // If we reach the end of the enumeration, we break out of the loop + break; + } + + // Check if the file extension is in the Extensions HashSet or Wildcard was used + if (extensions.Contains(subEnumerator.Current.Extension)) + { + bc.Add(subEnumerator.Current); + } } + catch { } } - catch { } } })); } @@ -162,18 +214,38 @@ internal static List GetFilesFast( } } + #endregion + + + #region Files + // If files are provided, process them if (files is { Length: > 0 }) { - foreach (FileInfo file in files) + // If user provided wildcard then add all files without checking their extensions + if (extensions.Contains("*")) { - if (extensions.Contains(file.Extension)) + foreach (FileInfo file in files) { bc.Add(file); } } + // If user provided no extensions to filter by or provided extensions that are not wildcard + else + { + foreach (FileInfo file in files) + { + if (extensions.Contains(file.Extension)) + { + bc.Add(file); + } + } + } } + #endregion + + // Wait for all tasks to be completed Task.WaitAll(tasks);