Search code examples
c#recursiondirectory

Walk and create directory tree level by level


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?


Solution

  • 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:

    copied directory structure