I wrote a method that needed to find all files within a path, and I want to get all the files using recursion. Here's my current method:
public void doStart(DirectoryInfo dir, string filePattern)
{
try
{
foreach (FileInfo fileInfo in dir.GetFiles(filePattern))
{
if (fileFound != null)
{
fileFound(fileInfo);
}
}
}
catch (Exception)
{
}
try
{
foreach (DirectoryInfo dirInfo in dir.GetDirectories())
{
doStart(dirInfo, filePattern);
}
}
catch (Exception)
{
}
}
public void Start(string path, string filePattern)
{
doStart(new DirectoryInfo(path), filePattern);
}
Is there is better way to write this kind of recursion or is this good enough ?
Try something like this:
string[] filePaths = Directory.GetFiles(#dir, "*.filetype", SearchOption.AllDirectories);
This would recursively look through the directory, finding all files with a certain filetype ('.filetype') and returns a string array containing all found files.
Also, I'd recommend not to use empty catch blocks, as your application won't let you know if something went wrong. Either show a message box (or something similar) or log it to a database or something.
Further, what would your DoStart() method do if there is a subdirectory in a subdirectory? From what I'm seeing, I'd say it only searches on 1 sublevel.
Don't swallow all exceptions. If you need to ignore specific exceptions, catch those but let others bubble up
(style) Methods should be PascalCased (e.g. DoStart and `FileFound'
(style) Create an OnFileFound method instead of calling FileFound directly (I assume fileFound is an event handler?)
Other than that it looks fine to me.
Here is an example of true recursion. This will search until there are no more sub-directories to find, unlike Directory.GetFiles SearchOption.AllDirectories. You can modify this to add search filters as a parameter.
public IEnumerable<string> GetFilesRecursive(string ParentDirectory)
{
string[] subDirectories = Directory.GetDirectories(ParentDirectory);
foreach (string file in Directory.GetFiles(ParentDirectory))
{
yield return file;
}
foreach (string subDirectory in subDirectories)
{
foreach (string file in GetFilesRecursive(subDirectory))
{
yield return file;
}
}
}
Related
I am trying to recursively delete all empty subdirectories in a root directory and (not files)using C#. I have referred this post (Recursive delete of files and directories in C#)
and wrote the function as follows.
private void DeleteEmptySubFolders()
{
DirectoryInfo root = new DirectoryInfo(folderPath);
RecursiveDelete(root,true);
}
public static void RecursiveDelete(DirectoryInfo baseDir,bool isRootDir)
{
if (!baseDir.Exists)
return;
foreach (var dir in baseDir.EnumerateDirectories())
{
RecursiveDelete(dir,false);
}
try
{
if (!isRootDir)
baseDir.Delete(false);
}
catch(Exception ex)
{
}
}
My question is baseDir.Delete(false); will give me an exception when a directory is not empty, and I am just passing that exception without handling it(I just want to skip those ones, don't have to log). Is there a better way to do this?
Instead of try/catch, check if any files exist:
bool isEmpty = !Directory.EnumerateFileSystemEntries(baseDir.FullName).Any();
Now you can avoid trying to delete non-empty folders:
if (!isRootDir && isEmpty)
baseDir.Delete(false);
How can append f to a list each time, then write the list into a text file ? I tried with the following code but the text file is always empty?
static void DirSearch(string dir, string pattern)
{
try
{
List<String> filesList = new List<string>();
foreach (string f in Directory.GetFiles(dir, pattern))
//Console.WriteLine(f);
{
// Console.WriteLine(f); // // <- This works correctly
filesList.Add(f);
}
foreach (string d in Directory.GetDirectories(dir))
{
//Console.WriteLine(d);
DirSearch(d, pattern);
}
File.WriteAllLines("files.txt", filesList.ToArray());
}
catch (System.Exception ex)
{
//Console.WriteLine(ex.Message);
}
}
I suspect the problem is that on each invocation of DirSearch, you're overwriting the file output from previous invocations. If the final invocation has no files (only directories) you'll end up with an empty file.
Options:
Use Directory.GetFiles(dir, pattern, SearchOption.AllDirectories so you don't need to use recursion at all.
Use File.AppendAllLines instead of File.WriteAllLines
Build up the list entirely in memory (recursively) and only at the very end call File.WriteAllLines.
The latter approach would look something like this:
public static DirSearch(string dir, string pattern)
{
List<string> files = new List<string>();
DirSearchImpl(dir, pattern, files);
File.WriteAllLines("files.txt", files);
}
private static DirSearchImpl(string dir, string pattern, List<string> files)
{
// Simpler than your previous loop...
files.AddRange(Directory.GetFiles(dir, pattern));
foreach (var subdirectory in Directory.GetDirectories(dir))
{
DirSearchImpl(subdirectory, pattern, files);
}
}
I'd also suggest changing your exception handling - catching Exception is rarely a good idea, and I'm not sure you really want to keep going if things fail, do you?
I wrote a program (on Windows 7) that call the method DirectoryInfo.GetFiles(), and in the folder "documents and settings", I have the exception of UnauthorizedAccess.
I tried lots of solutions, like:
create a manifest with
`<requestedExecutionLevel level="highestAvailable" uiAccess="false" />`
and also with this
DirectorySecurity dSecurity = Directory.GetAccessControl(dir.FullName);
dSecurity.AddAccessRule(new FileSystemAccessRule("Luca", FileSystemRights.FullControl, AccessControlType.Allow));
Directory.SetAccessControl(dir.FullName, dSecurity);
What could be the issue?
First off, you should be using DirectoryInfo.EnumerateFiles(...) instead of GetFiles(...). EnumerateFiles(...) keeps you from having to get the entire list until you actually need to.
I ran into this issue a while back and found that I ended up needing to implement a replacement IEnumerable in order to be able to complete an enumeration over folders that I may only have selected access to.
You can see the result of my research in the following thread. DirectoryInfo.EnumerateFiles(...) causes UnauthorizedAccessException (and other exceptions)
Just a Quick Copy Paste because I just had the same Problem.
Adjust the Code to your needs (because I calculate the the size, counting all files and "save" all the Files I want to copy in a List).
After you got all files in your List you can start copy them or what ever you wanna do with the Files:
private double CalculateSize(string sourcePath, Progress state, List<FileInfo> filesToCopy)
{
int _fileCount = 0;
DirectoryInfo sourceDirectory = new DirectoryInfo(sourcePath);
FileInfo[] files = null;
try
{
files = sourceDirectory.GetFiles();
}
catch(UnauthorizedAccessException ex)
{
// DO SOME LOGGING-MAGIC IN HERE...
}
if (files != null)
{
foreach (FileInfo file in files)
{
fullSizeToCopy += file.Length;
filesToCopy.Add(file);
_fileCount++;
}
}
DirectoryInfo[] directories = null;
try
{
directories = sourceDirectory.GetDirectories();
}
catch(UnauthorizedAccessException ex)
{
// Do more logging Magic in here...
}
if (directories != null)
foreach (DirectoryInfo direcotry in directories)
{
CalculateSize(direcotry.FullName, state, filesToCopy);
}
state.FileCount = _fileCount;
return fullSizeToCopy;
}
Your best bet might be to put a try/catch block around the call and ignore any directories you don't have access to. Maybe not the best solution, but it would at least make your method get all the directories you do have access to. Something like this:
try
{
directory.GetFiles();
}
catch (UnauthorizedAccessException)
{
string logMsg = string.Format("Unable to access directory {0}", directory.FullName);
//Handle any desired logging here
}
Just like blow, use EnumerateDirectories rather than DirectoryInfo.getfiles
private void ScanEmptyDirs(string dir, ref int cnt, CancellationToken token)
{
if (String.IsNullOrEmpty(dir))
{
throw new ArgumentException("Starting directory is a null reference or an empty string: dir");
}
try
{
foreach (var d in Directory.EnumerateDirectories(dir))
{
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
}
ScanEmptyDirs(d, ref cnt, token);
}
EmptyJudge(dir, ref cnt);
}
catch (UnauthorizedAccessException) { }
}
I have this line of code: (using LINQ)
//string folder <-- folder browser dialog.
listFiles = Directory.GetFiles(folder, "*.xml",
SearchOption.AllDirectories).Select(
fileName => Path.GetFullPath(fileName)).ToList();
But sometimes my program finds protected files, such as system files or even system folders that can't be opened.
How can I surpass this problem:
Only get file name of open/free files-folders.
You can't tell, you just have to catch the exception.
What if the file is free when doing the free check, but in use when processing?
That can be a problem. If it throws an exception when going through the directories, it stops.
If you want to ignore those directories and keep going, you have to write a recursive method to do it:
List<string> GetFiles(string folder, string filter)
{
List<string> files = new List<string>();
try
{
// get all of the files in this directory
files.AddRange(Directory.GetFiles(folder, filter));
// Now recursively visit the directories
foreach (var dir in Directory.GetDirectories(folder))
{
files.AddRange(GetFiles(dir, filter));
}
}
catch (UnauthorizedAccessException)
{
// problem accessing this directory.
// ignore it and move on.
}
return files;
}
A somewhat more memory efficient version would be:
private List<string> GetFiles(string folder, string filter)
{
var files = new List<string>();
// To create a recursive Action, you have to initialize it to null,
// and then reassign it. Otherwise the compiler complains that you're
// using an unassigned variable.
Action<string> getFilesInDir = null;
getFilesInDir = new Action<string>(dir =>
{
try
{
// get all the files in this directory
files.AddRange(Directory.GetFiles(dir, filter));
// and recursively visit the directories
foreach (var subdir in Directory.GetDirectories(dir))
{
getFilesInDir(subdir);
}
}
catch (UnauthorizedAccessException)
{
// ignore exception
}
});
getFilesInDir(folder);
return files;
}
you could use something like this, potentially you will have to tweak attribute check:
Directory.GetFiles(folder, "*.xml", SearchOption.AllDirectories)
.Select(fileName => new FileInfo(Path.GetFullPath(fileName)))
.Where(n => !n.Attributes.HasFlag(FileAttributes.System))
.Select(n => n.FullName)
.ToList();
Listing all files in a drive other than my system drive throws an UnauthorizedAccessException.
How can I solve this problem?
Is there a way to grant my application the access it needs?
My code:
Directory.GetFiles("S:\\", ...)
Here's a class that will work:
public static class FileDirectorySearcher
{
public static IEnumerable<string> Search(string searchPath, string searchPattern)
{
IEnumerable<string> files = GetFileSystemEntries(searchPath, searchPattern);
foreach (string file in files)
{
yield return file;
}
IEnumerable<string> directories = GetDirectories(searchPath);
foreach (string directory in directories)
{
files = Search(directory, searchPattern);
foreach (string file in files)
{
yield return file;
}
}
}
private static IEnumerable<string> GetDirectories(string directory)
{
IEnumerable<string> subDirectories = null;
try
{
subDirectories = Directory.EnumerateDirectories(directory, "*.*", SearchOption.TopDirectoryOnly);
}
catch (UnauthorizedAccessException)
{
}
if (subDirectories != null)
{
foreach (string subDirectory in subDirectories)
{
yield return subDirectory;
}
}
}
private static IEnumerable<string> GetFileSystemEntries(string directory, string searchPattern)
{
IEnumerable<string> files = null;
try
{
files = Directory.EnumerateFileSystemEntries(directory, searchPattern, SearchOption.TopDirectoryOnly);
}
catch (UnauthorizedAccessException)
{
}
if (files != null)
{
foreach (string file in files)
{
yield return file;
}
}
}
}
You can the use it like this:
IEnumerable<string> filesOrDirectories = FileDirectorySearcher.Search(#"C:\", "*.txt");
foreach (string fileOrDirectory in filesOrDirectories)
{
// Do something here.
}
It's recursive, but the use of yield gives it a low memory footprint (under 10KB in my testing). If you want only files that match the pattern and not directories as well just replace EnumerateFileSystemEntries with EnumerateFiles.
Are you allowed to access the drive? Can the program access the drive when it's not run from Visual Studio? Are restrictive permissions defined in the project's Security page ("Security Page, Project Designer")?
In .net core you can do something like this below. It can search for all subdirectories recursively with good performance and ignoring paths without access.
I also tried other methods found in
How to quickly check if folder is empty (.NET)? and
Is there a faster way than this to find all the files in a directory and all sub directories? and
https://www.codeproject.com/Articles/1383832/System-IO-Directory-Alternative-using-WinAPI
public static IEnumerable<string> ListFiles(string baseDir)
{
EnumerationOptions opt = new EnumerationOptions();
opt.RecurseSubdirectories = true;
opt.ReturnSpecialDirectories = false;
//opt.AttributesToSkip = FileAttributes.Hidden | FileAttributes.System;
opt.AttributesToSkip = 0;
opt.IgnoreInaccessible = true;
var tmp = Directory.EnumerateFileSystemEntries(baseDir, "*", opt);
return tmp;
}
I solved the problem. Not really but at least the source.
It was the SearchOption.AllDirectories option that caused the exception.
But when I just list the immediate files using Directories.GetFiles, it works.
This is good enough for me.
Any way to solve the recursive listing problem?