Listing a directory tree

We have seen how to use the Boost Filesystem library to do a simple operation like getting the size of a file, a good way to get acquainted to the library basic functionality, and dumping the content of a directory, getting introduced to the powerful directory_iterator.

But why listing just a directory content, when we could also show the content of all its subdirectories?

That is what we are going here, and we are about to do it using explicit recursion. We'll recursively create a new directory_iterator any time we will bump into a directory while scanning the current lot of files.

Then will see how to use recursive_directory_iterator to implement the same behavior, but leaving the burden of managing recursion to the Boost Filesystem library.

There are a couple of points that we have to be aware of. Firstly we could have an error trying to access a subdirectory - typically we could try to access a directory without having reading rights on it - and secondly we could find out that a subdirectory is actually a symbolic link to another "real" directory.

It is easy to see why the first fact could be an issue, but we can solve it shielding the code that creates the directory iterator in a try catch block. The second point could lead to a problem if the link drives us up to a directory already visited, creating in this way a vicious circle. The easiest way to avoid this risk is simply refuse to recurse in directory that are also symbolic link.

Dumping a file name

Let's write a function that dumps the name of a file accordingly to its level in the current hierarchy, and returns a flag specifying if that is a "good" directory:
bool checkAndDump(boost::filesystem::path path, int level) // 1.
{
bool isDir = boost::filesystem::is_directory(path);
bool isLnk = boost::filesystem::is_symlink(path); // 2.

std::cout << (isDir ? 'D' : ' ') << ' ';
std::cout << (isLnk ? 'L' : ' ') << ' ';
for(int i = 0; i < level; ++i)
std::cout << ' ';
std::cout << path.filename() << std::endl;

return isDir && !isLnk; // 3.
}

1. The caller should pass the name and the current level of the file in the hierarchy.
2. We check if the current file is a "real" one, or it is just a symbolic link.
3. We return true only for a "real" directory.

Actually, in this checkAndDump() function there is a lot of room for improvement. First of all, it should be refactored in two functions. As even its names shows, it is trying to do two things at the same time, and this is not usually a good idea. But, more importantly, that there is no error checking. The code should be enclosed in a try/catch block, to avoid unpleasant surprises. But let it go, for the time being.

Creating a directory_iterator

To keep the code more readable, we move the creation of the directory iterator in a dedicated function:
boost::filesystem::directory_iterator createIterator(boost::filesystem::path path)
{
try
{
return boost::filesystem::directory_iterator(path); // 1.
}
catch(boost::filesystem::filesystem_error& fex)
{
std::cout << fex.what() << std::endl;
return boost::filesystem::directory_iterator(); // 2.
}
}

1. We try to create the iterator.
2. I case of any trouble we return an invalid iterator.

Listing a tree

As said, we could use directory_iterator to scan our directory tree, explicitly recursing in each (good) directory we find:
void recursiveListTree(boost::filesystem::path path, int level)
{
if(!checkAndDump(path, level)) // 1.
return;

boost::filesystem::directory_iterator it = createIterator(path); // 2.
boost::filesystem::directory_iterator end;
std::for_each(it, end, [level](boost::filesystem::path p) // 3.
{
recursiveListTree(p, level + 1); // 4.
});
}

1. If the current path does not refer to a "good" directory, our job is done.
2. We create a directory iterator. Remember that in case of error we get an invalid iterator.
3. We loop on the current path content. If we couldn't access the directory, the first iterator was set to invalid, so the loop is not executed at all. Otherwise we run the lambda function passed as third parameter (giving to it access by value to the level) that would get as input the item pointed by the iterator, that means, a path.
4. Here we have the recursive step. We increase the depth level an call again the function.

Starting the recursion

The user would call a function that takes care of checking for possible errors before calling our recursive function:
void listTree(boost::filesystem::path path)
{
try
{
if(!boost::filesystem::exists(path))
{
std::cout << path << " does not exist" << std::endl;
return;
}

std::cout << "Listing directory tree" << std::endl;
recursiveListTree(path, 0); // 1.
}
catch(const boost::filesystem::filesystem_error& ex)
{
std::cout << ex.what() << std::endl;
}
}

1. Here we start the recursion, passing 0 as initial level.

No comments:

Post a Comment