Cicero denouncing Cataline

While testing my configuration editor app, one of the QA guys asked if it made a backup copy of the file being edited.  My reply was “not yet”.  And I thought about it a bit and decided to implement the backup feature and to kick it up a notch.

In prehistoric times, VMS would automagically version your files when you saved them.  When you saved a file, the file system  would append a version number starting at 1.  You could open any version of a file by including the version number.  If you left out the version number, you got the latest version.  This goes back to the days when monitors supported both colors, green and not green.  To clear out older versions, you would periodically need to run the purge command.

Prehistoric times for web developers

Prehistoric times for web developers

I wanted something close to that in my code, except without having to manually purge the file system.  I wrote a simple static class that takes the name of the file and the number of versions to keep.

public static class FileHelper
{
    /// <summary>
    /// Make a numbered backup copy of the specified files.  Backup files have the name filename.exe.yymmdd##, where yymmdd is the date and ## is a zero justified sequence number starting at 1
    /// </summary>
    /// <param name="fileName">Name of the file to backup.</param>
    /// <param name="maxBackups">The maximum backups to keep.</param>
    public static void MakeBackup(string fileName, int maxBackups)
    {
        // Make sure that the file exists, you don't backup a new file
        if (File.Exists(fileName))
        {
            // First backup copy of the day starts at 1
            int newSequence = 1;

            // Get the list of previous backups of the file, skipping the current file
            var backupFiles = Directory.GetFiles(Path.GetDirectoryName(fileName), Path.GetFileName(fileName) + ".*")
                .ToList()
                .Where(d => !d.Equals(fileName))
                .OrderBy(d => d);

            // Get the name of the last backup performed
            var lastBackupFilename = backupFiles.LastOrDefault();

            // If we have at least one previous backup copy
            if (lastBackupFilename != null)
            {
                // Get the last sequence number back taking the last 2 characters and convert them to an int. And add 1 to that number
                if (Int32.TryParse(Path.GetExtension(lastBackupFilename).GetLast(2), out newSequence))
                    newSequence++;

                // If we have more backups than we need to keep
                if (backupFiles.Count() >= maxBackups)
                {
                    // Get a list of the oldest files to delele
                    var expiredFiles = backupFiles.Take(backupFiles.Count() - maxBackups + 1);

                    foreach (var expiredFile in expiredFiles)
                    {
                        File.Delete(expiredFile);
                    }
                }
            }

            // Create the file name for the newest back up file.
            var latestBackup = String.Format("{0}.{1:yyMMdd}{2:00}", fileName, DateTime.Now, newSequence);

            // Copy the current file to the new backup name and overwrite any existing copy
            File.Copy(fileName, latestBackup, true);
        }
    }
}
// String Extension that was used in the code but left out when I first published
public static class StringExtension
{
    public static string GetLast(this string source, int tail_length)
    {
       if(tail_length >= source.Length)
          return source;
       return source.Substring(source.Length - tail_length);
    }
}

You would use this method like this:

FileHelper.MakeBackup("web.config", 5);

The first time you called it, you would get web.config.14031401. The next time would get web.config.14031402. When web.config.14031406 was created, web.config.14031401 would be deleted to keep the number of backups limited to 5.

[Updated August 17th, 2015]
When I posted this, I left out a string extension methiod that I use to get the last N characters from a string.  That code is now included.