Delta Engine Blog

All about multiplatform and game development

VS2010 coming soon .. and a little tool: KillEmptyDirectories

Visual Studio 2010 beta is apparently coming soon. Since I used VS2005 beta and VS2008 beta when they came out, I will be an early adopter once again. I really want to use the cool Parallel APIs in VS2010 and C# 4.0 dynamic stuff :)

Another topic: I try to synchronize several of my servers at different locations every day. This way it does not matter where I download or create a file, I still can use it the next day wherever I am (home, work, different work ^^). I use SyncBackSE for that job, great tool, but the problem with it is that on slow internet connections (e.g. slow DSL at work right now) it takes forever to even scan the directories. Its about 30000 directories that have to be synchronized and this takes a lot of hours, especially if the internet connection is already doing something else. Obviously uploading and downloading a few GB can also take a lot of time, but in 95% of the cases nothing or just a few MB change every day.

Because searching sub directories is so slow I tried to make it faster by removing directories when not necessary (just compressing 100 directories into a zip file, removing empty directories or old files, etc.). But this is obviously a lot of work for this many directories and I can easily overlook that in sub directory 3592 there is 1500 empty folders of some crap from some years ago no one will ever need again. For this reason I quickly wrote this little tool called KillEmptyDirectories, you can see a picture on the right! It only took me an hour to write, so there is no rocket science here and it is very straight forward. I did not even unit test it, I just tested it by executing on several directories. It also can remove hidden and read-only files by changing the file attributes (otherwise File.Delete will always throw UnauthorizedAccessException). I tested it on around 30000 directories and was able to kill around 8000 of those (deleting a lot of old stuff) ^^ with some additional compression of unused older directories I was able to get it down to around 3000 directories (from 30000), so the scanning process is now about 10 times faster.

If you want to try it out, you can download it here. But use with care, you can obviously kill directories with it and by using the "Always kill these directories" options you can even delete directories with files in it. An extra confirmation message will appear if you try to use that feature. And this is the main function that does all the directory killing:

/// <summary>
/// Recursively kill directories, will be called as many times as we have
/// sub directories (can be many thousand times). Will always go through
/// all subdirectories first in case we can remove them, which makes it
/// much easier to delete directories with just empty sub directories.
/// </summary>
private int RecursivelyKillDirectories(string directory,
    string[] includeFiles, string[] alwaysKillDirectories,
    bool alwaysKillThisDirectory)
{
    int directoriesKilled = 0;
    string[] subDirectories = Directory.GetDirectories(directory);

    // Only delete this directory if there are no useful files in here!
    // Note: GetFileName will give us the last part of the directory! 
    if (alwaysKillDirectories.Contains(Path.GetFileName(directory)))
        alwaysKillThisDirectory = true;

    // Handle subdirectories always first (maybe we can kill them too)
    foreach (string subDir in subDirectories)
        directoriesKilled += RecursivelyKillDirectories(subDir, includeFiles,
            alwaysKillDirectories, alwaysKillThisDirectory);

    // Get all files here and count how many of those we can ignore
    string[] files = Directory.GetFiles(directory);
    int ignoreFileCount = 0;
    foreach (string file in files)
        if (includeFiles.Contains(Path.GetFileName(file)))
            ignoreFileCount++;

    // Only found ignored files (or no files at all) or do we want to kill
    // this directory anyway?
    if (files.Length == ignoreFileCount ||
        alwaysKillThisDirectory)
    {
        // Check again if we don't have any sub directories left here
        // Maybe the subdirectories above were already killed now!
        subDirectories = Directory.GetDirectories(directory);
        if (subDirectories.Length == 0 ||
            alwaysKillThisDirectory)
        {
            try
            {
                // Kill all files in it (only ignored files anyway)
                foreach (string file in files)
                {
                    // Make sure we can delete hidden and readonly files
                    FileAttributes attributes = File.GetAttributes(file);
                    if ((attributes & FileAttributes.ReadOnly) != 0 ||
                        (attributes & FileAttributes.Hidden) != 0)
                        File.SetAttributes(file, FileAttributes.Normal);
                    File.Delete(file);
                } // foreach
                Directory.Delete(directory);

                // We killed something, yeah
                directoriesKilled++;
            } // try
            catch (Exception ex)
            {
                MessageBox.Show("Failed to delete " + directory + ": " +
                    ex.ToString());
            } // catch
        } // if
    } // if

    // Most of the time nothing was killed
    return directoriesKilled;
} // RecursivelyKillDirectories(directory, includeFiles)