c# How to iterate through the file system and assign numbering - c#

I have a problem that is doing my head in.
I want to iterate through the local PC folders, including directories, and calculate a numbering system against each folder in the file system hierarchy.
The root folders should calculate as 1,2,3 etc.
If there were three sub-folders in folder one the calculated numbers should be:
1.1, 1.2,1.3
If there were three sub-folders in the sub-folder above then the calculated numbers should be:
1.1.1, 1.1.2, 1.1.3
If there were three sub-folders in folder two (a root folder) the calculated numbers should be:
2.1, 2.2, 2.3
Or expressed in another way:
1 Root Folder
1.1 Root Sub Folder 1
1.1.1 Sub Folder
1.1.2 Sub Folder
1.2 Root Sub Folder 2
1.2.1 List item
1.2.2 List item
etc. etc.
This logic should then be applied to all folders and sub-folders.
Output Example
1.1.1 | "c:\Root\Folder1\Folder1\"
What I have so far appears to work ok in some situations but can fail in other situations:
private string rootpath = #"C:\FolderHierarchy\";
private string FolderSequenceCountBase = "";
private int CurrentRootPathCount = 0;
private int Counter = 0;
private void CalculateFolderHierarchyNumbers()
{
//Get First List of Folders
string[] Dirs = Directory.GetDirectories(rootpath, "*.*", SearchOption.TopDirectoryOnly);
for (int i = 0; i < Dirs.Count(); i++)
{
FolderSequenceCountBase = (i + 1).ToString();
CurrentRootPathCount = i + 1;
Console.WriteLine("Processed folder '{0}'.", Dirs[i] + " = " + (i + 1));
GetSubDirs(Dirs[i]);
}
}
private void GetSubDirs(string item)
{
//Get next list of folders in the folder hierarchy
string[] SubDirs = Directory.GetDirectories(item, "*.*", SearchOption.TopDirectoryOnly);
foreach (var DirPath in SubDirs)
{
//Increment count of folders within the current folder list
Counter += 1;
Console.WriteLine("Processed folder '{0}'.", DirPath + " = " + FolderSequenceCountBase + "." + Counter);
}
Counter = 0;
//Get next list of folders in the folder hierarchy
foreach (var DirPath in SubDirs)
{
FolderSequenceCountBase += ".1";
GetSubDirs(DirPath);
}
}
Hope this is clear.
Thanks
Rick

So you want to find a file and get it's the number of it in the of directory and all of it's parent directories? Since i found it interesting i've written something from scratch. Note that it's currently not tested but it might give you an idea anyway:
public static IEnumerable<FileEntryInfo> EnumerateFindFiles(string fileToFind, StringComparison comparison = StringComparison.CurrentCultureIgnoreCase, DirectoryInfo rootDir = null, string[] drivesToSearch = null)
{
IEnumerable<FileEntryInfo> foundEntries = Enumerable.Empty<FileEntryInfo>();
if (rootDir != null && drivesToSearch != null)
throw new ArgumentException("Specify either the root-dir or the drives to search, not both");
else if (rootDir != null)
{
foundEntries = EnumerateFindEntryRoot(fileToFind, rootDir, comparison);
}
else
{
if (drivesToSearch == null) // search the entire computer
drivesToSearch = System.Environment.GetLogicalDrives();
foreach (string dr in drivesToSearch)
{
System.IO.DriveInfo di = new System.IO.DriveInfo(dr);
if (!di.IsReady)
{
Console.WriteLine("The drive {0} could not be read", di.Name);
continue;
}
rootDir = di.RootDirectory;
foundEntries = foundEntries.Concat(EnumerateFindEntryRoot(fileToFind, rootDir, comparison));
}
}
foreach (FileEntryInfo entry in foundEntries)
yield return entry;
}
public class FileEntryInfo
{
public FileEntryInfo(string path, int number)
{
this.Path = path;
this.Number = number;
}
public int Number { get; set; }
public string Path { get; set; }
public FileEntryInfo Root { get; set; }
public IEnumerable<int> GetNumberTree()
{
Stack<FileEntryInfo> filo = new Stack<FileEntryInfo>();
FileEntryInfo entry = this;
while (entry.Root != null)
{
filo.Push(entry.Root);
entry = entry.Root;
}
while(filo.Count > 0)
yield return filo.Pop().Number;
yield return this.Number;
}
public override bool Equals(object obj)
{
FileEntryInfo fei = obj as FileEntryInfo;
if(obj == null) return false;
return Number == fei.Number && Path == fei.Path;
}
public override int GetHashCode()
{
return Path.GetHashCode();
}
public override string ToString()
{
return Path;
}
}
private static IEnumerable<FileEntryInfo> EnumerateFindEntryRoot(string fileNameToFind, DirectoryInfo rootDir, StringComparison comparison = StringComparison.CurrentCultureIgnoreCase)
{
Queue<FileEntryInfo> queue = new Queue<FileEntryInfo>();
FileEntryInfo root = new FileEntryInfo(rootDir.FullName, 1);
queue.Enqueue(root);
while (queue.Count > 0)
{
FileEntryInfo fe = queue.Dequeue();
List<FileEntryInfo> validFiles = new List<FileEntryInfo>();
try
{ // you cannot yield from try-catch, hence this approach
FileAttributes attr = File.GetAttributes(fe.Path);
//detect whether its a directory or file
bool isDirectory = (attr & FileAttributes.Directory) == FileAttributes.Directory;
if (isDirectory)
{
int entryCount = 0;
foreach (string entry in Directory.EnumerateFileSystemEntries(fe.Path))
{
entryCount++;
FileEntryInfo subEntry = new FileEntryInfo(entry, entryCount);
subEntry.Root = fe;
queue.Enqueue(subEntry);
attr = File.GetAttributes(entry);
isDirectory = (attr & FileAttributes.Directory) == FileAttributes.Directory;
if(!isDirectory)
validFiles.Add(subEntry);
}
}
} catch (Exception ex)
{
Console.Error.WriteLine(ex); // ignore, proceed
}
foreach (FileEntryInfo entry in validFiles)
{
string fileName = Path.GetFileName(entry.Path);
if (fileName.Equals(fileNameToFind, comparison))
yield return entry;
}
}
}
Here's how i tested it:
var allEntriesFound = EnumerateFindFiles("PresentationFramework.dll").ToList();
if(allEntriesFound.Any())
{
foreach (FileEntryInfo entry in allEntriesFound)
Console.WriteLine("{0}: Number: {1}", entry.Path, entry.Number);
// here your exact requirement:
string result = string.Join(".", allEntriesFound[0].GetNumberTree());
Console.WriteLine(result);
}
Added also a way to specify the root directory to prevent that the whole file system is searched, Usage:
var allEntriesFound = EnumerateFindFiles(
"PresentationFramework.dll",
StringComparison.CurrentCultureIgnoreCase,
new DirectoryInfo(#"C:\Windows"))
.ToList();

Related

Getting counter to return correct number after being recursively called c#

I have a method called clearFiles which can be called recursively if there is sub directories. I have created a counter that will count the number of files that have been deleted. The counter holds the correct number when the function is either called once or called recursively but the toast at the end of the function only returns the correct number when it is not called recursively. Is there anyway I can display the toast and then reset the number of filesCleared? Because it's just returning a 0 when it's called recursively.
From playing around with it for a bit it seems like the toast is getting called after the filesCleared variable is set to 0 which is not what I want.
filesCleared variable:
int filesCleared = 0;
clearFiles:
public async Task ClearFiles()
{
var pathName = FileFilter.PathName;
FileInfo[] files = SortFiles(pathName);
try
{
if(FileFilter.Filter == "all")
{
foreach(var file in files)
{
if(file.Extension == FileFilter.Extension || FileFilter.Extension == "all")
{
File.Delete(file.ToString());
filesCleared++;
}
}
}
if(FileFilter.Filter == "date")
{
foreach (var file in files) //regular files
{
if(file.CreationTime < FileFilter.DeleteDate) //based on time
{
if(file.Extension == FileFilter.Extension || FileFilter.Extension == "all") //based on extension
{
File.Delete(file.ToString());
filesCleared++;
}
}
}
}
if(FileFilter.Filter == "number")
{
var i = 0;
for(var j = files.Length-1; j >= 0 ; j--)
{
if(files[j].Extension == FileFilter.Extension || FileFilter.Extension == "all")
{
if(i++ >= FileFilter.FilesToKeep)
{
File.Delete(files[j].ToString());
filesCleared++;
}
}
}
}
if (FileFilter.SubFolders == true) //subfiles (will be called recursively w/ each filter)
{
foreach(var subDir in new DirectoryInfo(pathName).GetDirectories())
{
//subDir.Delete(true);
FileFilter.PathName = subDir.ToString();
ClearFiles();
//await ClearFiles(subDir.ToString());
}
FileFilter.PathName = pathName; //resets the pathName so it will go back to what it was before the recursion
}
}
catch (IOException ioExp)
{
Console.WriteLine(ioExp.Message);
Toast = Toast.Bad();
logger.LogError(ioExp, "Error Deleting");
}
Toast = Toast.Good(filesCleared + " Files Deleted");
filesCleared = 0;
}
If you want to do something once but also want to call a method recursively, you have to split it in two. After trying to simplify your code I get a ClearFiles method like this:
public void ClearFiles()
{
var filesCleared = 0;
try
{
filesCleared = DeleteFilesRecursively(FileFilter.PathName, FileFilter);
}
catch (IOException ioExp)
{
Console.WriteLine(ioExp.Message);
Toast = Toast.Bad();
logger.LogError(ioExp, "Error Deleting");
}
Toast = Toast.Good(filesCleared + " Files Deleted");
}
Now Toast.Good is only called once after all subfolders have been traversed.
Note that filesCleared is a local variable, since I don't see any point in making it global. That way you also don't need to reset it.
The implementation of DeleteFilesRecursively could be something like this and could be simplified more if you wanted:
private const string All = "all";
private const string FilterByDate = "date";
private const string FilterByNumber = "number";
int DeleteFilesRecursively(string dirPath, SomeFileFilterType fileFilter)
{
FileInfo[] files = SortFiles(dirPath);
var deleted = 0;
var toBeDeleted = files.Where(f => MatchesByExtension(f, fileFilter.Extension));
if (fileFilter.Filter == FilterByDate)
{
toBeDeleted = toBeDeleted.Where(f => MatchesByDate(f, fileFilter.DeleteDate));
}
else if (FileFilter.Filter == FilterByNumber)
{
// If your SortFiles method sorted in the other
// direction this call to Reverse would not be needed.
toBeDeleted = toBeDeleted.Reverse().Take(fileFilter.FilesToKeep);
}
foreach (var file in toBeDeleted)
{
File.Delete(file.ToString());
deleted++;
}
if (fileFilter.SubFolders)
{
foreach(var subDir in new DirectoryInfo(dirPath).GetDirectories())
{
deleted += DeleteFilesRecursively(subDir.FullName, fileFilter);
}
}
return deleted;
}
bool MatchesByExtension(FileInfo file, string extension)
=> file.Extension == extension || extension == All;
bool MatchesByDate(FileInfo file, DateTime deleteDate)
=> file.CreationTime < deleteDate;
Note that I also removed your magic strings, which could be even better by replacing them with an enum type.
I haven't tested this but I believe it should give you the same behavior as your current code (at least the parts about filtering and deleting).

Loop through sub directories in directory

I have a directory 'Folder' with many subdirectories inside this directory. Inside every subdirectory there are many images. I want to loop through subdirectories in the 'Folder' directory, then loop through all images in every directory to export the images out to Excel, with images from each subdirectory in one Excel worksheet.
For e.g. if I have ten subdirectories, I should have one Excel workbook with ten Excel worksheets, then in each Excel worksheet there will be images from each subdirectory.
This is what I have tried but images only appeared on Worksheet1 instead of all the worksheets:
public void ExportToExcel()
{
//for export
ExcelPackage objExcelPackage = new ExcelPackage(); //create new workbook
string[] filesindirectory = Directory.GetDirectories(Server.MapPath("~/Folder"));
int count = 0;
int count1 = 0;
int x = 25;
int finalValue = 0;
foreach (string subdir in filesindirectory)
{
count++;
ExcelWorksheet ws = objExcelPackage.Workbook.Worksheets.Add("Worksheet" + count); //create new worksheet
foreach (string img in Directory.GetFiles(subdir))
{
count1++;
System.Web.UI.WebControls.Image TEST_IMAGE = new System.Web.UI.WebControls.Image();
System.Drawing.Image myImage = System.Drawing.Image.FromFile(img);
var pic = ws.Drawings.AddPicture(count1.ToString(), myImage);
// Row, RowoffsetPixel, Column, ColumnOffSetPixel
if (count1 > 1)
{
pic.SetPosition(finalValue, 0, 2, 0);
finalValue += (x + 1); // Add 1 to have 1 row of empty row
}
else
{
pic.SetPosition(count1, 0, 2, 0);
finalValue = (count1 + x) + 1; // Add 1 to have 1 row of empty
}
}
}
var filepath = new FileInfo(#"C:\Users\user\Desktop\Test\" + datetime.ToString("dd-MM-yyyy_hh-mm-ss") + ".xlsx");
objExcelPackage.SaveAs(filepath);
}
How to loop through each sub directories in a directory, then loop through all images from each sub directory using C#?
Janne Matikainen answer is correct but you need to know how to modify in your code...
First change your code this line
string[] filesindirectory = Directory.GetFiles(Server.MapPath("~/Folder"));
to
string[] filesindirectory = Directory.GetDirectories(Server.MapPath("~/Folder"));
Secondly you need to search file in your sub folder path which is your
foreach (string subdir in filesindirectory)
subdir is your path for your directory.
Just do back the same thing what you did to get the files
foreach (string img in Directory.GetFiles(subdir))
After you finish the foreach subdirectory
foreach (string img in Directory.GetFiles(subdir))
{
// Your Code
}
// Reset the Count1
count1 = 0;
Reset it because you are increasing for dynamic row generating for each sheet.
Then you at new sheet and you didn't reset it. It will continue the count as per previous sheet.
To get the folder name you can easily get it by split.
Before you create the worksheet you do as per below.
string[] splitter = subdir.Split('\\');
string folderName = splitter[splitter.Length - 1];
Take NOTES if the folderName contain some symbol it might not able to set it to excel worksheet and also the name cannot be too long.
Please ensure that you replace with supportable symbol for excel worksheet
This should list all files starting from C:\Images and go through all subdirs and their subdirs as well.
public void ExportToExcel()
{
//for export
var objExcelPackage = new ExcelPackage(); //create new workbook
this.ListFiles(objExcelPackage, 0, Server.MapPath("~/Folder"));
var filepath = new FileInfo(#"C:\Users\user\Desktop\Test\" + datetime.ToString("dd-MM-yyyy_hh-mm-ss") + ".xlsx");
objExcelPackage.SaveAs(filepath);
}
public void ListFiles(ExcelPackage objExcelPackage, int worksheetIndex, string path)
{
var imageCount = 0;
var x = 25;
var finalValue = 0;
var files = Directory.GetFiles(path).Select(s => new FileInfo(s));
if (files.Any())
{
//create new worksheet
var ws = objExcelPackage.Workbook.Worksheets.Add("Worksheet" + (++worksheetIndex));
foreach (var file in files)
{
imageCount++;
var TEST_IMAGE = new System.Web.UI.WebControls.Image();
var myImage = System.Drawing.Image.FromFile(img);
var pic = ws.Drawings.AddPicture(imageCount.ToString(), myImage);
// Row, RowoffsetPixel, Column, ColumnOffSetPixel
if (imageCount > 1)
{
pic.SetPosition(finalValue, 0, 2, 0);
finalValue += (x + 1); // Add 1 to have 1 row of empty row
}
else
{
pic.SetPosition(imageCount, 0, 2, 0);
finalValue = (imageCount + x) + 1; // Add 1 to have 1 row of empty
}
}
}
foreach (var dir in Directory.GetDirectories(path))
{
this.ListFiles(objExcelPackage, worksheetIndex, dir);
}
}
Directory.GetFiles(dir) returns all files in dir, without folders
you should use Directory.EnumerateDirectories(dir)
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
class Program
{
private static void Main(string[] args)
{
try
{
string dirPath = #"\\archives\2009\reports";
List<string> dirs = new List<string>(Directory.EnumerateDirectories(dirPath));
foreach (var dir in dirs)
{
Console.WriteLine("{0}", dir.Substring(dir.LastIndexOf("\\") + 1));
}
Console.WriteLine("{0} directories found.", dirs.Count);
}
catch (UnauthorizedAccessException UAEx)
{
Console.WriteLine(UAEx.Message);
}
catch (PathTooLongException PathEx)
{
Console.WriteLine(PathEx.Message);
}
}
}
The Composite Pattern fits your problem here.
public interface IExcelWorksheetAdapter
{
//todo: implement this method, here you have everything you need for an image file
void AddPicture(FileSystemItem aFile);
}
public class FileSystemItem
{
public virtual string FullPath { get; protected set; }
public virtual int Level { get; set; }
public virtual string Name
{
get
{
if (string.IsNullOrWhiteSpace(FullPath)) return string.Empty;
return FullPath.Split('\\').Last();
}
}
public virtual void Operation(IExcelWorksheetAdapter ws) { }
}
public class FolderItem : FileSystemItem
{
public FolderItem(string fullPath)
{
Items = new List<FileSystemItem>();
if (!Directory.Exists(fullPath)) return;
FullPath = fullPath;
var files = Directory.GetFiles(FullPath).Select(p => new FileItem(p) { Level = this.Level + 1 }).ToList();
Items.AddRange(files);
var subFolders = Directory.GetDirectories(fullPath).Select(p => new FolderItem(p) {Level = this.Level + 1}).ToList();
Items.AddRange(subFolders);
}
public List<FileSystemItem> Items { get; set; }
public override void Operation(IExcelWorksheetAdapter ws)
{
Items.ForEach(x => x.Operation(ws));
}
}
public class FileItem : FileSystemItem
{
public FileItem(string path)
{
if (File.Exists(path))
{
FullPath = path;
}
}
public override void Operation(IExcelWorksheetAdapter ws)
{
ws.AddPicture(this);
}
}
[TestFixture]
public class DirectoryCompositeTest
{
[Test]
public void Operation_for_a_directory_files()
{
var directory = new FolderItem(AppDomain.CurrentDomain.BaseDirectory);
directory.Operation(new Sample()); // give your IExcelWorksheetAdapter implementation here.
}
}
public class Sample : IExcelWorksheetAdapter
{
public void AddPicture(FileSystemItem aFile)
{
Console.WriteLine(Indent(aFile.Level) + aFile.Name);
}
private string Indent(int level)
{
string result = "";
for (int i = 0; i < level; i++)
{
result += "-";
}
return result;
}
}

Walking through directory while controling depth - C#

I need to be able to get all files from a directory and sub directories, but I would like to give the user the option to choose the depth of sub-directories.
I.e., not just current directory or all directories, but he should be able to choose a depth of 1,2,3,4 directories etc.
I've seen many examples of walking through directory trees and none of them seemed to address this issue. And personally, I get confused with recursion... (which I currently use). I am not sure how I would track the depth during a recursive function.
Any help would be greatly appreciated.
Thanks,
David
Here is my current code (which I found here):
static void FullDirList(DirectoryInfo dir, string searchPattern, string excludeFolders, int maxSz, string depth)
{
try
{
foreach (FileInfo file in dir.GetFiles(searchPattern))
{
if (excludeFolders != "")
if (Regex.IsMatch(file.FullName, excludeFolders, RegexOptions.IgnoreCase)) continue;
myStream.WriteLine(file.FullName);
MasterFileCounter += 1;
if (maxSz > 0 && myStream.BaseStream.Length >= maxSz)
{
myStream.Close();
myStream = new StreamWriter(nextOutPutFile());
}
}
}
catch
{
// make this a spearate streamwriter to accept files that failed to be read.
Console.WriteLine("Directory {0} \n could not be accessed!!!!", dir.FullName);
return; // We alredy got an error trying to access dir so dont try to access it again
}
MasterFolderCounter += 1;
foreach (DirectoryInfo d in dir.GetDirectories())
{
//folders.Add(d);
// if (MasterFolderCounter > maxFolders)
FullDirList(d, searchPattern, excludeFolders, maxSz, depth);
}
}
using a maxdepth varibale that could be decremented each recursive call and then you cannot just return once reached the desired depth.
static void FullDirList(DirectoryInfo dir, string searchPattern, string excludeFolders, int maxSz, int maxDepth)
{
if(maxDepth == 0)
{
return;
}
try
{
foreach (FileInfo file in dir.GetFiles(searchPattern))
{
if (excludeFolders != "")
if (Regex.IsMatch(file.FullName, excludeFolders, RegexOptions.IgnoreCase)) continue;
myStream.WriteLine(file.FullName);
MasterFileCounter += 1;
if (maxSz > 0 && myStream.BaseStream.Length >= maxSz)
{
myStream.Close();
myStream = new StreamWriter(nextOutPutFile());
}
}
}
catch
{
// make this a spearate streamwriter to accept files that failed to be read.
Console.WriteLine("Directory {0} \n could not be accessed!!!!", dir.FullName);
return; // We alredy got an error trying to access dir so dont try to access it again
}
MasterFolderCounter += 1;
foreach (DirectoryInfo d in dir.GetDirectories())
{
//folders.Add(d);
// if (MasterFolderCounter > maxFolders)
FullDirList(d, searchPattern, excludeFolders, maxSz, depth - 1);
}
}
Let's start out by refactoring the code a little bit to make its work a little easier to understand.
So, the key exercise here is to recursively return all of the files that match the patterns required, but only to a certain depth. Let's get those files first.
public static IEnumerable<FileInfo> GetFullDirList(
DirectoryInfo dir, string searchPattern, int depth)
{
foreach (FileInfo file in dir.GetFiles(searchPattern))
{
yield return file;
}
if (depth > 0)
{
foreach (DirectoryInfo d in dir.GetDirectories())
{
foreach (FileInfo f in GetFullDirList(d, searchPattern, depth - 1))
{
yield return f;
}
}
}
}
This is just simplified the job of recursing for your files.
But you'll notice that it didn't exclude files based on the excludeFolders parameter. Let's tackle that now. Let's start building FullDirList.
The first line would be
var results =
from fi in GetFullDirList(dir, searchPattern, depth)
where String.IsNullOrEmpty(excludeFolders)
|| !Regex.IsMatch(fi.FullName, excludeFolders, RegexOptions.IgnoreCase)
group fi.FullName by fi.Directory.FullName;
This goes and gets all of the files, restricts them against excludeFolders and then groups all the files by the folders they belong to. We do this so that we can do this next:
var directoriesFound = results.Count();
var filesFound = results.SelectMany(fi => fi).Count();
Now I noticed that you were counting MasterFileCounter & MasterFolderCounter.
You could easily write:
MasterFolderCounter+= results.Count();
MasterFileCounter += results.SelectMany(fi => fi).Count();
Now, to write out these files it appears you are trying to aggregate the file names into separate files, but keeping a maximum length (maxSz) of the file.
Here's how to do that:
var aggregateByLength =
results
.SelectMany(fi => fi)
.Aggregate(new [] { new StringBuilder() }.ToList(),
(sbs, s) =>
{
var nl = s + Environment.NewLine;
if (sbs.Last().Length + nl.Length > maxSz)
{
sbs.Add(new StringBuilder(nl));
}
else
{
sbs.Last().Append(nl);
}
return sbs;
});
Writing the files now becomes extremely simple:
foreach (var sb in aggregateByLength)
{
File.WriteAllText(nextOutPutFile(), sb.ToString());
}
So, the full thing becomes:
static void FullDirList(
DirectoryInfo dir, string searchPattern, string excludeFolders, int maxSz, int depth)
{
var results =
from fi in GetFullDirList(dir, searchPattern, depth)
where String.IsNullOrEmpty(excludeFolders)
|| !Regex.IsMatch(fi.FullName, excludeFolders, RegexOptions.IgnoreCase)
group fi.FullName by fi.Directory.FullName;
var directoriesFound = results.Count();
var filesFound = results.SelectMany(fi => fi).Count();
var aggregateByLength =
results
.SelectMany(fi => fi)
.Aggregate(new [] { new StringBuilder() }.ToList(),
(sbs, s) =>
{
var nl = s + Environment.NewLine;
if (sbs.Last().Length + nl.Length > maxSz)
{
sbs.Add(new StringBuilder(nl));
}
else
{
sbs.Last().Append(nl);
}
return sbs;
});
foreach (var sb in aggregateByLength)
{
File.WriteAllText(nextOutPutFile(), sb.ToString());
}
}

c# find a FOLDER when path is unknown then list files and return

I am learning c# and need to find a folder when the complete path is unknown. An example would be you know the the album name but not the artist in the music folder. Finding an album name is NOT the final usage for this code but the best example for this question. I am doing this with recursion and limiting the depth of the search. Everything works good except when I find the folder and list the files I want it to stop and return but it does not, it just keeps the recursion going even after I have the the folder. I have also struggled with exception handling like how to skip a folder if permissions are not valid.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace listFoldersTest
{
class Program
{
static void Main(string[] args)
{
Console.SetWindowSize(100, 50);
DirectoryInfo dir = new DirectoryInfo(#"C:\Users\username\Music");
getDirsFiles(dir, 0, 2);
Console.ReadKey();
Console.WriteLine("done");
}
public static void getDirsFiles(DirectoryInfo d, int currentDepth, int maxDepth)
{
String folderToFindName = ("albumName");
bool foundIt = false;
if (currentDepth < maxDepth)
{
DirectoryInfo[] dirs = d.GetDirectories("*.*");
foreach (DirectoryInfo dir in dirs)
{
String pathName = (dir.FullName);
Console.WriteLine("\r{0} ", dir.Name);
if (currentDepth == (maxDepth - 1))
{
if (pathName.IndexOf(folderToFindName) != -1)
{
foundIt = true;
FileInfo[] files = dir.GetFiles("*.*");
foreach (FileInfo file in files)
{
Console.WriteLine("-------------------->> {0} ", file.Name);
} //end foreach files
} // end if pathName
} // end if of get files current depth
if (foundIt == true)
{
return;
}
getDirsFiles(dir, currentDepth + 1, maxDepth);
} //end if foreach directories
} //end if directories current depth
} // end getDirsFiles function
}
}
using System;
using System.IO;
namespace listFoldersTest
{
class Program
{
private static bool foundIt;
static void Main(string[] args)
{
Console.SetWindowSize(100, 50);
try
{
DirectoryInfo dir = new DirectoryInfo(args[0]);
getDirsFiles(dir, 0, 2);
}
catch
{
}
Console.ReadKey();
Console.WriteLine("done");
}
public static void getDirsFiles(DirectoryInfo d, int currentDepth, int maxDepth)
{
if(d == null || foundIt) return;
String folderToFindName = ("albumName");
if (currentDepth < maxDepth)
{
DirectoryInfo[] dirs = d.GetDirectories("*.*");
foreach (DirectoryInfo dir in dirs)
{
String pathName = (dir.FullName);
Console.WriteLine("\r{0} ", dir.Name);
if (currentDepth == (maxDepth - 1))
{
if (pathName.IndexOf(folderToFindName) != -1)
{
foundIt = true;
FileInfo[] files = dir.GetFiles("*.*");
foreach (FileInfo file in files)
{
Console.WriteLine("-------------------->> {0} ", file.Name);
} //end foreach files
return;
} // end if pathName
} // end if of get files current depth
getDirsFiles(dir, currentDepth + 1, maxDepth);
} //end if foreach directories
} //end if directories current depth
} // end getDirsFiles function
}
}
Create a boolean at a global scope.
Default it to false.
When the folder is found, set it to true.
In your recursive function, if the value is true, return and exit from the function without doing anything.
In your case, the foundIt variable is declared and initialized within the function. Declare it at the global level, and check it first thing in the function.
For the exception handling, simply use a try/catch and exit the function if it fails.
You can use the solution from below which doesn't use a global variable.
public static string FindFolder(DirectoryInfo rootDirectory, string folderToFind, int currentDepth, int maxDepth)
{
if(currentDepth == maxDepth)
{
return null;
}
foreach(var directory in rootDirectory.GetDirectories())
{
Console.WriteLine(directory.FullName);
if(directory.Name.Equals(folderToFind,StringComparison.OrdinalIgnoreCase))
{
return directory.FullName;
}
string tempFindResult;
if((tempFindResult = FindFolder(directory,folderToFind,++currentDepth,maxDepth)) != null)
{
return tempFindResult;
}
}
return null;
}
What I think you want to do is break out of the foreach:
if (foundIt == true)
{
break; // instead of return
}
edit
...right, of course, you need to make foundIt a class member. The current scope applied to each iteration of the method.

C#: How to make this method non-recursive

I have this recursive method which deletes empty folders:
private void DeleteEmpty(DirectoryInfo directory)
{
foreach (var d in directory.GetDirectories())
{
DeleteEmpty(d);
}
if (directory.GetFileSystemInfos().Length == 0)
{
try
{
directory.Delete();
}
catch (Exception)
{
// Already gone, no permission, not empty, et cetera
}
}
}
How can I refactor this method so that it is not recursive?
The standard refactoring is to store the data you would otherwise be passing to the function in a LIFO (i.e. a stack) or FIFO queue. Note that this doesn't change asymptotic space usage; you're using your own data structure rather than the call stack.
If you can define a "next sibling" function, you can visit the nodes with constant additional space. This is because the graph of directories (sans files) is essentially undirected due to parent pointers. Pseudocode:
nextBranchingSibling(sibling):
while sibling exists
if sibling has children
return sibling
sibling = nextSibling(sibling)
return null
nextBranch(node):
if node is marked
unmark node
else
if nextBranchingSibling(firstChild(node)) exists
return nextBranchingSibling(firstChild(node))
if nextBranchingSibling(nextSibling(node)) exists
return nextBranchingSibling(nextSibling(node))
mark parent(node)
return parent(node)
prune(node):
while node exists:
tmpNode = node
node = nextBranch(node)
if count of tmpNode's children is 0
delete tmpNode
Note that you're not actually using O(1) space total, since the directory structure is itself O(n). Methods like DirectoryInfo.GetDirectories can remove the need for loops in nextBranchingSibling.
private static Queue<DirectoryInfo> directoryQueue = new Queue<DirectoryInfo>();
private void DeleteEmpty(DirectoryInfo directory)
{
directoryQueue.Enqueue(directory);
while (directoryQueue.Count > 0)
{
var current = directoryQueue.Dequeue();
foreach (var d in current.GetDirectories())
{
directoryQueue.Enqueue(d);
}
if (directory.GetFileSystemInfos().Length == 0)
{
try
{
directory.Delete();
}
catch (Exception)
{
// Already gone, no permission, not empty, et cetera
}
}
}
}
Try this:
private void DeleteEmpty(string path)
{
string[] directories = Directory.GetDirectories(
path, "*", SearchOption.AllDirectories);
// you should delete deeper directories first
// .OrderByDescending(
// dir => dir.Split(Path.DirectorySeparatorChar).Length)
// .ToArray();
foreach (string directory in directories)
{
DirectoryInfo info = new DirectoryInfo(directory);
if (info.GetFileSystemInfos().Length == 0)
{
info.Delete();
}
}
// If you wanna a LINQ-ish version
// directories.Where(dir =>
// new DirectoryInfo(dir).GetFileSystemInfos().Length == 0)
// .ToList().ForEach(dir => Directory.Delete(dir));
}
Another performance step could be: if you tried to remove a directory and it contains files, all parent levels should be skipped since they WILL fail too.
You could use a local Stack and loop while the stack is not empty.
public void DeleteDirectories(DirectoryInfo directoryInfo, bool deleteFiles)
{
Stack<DirectoryInfo> directories = new Stack<DirectoryInfo>();
directories.Push(directoryInfo);
while (directories.Count > 0)
{
var current = directories.Peek();
foreach (var d in current.GetDirectories())
directories.Push(d);
if (current != directories.Peek())
continue;
if (deleteFiles)
foreach (var f in current.GetFiles())
{
f.Delete();
}
if (current.GetFiles().Length > 0 || current.GetDirectories().Length > 0)
throw new InvalidOperationException("The directory " + current.FullName + " was not empty and could not be deleted.");
current.Delete();
directories.Pop();
}
}
I had the same problem and I created a nice (imho) solution: beggining in a root directory, I "recursively" get the children directories and I store them in an ArrayList object. In this way, I create a list contaning first the higher level dirs, and at the end the deeper nested directories. This array is ideally divided in sub-arrays using the indexes stored in the levels ArrayList object.
Doing this, I can first check the deeper directories and delete them if they're empty, and then go back to the root level by level.
For example:
private void directoryCleanup(string root)
{
try
{
// Create directory "tree"
ArrayList dirs = new ArrayList();
// Beginning and ending indexes for each level
ArrayList levels = new ArrayList();
int start = 0;
dirs.Add(root);
while (start < dirs.Count)
{
ArrayList temp = new ArrayList();
for (int i = start; i < dirs.Count; i++)
{
DirectoryInfo dinfo = new DirectoryInfo((string)dirs[i]);
DirectoryInfo[] children = dinfo.GetDirectories();
for (int j = 0; j < children.Length; j++)
{
temp.Add(children[j].FullName);
}
Array.Clear(children, 0, children.Length);
children = null;
dinfo = null;
}
start = dirs.Count;
levels.Add(dirs.Count);
dirs.AddRange(temp);
temp.Clear();
temp = null;
}
levels.Reverse();
// Navigate the directory tree level by level, starting with the deepest one
for (int i = 0; i < levels.Count - 1; i++)
{
int end = (int)levels[i] - 1;
int begin = (int)levels[i + 1];
for (int j = end; j >= begin; j--)
{
string path = (string)dirs[j];
if (Directory.GetFileSystemEntries(path).Length == 0)
{
Directory.Delete(path);
}
}
}
levels.Clear();
levels = null;
dirs.Clear();
dirs = null;
}
catch (IOException ioex)
{
// Manage exception
return;
}
catch (Exception e)
{
// Manage exception
return;
}
}
Create a queue that has all the directories in the starting directory, then while it's not empty, take the next item, check if the directory's empty, if it is delete it, if not add all the subdirectories to the queue.
I don't know C#, but if there isn't a standard queue type, a linked list or mutable array type thing would work just as well.
Pseudocode;
directories = empty queue
until directories is not empty
next = directories.shift
if next is an empty folder
delete it
or else
add all the subdiretories to the queue

Categories