//******************************************************************************************************// FilePath.cs - Gbtc//// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.//// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See// the NOTICE file distributed with this work for additional information regarding copyright ownership.// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may// not use this file except in compliance with the License. You may obtain a copy of the License at://// http://www.opensource.org/licenses/MIT//// Unless agreed to in writing, the subject software distributed under the License is distributed on an// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the// License for the specific language governing permissions and limitations.//// Code Modification History:// ----------------------------------------------------------------------------------------------------// 02/05/2003 - J. Ritchie Carroll// Generated original version of source code.// 12/29/2005 - Pinal C. Patel// Migrated 2.0 version of source code from 1.1 source (GSF.Shared.FilePath).// 08/22/2007 - Darrell Zuercher// Edited code comments.// 09/19/2008 - J. Ritchie Carroll// Converted to C#.// 10/24/2008 - Pinal C. Patel// Edited code comments.// 12/17/2008 - F. Russell Robertson// Fixed issue in GetFilePatternRegularExpression().// 06/30/2009 - Pinal C. Patel// Removed FilePathHasFileName() since the result was error prone.// 09/14/2009 - Stephen C. Wills// Added new header and license agreement.// 09/17/2009 - Pinal C. Patel// Modified GetAbsolutePath() to remove dependency on HttpContext.Current.// 04/19/2010 - Pinal C. Patel// Added GetApplicationDataFolder() method.// 04/21/2010 - Pinal C. Patel// Updated GetApplicationDataFolder() to include the company name if available.// 01/28/2011 - J. Ritchie Carroll// Added IsValidFileName function.// 02/14/2011 - J. Ritchie Carroll// Fixed issue in GetDirectoryName where last directory was being truncated as a file name.// 06/06/2011 - Stephen C. Wills// Fixed issue in GetFileName where path suffix was being removed before extracting the file name.// 07/29/2011 - Pinal C. Patel// Updated GetApplicationDataFolder() to use the TEMP directory for web applications.// 12/14/2012 - Starlynn Danyelle Gilliam// Modified Header.////****************************************************************************************************** using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Text;using System.Text.RegularExpressions;using System.Threading;using GSF.Diagnostics; namespace GSF.IO{ /// <summary> /// Contains File and Path manipulation methods. /// </summary> public static partial class FilePath { #region [ Members ] // Fields private static readonly string s_directorySeparatorCharPattern; private static readonly string s_fileNameCharPattern; #endregion #region [ Constructor ] static FilePath() { char[] directorySeparatorChars = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; char[] encodedInvalidFileNameChars = Path.GetInvalidFileNameChars() .SelectMany(c => c.RegexEncode()) .ToArray(); char[] encodedDirectorySeparatorChars = directorySeparatorChars .Distinct() .SelectMany(c => c.RegexEncode()) .ToArray(); // Defines a regular expression pattern for a valid directory separator character. s_directorySeparatorCharPattern = $"[{new string(encodedDirectorySeparatorChars)}]"; // Defines a regular expression pattern for a valid file name character. We do this by // allowing any characters except those that would not be valid as part of a filename. // This essentially builds the "?" wildcard pattern match. s_fileNameCharPattern = $"[^{new string(encodedInvalidFileNameChars)}]"; } #endregion #region [ Methods ] /// <summary> /// Returns the names of the subdirectories (including their paths) that match the specified search pattern in the specified directory, and optionally searches subdirectories. /// </summary> /// <param name="path">The relative or absolute path to the directory to search. This string is not case-sensitive.</param> /// <param name="searchPattern">The search string to match against the names of subdirectories in <paramref name="path"/>. This parameter can contain a combination of valid literal and wildcard characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include all subdirectories or only the current directory.</param> /// <param name="exceptionHandler">Handles exceptions thrown during directory enumeration.</param> /// <returns>An array of the full names (including paths) of the subdirectories that match the specified criteria, or an empty array if no directories are found.</returns> public static string[] GetDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { return EnumerateDirectories(path, searchPattern, searchOption, exceptionHandler).ToArray(); } /// <summary> /// Returns an enumerable collection of file names that match a search pattern in a specified path, and optionally searches subdirectories. /// </summary> /// <param name="path">The relative or absolute path to the directory to search. This string is not case-sensitive.</param> /// <param name="searchPattern">The search string to match against the names of directories in <paramref name="path"/>. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include only the current directory or should include all subdirectories.</param> /// <param name="exceptionHandler">Handles exceptions thrown during directory enumeration.</param> /// <returns>An enumerable collection of the full names (including paths) for the directories in the directory specified by <paramref name="path"/> and that match the specified search pattern and option.</returns> public static IEnumerable<string> EnumerateDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { IEnumerable<string> enumerable; IEnumerator<string> enumerator; try { IEnumerable<string> topDirectory = Directory.EnumerateDirectories(path, searchPattern, SearchOption.TopDirectoryOnly); IEnumerable<string> recursive = Enumerable.Empty<string>(); if (searchOption == SearchOption.AllDirectories) { recursive = Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly) .SelectMany(directory => EnumerateDirectories(directory, searchPattern, searchOption, exceptionHandler)); } enumerable = topDirectory.Concat(recursive); enumerator = enumerable.GetEnumerator(); } catch (Exception ex) { exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating directories in \"{path}\": {ex.Message}", ex)); yield break; } // yield return cannot be used in a try block with a catch clause, // so in order to handle errors in enumerator.MoveNext() and enumerator.Current, // the enumerator must be accessed directly rather than using foreach using (enumerable as IDisposable) using (enumerator) { while (true) { string current; try { if (!enumerator.MoveNext()) break; current = enumerator.Current; } catch (Exception ex) { exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating directories in \"{path}\": {ex.Message}", ex)); // To avoid an infinite exception loop, // break out at the first sign of trouble break; } yield return current; } } } /// <summary> /// Returns the names of files (including their paths) that match the specified search pattern in the specified directory, using a value to determine whether to search subdirectories. /// </summary> /// <param name="path">The relative or absolute path to the directory to search. This string is not case-sensitive.</param> /// <param name="searchPattern">The search string to match against the names of files in <paramref name="path"/>. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include all subdirectories or only the current directory.</param> /// <param name="exceptionHandler">Handles exceptions thrown during file enumeration.</param> /// <returns>An array of the full names (including paths) for the files in the specified directory that match the specified search pattern and option, or an empty array if no files are found.</returns> public static string[] GetFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { return EnumerateFiles(path, searchPattern, searchOption, exceptionHandler).ToArray(); } /// <summary> /// Returns an enumerable collection of file names that match a search pattern in a specified path, and optionally searches subdirectories. /// </summary> /// <param name="path">The relative or absolute path to the directory to search. This string is not case-sensitive.</param> /// <param name="searchPattern">The search string to match against the names of files in <paramref name="path"/>. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include only the current directory or should include all subdirectories.</param> /// <param name="exceptionHandler">Handles exceptions thrown during file enumeration.</param> /// <returns>An enumerable collection of the full names (including paths) for the files in the directory specified by <paramref name="path"/> and that match the specified search pattern and option.</returns> public static IEnumerable<string> EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { IEnumerable<string> enumerable; IEnumerator<string> enumerator; try { IEnumerable<string> topDirectory = Directory.EnumerateFiles(path, searchPattern, SearchOption.TopDirectoryOnly); IEnumerable<string> recursive = Enumerable.Empty<string>(); if (searchOption == SearchOption.AllDirectories) { recursive = Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly) .SelectMany(directory => EnumerateFiles(directory, searchPattern, searchOption, exceptionHandler)); } enumerable = topDirectory.Concat(recursive); enumerator = enumerable.GetEnumerator(); } catch (Exception ex) { exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating files in \"{path}\": {ex.Message}", ex)); yield break; } // yield return cannot be used in a try block with a catch clause, // so in order to handle errors in enumerator.MoveNext() and enumerator.Current, // the enumerator must be accessed directly rather than using foreach using (enumerable as IDisposable) using (enumerator) { while (true) { string current; try { if (!enumerator.MoveNext()) break; current = enumerator.Current; } catch (Exception ex) { exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating files in \"{path}\": {ex.Message}", ex)); // To avoid an infinite exception loop, // break out at the first sign of trouble break; } yield return current; } } } /// <summary> /// Returns the subdirectories that match the specified search pattern in the specified directory, and optionally searches subdirectories. /// </summary> /// <param name="directory">The directory to search.</param> /// <param name="searchPattern">The search string to match against the names of subdirectories in the <paramref name="directory"/>. This parameter can contain a combination of valid literal and wildcard characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include all subdirectories or only the current directory.</param> /// <param name="exceptionHandler">Handles exceptions thrown during directory enumeration.</param> /// <returns>An array of the <see cref="DirectoryInfo"/> objects representing the subdirectories that match the specified search criteria.</returns> public static DirectoryInfo[] GetDirectories(DirectoryInfo directory, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { return EnumerateDirectories(directory, searchPattern, searchOption, exceptionHandler).ToArray(); } /// <summary> /// Returns an enumerable collection of subdirectories that match a search pattern in a specified path, and optionally searches subdirectories. /// </summary> /// <param name="directory">The directory to search.</param> /// <param name="searchPattern">The search string to match against the names of subdirectories in the <paramref name="directory"/>. This parameter can contain a combination of valid literal and wildcard characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include only the current directory or should include all subdirectories.</param> /// <param name="exceptionHandler">Handles exceptions thrown during directory enumeration.</param> /// <returns>An enumerable collection of the <see cref="DirectoryInfo"/> objects representing the subdirectories that match the specified search criteria.</returns> public static IEnumerable<DirectoryInfo> EnumerateDirectories(DirectoryInfo directory, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { IEnumerable<DirectoryInfo> enumerable; IEnumerator<DirectoryInfo> enumerator; try { IEnumerable<DirectoryInfo> topDirectory = directory.EnumerateDirectories(searchPattern, SearchOption.TopDirectoryOnly); IEnumerable<DirectoryInfo> recursive = Enumerable.Empty<DirectoryInfo>(); if (searchOption == SearchOption.AllDirectories) { recursive = directory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly) .SelectMany(subdirectory => EnumerateDirectories(subdirectory, searchPattern, searchOption, exceptionHandler)); } enumerable = topDirectory.Concat(recursive); enumerator = enumerable.GetEnumerator(); } catch (Exception ex) { string path = directory.FullName; exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating directories in \"{path}\": {ex.Message}", ex)); yield break; } // yield return cannot be used in a try block with a catch clause, // so in order to handle errors in enumerator.MoveNext() and enumerator.Current, // the enumerator must be accessed directly rather than using foreach using (enumerable as IDisposable) using (enumerator) { while (true) { DirectoryInfo current; try { if (!enumerator.MoveNext()) break; current = enumerator.Current; } catch (Exception ex) { string path = directory.FullName; exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating directories in \"{path}\": {ex.Message}", ex)); // To avoid an infinite exception loop, // break out at the first sign of trouble break; } yield return current; } } } /// <summary> /// Returns the files that match the specified search pattern in the specified directory, using a value to determine whether to search subdirectories. /// </summary> /// <param name="directory">The directory to search.</param> /// <param name="searchPattern">The search string to match against the names of files in the <paramref name="directory"/>. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include all subdirectories or only the current directory.</param> /// <param name="exceptionHandler">Handles exceptions thrown during file enumeration.</param> /// <returns>An array of the <see cref="FileInfo"/> objects representing the files in the specified directory that match the specified search pattern, or an empty array if no files are found.</returns> public static FileInfo[] GetFiles(DirectoryInfo directory, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { return EnumerateFiles(directory, searchPattern, searchOption, exceptionHandler).ToArray(); } /// <summary> /// Returns an enumerable collection of files that match a search pattern in a specified path, and optionally searches subdirectories. /// </summary> /// <param name="directory">The directory to search.</param> /// <param name="searchPattern">The search string to match against the names of files in the <paramref name="directory"/>. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include only the current directory or should include all subdirectories.</param> /// <param name="exceptionHandler">Handles exceptions thrown during file enumeration.</param> /// <returns>An enumerable collection of the <see cref="FileInfo"/> objects representing the files in the specified directory that match the specified search pattern, or an empty array if no files are found.</returns> public static IEnumerable<FileInfo> EnumerateFiles(DirectoryInfo directory, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { IEnumerable<FileInfo> enumerable; IEnumerator<FileInfo> enumerator; try { IEnumerable<FileInfo> topDirectory = directory.EnumerateFiles(searchPattern, SearchOption.TopDirectoryOnly); IEnumerable<FileInfo> recursive = Enumerable.Empty<FileInfo>(); if (searchOption == SearchOption.AllDirectories) { recursive = directory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly) .SelectMany(subdirectory => EnumerateFiles(subdirectory, searchPattern, searchOption, exceptionHandler)); } enumerable = topDirectory.Concat(recursive); enumerator = enumerable.GetEnumerator(); } catch (Exception ex) { string path = directory.FullName; exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating files in \"{path}\": {ex.Message}", ex)); yield break; } // yield return cannot be used in a try block with a catch clause, // so in order to handle errors in enumerator.MoveNext() and enumerator.Current, // the enumerator must be accessed directly rather than using foreach using (enumerable as IDisposable) using (enumerator) { while (true) { FileInfo current; try { if (!enumerator.MoveNext()) break; current = enumerator.Current; } catch (Exception ex) { string path = directory.FullName; exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating files in \"{path}\": {ex.Message}", ex)); // To avoid an infinite exception loop, // break out at the first sign of trouble break; } yield return current; } } } /// <summary> /// Ensures the supplied path name is valid. /// </summary> /// <param name="filePath">The file path to be validated.</param> /// <remarks> /// Throws argument exceptions if the <see param="pathName"/> is invalid. /// </remarks> public static void ValidatePathName(string filePath) { if (filePath.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(filePath), "Path cannot be null or empty space"); if (filePath.IndexOfAny(Path.GetInvalidPathChars()) >= 0) throw new ArgumentException("Path has invalid characters.", nameof(filePath)); } /// <summary> /// Determines whether the specified file name matches any of the given file specs (wildcards are defined as '*' or '?' characters). /// </summary> /// <param name="fileSpecs">The file specs used for matching the specified file name.</param> /// <param name="filePath">The file path to be tested against the specified file specs for a match.</param> /// <param name="ignoreCase"><c>true</c> to specify a case-insensitive match; otherwise <c>false</c>.</param> /// <returns><c>true</c> if the specified file name matches any of the given file specs; otherwise <c>false</c>.</returns> /// <exception cref="ArgumentException"><paramref name="filePath"/> contains one or more of the invalid characters defined in <see cref="Path.GetInvalidPathChars"/>.</exception> /// <remarks> /// The syntax for <paramref name="fileSpecs"/> adheres to the following rules: /// /// <ul> /// <li>Either '\' or '/' (as defined by <see cref="Path.DirectorySeparatorChar"/> and <see cref="Path.AltDirectorySeparatorChar"/>) can match the other.</li> /// <li>A single '\' or '/' at the beginning of the pattern matches any valid path root (such as "C:\" or "\\server\share").</li> /// <li>A '?' matches a single character which would be valid in a file name (as defined by <see cref="Path.GetInvalidFileNameChars"/>).</li> /// <li>A '*' matches any number of characters which would be valid in a file name.</li> /// <li>A sequence of "**\" or "**/" matches any number of sequential directories.</li> /// <li>Any other character matches itself.</li> /// </ul> /// </remarks> public static bool IsFilePatternMatch(string[] fileSpecs, string filePath, bool ignoreCase) { foreach (string fileSpec in fileSpecs) { if (IsFilePatternMatch(fileSpec, filePath, ignoreCase)) return true; } return false; } /// <summary> /// Determines whether the specified file name matches the given file spec (wildcards are defined as '*' or '?' characters). /// </summary> /// <param name="fileSpec">The file spec used for matching the specified file name.</param> /// <param name="filePath">The file path to be tested against the specified file spec for a match.</param> /// <param name="ignoreCase"><c>true</c> to specify a case-insensitive match; otherwise <c>false</c>.</param> /// <returns><c>true</c> if the specified file name matches the given file spec; otherwise <c>false</c>.</returns> /// <exception cref="ArgumentException"><paramref name="filePath"/> contains one or more of the invalid characters defined in <see cref="Path.GetInvalidPathChars"/>.</exception> /// <remarks> /// The syntax for <paramref name="fileSpec"/> adheres to the following rules: /// /// <ul> /// <li>Either '\' or '/' (as defined by <see cref="Path.DirectorySeparatorChar"/> and <see cref="Path.AltDirectorySeparatorChar"/>) can match the other.</li> /// <li>A single '\' or '/' at the beginning of the pattern matches any valid path root (such as "C:\" or "\\server\share").</li> /// <li>A '?' matches a single character which would be valid in a file name (as defined by <see cref="Path.GetInvalidFileNameChars"/>).</li> /// <li>A '*' matches any number of characters which would be valid in a file name.</li> /// <li>A sequence of "**\" or "**/" matches any number of sequential directories.</li> /// <li>Any other character matches itself.</li> /// </ul> /// </remarks> public static bool IsFilePatternMatch(string fileSpec, string filePath, bool ignoreCase) { if (string.IsNullOrWhiteSpace(filePath)) return false; // Define regular expression patterns for the three possible sequences that can match the path root. string recursiveDirPattern = $"{(Regex.Escape("**"))}{s_directorySeparatorCharPattern}"; string pathRootPattern = string.Format("({0}[^{0}]|{0}$)", Regex.Escape(Path.DirectorySeparatorChar.ToString())); string altPathRootPattern = string.Format("({0}[^{0}]|{0}$)", Regex.Escape(Path.AltDirectorySeparatorChar.ToString())); // If any of the three patterns are found at the start of fileSpec and the fileName refers to a rooted path, // remove the path root from fileName and then remove all leading directory separator chars from both fileSpec and fileName.Lines 1–500 of 1209. Load more
//****************************************************************************************************** // FilePath.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 02/05/2003 - J. Ritchie Carroll // Generated original version of source code. // 12/29/2005 - Pinal C. Patel // Migrated 2.0 version of source code from 1.1 source (GSF.Shared.FilePath). // 08/22/2007 - Darrell Zuercher // Edited code comments. // 09/19/2008 - J. Ritchie Carroll // Converted to C#. // 10/24/2008 - Pinal C. Patel // Edited code comments. // 12/17/2008 - F. Russell Robertson // Fixed issue in GetFilePatternRegularExpression(). // 06/30/2009 - Pinal C. Patel // Removed FilePathHasFileName() since the result was error prone. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 09/17/2009 - Pinal C. Patel // Modified GetAbsolutePath() to remove dependency on HttpContext.Current. // 04/19/2010 - Pinal C. Patel // Added GetApplicationDataFolder() method. // 04/21/2010 - Pinal C. Patel // Updated GetApplicationDataFolder() to include the company name if available. // 01/28/2011 - J. Ritchie Carroll // Added IsValidFileName function. // 02/14/2011 - J. Ritchie Carroll // Fixed issue in GetDirectoryName where last directory was being truncated as a file name. // 06/06/2011 - Stephen C. Wills // Fixed issue in GetFileName where path suffix was being removed before extracting the file name. // 07/29/2011 - Pinal C. Patel // Updated GetApplicationDataFolder() to use the TEMP directory for web applications. // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading; using GSF.Diagnostics; namespace GSF.IO { /// <summary> /// Contains File and Path manipulation methods. /// </summary> public static partial class FilePath { #region [ Members ] // Fields private static readonly string s_directorySeparatorCharPattern; private static readonly string s_fileNameCharPattern; #endregion #region [ Constructor ] static FilePath() { char[] directorySeparatorChars = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; char[] encodedInvalidFileNameChars = Path.GetInvalidFileNameChars() .SelectMany(c => c.RegexEncode()) .ToArray(); char[] encodedDirectorySeparatorChars = directorySeparatorChars .Distinct() .SelectMany(c => c.RegexEncode()) .ToArray(); // Defines a regular expression pattern for a valid directory separator character. s_directorySeparatorCharPattern = $"[{new string(encodedDirectorySeparatorChars)}]"; // Defines a regular expression pattern for a valid file name character. We do this by // allowing any characters except those that would not be valid as part of a filename. // This essentially builds the "?" wildcard pattern match. s_fileNameCharPattern = $"[^{new string(encodedInvalidFileNameChars)}]"; } #endregion #region [ Methods ] /// <summary> /// Returns the names of the subdirectories (including their paths) that match the specified search pattern in the specified directory, and optionally searches subdirectories. /// </summary> /// <param name="path">The relative or absolute path to the directory to search. This string is not case-sensitive.</param> /// <param name="searchPattern">The search string to match against the names of subdirectories in <paramref name="path"/>. This parameter can contain a combination of valid literal and wildcard characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include all subdirectories or only the current directory.</param> /// <param name="exceptionHandler">Handles exceptions thrown during directory enumeration.</param> /// <returns>An array of the full names (including paths) of the subdirectories that match the specified criteria, or an empty array if no directories are found.</returns> public static string[] GetDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { return EnumerateDirectories(path, searchPattern, searchOption, exceptionHandler).ToArray(); } /// <summary> /// Returns an enumerable collection of file names that match a search pattern in a specified path, and optionally searches subdirectories. /// </summary> /// <param name="path">The relative or absolute path to the directory to search. This string is not case-sensitive.</param> /// <param name="searchPattern">The search string to match against the names of directories in <paramref name="path"/>. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include only the current directory or should include all subdirectories.</param> /// <param name="exceptionHandler">Handles exceptions thrown during directory enumeration.</param> /// <returns>An enumerable collection of the full names (including paths) for the directories in the directory specified by <paramref name="path"/> and that match the specified search pattern and option.</returns> public static IEnumerable<string> EnumerateDirectories(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { IEnumerable<string> enumerable; IEnumerator<string> enumerator; try { IEnumerable<string> topDirectory = Directory.EnumerateDirectories(path, searchPattern, SearchOption.TopDirectoryOnly); IEnumerable<string> recursive = Enumerable.Empty<string>(); if (searchOption == SearchOption.AllDirectories) { recursive = Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly) .SelectMany(directory => EnumerateDirectories(directory, searchPattern, searchOption, exceptionHandler)); } enumerable = topDirectory.Concat(recursive); enumerator = enumerable.GetEnumerator(); } catch (Exception ex) { exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating directories in \"{path}\": {ex.Message}", ex)); yield break; } // yield return cannot be used in a try block with a catch clause, // so in order to handle errors in enumerator.MoveNext() and enumerator.Current, // the enumerator must be accessed directly rather than using foreach using (enumerable as IDisposable) using (enumerator) { while (true) { string current; try { if (!enumerator.MoveNext()) break; current = enumerator.Current; } catch (Exception ex) { exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating directories in \"{path}\": {ex.Message}", ex)); // To avoid an infinite exception loop, // break out at the first sign of trouble break; } yield return current; } } } /// <summary> /// Returns the names of files (including their paths) that match the specified search pattern in the specified directory, using a value to determine whether to search subdirectories. /// </summary> /// <param name="path">The relative or absolute path to the directory to search. This string is not case-sensitive.</param> /// <param name="searchPattern">The search string to match against the names of files in <paramref name="path"/>. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include all subdirectories or only the current directory.</param> /// <param name="exceptionHandler">Handles exceptions thrown during file enumeration.</param> /// <returns>An array of the full names (including paths) for the files in the specified directory that match the specified search pattern and option, or an empty array if no files are found.</returns> public static string[] GetFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { return EnumerateFiles(path, searchPattern, searchOption, exceptionHandler).ToArray(); } /// <summary> /// Returns an enumerable collection of file names that match a search pattern in a specified path, and optionally searches subdirectories. /// </summary> /// <param name="path">The relative or absolute path to the directory to search. This string is not case-sensitive.</param> /// <param name="searchPattern">The search string to match against the names of files in <paramref name="path"/>. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include only the current directory or should include all subdirectories.</param> /// <param name="exceptionHandler">Handles exceptions thrown during file enumeration.</param> /// <returns>An enumerable collection of the full names (including paths) for the files in the directory specified by <paramref name="path"/> and that match the specified search pattern and option.</returns> public static IEnumerable<string> EnumerateFiles(string path, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { IEnumerable<string> enumerable; IEnumerator<string> enumerator; try { IEnumerable<string> topDirectory = Directory.EnumerateFiles(path, searchPattern, SearchOption.TopDirectoryOnly); IEnumerable<string> recursive = Enumerable.Empty<string>(); if (searchOption == SearchOption.AllDirectories) { recursive = Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly) .SelectMany(directory => EnumerateFiles(directory, searchPattern, searchOption, exceptionHandler)); } enumerable = topDirectory.Concat(recursive); enumerator = enumerable.GetEnumerator(); } catch (Exception ex) { exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating files in \"{path}\": {ex.Message}", ex)); yield break; } // yield return cannot be used in a try block with a catch clause, // so in order to handle errors in enumerator.MoveNext() and enumerator.Current, // the enumerator must be accessed directly rather than using foreach using (enumerable as IDisposable) using (enumerator) { while (true) { string current; try { if (!enumerator.MoveNext()) break; current = enumerator.Current; } catch (Exception ex) { exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating files in \"{path}\": {ex.Message}", ex)); // To avoid an infinite exception loop, // break out at the first sign of trouble break; } yield return current; } } } /// <summary> /// Returns the subdirectories that match the specified search pattern in the specified directory, and optionally searches subdirectories. /// </summary> /// <param name="directory">The directory to search.</param> /// <param name="searchPattern">The search string to match against the names of subdirectories in the <paramref name="directory"/>. This parameter can contain a combination of valid literal and wildcard characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include all subdirectories or only the current directory.</param> /// <param name="exceptionHandler">Handles exceptions thrown during directory enumeration.</param> /// <returns>An array of the <see cref="DirectoryInfo"/> objects representing the subdirectories that match the specified search criteria.</returns> public static DirectoryInfo[] GetDirectories(DirectoryInfo directory, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { return EnumerateDirectories(directory, searchPattern, searchOption, exceptionHandler).ToArray(); } /// <summary> /// Returns an enumerable collection of subdirectories that match a search pattern in a specified path, and optionally searches subdirectories. /// </summary> /// <param name="directory">The directory to search.</param> /// <param name="searchPattern">The search string to match against the names of subdirectories in the <paramref name="directory"/>. This parameter can contain a combination of valid literal and wildcard characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include only the current directory or should include all subdirectories.</param> /// <param name="exceptionHandler">Handles exceptions thrown during directory enumeration.</param> /// <returns>An enumerable collection of the <see cref="DirectoryInfo"/> objects representing the subdirectories that match the specified search criteria.</returns> public static IEnumerable<DirectoryInfo> EnumerateDirectories(DirectoryInfo directory, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { IEnumerable<DirectoryInfo> enumerable; IEnumerator<DirectoryInfo> enumerator; try { IEnumerable<DirectoryInfo> topDirectory = directory.EnumerateDirectories(searchPattern, SearchOption.TopDirectoryOnly); IEnumerable<DirectoryInfo> recursive = Enumerable.Empty<DirectoryInfo>(); if (searchOption == SearchOption.AllDirectories) { recursive = directory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly) .SelectMany(subdirectory => EnumerateDirectories(subdirectory, searchPattern, searchOption, exceptionHandler)); } enumerable = topDirectory.Concat(recursive); enumerator = enumerable.GetEnumerator(); } catch (Exception ex) { string path = directory.FullName; exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating directories in \"{path}\": {ex.Message}", ex)); yield break; } // yield return cannot be used in a try block with a catch clause, // so in order to handle errors in enumerator.MoveNext() and enumerator.Current, // the enumerator must be accessed directly rather than using foreach using (enumerable as IDisposable) using (enumerator) { while (true) { DirectoryInfo current; try { if (!enumerator.MoveNext()) break; current = enumerator.Current; } catch (Exception ex) { string path = directory.FullName; exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating directories in \"{path}\": {ex.Message}", ex)); // To avoid an infinite exception loop, // break out at the first sign of trouble break; } yield return current; } } } /// <summary> /// Returns the files that match the specified search pattern in the specified directory, using a value to determine whether to search subdirectories. /// </summary> /// <param name="directory">The directory to search.</param> /// <param name="searchPattern">The search string to match against the names of files in the <paramref name="directory"/>. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include all subdirectories or only the current directory.</param> /// <param name="exceptionHandler">Handles exceptions thrown during file enumeration.</param> /// <returns>An array of the <see cref="FileInfo"/> objects representing the files in the specified directory that match the specified search pattern, or an empty array if no files are found.</returns> public static FileInfo[] GetFiles(DirectoryInfo directory, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { return EnumerateFiles(directory, searchPattern, searchOption, exceptionHandler).ToArray(); } /// <summary> /// Returns an enumerable collection of files that match a search pattern in a specified path, and optionally searches subdirectories. /// </summary> /// <param name="directory">The directory to search.</param> /// <param name="searchPattern">The search string to match against the names of files in the <paramref name="directory"/>. This parameter can contain a combination of valid literal path and wildcard (* and ?) characters, but doesn't support regular expressions.</param> /// <param name="searchOption">One of the enumeration values that specifies whether the search operation should include only the current directory or should include all subdirectories.</param> /// <param name="exceptionHandler">Handles exceptions thrown during file enumeration.</param> /// <returns>An enumerable collection of the <see cref="FileInfo"/> objects representing the files in the specified directory that match the specified search pattern, or an empty array if no files are found.</returns> public static IEnumerable<FileInfo> EnumerateFiles(DirectoryInfo directory, string searchPattern = "*", SearchOption searchOption = SearchOption.AllDirectories, Action<Exception> exceptionHandler = null) { IEnumerable<FileInfo> enumerable; IEnumerator<FileInfo> enumerator; try { IEnumerable<FileInfo> topDirectory = directory.EnumerateFiles(searchPattern, SearchOption.TopDirectoryOnly); IEnumerable<FileInfo> recursive = Enumerable.Empty<FileInfo>(); if (searchOption == SearchOption.AllDirectories) { recursive = directory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly) .SelectMany(subdirectory => EnumerateFiles(subdirectory, searchPattern, searchOption, exceptionHandler)); } enumerable = topDirectory.Concat(recursive); enumerator = enumerable.GetEnumerator(); } catch (Exception ex) { string path = directory.FullName; exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating files in \"{path}\": {ex.Message}", ex)); yield break; } // yield return cannot be used in a try block with a catch clause, // so in order to handle errors in enumerator.MoveNext() and enumerator.Current, // the enumerator must be accessed directly rather than using foreach using (enumerable as IDisposable) using (enumerator) { while (true) { FileInfo current; try { if (!enumerator.MoveNext()) break; current = enumerator.Current; } catch (Exception ex) { string path = directory.FullName; exceptionHandler?.Invoke(new InvalidOperationException($"Failed while enumerating files in \"{path}\": {ex.Message}", ex)); // To avoid an infinite exception loop, // break out at the first sign of trouble break; } yield return current; } } } /// <summary> /// Ensures the supplied path name is valid. /// </summary> /// <param name="filePath">The file path to be validated.</param> /// <remarks> /// Throws argument exceptions if the <see param="pathName"/> is invalid. /// </remarks> public static void ValidatePathName(string filePath) { if (filePath.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(filePath), "Path cannot be null or empty space"); if (filePath.IndexOfAny(Path.GetInvalidPathChars()) >= 0) throw new ArgumentException("Path has invalid characters.", nameof(filePath)); } /// <summary> /// Determines whether the specified file name matches any of the given file specs (wildcards are defined as '*' or '?' characters). /// </summary> /// <param name="fileSpecs">The file specs used for matching the specified file name.</param> /// <param name="filePath">The file path to be tested against the specified file specs for a match.</param> /// <param name="ignoreCase"><c>true</c> to specify a case-insensitive match; otherwise <c>false</c>.</param> /// <returns><c>true</c> if the specified file name matches any of the given file specs; otherwise <c>false</c>.</returns> /// <exception cref="ArgumentException"><paramref name="filePath"/> contains one or more of the invalid characters defined in <see cref="Path.GetInvalidPathChars"/>.</exception> /// <remarks> /// The syntax for <paramref name="fileSpecs"/> adheres to the following rules: /// /// <ul> /// <li>Either '\' or '/' (as defined by <see cref="Path.DirectorySeparatorChar"/> and <see cref="Path.AltDirectorySeparatorChar"/>) can match the other.</li> /// <li>A single '\' or '/' at the beginning of the pattern matches any valid path root (such as "C:\" or "\\server\share").</li> /// <li>A '?' matches a single character which would be valid in a file name (as defined by <see cref="Path.GetInvalidFileNameChars"/>).</li> /// <li>A '*' matches any number of characters which would be valid in a file name.</li> /// <li>A sequence of "**\" or "**/" matches any number of sequential directories.</li> /// <li>Any other character matches itself.</li> /// </ul> /// </remarks> public static bool IsFilePatternMatch(string[] fileSpecs, string filePath, bool ignoreCase) { foreach (string fileSpec in fileSpecs) { if (IsFilePatternMatch(fileSpec, filePath, ignoreCase)) return true; } return false; } /// <summary> /// Determines whether the specified file name matches the given file spec (wildcards are defined as '*' or '?' characters). /// </summary> /// <param name="fileSpec">The file spec used for matching the specified file name.</param> /// <param name="filePath">The file path to be tested against the specified file spec for a match.</param> /// <param name="ignoreCase"><c>true</c> to specify a case-insensitive match; otherwise <c>false</c>.</param> /// <returns><c>true</c> if the specified file name matches the given file spec; otherwise <c>false</c>.</returns> /// <exception cref="ArgumentException"><paramref name="filePath"/> contains one or more of the invalid characters defined in <see cref="Path.GetInvalidPathChars"/>.</exception> /// <remarks> /// The syntax for <paramref name="fileSpec"/> adheres to the following rules: /// /// <ul> /// <li>Either '\' or '/' (as defined by <see cref="Path.DirectorySeparatorChar"/> and <see cref="Path.AltDirectorySeparatorChar"/>) can match the other.</li> /// <li>A single '\' or '/' at the beginning of the pattern matches any valid path root (such as "C:\" or "\\server\share").</li> /// <li>A '?' matches a single character which would be valid in a file name (as defined by <see cref="Path.GetInvalidFileNameChars"/>).</li> /// <li>A '*' matches any number of characters which would be valid in a file name.</li> /// <li>A sequence of "**\" or "**/" matches any number of sequential directories.</li> /// <li>Any other character matches itself.</li> /// </ul> /// </remarks> public static bool IsFilePatternMatch(string fileSpec, string filePath, bool ignoreCase) { if (string.IsNullOrWhiteSpace(filePath)) return false; // Define regular expression patterns for the three possible sequences that can match the path root. string recursiveDirPattern = $"{(Regex.Escape("**"))}{s_directorySeparatorCharPattern}"; string pathRootPattern = string.Format("({0}[^{0}]|{0}$)", Regex.Escape(Path.DirectorySeparatorChar.ToString())); string altPathRootPattern = string.Format("({0}[^{0}]|{0}$)", Regex.Escape(Path.AltDirectorySeparatorChar.ToString())); // If any of the three patterns are found at the start of fileSpec and the fileName refers to a rooted path, // remove the path root from fileName and then remove all leading directory separator chars from both fileSpec and fileName.