The beauty of LINQ and extension methods

I don’t use LINQ terribly often because it seems that most of the work I do is pre-CF 3.5.  It only creeps into my code in things like unit tests or test projects. 

This weekend I spent a little time updating my AudioTagR project and adding some tools and utilities to it.  One of the challenges I faced was let’s assume I have two root directories that contain music files (they really can be any file, but this is a real-world example, so we’ll use the exact scenario I had).  In each of these roots are folders for Artists.  In each of those are one or more folders that represent albums by that artist.

Now let’s assume one of those root folders (Root A) is my “root” music collection and it contains a few thousand artists.  The other root folder (Root B) is a collection of music off my old hard drives, CDs, etc.  I know that there are duplicates between these folders – artists that exist in both as well as albums that exist in both.  I want to go through Root B and move only those albums to Root A if they don’t already exist.  That means that I want to move all albums that aren’t already in Root A, creating the containing Artist folder if it’s there.  If the folder is there, I want to skip it and I’ll manually check the list of songs later.

The traditional way to do this would be to write a recursive method that would look something like this pseudocode:

void Parse(sourcefolder, destfolder)
{
   foreach(directory in sourcefolder.directories)
   {
       if(! exists(destfolder[folder]))
       {
          Copy(sourcefolder, destfolder);
       }
       else
       {
           Parse(directory, destfolder + directory);
       }
   }
}

Well since this code really is nothing but a series of operations on a collection, LINQ immediately comes to mind as a good fit.

The first challenge I ran into is that my algorithm requires a list of directories contained in a parent directory.  Unfortunately Directory.GetDirectories returns a list with the fully qualified path and I just needed the name of the folder itself.  So I wrote a couple of extension methods that get purely a list of the directory names, not the paths.  I also added one for checking to see if a directory was empty (to be used later for cleanliness):



public static class IO
{
  public static bool IsEmptyDirectory(this string path)
  {
    return (Directory.GetDirectories(path).Count() + Directory.GetFiles(path).Count()) == 0;
  }

  public static string FolderNameWithoutPath(this string path)
  {
    return Path.GetFileName(path);
  }

  public static string[] GetFolderNamesWithoutPath(this string path)
  {
    return (from folder in Directory.GetDirectories(path)
        select folder.FolderNameWithoutPath()).ToArray();
  }
}


Once I had those, the resulting merge method was a simple iteration across a couple LINQ queries.

private void MergeFolders(string sourceFolder, string destinationFolder)
{
  // copy those in A but not in B
  foreach(string folder in
      sourceFolder.GetFolderNamesWithoutPath().Except(
      destinationFolder.GetFolderNamesWithoutPath(), StringComparer.InvariantCultureIgnoreCase))
  {
    Directory.Move(Path.Combine(sourceFolder, folder), Path.Combine(destinationFolder, folder));
  }

  // now go through those in both and repeat
  foreach (string folder in
      sourceFolder.GetFolderNamesWithoutPath().Intersect(
      destinationFolder.GetFolderNamesWithoutPath(), StringComparer.InvariantCultureIgnoreCase))
  {
    string src = Path.Combine(sourceFolder, folder);
    MergeFolders(src, Path.Combine(destinationFolder, folder));

    if (src.IsEmptyDirectory()) Directory.Delete(src);
  }
}


 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s