C# Remove all empty subdirectories - c#

I have a task to clean up a large number of directories. I want to start at a directory and delete any sub-directories (no matter how deep) that contain no files (files will never be deleted, only directories). The starting directory will then be deleted if it contains no files or subdirectories. I was hoping someone could point me to some existing code for this rather than having to reinvent the wheel. I will be doing this using C#.

Using C# Code.
static void Main(string[] args)
{
processDirectory(#"c:\temp");
}
private static void processDirectory(string startLocation)
{
foreach (var directory in Directory.GetDirectories(startLocation))
{
processDirectory(directory);
if (Directory.GetFiles(directory).Length == 0 &&
Directory.GetDirectories(directory).Length == 0)
{
Directory.Delete(directory, false);
}
}
}

If you can target the .NET 4.0 you can use the new methods on the Directory class to enumerate the directories in order to not pay a performance penalty in listing every file in a directory when you just want to know if there is at least one.
The methods are:
Directory.EnumerateDirectories
Directory.EnumerateFiles
Directory.EnumerateFileSystemEntries
A possible implementation using recursion:
static void Main(string[] args)
{
DeleteEmptyDirs("Start");
}
static void DeleteEmptyDirs(string dir)
{
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))
{
DeleteEmptyDirs(d);
}
var entries = Directory.EnumerateFileSystemEntries(dir);
if (!entries.Any())
{
try
{
Directory.Delete(dir);
}
catch (UnauthorizedAccessException) { }
catch (DirectoryNotFoundException) { }
}
}
catch (UnauthorizedAccessException) { }
}
You also mention that the directory tree could be very deep so it's possible you might get some exceptions if the path you are probing are too long.

Running the test on C:\Windows 1000 times on the 3 methods mentioned so far yielded this:
GetFiles+GetDirectories:630ms
GetFileSystemEntries:295ms
EnumerateFileSystemEntries.Any:71ms
Running it on an empty folder yielded this (1000 times again):
GetFiles+GetDirectories:131ms
GetFileSystemEntries:66ms
EnumerateFileSystemEntries.Any:64ms
So EnumerateFileSystemEntries is by far the best overall when you are checking for empty folders.

Here's a version that takes advantage of parallel execution to get it done faster in some cases:
public static void DeleteEmptySubdirectories(string parentDirectory){
System.Threading.Tasks.Parallel.ForEach(System.IO.Directory.GetDirectories(parentDirectory), directory => {
DeleteEmptySubdirectories(directory);
if(!System.IO.Directory.EnumerateFileSystemEntries(directory).Any()) System.IO.Directory.Delete(directory, false);
});
}
Here's the same code in single threaded mode:
public static void DeleteEmptySubdirectoriesSingleThread(string parentDirectory){
foreach(string directory in System.IO.Directory.GetDirectories(parentDirectory)){
DeleteEmptySubdirectories(directory);
if(!System.IO.Directory.EnumerateFileSystemEntries(directory).Any()) System.IO.Directory.Delete(directory, false);
}
}
... and here's some sample code you could use to test results in your scenario:
var stopWatch = new System.Diagnostics.Stopwatch();
for(int i = 0; i < 100; i++) {
stopWatch.Restart();
DeleteEmptySubdirectories(rootPath);
stopWatch.Stop();
StatusOutputStream.WriteLine("Parallel: "+stopWatch.ElapsedMilliseconds);
stopWatch.Restart();
DeleteEmptySubdirectoriesSingleThread(rootPath);
stopWatch.Stop();
StatusOutputStream.WriteLine("Single: "+stopWatch.ElapsedMilliseconds);
}
... and here're some results from my machine for a directory that is on a file share across a wide area network. This share currently has only 16 subfolders and 2277 files.
Parallel: 1479
Single: 4724
Parallel: 1691
Single: 5603
Parallel: 1540
Single: 4959
Parallel: 1592
Single: 4792
Parallel: 1671
Single: 4849
Parallel: 1485
Single: 4389

From here, Powershell script to remove empty directories:
$items = Get-ChildItem -Recurse
foreach($item in $items)
{
if( $item.PSIsContainer )
{
$subitems = Get-ChildItem -Recurse -Path $item.FullName
if($subitems -eq $null)
{
"Remove item: " + $item.FullName
Remove-Item $item.FullName
}
$subitems = $null
}
}
Note: use at own risk!

If you rely on DirectoryInfo.Delete only deleting empty directories, you can write a succinct extension method
public static void DeleteEmptyDirs(this DirectoryInfo dir)
{
foreach (DirectoryInfo d in dir.GetDirectories())
d.DeleteEmptyDirs();
try { dir.Delete(); }
catch (IOException) {}
catch (UnauthorizedAccessException) {}
}
Usage:
static void Main()
{
new DirectoryInfo(#"C:\temp").DeleteEmptyDirs();
}

private static void deleteEmptySubFolders(string ffd, bool deleteIfFileSizeZero=false)
{
DirectoryInfo di = new DirectoryInfo(ffd);
foreach (DirectoryInfo diSon in di.GetDirectories("*", SearchOption.TopDirectoryOnly))
{
FileInfo[] fis = diSon.GetFiles("*.*", SearchOption.AllDirectories);
if (fis == null || fis.Length < 1)
{
diSon.Delete(true);
}
else
{
if (deleteIfFileSizeZero)
{
long total = 0;
foreach (FileInfo fi in fis)
{
total = total + fi.Length;
if (total > 0)
{
break;
}
}
if (total == 0)
{
diSon.Delete(true);
continue;
}
}
deleteEmptySubFolders(diSon.FullName, deleteIfFileSizeZero);
}
}
}

//Recursive scan of empty dirs. See example output bottom
string startDir = #"d:\root";
void Scan(string dir, bool stepBack)
{
//directory not empty
if (Directory.GetFileSystemEntries(dir).Length > 0)
{
if (!stepBack)
{
foreach (string subdir in Directory.GetDirectories(dir))
Scan(subdir, false);
}
}
//directory empty so delete it.
else
{
Directory.Delete(dir);
string prevDir = dir.Substring(0, dir.LastIndexOf("\\"));
if (startDir.Length <= prevDir.Length)
Scan(prevDir, true);
}
}
//call like this
Scan(startDir, false);
/*EXAMPLE outputof d:\root with empty subfolders and one filled with files
Scanning d:\root
Scanning d:\root\folder1 (not empty)
Scanning d:\root\folder1\folder1sub1 (not empty)
Scanning d:\root\folder1\folder1sub1\folder2sub2 (deleted!)
Scanning d:\root\folder1\folder1sub1 (deleted!)
Scanning d:\root\folder1 (deleted)
Scanning d:\root (not empty)
Scanning d:\root\folder2 (not empty)
Scanning d:\root\folder2\folder2sub1 (deleted)
Scanning d:\root\folder2 (not empty)
Scanning d:\root\folder2\notempty (not empty) */

foreach (var folder in Directory.GetDirectories(myDir, "*", System.IO.SearchOption.AllDirectories))
{
{
try
{
if (Directory.GetFiles(folder, "*", System.IO.SearchOption.AllDirectories).Length == 0)
Directory.Delete(folder, true);
}
catch { }
}
}

Related

C# Minor Issue when Iterating through file

I have a program that iterates through all files from directories and subdirectories, it's working smoothly but there is just a minor issue that my brain can't solve.
The one finding the simplest way to solve it is a genius :)
Here is the code :
int hello(string locat)
{
string[] files = Directory.GetFiles(locat);
string[] dirs = Directory.GetDirectories(locat);
int cpt = 0;
foreach (var file in files)
{
try
{
textBox1.AppendText(file+"\r\n");
cpt++;
textBox2.AppendText(cpt.ToString()+"\r\n");
}
catch { }
}
foreach (string directory in dirs)
{
try
{
cpt += hello(directory);
}
catch { }
}
return cpt;
}
So the problem is that the output of cpt inside textBox2 have a logic behavior but a behavior that is not adequate for my needs
This is how it looks like :
1
2
3
1
2
1
2
...
And I want it to be 1,2,3,4,5,6,7,8,9,...
I tried with EnumerateFiles instead of GetFiles, it was working smoothly too but i got some permissions issue and I'm working on .NET framework for this project
I haven't tried this but you can just make hello take cpt as a parameter.
int hello(string locat, ref int cpt)
{
string[] files = Directory.GetFiles(locat);
string[] dirs = Directory.GetDirectories(locat);
foreach (var file in files)
{
try
{
textBox1.AppendText(file+"\r\n");
cpt++;
textBox2.AppendText(cpt.ToString()+"\r\n");
}
catch { }
}
foreach (string directory in dirs)
{
try
{
hello(directory, ref cpt);
}
catch { }
}
return cpt;
}
Edit:
You need to run it with ref
int cpt = 0;
hello("C:\\", ref cpt);
Here is the output I get if I run it with the following folder structure:
testfolder/
> folder1/
> a.txt
> b.txt
> c.txt
> folder2/
> a.txt
> b.txt
> c.txt
> folder3/
> a.txt
> b.txt
> c.txt
Output:
D:\testfolder\folder1\a.txt
1
D:\testfolder\folder1\b.txt
2
D:\testfolder\folder1\c.txt
3
D:\testfolder\folder2\a.txt
4
D:\testfolder\folder2\b.txt
5
D:\testfolder\folder2\c.txt
6
D:\testfolder\folder3\a.txt
7
D:\testfolder\folder3\b.txt
8
D:\testfolder\folder3\c.txt
9
A variation that avoids ref
int hello(string locat, int counter = 0)
{
string[] files = Directory.GetFiles(locat);
string[] dirs = Directory.GetDirectories(locat);
foreach (var file in files)
{
try
{
textBox2.AppendText(file + "\r\n");
counter++;
textBox2.AppendText(counter.ToString() + "\r\n");
}
catch { }
}
foreach (string directory in dirs)
{
try
{
counter = hello(directory, counter);
}
catch { }
}
return counter;
}
Your variable cpt is locally scoped, so you get a new variable instance for every recursive call. You can instead use a field (and don't increment it based on the result of your recursive call):
int cpt = 0;
void hello(string locat)
{
string[] files = Directory.GetFiles(locat);
string[] dirs = Directory.GetDirectories(locat);
foreach (var file in files)
{
try
{
textBox1.AppendText(file + "\r\n");
cpt++;
textBox2.AppendText(cpt.ToString() + "\r\n");
}
catch { }
}
foreach (string directory in dirs)
{
try
{
hello(directory);
}
catch { }
}
}
This code is not thread-safe.

Why an empty foreach clause change the yield output of recursive IEnumerable function?

I was looking for a way to loop into all the files and folders in a given path and I stumbled into this:
get tree structure of a directory with its subfolders and files using C#.net in windows application
I was fascinated by Xiaoy312 repost. So I took their code and modified it to serve my intended purpose, which is returning a list of all files' paths in a given path:
using System;
using System.Collections.Generic;
using System.IO;
class Whatever
{
static List<string> filePaths = new List<string>();
static void Main()
{
string path = "some folder path";
DirectoryInfo directoryInfo = new DirectoryInfo(path);
IEnumerable<HierarchicalItem> items = SearchDirectory(directoryInfo, 0);
foreach (var item in items) { } // my query is about this line.
PrintList(filePaths);
Console.Read();
}
static void PrintList(List<string> list)
{
foreach(string path in list)
{
Console.WriteLine(path);
}
}
public static IEnumerable<HierarchicalItem> SearchDirectory(DirectoryInfo directory, int deep = 0)
{
yield return new HierarchicalItem(directory.Name, deep);
foreach (DirectoryInfo subdirectory in directory.GetDirectories())
{
foreach (HierarchicalItem item in SearchDirectory(subdirectory, deep + 1))
{
yield return item;
}
}
foreach (var file in directory.GetFiles())
{
filePaths.Add(file.FullName);
yield return new HierarchicalItem(file.Name + file.Extension, deep + 1);
}
}
}
Now I know the general theme of recursiveness and how the function calls itself, etc. But while I was testing the code by trail an error, I noticed that it doesn't matter whether that last foreach in the "Main" method is empty or not, also, when that foreach is removed, filePaths are not filled anymore.
My Questions:
So why that last foreach in "Main" method fills the list even if it is empty? And why when it is removed, filling the list fails?
Can someone mention the steps of the recursiveness cycle, such as
SearchDirectory called,
the Empty foreach iterates the first item,
SearchDirectory returns new HierarchicalItem of the path folder.
SearchDirectory loops inside each directory, etc.
I will be grateful for that, especially Question 2.
Thank you very much
IEnumerables are generally lazy – they are only evaluated/produced when they are enumerated/iterated. Without the foreach loop, it is never iterated, therefore never executed.
It is somewhat odd for your IEnumerable generator function to have side-effects that will only be executed when the enumerable is consumed.
Behind the scenes, functions with yield return statements are transformed into state machines which will produce the output on-demand.
Here's a simpler example show-casing the lazy behavior:
class Program
{
static void Main()
{
Console.Out.WriteLine("0");
IEnumerable<string> items = Generate("a", "b", "c");
Console.Out.WriteLine("1");
foreach (string item in items) {
Console.Out.WriteLine("for: " + item);
}
Console.Out.WriteLine("2");
foreach (string item in items)
;
Console.Out.WriteLine("3");
}
public static IEnumerable<string> Generate(params string[] args)
{
foreach (string arg in args) {
Console.Out.WriteLine("Generate: " + arg);
yield return arg;
}
}
}
Output of the above program:
0
1
Generate: a
for: a
Generate: b
for: b
Generate: c
for: c
2
Generate: a
Generate: b
Generate: c
3
Furthermore, yield return doesn't have to occur inside a loop, it can be used standalone and multiple times in a single function:
class Program
{
static void Main()
{
Console.Out.WriteLine("0");
IEnumerable<string> items = Generate();
Console.Out.WriteLine("1");
foreach (string item in items) {
Console.Out.WriteLine(item);
}
Console.Out.WriteLine("2");
}
public static IEnumerable<string> Generate()
{
yield return "x";
yield return "y";
yield return "z";
}
}
Output:
0
1
x
y
z
2
And for bonus points, consider the following program:
class Program
{
static void Main()
{
foreach (string item in Generate("a", "b", "c")) {
Console.Out.WriteLine("for: " + item);
}
Generate("42").ToList();
}
public static IEnumerable<string> Generate(params string[] args)
{
foreach (string arg in args) {
Console.Out.WriteLine("Generating: " + arg);
yield return arg;
yield return arg;
Console.Out.WriteLine("Generated: " + arg);
}
}
}
Its output is:
Generating: a
for: a
for: a
Generated: a
Generating: b
for: b
for: b
Generated: b
Generating: c
for: c
for: c
Generated: c
Generating: 42
Generated: 42
Now that we have covered the basics, what your code should probably be doing instead is to get rid of the side effect:
Yield all directories
Iterate those directories and yield their files
Something along the lines of:
static void Main()
{
string path = "some folder path";
DirectoryInfo directoryInfo = new DirectoryInfo(path);
IEnumerable<DirectoryInfo> dirs = SearchDirectory(directoryInfo);
IEnumerable<string> filePaths = GetFiles(dirs);
PrintList(filePaths);
Console.Read();
}
public static IEnumerable<DirectoryInfo> SearchDirectory(DirectoryInfo directory, int deep = 0)
{
yield return directory;
foreach (DirectoryInfo subdirectory in directory.GetDirectories())
{
foreach (DirectoryInfo item in SearchDirectory(subdirectory, deep + 1))
{
yield return item;
}
}
}
public static IEnumerable<string> GetFiles(IEnumerable<DirectoryInfo> dirs) {
foreach (var dir in dirs)
{
foreach (var file in dir.GetFiles())
{
yield return file.FullName;
}
}
}

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());
}
}

How to remove Folders whose contents are empty

This question may seem a bit absurd but here goes..
I have a directory structure: It has 8 levels. So, for example this is 1 path:
C:\Root\Catalogue\000EC902F17F\2\2013\11\15\13
The '2' is an index for a webcam. I have 4 in total. so..
C:\Root\Catalogue\000EC902F17F\1\2013\11\15\13
C:\Root\Catalogue\000EC902F17F\2\2013\11\15\13
C:\Root\Catalogue\000EC902F17F\3\2013\11\15\13
C:\Root\Catalogue\000EC902F17F\4\2013\11\15\13
The '000EC902F17F' is my own uuid for my webcam.
The '2013' is the year.
The '11' is the month.
The '13' is the day.
When I capture motion the jpegs are saved in a directory that signifies when that image was captured.
I have a timer that goes through each directory and create a video file from the images. The images are then deleted.
Now, I want to have another timer that will go through each directory to check for empty directories. If they are empty the folder is deleted.
This tidy-up timer will look at directories created that are older than the current day it runs.
I presently have this:
private List<string> GetFoldersToDelete()
{
DateTime to_date = DateTime.Now.AddDays(-1);
List<string> paths = Directory.EnumerateDirectories(#"C:\MotionWise\Catalogue\" + Shared.ActiveMac, "*", SearchOption.AllDirectories)
.Where(path =>
{
DateTime lastWriteTime = File.GetLastWriteTime(path);
return lastWriteTime <= to_date;
})
.ToList();
return paths;
}
called by:
List<string> _deleteMe = new List<string>();
List<string> _folders2Delete = GetFoldersToDelete();
foreach (string _folder in _folders2Delete)
{
List<string> _folderContents = Directory.EnumerateFiles(_folder).ToList();
if (_folderContents.Count == 0)
{
_folders2Delete.Add(_folder);
}
}
for (int _index = 0; _index < _folders2Delete.Count; _index++)
{
Directory.Delete(_folders2Delete[_index];
}
Is there a better way to achieve what I want?
Something like this?
private void KillFolders()
{
DateTime to_date = DateTime.Now.AddDays(-1);
List<string> paths = Directory.EnumerateDirectories(#"C:\MotionWise\Catalogue\" + Shared.ActiveMac, "*", SearchOption.TopDirectoryOnly)
.Where(path =>
{
DateTime lastWriteTime = File.GetLastWriteTime(path);
return lastWriteTime <= to_date;
})
.ToList();
foreach (var path in paths))
{
cleanDirs(path);
}
}
private static void cleanDirs(string startLocation)
{
foreach (var directory in Directory.GetDirectories(startLocation))
{
cleanDirs(directory);
if (Directory.GetFiles(directory).Length == 0 && Directory.GetDirectories(directory).Length == 0)
{
Directory.Delete(directory, false);
}
}
}
Note; this wont regard subdirs last writeTime. It will jsut take from the topDir where you have all the diff folders with dates older than a day and clean empty subdirs.
And if your goal is to simply clean empty folders in a target Dir the "cleanDirs" function woorks standalone..
A slightly different take, for comparison:
public static void DeleteEmptyFolders(string rootFolder)
{
foreach (string subFolder in Directory.EnumerateDirectories(rootFolder))
DeleteEmptyFolders(subFolder);
DeleteFolderIfEmpty(rootFolder);
}
public static void DeleteFolderIfEmpty(string folder)
{
if (!Directory.EnumerateFileSystemEntries(folder).Any())
Directory.Delete(folder);
}
(I find this slightly more readable.)
Here's a quick piece of code:
static void Main(string[] args)
{
var baseDirectory = ".";
DeleteEmptyDirectory(baseDirectory);
}
static bool DeleteEmptyDirectory(string directory)
{
var subDirs = Directory.GetDirectories(directory);
var canDelete = true;
if (subDirs.Any())
foreach (var dir in subDirs)
canDelete = DeleteEmptyDirectory(dir) && canDelete;
if (canDelete && !Directory.GetFiles(directory).Any())
{
Directory.Delete(directory);
return true;
}
else
return false;
}
This will delete all empty folders and leave anything with any files in it intact.
Regarding the comment you made about recursion... I wouldn't worry about it, unless you have crazy symlinks creating an infinite directory structure. ;)

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.

Categories