Introduction
The new Windows FileHistory achieves its goal of being an easy to set up archiving and backup mechanism needing minimal intervention to achieve a fairly sophisticated multi-generation file history. The only problem is that, using the default "Back up my files" setting of one hour, the number of items rapidly escalates for files which are constantly updated. This little utility lets you periodically trim and retain only the last n number of files on your FileHistory/backup sets.
The Code
This article is intended as a useful utility rather than as a demontration of any fancy coding techniques. This is the first time I have submitted code. I must say that the thought of other people looking at ones code tends to focus the mind. Instead of the usual quick and dirty approach I found myself going the extra mile to do things "properly", do some error checking and even the occasional comment.
One feature needs some explanation. The intuitive way to find which files to purge would be to sort the files and then scan through looking for duplicates of the original fileneme i.e. the part before "(archive date)". The problem is that the suffix (e.g. ".txt") is not taken into account and files with the same basic name but different suffixes will all be lumped together for the purpose of deciding how many are present. I got around this by creating a sort array consisting of:
- The suffix
- a placemarker '*' (used because it can never be part of a filename or directory name)
- the full path
The array is then sorted into descending sequence so that the newest files are first. The 'compare name' is that text before the last '(' and the original path/filename is the text after '*'. This technique will fail in the fairly obscure case where the suffix contains brackets - there will always be limits.
I would like to add functionality where instead of, or in addition to, a number of generations, the FileHistory consists of, say, one file that is at least a month old, one file at least a week and one at least a day. Any bright young minds with nothing to do are welcome to have a go.
I would really value comments and perhaps suggested alternatives to the way this old guy does things.
using System;
using System.IO;
using System.Text;
using System.Collections;
public class RecursiveFileProcessor
{
public static void Main(string[] args)
{
int MaxRetain;
if (args.Length != 2 || !int.TryParse(args[1], out MaxRetain))
{
Console.WriteLine("Usage - " + System.AppDomain.CurrentDomain.FriendlyName + " Path NumberOfGenerations ");
Console.WriteLine("For example " + System.AppDomain.CurrentDomain.FriendlyName + " C:\\FileHistory 2");
return;
}
if (Directory.Exists(args[0]))
{
ProcessDirectory(args[0], MaxRetain);
}
else
{
Console.WriteLine("{0} is not available or is not a valid directory.", args[0]);
}
}
public static void ProcessDirectory(string targetDirectory, int MaxRetain)
{
string[] fileEntries = Directory.GetFiles(targetDirectory);
string[] SortEntries = new string[fileEntries.Length];
if (SortEntries.Length > 0)
{
int x = 0;
foreach (string fileName in fileEntries)
{
int LastClose = fileName.LastIndexOf(')');
int LenSuffix = fileName.Length - LastClose - 1;
SortEntries[x] = "";
if (LastClose > 0 && LenSuffix > 0)
SortEntries[x] = fileName.Substring(fileName.Length - LenSuffix);
SortEntries[x] = SortEntries[x] + '*';
SortEntries[x] = SortEntries[x] + fileName;
x++;
}
Array.Sort(SortEntries);
Array.Reverse(SortEntries);
string PrevName = "";
int CountDup = 0;
foreach (string SortFile in SortEntries)
{
if (SortFile == null) continue;
string OriginalName = SortFile.Substring(SortFile.IndexOf('*') + 1);
int OpenPos = SortFile.LastIndexOf('(');
string CompareName = SortFile;
if (OpenPos > 0)
{
CompareName = SortFile.Substring(0, SortFile.LastIndexOf('('));
}
if (CompareName == PrevName)
{
CountDup++;
}
else
{
CountDup = 0;
PrevName = CompareName;
}
if (CountDup >= MaxRetain)
{
try
{
FileAttributes attributes = File.GetAttributes(OriginalName);
File.SetAttributes(OriginalName, attributes & ~FileAttributes.ReadOnly);
File.Delete(OriginalName);
Console.WriteLine("{0} deleted", OriginalName);
}
catch(Exception e)
{
Console.WriteLine( "Unable to delete {0} because {1}", OriginalName, e);
continue;
}
}
}
}
string[] subdirectoryEntries = Directory.GetDirectories(targetDirectory);
foreach (string subdirectory in subdirectoryEntries)
ProcessDirectory(subdirectory, MaxRetain);
}
}