I am not looking for specific files but rather specific directories to index all files in those directories. I am aware of how to search for file types and names but not how to do that with directories for indexing.
Try something like this: (as taken from MS code examples)
public class StackBasedIteration
{
static void Main(string[] args)
{
// Specify the starting folder on the command line, or in
// Visual Studio in the Project > Properties > Debug pane.
TraverseTree(args[0]);
Console.WriteLine("Press any key");
Console.ReadKey();
}
public static void TraverseTree(string root)
{
// Data structure to hold names of subfolders to be
// examined for files.
Stack<string> dirs = new Stack<string>(20);
if (!System.IO.Directory.Exists(root))
{
throw new ArgumentException();
}
dirs.Push(root);
while (dirs.Count > 0)
{
string currentDir = dirs.Pop();
string[] subDirs;
try
{
subDirs = System.IO.Directory.GetDirectories(currentDir);
}
// An UnauthorizedAccessException exception will be thrown if we do not have
// discovery permission on a folder or file. It may or may not be acceptable
// to ignore the exception and continue enumerating the remaining files and
// folders. It is also possible (but unlikely) that a DirectoryNotFound exception
// will be raised. This will happen if currentDir has been deleted by
// another application or thread after our call to Directory.Exists. The
// choice of which exceptions to catch depends entirely on the specific task
// you are intending to perform and also on how much you know with certainty
// about the systems on which this code will run.
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
continue;
}
catch (System.IO.DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
continue;
}
string[] files = null;
try
{
files = System.IO.Directory.GetFiles(currentDir);
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
continue;
}
catch (System.IO.DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
continue;
}
// Perform the required action on each file here.
// Modify this block to perform your required task.
foreach (string file in files)
{
try
{
// Perform whatever action is required in your scenario.
System.IO.FileInfo fi = new System.IO.FileInfo(file);
Console.WriteLine("{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime);
}
catch (System.IO.FileNotFoundException e)
{
// If file was deleted by a separate application
// or thread since the call to TraverseTree()
// then just continue.
Console.WriteLine(e.Message);
continue;
}
}
// Push the subdirectories onto the stack for traversal.
// This could also be done before handing the files.
foreach (string str in subDirs)
dirs.Push(str);
}
}
}
I would simply modify to only get the files once you are in the directory you want to be looking in.
I was able to get the path of each logical drive to begin my search by adding System.IO.DriveInfo.GetDrives(); and storing the resulting list then passing those strings into the answer by Charles
Related
I've been working on a sort of a directory info viewer.
1st stage was to create and populate a TreeView model and during coding I've nested a method to gather information about files on that dir.
All worked fine, values returned we're correct etc.
Now at the stage of code cleanup when I try to get that method out of TreeView creation one I get values that are not even close to original (like 90 MB instead of 1.2 TB).
Below code with issues marked:
private static int itemCount = 0;
private static long folderSizeInfo = 0;
public static void ListDirectory(string path)
{
(...)
var rootDirectoryInfo = new DirectoryInfo(path);
MainWindow.newWindowReport.Drzewko.Items.Add(CreateDirectoryNode(rootDirectoryInfo));
//this does not work if invoked from here <----------------- FIX ME!
//DirectoryMainOperation(rootDirectoryInfo);
//need to run this once more to get data from root
DirectoryContent_Operation(rootDirectoryInfo);
(...)
}
private static TreeViewItem CreateDirectoryNode(DirectoryInfo directoryInfo)
{
var directoryNode = new TreeViewItem { Header = directoryInfo.Name };
try
{
foreach (var directory in directoryInfo.GetDirectories())
{
if (!IsIgnorable(directory.Name))
{
directoryNode.Items.Add(CreateDirectoryNode(directory));
//this method somehow only works here <------------------------ FIX ME!
DirectoryContent_Operation(directory);
}
}
}
catch (UnauthorizedAccessException)
{
//Console.WriteLine("Path is not accessible: {0}", i);
}
return directoryNode;
}
//-------------------TO BE FIXED------------------
private static void DirectoryMainOperation(DirectoryInfo directoryInfo)
{
try
{
foreach (var directory in directoryInfo.GetDirectories())
{
if (!IsIgnorable(directory.Name))
{
DirectoryContent_Operation(directory);
}
}
}
catch (UnauthorizedAccessException)
{
//Console.WriteLine("Path is not accessible: {0}", i);
}
}
private static void DirectoryContent_Operation(DirectoryInfo targetDir)
{
try
{
foreach (var file in targetDir.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
{
itemCount++;
folderSizeInfo += file.Length;
fileTable.Add(new FileExtension_List
{
FileFormat = file.Extension,
Category = extDB.Translation(file.Extension.ToUpper()),
Count = 1,
TotalSize = file.Length
});
}
}
catch (UnauthorizedAccessException)
{
}
catch (Exception ex)
{
}
}
The gist of it is that if I invoke "DirectoryContent_Operation(directory)" from:
"CreateDirectoryNode(DirectoryInfo directoryInfo)" it returns 1.2 TB (correct value)
"DirectoryMainOperation(DirectoryInfo directoryInfo)" it returns 90 MB.
From what it looks like DirectoryMainOperation returns prematurely due to an UnauthorizedAccessException, hence not returning all directories. In DirectoryContent_Operation you swallow every exception by catching Exception!
Please remove the try-catch (everywhere) to see what the (inner) exception message exactly is about and where the exception is thrown..
Note that swallowing exceptions is always a code smell and will likely introduce bugs, that can be really hard to identify.
If you can't handle the exception (put the application back into a stable state) the application must crash. Then fix the reason for the crash.
Before you think about if and how to handle an exception, think about how to avoid it.
Also from a performance perspective, it is highly recommended to avoid expensive exceptions.
Your problem shows how important it is to let exceptions crash the application in order to learn about implementation errors and also to prevent unwanted silent side effects like your incomplete directory list. Swallowing exceptions has put your application into an unpredictable state, which will lead to a lot faulty behavior, which may be unnoticed by users at first. This ca be very costly for a customer who uses your application to manage his business.
To avoid the UnauthorizedAccessException in your case, you can use DirectoryInfo.EnumerateDirectories(String, EnumerationOptions), which is available for .NET Core only (since version 2.1).
It avoids throwing an UnauthorizedAccessException exception by skipping forbidden directories by default.
Alternatively, only enumerate top level directories. Then check each child directory if it's forbidden, before you continue to recursively enumerate the child's top level directories. It is very likely that the following recursive enumeration will solve your problem.
public void ListDirectory(string path)
{
var rootDirectoryInfo = new DirectoryInfo(path);
var rootDirectoryNode = new TreeViewItem { Header = directoryInfo.Name };
MainWindow.newWindowReport.Drzewko.Items.Add(rootDirectoryNode);
CreateDirectoryNode(rootDirectoryInfo, rootDirectoryNode);
}
private static void CreateDirectoryNode(DirectoryInfo parentDirectory, TreeViewItem parentDirectoryNode)
{
foreach (DirectoryInfo childDirectory in parentDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly))
{
var childDirectoryNode = new TreeViewItem { Header = childDirectory.Name };
parentDirectoryNode.Items.Add(childDirectoryNode);
// Don't enter forbidden directories
// and optionally hidden directories too
if (childDirectory.Attributes.HasFlag(FileAttributes.System)
|| childDirectory.Attributes.HasFlag(FileAttributes.Hidden))
{
continue;
}
// Recursively iterate over child's subdirectories
CreateDirectoryNode(childDirectory, childDirectoryNode);
DirectoryContent_Operation(childDirectory);
}
}
private static void DirectoryMainOperation(DirectoryInfo parentDirectory)
{
foreach (DirectoryInfo childDirectory in parentDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly))
{
if (!IsIgnorable(childDirectory.Name))
{
DirectoryContent_Operation(childDirectory);
}
// Don't enter forbidden directories
// and optionally hidden directories too
if (childDirectory.Attributes.HasFlag(FileAttributes.System)
|| childDirectory.Attributes.HasFlag(FileAttributes.Hidden))
{
continue;
}
// Recursively iterate over child's subdirectories
DirectoryMainOperation(childDirectory);
}
}
Generally prefer DirectoryInfo.EnumerateDirectories over DirectoryInfo.GetDirectories.
switch (filesToAdd.Count)
{
case 0:
Console.WriteLine("No files to ZIP detected. Returning to menu.");
return;
default:
Console.Clear();
foreach (var file in filesToAdd)
{
try
{
Directory.Move(file, folderWithFolders);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
break;
}
This code constantly catches the exception where folderWithFolders already exists, because I create it earlier on based on user input.
Ideally, it should contain multiple folders that are stored in the list filesToAdd once the switch statement default case is finished.
Is it possible to use this Directory.Create method without creating an entirely new directory (since that means that it will be impossible for me to add every folder from the list) and instead be able to add a folder at a time through the foreach loop to folderWithFolders without creating a new directory; do any such methods exist?
Thank you.
Edit: So just in case someone stumbles upon this a few months in the future, I got it working by doing the following:
I changed:
try
{
Directory.Move(file, folderWithFolders);
}
Instead I changed it to:
try
{
Directory.Move(file, folderWithFolders + "\\"
+ Path.GetFileName(file));
}
If indeed filesToAdd is the list of folders you want to move to the folder folderWithFolders, then I would suggest
foreach (var file in filesToAdd)
{
try
{
Directory.Move(file, Path.Combine(folderWithFolders,file));
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
I have a image slideshow project for which the user is expected to choose an images folder which the form takes in a PictureBox and slideshows, the project runs and allows me to select an images folder after which it then throws an ArgumentException meant to be thrown if the directory does not exist. Below is the code for the method within which the exception is thrown:
public static string[] GetFiles(string path, string searchPattern)
{
string[] patterns = searchPattern.Split(';');
List<string> files = new List<string>();
foreach (string filter in patterns)
{
// Iterate through the directory tree and ignore the
// DirectoryNotFoundException or UnauthorizedAccessException
// exceptions.
// Data structure to hold names of subfolders to be
// examined for files.
Stack<string> dirs = new Stack<string>(20);
if (!Directory.Exists(path))
{
throw new ArgumentException();
}
dirs.Push(path);
while (dirs.Count > 0)
{
string currentDir = dirs.Pop();
string[] subDirs;
try
{
subDirs = Directory.GetDirectories(currentDir);
}
// An UnauthorizedAccessException exception will be thrown
// if we do not have discovery permission on a folder or
// file. It may or may not be acceptable to ignore the
// exception and continue enumerating the remaining files
// and folders. It is also possible (but unlikely) that a
// DirectoryNotFound exception will be raised. This will
// happen if currentDir has been deleted by another
// application or thread after our call to Directory.Exists.
// The choice of which exceptions to catch depends entirely
// on the specific task you are intending to perform and
// also on how much you know with certainty about the
// systems on which this code will run.
catch (UnauthorizedAccessException)
{
continue;
}
catch (DirectoryNotFoundException)
{
continue;
}
try
{
files.AddRange(Directory.GetFiles(currentDir, filter));
}
catch (UnauthorizedAccessException)
{
continue;
}
catch (DirectoryNotFoundException)
{
continue;
}
// Push the subdirectories onto the stack for traversal.
// This could also be done before handing the files.
foreach (string str in subDirs)
{
dirs.Push(str);
}
}
}
return files.ToArray();
}
Assistance will be highly appreciated.
I have the following method I'm working on:
private IEnumerable<TreeNode> GetChildNodes(TreeNode parent)
{
string path = parent.Tag.ToString();
// Add Directories
string[] subdirs = Directory.GetDirectories(path);
foreach (string subdir in subdirs)
{
yield return GetChildNode(subdir);
}
// Add Files
string[] files = Directory.GetFiles(path);
foreach (string file in files)
{
var child = GetChildNode(file);
fileNodeMap[file] = child;
yield return child;
}
}
This works fine with the exception of Directory.GetDirectories() and Directory.GetFiles() can both throw exceptions that I want to catch.
I can't catch the pieces of code which utilize those methods due to my use of yield (yields can't be placed within the body of a try if there is a catch). I know I could remove the yield and simply add to my children to a collection but I'm curious how someone would catch IOExceptions from both of those methods and still utilize yield?
How about something like (for the first part):
string[] subdirs;
try
{
subdirs = Directory.GetDirectories(path);
}
catch (IOException e)
{
// Do whatever you need here
subdirs = new string[0];
}
And similarly for the second. You don't need to yield within that try block. If this doesn't help, please write whatever code you would want to be valid, so that we can see what you're planning to do if an exception is thrown.
Could you not catch exceptions outside, in the code that calls them?
You could make helper methods that add your special error handling sauce:
private string[] GetSubdirectoriesWithSpecialSauce(string path)
{
string[] subdirectories;
try
{
subdirectories = Directory.GetDirectories(path);
}
catch (IOException ioe)
{
ShutdownWOPR();
CallDrFalken();
}
return subdirectories;
}
And obviously substitute the relevant calls. I of course assumed you wanted to yield even on errors, but I humbly accept that this assumption may be wrong :)
I would caution against using exceptions as a method of control flow - if you aren't sure that the directory or path is going to return a valid result, check for it first - almost all of those exceptions can be prevented by argument checking, something generally like the below.
private IEnumerable<TreeNode> GetChildNodes(TreeNode parent)
{
string path = parent.Tag.ToString();
if (String.IsNullOrEmpty (path) || String.IsNullOrWhiteSpace (path))
yield break;
// I'm not aware of a constant/enum for the maximum allowed path length here :(
if (path.Length > 260 || path.Any (Path.GetInvalidPathChars ().Contains))
yield break;
if (!Directory.Exists (path))
yield break;
Func<string[], Func<string[],string>,> SafeIO = (fn, arg) => {
try {
return fn (p);
} catch (IOException) {
return new string[0];
}
};
// Add Directories
string[] subdirs = SafeIO (Directory.GetDirectories, path);
foreach (string subdir in subdirs)
yield return GetChildNode(subdir);
// Add Files
string[] files = SafeIO (Directory.GetFiles, path);
foreach (string file in files) {
var child = GetChildNode(file);
fileNodeMap[file] = child;
yield return child;
}
}
Plenty of room for optimization there (and ripe for further decomposition), and the usual comments apply about race conditions and the lack of a guarantee for checking if a directory exists before it is deleted on another thread, so now you can make this more robust by wrapping a try/catch around the Get{Directories,Files} calls like Jon or xanatos suggested (EDIT: and that I've now wrapped up here as SafeIO) - but now you can catch only the specific exception that is susceptible to this (IOException or DirectoryNotFoundException) and reserve it for truly exceptional cases.
The exception will be throw by the call of GetDirectories and GetFiles, so you can try-catch THEM instead of the for-each,
Given a folder I want to make sure that ALL the files on that directory are deleted.
I know there maybe IOExceptions or Access Denied errors but how do just leave them aside and continue with my deletion of the files that I actually can delete? Is this possible?
Please shed some light on me on where I can begin.
If you want to delete all files you can delete, you could create a list of files (recursively for subdirections) and then delete them separately, skipping the ones that throw an exception.
IF you loop through the files in the directory and delete each one within a try/catch you can then continue even after an exception. If you try to delete the entire directory then once it fails it fails.
Edit: Code as requested
private void DeleteFiles(DirectoryInfo Directory)
{
bool AllFilesDeleted = true;
foreach(FileInfo oFile in Directory.GetFiles())
{
try
{
oFile.Delete();
}
catch (Exception ex) { AllFilesDeleted = false; }
}
foreach (DirectoryInfo oDirectory in Directory.GetDirectories())
{
DeleteFiles(oDirectory);
}
if (AllFilesDeleted)
{
try
{
Directory.Delete();
}
catch (Exception ex){}
}
}
IOExceptions or Access Denied errors but how do just leave them aside and continue with my deletion
Huh? If you are having IO issues or you don't have access to the files you can't delete them. They are exceptions. They are telling you "hey, this went wrong, and here's why". They aren't polite warning messages that you can just ignore, they are the reason your delete did not work in the first place.
Answering the recursive search question:
void delete(DirectoryInfo di) {
foreach(DirectoryInfo di2 in di.GetDirectories()) {
delete(di2);
}
foreach(FileInfo fi in di.GetFiles()) {
fi.Delete();
}
}
...as suggested above, try...catch around various parts will cope with the inability to delete certain files.
If you change the order a bit in what #Will A suggested and add a line to delete the directory itself - it should do the trick. Something like
static void delete(DirectoryInfo di)
{
foreach (FileInfo fi in di.GetFiles())
{
try
{
fi.Delete();
}
catch (Exception)
{
}
}
foreach (DirectoryInfo di2 in di.GetDirectories())
{
delete(di2);
}
try
{
di.Delete();
}
catch (Exception)
{
}
}
it should clear the empty folders