Get directories and files with recursion? - c#

Is possible to get all directories, subdirectories and files with recursion.
I do this because i want to increase my programming logic, and learn how recursion work.
I know to do that with this way:
string path = "D://";
string rezdir,newpath;
DirectoryInfo di = new DirectoryInfo(path);
DirectoryInfo[] dir = di.GetDirectories().ToArray();
for (int i = 0; i < di.GetDirectories().Length; i++)
{
Console.WriteLine(dir[i].ToString());
}
Console.WriteLine("\n\nChoose File: ");
rezdir = Console.ReadLine();
newpath = path + rezdir;
di = new DirectoryInfo(newpath);
dir = di.GetDirectories().ToArray();
for (int i = 0; i < di.GetDirectories().Length; i++)
{
Console.WriteLine(dir[i].ToString());
}
Console.ReadKey();
But i don't do that with recursion way, so ff someone can to do this, i'll be grateful to him.

Going by the code you posted - you seem to want some user interaction - so try something like this:
public static class RecursiveTest
{
public static string Foo(DirectoryInfo currentPath)
{
if (!currentPath.Exists) return string.Empty;
foreach (var directory in currentPath.EnumerateDirectories())
Console.WriteLine("Directory {0}", directory.Name);
foreach (var file in currentPath.EnumerateFiles())
Console.WriteLine("File {0}", file.Name);
while(true)
{
Console.WriteLine("Choose directory or file: ");
string chosenPath = Console.ReadLine();
string newPath = Path.Combine(currentPath.FullName, chosenPath);
if(Directory.Exists(newPath))
return Foo(new DirectoryInfo(newPath));
if(File.Exists(newPath))
return newPath;
Console.WriteLine("File {0} doesn't exist!", newPath);
}
}
}
And call with something like this:
class Program
{
static void Main(string[] args)
{
Console.WriteLine(RecursiveTest.Foo(new DirectoryInfo(#"d:\dev")));
Console.ReadLine();
}
}
HTH

I will avoid coding, because this is a valuable learning exercise. Try completing it yourself: once you do, you'll know that you understand recursion.
To be recursive, a method needs to call itself. Imagine that a method
public static void ShowDirectory(int indentationLevel, DirectoryInfo path)
is already written for you. This makes it easier to write the body:
Get all files in the directory, and print their names in the loop
Get all directories in the directory, and show their content at the next indentation level. You need another loop for that.
The first step is a simple exercise in writing loops. The second exercise becomes easy, too, because you can think of the ShowDirectory as pre-written.

Yeah, it's possible. But I do recommend that you first take a grasp of what recursion is. To put it simply, a recursion has a one-time executing part, and many-time executing part. That one time triggers the many-time part.
In this question, the one-time execution part might be to get the list of all directories beneath the root directory.
Then for each directory, you get all the sub-directories and files. This is the many-times part. However, to run a batch of codes many times, you need to bundle them into a callable routine, or procedure, or method, or function, whatever you call it. Just code bundle.
public void DoDirectories()
{
// one-time part; get a list of directories to start with.
List<string> rootDirectories = Directory.GetDirectories("c:\\").ToList();
foreach (string rootDirectory in rootDirectories)
{
GetSubdirectories(rootDirectory);
}
}
public List<string> GetSubdirectories(string parentDirectory)
{
List<string> subdirecotries = Directory.GetDirectories(
parentDirectory, "*.*", SearchOption.TopDirectoryOnly).ToList();
foreach (string subdirectory in subdirecotries)
{
GetSubdirectories(subdirectory); // recursing happens here
}
return subdirecotries;
}

Related

Output an index number and remove the path from file

I wrote this code, it works but there are 2 features I'd like to implement and I can't find out how.
First:
How to add an index number before each song so it looks like this:
Song1
Song2
Song3
Second:
The output to file includes the path to the file. How do I remove it?
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
string[] files = Directory.GetFiles("E:\\Music", "*", SearchOption.AllDirectories);
foreach (string file in files)
{
string FileName = Path.GetFileName(file);
Console.WriteLine(FileName);
System.IO.File.WriteAllLines("E:\\songs.txt", (files));
}
Console.WriteLine("Press any key to exit");
Console.ReadLine();
}
}
}
So, in order to only write the name of the songs and not the whole path, you'll have to use Path.GetFileNameWithoutExtensions.
To write the index before every song, you'll have to switch to a for loop (to work with the index).
Another problem is that you're writing to songs.txt for every song, which is bad. Here's a code to help you out:
string[] files = Directory.GetFiles(#"C:\Users\Admin\Music\Playlists", "*", SearchOption.AllDirectories);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < files.Length; i++)
{
sb.AppendLine($"{i + 1}. {Path.GetFileNameWithoutExtension(files[i])}");
// Each line will be something like: Number. NameOfTheSong
}
// Only save to the file when everything is done
File.WriteAllText("E:\\songs.txt", sb.ToString());
Console.WriteLine("Press any key to exit");
Console.ReadLine();
First of all, I think that you have a problem with this line:
System.IO.File.WriteAllLines("E:\\songs.txt", (files));
Because in every loop you are writting the file songs.txt
About the new features:
For getting the index, an easy way is to change the foreach loop by a for loop.
For getting the file name you can use Path.GetFileName() or Path.GetFileNameWithoutExtension()
I hope this can help you.

How to find Full Path from a given 'part of string path' using C#

I've a part of a string path: "\MVVM\MyFirstTest2016\MyFirstTest\bin\Debug\MyFirstTest.exe"
I want to search the above path in C: and need to get the complete full path of the directory.
I tried with Path.GetFullPath("") and other in-built methods but didnt get complete full path.
Here's the code:
The sourceDir will be the full path.
string defaultFolder = System.AppDomain.CurrentDomain.BaseDirectory;
string navigateToFolder = "\\MVVM\\MyFirstTest2016\\MyFirstTest\\bin\\Debug\\MyFirstTest.exe";
string sourceDir = Path.Combine(defaultFolder, navigateToFolder);
It sounds like your problem is similar to this question. You've got a partial path, but no idea where it is on this drive. The naive approach would be as follows.
You're first step would be to start at the root of the drive, and get the list of directories:
string[] dirs = Directory.GetDirectories(#"c:\", "*");
You'd then check if any of these strings matched the first directory of your root path (MVVM). If it does, you'd go into that folder and check if it contained the next directory. If it does, check the next and next, etc. until you've exhausted the path.
If not, you'd iterate over the directories, and run the same logic: get the directories, check if any match your first folder, etc.
So a bit of pseudo code would look like:
string directoryPath = "\MVVM\MyFirstTest2016\MyFirstTest\bin\Debug\MyFirstTest.exe"
string[] splitPath = directoryPath.split("\")
check("c:\")
public void check(string directory)
string[] directories = Directory.GetDirectories(#directory, "*")
if(checkDirectories(directories, splitPath))
// Success!
else
for(string subDirectory : directories)
string newDirectory = Path.combine(directory, subDirectory)
check(newDirectory)
public boolean checkDirectories(string[] directories, string[] splitPath)
// Horrible, but just for example - finding the file at the end
if(splitPath.size == 1)
// Get file list in current directory and check the last part of splitPath
if(directories.contains(splitPath[0])
// Recursively call checkDirectories with the sub directories of this folder, an splitPath missing the first item. This can be done using Array.Copy(splitPath, 1, newArray, 0)
Obviously that's nowhere near runnable, but it should give you the basic idea. The other question I linked earlier also has an accepted answer which will help more.
You could iterate over all directories and check if the sub directory is available.
The normal Directory.EnumerateDirectories may throw a UnauthorizedAccessException which stops the process of finding the directory.
So you could write your own EnumerateDirectories as shown below.
The example provided will return the folders found.
void Main()
{
string path = #"temp\A\B";
var parts = path.Split(new [] { Path.DirectorySeparatorChar });
var rootPath = "c:\\";
foreach(var result in DirectoryEx.EnumerateDirectories(rootPath, parts.First()))
{
var checkPath = Path.Combine(result, String.Join(""+Path.DirectorySeparatorChar, parts.Skip(1).ToArray()));
if (Directory.Exists(checkPath))
Console.WriteLine("Found : " + checkPath);
}
}
public class DirectoryEx
{
public static IEnumerable<string> EnumerateDirectories(string dir, string name)
{
IEnumerable<string> dirs;
// yield return may not be used in try with catch.
try { dirs = Directory.GetDirectories(dir); }
catch(UnauthorizedAccessException) { yield break; }
foreach (var subdir in dirs)
{
if(Path.GetFileName(subdir).Equals(name, StringComparison.OrdinalIgnoreCase))
yield return subdir;
foreach(var heir in EnumerateDirectories(subdir, name))
yield return heir;
}
}
}
In my case results in :
Found : c:\dev\temp\A\B
Found : c:\Temp\A\B

how to stop Directory.CreateDirectory creating parents?

I know that Directory.CreateDirectory actually creates parents, so how can I STOP this from happening? i.e. is there a mode that I can utilise like a stricter way of doing so, the reason is that I have a watch program watching the parent top tree dir and it goes beserk if Directory.CreateDirectory makes more than one dir at a time.
Is there an equivalent to Directory.CreateDirectory which will NOT make parents?
Do you understand what for you need such method? It seems like you don't want to create all folders needed to create your target folder, like: C:\this\is\your\path\TargetFolder
In this case you can just do the following:
const string path = #"C:\this\is\your\path";
if (Directory.Exists(path))
{
Directory.CreateDirectory(Path.Combine(path, "TargetDirectory"));
}
If you have other purpose for that method, please help us to understand which one
List<string> missingDirectories = null;
private void MakeParents(string path)
{
missingDirectories = new List<string>();
missingDirectories.Add(path);
parentDir(path);
missingDirectories = missingDirectories.OrderBy(x => x.Length).ToList<string>();
foreach (string directory in missingDirectories)
{
Directory.CreateDirectory(directory);
}
}
private void parentDir(string path)
{
string newPath = path.Substring(0, path.LastIndexOf(Path.DirectorySeparatorChar));
if (!Directory.Exists(newPath))
{
missingDirectories.Add(newPath);
parentDir(newPath);
}
}
this does it, the issue is that if you want to "gently" roll up the paths one dir at a time making them, something like this is the only way you can do it :/

How do I compare one collection of files to another in c#?

I am just learning C# (have been fiddling with it for about 2 days now) and I've decided that, for leaning purposes, I will rebuild an old app I made in VB6 for syncing files (generally across a network).
When I wrote the code in VB 6, it worked approximately like this:
Create a Scripting.FileSystemObject
Create directory objects for the source and destination
Create file listing objects for the source and destination
Iterate through the source object, and check to see if it exists in the destination
if not, create it
if so, check to see if the source version is newer/larger, and if so, overwrite the other
So far, this is what I have:
private bool syncFiles(string sourcePath, string destPath) {
DirectoryInfo source = new DirectoryInfo(sourcePath);
DirectoryInfo dest = new DirectoryInfo(destPath);
if (!source.Exists) {
LogLine("Source Folder Not Found!");
return false;
}
if (!dest.Exists) {
LogLine("Destination Folder Not Found!");
return false;
}
FileInfo[] sourceFiles = source.GetFiles();
FileInfo[] destFiles = dest.GetFiles();
foreach (FileInfo file in sourceFiles) {
// check exists on file
}
if (optRecursive.Checked) {
foreach (DirectoryInfo subDir in source.GetDirectories()) {
// create-if-not-exists destination subdirectory
syncFiles(sourcePath + subDir.Name, destPath + subDir.Name);
}
}
return true;
}
I have read examples that seem to advocate using the FileInfo or DirectoryInfo objects to do checks with the "Exists" property, but I am specifically looking for a way to search an existing collection/list of files, and not live checks to the file system for each file, since I will be doing so across the network and constantly going back to a multi-thousand-file directory is slow slow slow.
Thanks in Advance.
The GetFiles() method will only get you files that does exist. It doesn't make up random files that doesn't exist. So all you have to do is to check if it exists in the other list.
Something in the lines of this could work:
var sourceFiles = source.GetFiles();
var destFiles = dest.GetFiles();
foreach (var file in sourceFiles)
{
if(!destFiles.Any(x => x.Name == file.Name))
{
// Do whatever
}
}
Note: You have of course no guarantee that something hasn't changed after you have done the calls to GetFiles(). For example, a file could have been deleted or renamed if you try to copy it later.
Could perhaps be done nicer somehow by using the Except method or something similar. For example something like this:
var sourceFiles = source.GetFiles();
var destFiles = dest.GetFiles();
var sourceFilesMissingInDestination = sourceFiles.Except(destFiles, new FileNameComparer());
foreach (var file in sourceFilesMissingInDestination)
{
// Do whatever
}
Where the FileNameComparer is implemented like so:
public class FileNameComparer : IEqualityComparer<FileInfo>
{
public bool Equals(FileInfo x, FileInfo y)
{
return Equals(x.Name, y.Name);
}
public int GetHashCode(FileInfo obj)
{
return obj.Name.GetHashCode();
}
}
Untested though :p
One little detail, instead of
sourcePath + subDir.Name
I would use
System.IO.Path.Combine(sourcePath, subDir.Name)
Path does reliable, OS independent operations on file- and foldernames.
Also I notice optRecursive.Checked popping out of nowhere. As a matter of good design, make that a parameter:
bool syncFiles(string sourcePath, string destPath, bool checkRecursive)
And since you mention it may be used for large numbers of files, keep an eye out for .NET 4, it has an IEnumerable replacement for GetFiles() that will let you process this in a streaming fashion.

Quickest way in C# to find a file in a directory with over 20,000 files

I have a job that runs every night to pull xml files from a directory that has over 20,000 subfolders under the root. Here is what the structure looks like:
rootFolder/someFolder/someSubFolder/xml/myFile.xml
rootFolder/someFolder/someSubFolder1/xml/myFile1.xml
rootFolder/someFolder/someSubFolderN/xml/myFile2.xml
rootFolder/someFolder1
rootFolder/someFolderN
So looking at the above, the structure is always the same - a root folder, then two subfolders, then an xml directory, and then the xml file.
Only the name of the rootFolder and the xml directory are known to me.
The code below traverses through all the directories and is extremely slow. Any recommendations on how I can optimize the search especially if the directory structure is known?
string[] files = Directory.GetFiles(#"\\somenetworkpath\rootFolder", "*.xml", SearchOption.AllDirectories);
Rather than doing GetFiles and doing a brute force search you could most likely use GetDirectories, first to get a list of the "First sub folder", loop through those directories, then repeat the process for the sub folder, looping through them, lastly look for the xml folder, and finally searching for .xml files.
Now, as for performance the speed of this will vary, but searching for directories first, THEN getting to files should help a lot!
Update
Ok, I did a quick bit of testing and you can actually optimize it much further than I thought.
The following code snippet will search a directory structure and find ALL "xml" folders inside the entire directory tree.
string startPath = #"C:\Testing\Testing\bin\Debug";
string[] oDirectories = Directory.GetDirectories(startPath, "xml", SearchOption.AllDirectories);
Console.WriteLine(oDirectories.Length.ToString());
foreach (string oCurrent in oDirectories)
Console.WriteLine(oCurrent);
Console.ReadLine();
If you drop that into a test console app you will see it output the results.
Now, once you have this, just look in each of the found directories for you .xml files.
I created a recursive method GetFolders using a Parallel.ForEach to find all the folders named as the variable yourKeyword
List<string> returnFolders = new List<string>();
object locker = new object();
Parallel.ForEach(subFolders, subFolder =>
{
if (subFolder.ToUpper().EndsWith(yourKeyword))
{
lock (locker)
{
returnFolders.Add(subFolder);
}
}
else
{
lock (locker)
{
returnFolders.AddRange(GetFolders(Directory.GetDirectories(subFolder)));
}
}
});
return returnFolders;
Are there additional directories at the same level as the xml folder? If so, you could probably speed up the search if you do it yourself and eliminate that level from searching.
System.IO.DirectoryInfo root = new System.IO.DirectoryInfo(rootPath);
List<System.IO.FileInfo> xmlFiles=new List<System.IO.FileInfo>();
foreach (System.IO.DirectoryInfo subDir1 in root.GetDirectories())
{
foreach (System.IO.DirectoryInfo subDir2 in subDir1.GetDirectories())
{
System.IO.DirectoryInfo xmlDir = new System.IO.DirectoryInfo(System.IO.Path.Combine(subDir2.FullName, "xml"));
if (xmlDir.Exists)
{
xmlFiles.AddRange(xmlDir.GetFiles("*.xml"));
}
}
}
I can't think of anything faster in C#, but do you have indexing turned on for that file system?
Only way I can see that would make much difference is to change from a brute strength hunt and use some third party or OS indexing routine to speed the return. that way the search is done off line from your app.
But I would also suggest you should look at better ways to structure that data if at all possible.
Use P/Invoke on FindFirstFile/FindNextFile/FindClose and avoid overhead of creating lots of FileInfo instances.
But this will be hard work to get right (you will have to do all the handling of file vs. directory and recursion yourself). So try something simple (Directory.GetFiles(), Directory.GetDirectories()) to start with and get things working. If it is too slow look at alternatives (but always measure, too easy to make it slower).
Depending on your needs and configuration, you could utilize the Windows Search Index: https://msdn.microsoft.com/en-us/library/windows/desktop/bb266517(v=vs.85).aspx
Depending on your configuration this could increase performance greatly.
For file and directory search purpose I would want to offer use multithreading .NET library that possess a wide search opportunities.
All information about library you can find on GitHub: https://github.com/VladPVS/FastSearchLibrary
If you want to download it you can do it here: https://github.com/VladPVS/FastSearchLibrary/releases
If you have any questions please ask them.
Works really fast. Check it yourself!
It is one demonstrative example how you can use it:
class Searcher
{
private static object locker = new object();
private FileSearcher searcher;
List<FileInfo> files;
public Searcher()
{
files = new List<FileInfo>();
}
public void Startsearch()
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
searcher = new FileSearcher(#"C:\", (f) =>
{
return Regex.IsMatch(f.Name, #".*[Dd]ragon.*.jpg$");
}, tokenSource);
searcher.FilesFound += (sender, arg) =>
{
lock (locker) // using a lock is obligatorily
{
arg.Files.ForEach((f) =>
{
files.Add(f);
Console.WriteLine($"File location: {f.FullName}, \nCreation.Time: {f.CreationTime}");
});
if (files.Count >= 10)
searcher.StopSearch();
}
};
searcher.SearchCompleted += (sender, arg) =>
{
if (arg.IsCanceled)
Console.WriteLine("Search stopped.");
else
Console.WriteLine("Search completed.");
Console.WriteLine($"Quantity of files: {files.Count}");
};
searcher.StartSearchAsync();
}
}
It's part of other example:
***
List<string> folders = new List<string>
{
#"C:\Users\Public",
#"C:\Windows\System32",
#"D:\Program Files",
#"D:\Program Files (x86)"
}; // list of search directories
List<string> keywords = new List<string> { "word1", "word2", "word3" }; // list of search keywords
FileSearcherMultiple multipleSearcher = new FileSearcherMultiple(folders, (f) =>
{
if (f.CreationTime >= new DateTime(2015, 3, 15) &&
(f.Extension == ".cs" || f.Extension == ".sln"))
foreach (var keyword in keywords)
if (f.Name.Contains(keyword))
return true;
return false;
}, tokenSource, ExecuteHandlers.InCurrentTask, true);
***
Moreover one can use simple static method:
List<FileInfo> files = FileSearcher.GetFilesFast(#"C:\Users", "*.xml");
Note that all methods of this library DO NOT throw UnauthorizedAccessException instead standard .NET search methods.
Furthermore fast methods of this library are performed at least in 2 times faster than simple one-thread recursive algorithm if you use multicore processor.
For those of you who want to search for a single file and you know your root directory then I suggest you keep it simple as possible. This approach worked for me.
private void btnSearch_Click(object sender, EventArgs e)
{
string userinput = txtInput.Text;
string sourceFolder = #"C:\mytestDir\";
string searchWord = txtInput.Text + ".pdf";
string filePresentCK = sourceFolder + searchWord;
if (File.Exists(filePresentCK))
{
pdfViewer1.LoadFromFile(sourceFolder+searchWord);
}
else if(! File.Exists(filePresentCK))
{
MessageBox.Show("Unable to Find file :" + searchWord);
}
txtInput.Clear();
}// end of btnSearch method

Categories