I was tasked creating a small(?) utility that walks a directory tree level by level and creating each level by calling a CreateDir(folder, parent) function that I have no access to its source code and creates a directory if it finds its parent.
My question is similar to this question.
Assuming I have this structure
A
-B
--D
---G
--E
-C
--F
I should create A first, then B and C, then D,E, and F and then G.
Of course there is no limit to the depth.
I have created this method.
(IEnumerable<string>, IEnumerable<string>) GetAllFolders(string root)
{
var folders = Directory.EnumerateDirectories(root, "*", SearchOption.AllDirectories).Select(path => path.Replace(root, "").Substring(1));
var parents = folders.Select(path => path.Substring(0, (path.LastIndexOf(#"\") + 1) == 0 ? (path.LastIndexOf(#"\") + 1) : (path.LastIndexOf(#"\") ) ));
var listOfFolders = folders.ToList();
var listOfParents = parents.ToList();
return (listOfFolders, listOfParents);
}
And I try to create the structure using
foreach (var tuple in folders.Zip(parents, (x, y) => (x, y)))
{
if (tuple.y == String.Empty)
CreateDir(tuple.x,destination);
else
CreateDir(tuple.x, tuple.y);
}
where destination is a hardcoded path for the destination folder, folders and parents are the results of GetAllFolders.
I believe that I am overthinking it and that's why it is not working. Any simpler ideas?
Is this actually simpler? Not sure. Does it work? Yes. You could try a Linq.GroupBy query that ensures the list of source directories are sorted by depth and then combine this expression with a string.Replace that removes the source folder path, leaving only short names of all the directories it contains. By calling in order of depth, the parent is guaranteed to exist when the child folder is added.
This test routine uses a mock (shown at bottom) of the inaccessible CreateDirectory method you mention in your post.
static void Main(string[] args)
{
// The Copy-From source is mocked in the output directory.
var source = AppDomain.CurrentDomain.BaseDirectory;
// The Copy-To destination is mocked in local app data for this app
var destination = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"create_directories_by_depth"
);
// Making sure the list of directories is sorted by depth
var directoryLevels =
Directory
.GetDirectories(source, String.Empty, SearchOption.AllDirectories)
.Select(path=>path.Replace(source, String.Empty))
.GroupBy(path=>path.Split(Path.DirectorySeparatorChar).Length)
.OrderBy(group=>group.Key);
// Ensure that the top-level folder exists.
Directory.CreateDirectory(destination);
foreach (
var directoryLevel
in directoryLevels)
{
var shortPaths = directoryLevel.ToArray();
foreach (
var folder
in shortPaths.Select(shortPath=>Path.Combine(destination, shortPath)))
{
var parse = folder.Split(Path.DirectorySeparatorChar).ToList();
parse.Remove(parse.Last());
var parent = Path.Combine(parse.ToArray());
// MAKE THE CALL
CreateDirectory(folder, parent);
}
}
foreach (var directory in Directory.GetDirectories(destination, String.Empty, SearchOption.AllDirectories))
{
Console.WriteLine(directory);
}
Process.Start("explorer.exe", destination);
}
The inaccessible method:
// No access to source code
// Creates a directory if it finds its parent
private static void CreateDirectory(string folder, string parent)
{
if (Directory.Exists(parent))
{
Directory.CreateDirectory(folder);
}
else Debug.Assert(false, "Expecting to find existing parent!");
}
Here is the copied directory structure:
Related
I create a TreeView of folders and files in C# Winform, this is my source code:
using System.IO;
using System.Linq;
public partial class WindowExplorer : Form
{
public void LoadFile(TreeNode parent, string path)
{
if (Directory.Exists(path))
{
string dirName = new FileInfo(path).Name;
TreeNode dirNode = new TreeNode(dirName);
parent.Nodes.Add(dirNode);
string[] subDir = Directory.GetDirectories(path);
string[] subFile = Directory.GetFiles(path);
string[] allSubItem = subDir.Concat(subFile).ToArray();
foreach (string subItem in allSubItem)
{
LoadFile(dirNode, subItem);
}
}
if (File.Exists(path))
{
string fileName = new FileInfo(path).Name;
TreeNode fileNode = new TreeNode(fileName);
parent.Nodes.Add(fileNode);
}
}
private void WindowExplorer_Load(object sender, EventArgs e)
{
string path = "D:\\Laravel";
TreeNode dNode = new TreeNode(new FileInfo(path).Name);
fileView.Nodes[0].Nodes.Add(dNode);
LoadFile(dNode, path);
}
}
The Nodes[0] is "My Computer" node.
I just test with root is "D:\Laravel", and here is result:
As you see, the "Laravel" folder appears twice.
I can't fix it, although I have tried remove fileView.Nodes[0].Nodes.Add(dNode); but not working.
In the Load event handler, you first create a Node, add it to the existing Root Node (My Computer), then you pass that Node to the LoadFile() method.
This ne Node becomes the Root Node, TreeNode parent, when passed to the method, which adds a new Node to it using the string path argument, so the Root Node is duplicated.
You can preserve the Root Node, passing it to the method, along with the Path value that becomes the new child Node or the Root Node, e.g.,
private void WindowExplorer_Load(object sender, EventArgs e)
{
string path = "D:\\Laravel";
// Clear the TreeView if necessary / preferable:
// fileView.Nodes.Clear();
// var root = fileView.Nodes.Add("My Computer");
// LoadFile(root, path);
LoadFile(fileView.Nodes[0], path);
}
Your LoadFile() method could use some refactoring, since it generates a whole lot of strings and queries (synchronously) the file system twice in the same procedure.
You could use the DirectoryInfo.GetFileSystemInfos() method, to retrive both files and directories at the same time, use the file attribute to determine whether the file has the FileAttributes.Directory attribute and also order by file name.
Note that the loop is performed over an IOrderedEnumerable<FileSystemInfo> collection, so the loop operates while the enumeration is still ongoing.
public void LoadFile(TreeNode parent, string path)
{
var dir = new TreeNode(Path.GetFileName(path));
parent.Nodes.Add(dir);
var files = new DirectoryInfo(path).GetFileSystemInfos()
.OrderBy(fsi => fsi.Attributes).ThenBy(fsi => fsi.Name);
foreach (var file in files) {
// If the current file is a Directory, recurse its content
if (file.Attributes.HasFlag(FileAttributes.Directory)) {
LoadFile(dir, file.FullName);
}
else {
// Not a directory, add the file to the current Node
dir.Nodes.Add(file.Name);
}
}
}
try
{
string[] SetupFolderKeywords = {"Setup", "Installed"};
DirectoryInfo SearchedDirectory = new DirectoryInfo(Game.SelectedPath);
FileSystemInfo[] filesAndDirs = SearchedDirectory.GetFileSystemInfos($"*{SetupFolderKeywords[0]}*|*{SetupFolderKeywords[1]}*"); // <-- This doesn't work
// FileSystemInfo[] filesAndDirs = SearchedDirectory.GetFileSystemInfos("*" + SetupFolderKeywords[0] + "*"); <-- This Works
foreach (FileSystemInfo foundFile in filesAndDirs)
{
string FullName = foundFile.FullName;
MessageBox.Show(FullName);
}
}
catch (IOException ExpMoveFolder)
{
MessageBox.Show(Convert.ToString(ExpMoveFolder));
}
I'm trying to look for a folder that has either the keyword "Setup" or "Installed" inside the Game.SelectedPath directory. (I used a FolderBrowserDialog to select this folder) and make a MessageBox appear with its path.
When I try to search for a folder that matches one keyword, the MessageBox appears with the path of the folder. It works great, but when I try to search for keyword "Setup" or "Installed" MessageBox doesn't show at all.
No error messages or warnings appear in visual studio and no program exception occurs when I try to look for either one of the keywords instead of just one keyword.
You can't search for multiple patterns with a single call. Your attempt at a Boolean expression is just interpreted as a single pattern and, of course, there are no entries that match that pattern. If you want to match multiple patterns then you have to make multiple calls. One option might be like this:
var folder = new DirectoryInfo(Game.SelectedPath);
var entries = folder.EnumerateFileSystemInfos(patterns[0]);
for (var i = 1; i < patterns.Length; i++)
{
entries = entries.Concat(folder.EnumerateFileSystemInfos(patterns[i]));
}
foreach (var entry in entries)
{
// Use entry here.
}
EDIT:
I just created this folder:
I then executed this code:
var patterns = new[] { "123", "789" };
var folder = new DirectoryInfo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Test"));
var entries = folder.EnumerateFileSystemInfos($"*{patterns[0]}*");
for (var i = 1; i < patterns.Length; i++)
{
entries = entries.Concat(folder.EnumerateFileSystemInfos($"*{patterns[i]}*"));
}
foreach (var entry in entries)
{
Console.WriteLine(entry.Name);
}
That's basically exactly what I posted above except I added wildcards to the EnumerateFileSystemInfos calls where the original code would have required them to be in strings already. This is the output I got:
File123.txt
Folder123
File789.txt
Folder789
I then changed the filters to this:
var patterns = new[] { "456" };
and ran the code again and got this output:
File456.txt
Folder456
Clearly, the code works exactly as it is supposed to and, if what you did didn't work then you did it wrong. If you can't work out what you did wrong, I suggest that you update your question and add the new relevant information.
I want to create folders and add classes within the folders. I can create the folder once, but once I have created it, I just want to add classes. My code gives error because I'm trying to create a folder several times, which is not right. So before adding a class to the folder, I want to check if the folder already exists and if it exists I just want to add the class.
ProjectItem rootFolder = project.ProjectItems.AddFolder(folder);
ProjectItem item = rootFolder.ProjectItems.AddFromTemplate(itemPath, className);
According to documentation, there is no kind of Exists function which would tell us if a folder already existed.
So you have at least two options:
1. Try and ignore
Simply:
try
{
var rootFolder = project.ProjectItems.AddFolder(folder);
}
catch
{
/* folder already exists, nothing to do */
}
2. Solution folders can only occur in the first level below the solution root node, so we can get away with a non-recursive solution.
public static bool CheckIfSolutionFolderExists(ProjectItems projectItems, string foldername)
{
foreach(var projectItem in projectItems)
{
if(projectItem.Kind == EnvDTE.vsProjectItemKindVirtualFolder)
{
if(projectItem.Name == foldername)
{
return true;
}
}
}
return false;
}
For a recursive solution, I found this, which boils down to:
public static bool CheckIfFileExistsInProject(ProjectItems projectItems, string fullpath)
{
foreach(ProjectItem projectItem in projectItems)
{
if(projectItem.Name == fullpath)
{
return true;
}
else if ((projectItem.ProjectItems != null) && (projectItem.ProjectItems.Count > 0))
{
/* recursive search */
return CheckIfFileExistsInProject(projectItem.ProjectItems, fullpath);
}
}
return false;
}
A robust pattern for solution and project folder management would use AddFromDirectory whenever you want to mirror the file system hierarchy in the project tree, and AddFolder only for virtual folders that have no representation in the file system.
ProjectItem item;
if(Directory.Exists(itemname))
{
item = project.AddFromDirectory(itemname);
}
else
{
item = project.AddFolder(itemname);
}
(source: inspired from this)
You can use method Directory.Exists
and in your code it'll be look like there
if(!Directory.Exists(folder)) {
ProjectItem rootFolder = project.ProjectItems.AddFolder(folder);
}
ProjectItem item = rootFolder.ProjectItems.AddFromTemplate(itemPath, className);
You need to do the following:
if(Directory.Exists(path))
{
// The folder already exists
}
else
{
//Create a new folder here ...
}
Please check this out for more details on Directory.Exists
I have a folder structure on a network drive that is
Booking Centre -> Facility -> Files
eg
EUR/12345678/File_archive1.txt
EUR/12345678/File_archive2.txt
EUR/12345678/File_latest.txt
EUR/5555/File_archive1.txt
EUR/5555/File_archive2.txt
EUR/5555/File_latest.txt
When a user selects a booking centre from the drop down, I want the code to look in the above network path for that booking centre, to look at all sub folders and find the most recent file in each of the sub folders and use that to populate a list of portfolios for a second dropdown. It is incredibly slow though, my code given below. Can anyone suggest a faster approach?
public IDictionary<string, Portfolio> ReadPortfolios()
{
var portfolios = new Dictionary<string, Portfolio>();
var di = new DirectoryInfo(PortfolioPath);
var possibleFacilities = di.GetDirectories();
foreach (var possibleFacility in possibleFacilities)
{
try
{
if (possibleFacility.GetFiles().Any())
{
var mostRecentFile = possibleFacility.GetFiles().OrderBy(file => file.LastWriteTimeUtc).Last();
var portfolio = UnzipAndReadPortfolio(mostRecentFile);
if (portfolio == null) continue;
portfolios.Add(possibleFacility.Name, portfolio);
}
}
catch (Exception ex)
{
Console.WriteLine(#"Failed to read portfolio: " + ex.Message);
}
}
return portfolios;
}
If you're interested by all subdirectories of "PortFolioPath", try to use the overload of GetDirectories and / or GetFiles which allows you to pass the SearchOption.AllDirectories parameter : it will avoid multiple access to network.
You also have TWO calls of GetFiles() in your loop, you should rather store the result of first call in a local variable.
You don't provide the code of UnzipAndReadPortfolio, which is maybe the slowest part (... or not ?).
Remember : in your code often you can think "one method call = one network access". So try to flatten your loops, reduce FSO access, etc.
A probably real little performance gain
var mostRecentFile = possibleFacility.GetFiles()
.OrderBy(file => file.LastWriteTimeUtc)
.LastOrDefault();
if(mostRecentFile != null)
....
and comment out the first
// if(possibleFacility.GetFiles().Any())
The most obvious thing:
Every time you call possibleFacility.GetFiles() you get all files within the folder.
you have to call it and save it in a variable and then use this variable.
On my FTP Server I have the following folder structure
- Parent Directory
-a.txt
-b.txt.old
-SubDirectory1
-c.txt
-NestedSubDirectory1
-d.txt
-SubDirectory2
-e.txt
-f.txt.old
The number of SDs are not fixed. I need a way to get all the files(can be any format) without the .old extension from the Parent Directory.
I'm currently using the 3rd party dll edtFTPnet.
ftpConnection.GetFileInfos()Where(f => !(f.Name.EndsWith(".old"))).ToList();
This helps me get the details of the files and folders at the current working directory level.
Can someone tell me a way to get all the files with the parentdirectory, subdirectories and nested subdirectories.
The solution may or may not use edtFTPnet.
FTPConnection.GetFileInfos() returns an array of FTPFile. The class FTPFile has a boolean property Dir which indicates whether its filename accesses a file (false) or directory (true).
Something like this should work:
void ReadSubDirectories(FTPConncetion connection, FTPFile[] files)
{
foreach (var file in files)
{
if (file.Dir)
{
// Save parent directory
var curDir = connection.ServerDirectory;
// Move into directory
connection.ChangeWorkingDirectory(file.Name)
// Read all files
ReadSubDirectories(connection, connection.GetFileInfos());
// Move back into parent directory
connection.ChangeWorkingDirectory(curDir)
}
else
{
// Do magic with your files
}
}
}
However you might be better off using just .NET's built-in FtpWebRequest class since its methods and naming conventions are clearer, it's better documented and it's easier to find references online.
Try to use extensions like this:
class Program
{
static void Main(string[] args)
{
using (var connection = new FTPConnection
{
ServerAddress = "127.0.0.1",
UserName = "Admin",
Password = "1",
})
{
connection.Connect();
connection.ServerDirectory = "/recursive_folder";
var resultRecursive =
connection.GetFileInfosRecursive().Where(f => !(f.Name.EndsWith(".old"))).ToList();
var resultDefault = connection.GetFileInfos().Where(f => !(f.Name.EndsWith(".old"))).ToList();
}
}
}
public static class FtpClientExtensions
{
public static FTPFile[] GetFileInfosRecursive(this FTPConnection connection)
{
var resultList = new List<FTPFile>();
var fileInfos = connection.GetFileInfos();
resultList.AddRange(fileInfos);
foreach (var fileInfo in fileInfos)
{
if (fileInfo.Dir)
{
connection.ServerDirectory = fileInfo.Path;
resultList.AddRange(connection.GetFileInfosRecursive());
}
}
return resultList.ToArray();
}
}