using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using SabreTools.Collections.Extensions;
using SabreTools.Text.Compare;
namespace SabreTools.IO.Extensions
{
///
/// Methods around path operations
///
public static class IOExtensions
{
#region Directory
///
/// Ensure the output directory is a proper format and can be created
///
/// Directory to check
/// True if the directory should be created, false otherwise (default)
/// Full path to the directory
public static string EnsureDirectory(this string? dir, bool create = false)
{
// If the output directory is invalid
if (string.IsNullOrEmpty(dir))
dir = PathTool.GetRuntimeDirectory();
// Get the full path for the output directory
dir = Path.GetFullPath(dir!.Trim('"'));
// If we're creating the output folder, do so
if (create && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
return dir;
}
///
/// Retrieve a list of just directories from inputs
///
/// List of strings representing directories and files
/// True if the parent name should be included in the ParentablePath, false otherwise (default)
/// List of strings representing just directories from the inputs
public static List GetDirectoriesOnly(List inputs, bool appendParent = false)
{
var outputs = new List();
for (int i = 0; i < inputs.Count; i++)
{
string input = inputs[i];
// If we have a null or empty path
if (string.IsNullOrEmpty(input))
continue;
// If we have a wildcard
string pattern = "*";
if (input.Contains("*") || input.Contains("?"))
{
pattern = Path.GetFileName(input);
input = input.Substring(0, input.Length - pattern.Length);
}
// Get the parent path in case of appending
string parentPath = Path.GetFullPath(input);
if (Directory.Exists(input))
{
List directories = input.GetDirectoriesOrdered(pattern);
foreach (string dir in directories)
{
outputs.Add(new ParentablePath(Path.GetFullPath(dir), appendParent ? parentPath : string.Empty));
}
}
}
return outputs;
}
///
/// Retrieve a list of directories from a directory recursively in proper order
///
/// Directory to parse
/// Optional pattern to search for directory names
/// List with all new files
public static List GetDirectoriesOrdered(this string dir, string pattern = "*")
=> GetDirectoriesOrderedHelper(dir, [], pattern);
///
/// Retrieve a list of directories from a directory recursively in proper order
///
/// Directory to parse
/// List representing existing files
/// Optional pattern to search for directory names
/// List with all new files
private static List GetDirectoriesOrderedHelper(string dir, List infiles, string pattern)
{
// Take care of the files in the top directory
List toadd = [.. dir.SafeEnumerateDirectories(pattern, SearchOption.TopDirectoryOnly)];
toadd.Sort(new NaturalComparer());
infiles.AddRange(toadd);
// Then recurse through and add from the directories
foreach (string subDir in toadd)
{
infiles = GetDirectoriesOrderedHelper(subDir, infiles, pattern);
}
// Return the new list
return infiles;
}
///
/// Get all empty folders within a root folder
///
/// Root directory to parse
/// IEumerable containing all directories that are empty, an empty enumerable if the root is empty, null otherwise
public static List? ListEmpty(this string? root)
{
// Check null or empty first
if (root is null)
return null;
// Then, check if the root exists
if (!Directory.Exists(root))
return null;
// Otherwise, get the complete list
var empty = new List();
foreach (var dir in SafeGetDirectories(root, "*", SearchOption.AllDirectories))
{
if (SafeGetFiles(dir).Length == 0)
empty.Add(dir);
}
return empty;
}
#endregion
#region File
///
/// Concatenate all files in the order provided, if possible
///
/// List of paths to combine
/// Path to the output file
/// True if the files were concatenated successfully, false otherwise
public static bool Concatenate(List paths, string output)
{
// If the path list is empty
if (paths.Count == 0)
return false;
// If the output filename is invalid
if (string.IsNullOrEmpty(output))
return false;
try
{
// Try to build the new output file
using var ofs = File.Open(output, FileMode.Create, FileAccess.Write, FileShare.None);
for (int i = 0; i < paths.Count; i++)
{
// Get the next file
string next = paths[i];
if (!File.Exists(next))
break;
// Copy the next input to the output
using var ifs = File.Open(next, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
ifs.BlockCopy(ofs, blockSize: 3 * 1024 * 1024);
}
return true;
}
catch
{
// Absorb the exception right now
return false;
}
}
///
/// Determines a text file's encoding by analyzing its byte order mark (BOM).
/// Defaults to ASCII when detection of the text file's endianness fails.
///
/// The text file to analyze.
/// The detected encoding.
/// http://stackoverflow.com/questions/3825390/effective-way-to-find-any-files-encoding
public static Encoding GetEncoding(this string filename)
{
if (string.IsNullOrEmpty(filename))
return Encoding.Default;
if (!File.Exists(filename))
return Encoding.Default;
// Try to open the file
try
{
var file = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
if (file is null)
return Encoding.Default;
// Read the BOM
var bom = new byte[4];
int read = file.Read(bom, 0, 4);
file.Dispose();
// Disable warning about UTF7 usage
#if NET5_0_OR_GREATER
#pragma warning disable SYSLIB0001
#endif
// Analyze the BOM
if (bom[0] == 0x2b && bom[1] == 0x2f && bom[2] == 0x76) return Encoding.UTF7;
if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) return Encoding.UTF8;
if (bom[0] == 0xff && bom[1] == 0xfe) return Encoding.Unicode; //UTF-16LE
if (bom[0] == 0xfe && bom[1] == 0xff) return Encoding.BigEndianUnicode; //UTF-16BE
if (bom[0] == 0 && bom[1] == 0 && bom[2] == 0xfe && bom[3] == 0xff) return Encoding.UTF32;
return Encoding.Default;
// Restore warning about UTF7 usage
#if NET5_0_OR_GREATER
#pragma warning restore SYSLIB0001
#endif
}
catch
{
return Encoding.Default;
}
}
///
/// Retrieve a list of just files from inputs
///
/// List of strings representing directories and files
/// True if the parent name should be be included in the ParentablePath, false otherwise (default)
/// List of strings representing just files from the inputs
public static List GetFilesOnly(List inputs, bool appendParent = false)
{
var outputs = new List();
for (int i = 0; i < inputs.Count; i++)
{
string input = inputs[i].Trim('"');
// If we have a null or empty path
if (string.IsNullOrEmpty(input))
continue;
// If we have a wildcard
string pattern = "*";
if (input.Contains("*") || input.Contains("?"))
{
pattern = Path.GetFileName(input);
input = input.Substring(0, input.Length - pattern.Length);
}
// Get the parent path in case of appending
string parentPath = Path.GetFullPath(input);
if (Directory.Exists(input))
{
List files = input.GetFilesOrdered(pattern);
foreach (string file in files)
{
outputs.Add(new ParentablePath(Path.GetFullPath(file), appendParent ? parentPath : string.Empty));
}
}
else if (File.Exists(input))
{
outputs.Add(new ParentablePath(Path.GetFullPath(input), appendParent ? parentPath : string.Empty));
}
}
return outputs;
}
///
/// Retrieve a list of files from a directory recursively in proper order
///
/// Directory to parse
/// Optional pattern to search for directory names
/// List with all new files
public static List GetFilesOrdered(this string dir, string pattern = "*")
=> GetFilesOrderedHelper(dir, [], pattern);
///
/// Retrieve a list of files from a directory recursively in proper order
///
/// Directory to parse
/// List representing existing files
/// Optional pattern to search for directory names
/// List with all new files
private static List GetFilesOrderedHelper(string dir, List infiles, string pattern)
{
// Take care of the files in the top directory
List toadd = [.. dir.SafeEnumerateFiles(pattern, SearchOption.TopDirectoryOnly)];
toadd.Sort(new NaturalComparer());
infiles.AddRange(toadd);
// Then recurse through and add from the directories
List subDirs = [.. dir.SafeEnumerateDirectories(pattern, SearchOption.TopDirectoryOnly)];
subDirs.Sort(new NaturalComparer());
foreach (string subdir in subDirs)
{
infiles = GetFilesOrderedHelper(subdir, infiles, pattern);
}
// Return the new list
return infiles;
}
///
/// Helper to get the filesize from a path
///
/// Size of the file path, -1 on error
public static long GetFileSize(this string? filename)
{
// Invalid filenames are ignored
if (string.IsNullOrEmpty(filename))
return -1;
// Non-file paths are ignored
if (!File.Exists(filename))
return -1;
try
{
return new FileInfo(filename).Length;
}
catch
{
// Ignore errors
return -1;
}
}
///
/// Get the extension from the path, if possible
///
/// Path to get extension from
/// Extension, if possible
public static string? GetNormalizedExtension(this string? path)
{
// Check null or empty first
if (string.IsNullOrEmpty(path))
return null;
// Get the extension from the path, if possible
string? ext = Path.GetExtension(path)?.ToLowerInvariant();
// Check if the extension is null or empty
if (string.IsNullOrEmpty(ext))
return null;
// Make sure that extensions are valid
ext = ext!.TrimStart('.');
return ext;
}
///
public static string NormalizeFilePath(this string? path)
=> path.NormalizeFilePath(fullPath: true);
///
/// Normalize a file path
///
/// Path value to normalize
/// Indicates if the full path is used
/// The normalized path on success, an empty path if null, or the original path on error
///
/// This method performs the following steps:
/// - Remove quotes and angle brackets from the start and end
/// - Replaces invalid path characters with '_'
/// - Remove spaces before and after directory separators
///
public static string NormalizeFilePath(this string? path, bool fullPath)
{
try
{
// If we have an invalid path
if (string.IsNullOrEmpty(path))
return string.Empty;
// Remove quotes and angle brackets from path
path = path!.Trim('\"');
path = path!.Trim('<');
path = path!.Trim('>');
// Remove invalid path characters
foreach (char c in Path.GetInvalidPathChars())
{
path = path.Replace(c, '_');
}
// Try getting the combined path and returning that directly
string usablePath = fullPath ? Path.GetFullPath(path) : path;
var fullDirectory = Path.GetDirectoryName(usablePath)?.Trim();
string fullFile = Path.GetFileName(usablePath).Trim();
// Remove invalid filename characters
foreach (char c in Path.GetInvalidFileNameChars())
{
fullFile = fullFile.Replace(c, '_');
}
// Rebuild the path, if necessary
if (!string.IsNullOrEmpty(fullDirectory))
fullFile = Path.Combine(fullDirectory, fullFile);
// Remove spaces before and after separators
return Regex.Replace(fullFile, @"\s*([\\|/])\s*", @"$1");
}
catch { }
return path ?? string.Empty;
}
///
/// Resolve a file path that may be absolute, relative,
/// within the runtime directory, or contained within a PATH directory.
///
/// Raw value from the user's options
/// The absolute path of the located files, or null on failure
public static string? ResolvePath(this string? path)
{
// Invalid paths always return null
if (string.IsNullOrEmpty(path))
return null;
// If the configured path starts with a home character
if (path!.StartsWith("~/") || path.StartsWith("~\\"))
{
string homeDirectory = PathTool.GetHomeDirectory();
path = path.Substring(2);
path = Path.Combine(homeDirectory, path);
return File.Exists(path) ? Path.GetFullPath(path) : null;
}
// Explicit location (absolute or relative path)
if (path.Contains("/") || path.Contains("\\"))
return File.Exists(path) ? Path.GetFullPath(path) : null;
// Check the runtime directory if no directory path is provided
string runtimeDir = PathTool.GetRuntimeDirectory();
if (!string.IsNullOrEmpty(runtimeDir))
{
string candidate = Path.Combine(runtimeDir, path);
if (File.Exists(candidate))
return candidate;
}
// Attempt to get the PATH variable for searching
string? pathEnv = Environment.GetEnvironmentVariable("PATH");
if (string.IsNullOrEmpty(pathEnv))
return null;
// Loop through all entries in PATH
var pathParts = pathEnv!.Split(Path.PathSeparator);
foreach (string dir in pathParts)
{
if (string.IsNullOrEmpty(dir))
continue;
string candidate = Path.Combine(dir, path);
if (File.Exists(candidate))
return candidate;
}
// All options failed
return null;
}
#endregion
#region Safe Directory Enumeration
///
/// Returns an empty enumerable on any exception
public static string[] SafeGetDirectories(this string path)
{
try
{
return Directory.GetDirectories(path);
}
catch
{
return [];
}
}
///
/// Returns an empty enumerable on any exception
public static string[] SafeGetDirectories(this string path, string searchPattern)
{
try
{
return Directory.GetDirectories(path, searchPattern);
}
catch
{
return [];
}
}
///
/// Returns an empty enumerable on any exception
public static string[] SafeGetDirectories(this string path, string searchPattern, SearchOption searchOption)
{
try
{
// Outside of Windows, linked directories are not followed
if (SkipLinkedDirectories(searchPattern, searchOption))
return [.. WalkWithoutLinkedDirectories(path, searchPattern, Directory.GetDirectories, Directory.GetDirectories)];
return Directory.GetDirectories(path, searchPattern, searchOption);
}
catch
{
return [];
}
}
///
/// Returns an empty enumerable on any exception
public static string[] SafeGetFiles(this string path)
{
try
{
return Directory.GetFiles(path);
}
catch
{
return [];
}
}
///
/// Returns an empty enumerable on any exception
public static string[] SafeGetFiles(this string path, string searchPattern)
{
try
{
return Directory.GetFiles(path, searchPattern);
}
catch
{
return [];
}
}
///
/// Returns an empty enumerable on any exception
public static string[] SafeGetFiles(this string path, string searchPattern, SearchOption searchOption)
{
try
{
// Outside of Windows, linked directories are not followed
if (SkipLinkedDirectories(searchPattern, searchOption))
return [.. WalkWithoutLinkedDirectories(path, searchPattern, Directory.GetFiles, Directory.GetDirectories)];
return Directory.GetFiles(path, searchPattern, searchOption);
}
catch
{
return [];
}
}
///
/// Returns an empty enumerable on any exception
public static string[] SafeGetFileSystemEntries(this string path)
{
try
{
return Directory.GetFileSystemEntries(path);
}
catch
{
return [];
}
}
///
/// Returns an empty enumerable on any exception
public static string[] SafeGetFileSystemEntries(this string path, string searchPattern)
{
try
{
return Directory.GetFileSystemEntries(path, searchPattern);
}
catch
{
return [];
}
}
///
/// Returns an empty enumerable on any exception
public static IEnumerable SafeGetFileSystemEntries(this string path, string searchPattern, SearchOption searchOption)
{
try
{
var enumerable = Directory.GetFileSystemEntries(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
#if NET20 || NET35
///
/// Calls implementation
public static IEnumerable SafeEnumerateDirectories(this string path)
=> path.SafeGetDirectories();
///
/// Calls implementation
public static IEnumerable SafeEnumerateDirectories(this string path, string searchPattern)
=> path.SafeGetDirectories(searchPattern);
///
/// Calls implementation
public static IEnumerable SafeEnumerateDirectories(this string path, string searchPattern, SearchOption searchOption)
=> path.SafeGetDirectories(searchPattern, searchOption);
///
/// Calls implementation
public static IEnumerable SafeEnumerateFiles(this string path)
=> path.SafeGetFiles();
///
/// Calls implementation
public static IEnumerable SafeEnumerateFiles(this string path, string searchPattern)
=> path.SafeGetFiles(searchPattern);
///
/// Calls implementation
public static IEnumerable SafeEnumerateFiles(this string path, string searchPattern, SearchOption searchOption)
=> path.SafeGetFiles(searchPattern, searchOption);
///
/// Calls implementation
public static IEnumerable SafeEnumerateFileSystemEntries(this string path)
=> path.SafeGetFileSystemEntries();
///
/// Calls implementation
public static IEnumerable SafeEnumerateFileSystemEntries(this string path, string searchPattern)
=> path.SafeGetFileSystemEntries(searchPattern);
///
/// Calls implementation
public static IEnumerable SafeEnumerateFileSystemEntries(this string path, string searchPattern, SearchOption searchOption)
=> path.SafeGetFileSystemEntries(searchPattern, searchOption);
#elif NET40_OR_GREATER || NETSTANDARD2_0
///
public static IEnumerable SafeEnumerateDirectories(this string path)
{
try
{
var enumerable = Directory.EnumerateDirectories(path);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateDirectories(this string path, string searchPattern)
{
try
{
var enumerable = Directory.EnumerateDirectories(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateDirectories(this string path, string searchPattern, SearchOption searchOption)
{
try
{
// Outside of Windows, linked directories are not followed
if (SkipLinkedDirectories(searchPattern, searchOption))
return WalkWithoutLinkedDirectories(path, searchPattern, SafeEnumerateDirectories, SafeEnumerateDirectories);
var enumerable = Directory.EnumerateDirectories(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateFiles(this string path)
{
try
{
var enumerable = Directory.EnumerateFiles(path);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateFiles(this string path, string searchPattern)
{
try
{
var enumerable = Directory.EnumerateFiles(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateFiles(this string path, string searchPattern, SearchOption searchOption)
{
try
{
// Outside of Windows, linked directories are not followed
if (SkipLinkedDirectories(searchPattern, searchOption))
return WalkWithoutLinkedDirectories(path, searchPattern, SafeEnumerateFiles, SafeEnumerateDirectories);
var enumerable = Directory.EnumerateFiles(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateFileSystemEntries(this string path)
{
try
{
var enumerable = Directory.EnumerateFileSystemEntries(path);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateFileSystemEntries(this string path, string searchPattern)
{
try
{
var enumerable = Directory.EnumerateFileSystemEntries(path, searchPattern);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateFileSystemEntries(this string path, string searchPattern, SearchOption searchOption)
{
try
{
// Outside of Windows, linked directories are not followed
if (SkipLinkedDirectories(searchPattern, searchOption))
return WalkWithoutLinkedDirectories(path, searchPattern, SafeEnumerateFileSystemEntries, SafeEnumerateDirectories);
var enumerable = Directory.EnumerateFileSystemEntries(path, searchPattern, searchOption);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
#else
///
public static IEnumerable SafeEnumerateDirectories(this string path)
{
try
{
string searchPattern = "*";
SearchOption searchOption = SearchOption.TopDirectoryOnly;
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateDirectories(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateDirectories(this string path, string searchPattern)
{
try
{
SearchOption searchOption = SearchOption.TopDirectoryOnly;
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateDirectories(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateDirectories(this string path, string searchPattern, SearchOption searchOption)
{
try
{
// Outside of Windows, linked directories are not followed
if (SkipLinkedDirectories(searchPattern, searchOption))
return WalkWithoutLinkedDirectories(path, searchPattern, SafeEnumerateDirectories, SafeEnumerateDirectories);
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateDirectories(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateFiles(this string path)
{
try
{
string searchPattern = "*";
SearchOption searchOption = SearchOption.TopDirectoryOnly;
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateFiles(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateFiles(this string path, string searchPattern)
{
try
{
SearchOption searchOption = SearchOption.TopDirectoryOnly;
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateFiles(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateFiles(this string path, string searchPattern, SearchOption searchOption)
{
try
{
// Outside of Windows, linked directories are not followed
if (SkipLinkedDirectories(searchPattern, searchOption))
return WalkWithoutLinkedDirectories(path, searchPattern, SafeEnumerateFiles, SafeEnumerateDirectories);
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateFiles(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateFileSystemEntries(this string path)
{
try
{
string searchPattern = "*";
SearchOption searchOption = SearchOption.TopDirectoryOnly;
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateFileSystemEntries(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateFileSystemEntries(this string path, string searchPattern)
{
try
{
SearchOption searchOption = SearchOption.TopDirectoryOnly;
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateFileSystemEntries(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
///
public static IEnumerable SafeEnumerateFileSystemEntries(this string path, string searchPattern, SearchOption searchOption)
{
try
{
// Outside of Windows, linked directories are not followed
if (SkipLinkedDirectories(searchPattern, searchOption))
return WalkWithoutLinkedDirectories(path, searchPattern, SafeEnumerateFileSystemEntries, SafeEnumerateDirectories);
var enumerationOptions = FromSearchOption(searchOption);
enumerationOptions.IgnoreInaccessible = true;
var enumerable = Directory.EnumerateFileSystemEntries(path, searchPattern, enumerationOptions);
return enumerable.SafeEnumerate();
}
catch
{
return [];
}
}
/// Initializes a new instance of the class with the recommended default options.
///
private static EnumerationOptions FromSearchOption(SearchOption searchOption)
{
if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories))
throw new ArgumentOutOfRangeException(nameof(searchOption));
return searchOption == SearchOption.AllDirectories
? new EnumerationOptions { RecurseSubdirectories = true, MatchType = MatchType.Win32, AttributesToSkip = 0, IgnoreInaccessible = false }
: new EnumerationOptions { MatchType = MatchType.Win32, AttributesToSkip = 0, IgnoreInaccessible = false };
}
#endif
///
/// Lists the entries of one directory that match a pattern
///
private delegate IEnumerable DirectoryListing(string directory, string searchPattern);
///
/// Indicates if a path is a linked directory
///
/// Path to check
/// True if the path is a directory that carries FileAttributes.ReparsePoint, false otherwise
///
/// Returns false if the directory cannot be read, such as below a directory without
/// execute permission, so the directory is entered and fails like in the recursive search
///
private static bool IsLinkedDirectory(string path)
{
// Exists returns false instead of throwing if the directory cannot be read
var info = new DirectoryInfo(path);
if (!info.Exists)
return false;
#if NET20 || NET35
return (info.Attributes & FileAttributes.ReparsePoint) != 0;
#else
return info.Attributes.HasFlag(FileAttributes.ReparsePoint);
#endif
}
///
/// Indicates if a search walks the directories itself to skip linked directories
///
/// Pattern to match the entry names against
/// Whether to search the top directory only or all subdirectories
/// True if the walk is used, false if the recursive search is used
private static bool SkipLinkedDirectories(string searchPattern, SearchOption searchOption)
{
// Only a search of all subdirectories enters subdirectories
if (searchOption != SearchOption.AllDirectories)
return false;
// Linked directories on discs are only read as links outside of Windows
if (SystemEnvironment.IsWindows())
return false;
// A directory part of the pattern is matched once from the top directory, but the walk would match it in each directory
if (!string.IsNullOrEmpty(Path.GetDirectoryName(searchPattern)))
return false;
return true;
}
///
/// Walk a directory and all subdirectories without following linked directories
///
/// Directory to search
/// Pattern to match the entry names against
/// Lists the matching entries of one directory
/// Lists the subdirectories of one directory
/// The same entries in the same order as the recursive search, minus those under linked directories
///
/// A linked directory that points back into the tree, such as a UDF symlink whose
/// target reads as empty on Linux, makes the recursive search run without end
///
private static IEnumerable WalkWithoutLinkedDirectories(string path, string searchPattern, DirectoryListing getEntries, DirectoryListing getDirectories)
{
var pending = new Queue();
pending.Enqueue(path);
while (pending.Count > 0)
{
string directory = pending.Dequeue();
foreach (string entry in getEntries(directory, searchPattern))
{
yield return entry;
}
foreach (string subdirectory in getDirectories(directory, "*"))
{
if (!IsLinkedDirectory(subdirectory))
pending.Enqueue(subdirectory);
}
}
}
#endregion
}
}