using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Text;
namespace CodeBureau.DeleteOld
{
/// <summary>
/// Console Application to delete files older than a specified age.
/// </summary>
public class DeleteOld
{
#region Private Variables
private Arguments arguments;
private bool validated = false;
private bool recurseSubFolders = false;
private bool removeEmptyFolders = false;
private bool dontRemoveEmptyRootFolder = false;
private bool showHelp = false;
private bool deleteNewer = false;
private string path = Environment.CurrentDirectory;
private string filter = "*.*";
private int age = int.MaxValue;
private string timeFrame = AgeIncrement.Day;
private bool quietMode = false;
private bool simulateOnly = false;
private DateTime absoluteDate;
private DateTime deleteBaselineDate;
private DateTime runTime;
private StreamWriter outputStream;
private StreamWriter errorOutputStream;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="DeleteOld"/> class.
/// </summary>
/// <param name="args">The arguments.</param>
public DeleteOld(Arguments args)
{
outputStream = new StreamWriter(Console.OpenStandardOutput());
outputStream.AutoFlush = true;
errorOutputStream = new StreamWriter(Console.OpenStandardError());
errorOutputStream.AutoFlush = true;
arguments = args;
if (!ParseArguments() || showHelp)
{
ShowUsage();
validated = false;
}
else
validated = true;
}
/// <summary>
/// Initializes a new instance of the <see cref="DeleteOld"/> class.
/// </summary>
/// <param name="args">The args.</param>
/// <param name="outputStream">The output stream (used primarily for unit testing).</param>
/// <param name="errorOutputStream">The error output stream (used primarily for unit testing).</param>
public DeleteOld(Arguments args, Stream outputStream, Stream errorOutputStream)
{
this.outputStream = new StreamWriter(outputStream);
this.outputStream.AutoFlush = true;
this.errorOutputStream = new StreamWriter(errorOutputStream);
this.errorOutputStream.AutoFlush = true;
arguments = args;
if (!ParseArguments() || showHelp)
{
ShowUsage();
validated = false;
}
else
validated = true;
}
#endregion
#region Public Static Methods
/// <summary>
/// Program entry point
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
public static int Main(string[] args)
{
DeleteOld app = new DeleteOld(new Arguments(args));
if (app.validated)
{
return app.ProcessCommand();
}
else
return -1;
}
/// <summary>
/// Get's a usage string for the application.
/// </summary>
/// <returns></returns>
public static string Usage()
{
StringBuilder sb = new StringBuilder();
sb.Append("\n");
sb.Append("Deletes files older/newer than a specified age\n\n");
sb.Append("DeleteOld [-p] [-f] -a|-d [-t] [--s] [--r] [--nr] [--w] [--q] [--n]\n\n");
sb.Append("-p\tSpecifies path to search (enclose long paths in \"\"). \n\tDefault is current directory.\n");
sb.Append("-f\tSpecifies filter to search. Default is *.*\n");
sb.Append("-a\tYoungest age of files to delete. Used in conjunction with TimeFrame.\n");
sb.Append("-t\tTimeFrame. One of the following:\n");
sb.Append("\t\ts = Second\tn = Minute\th = Hour\n");
sb.Append("\t\td = day\t\tm = Month\ty = Year\n");
sb.Append("\tDefault is d (Day).\n");
sb.Append("-d\tAbsolute Date (overrides -a and -t). Can be any valid datetime string.\n");
sb.Append("--s\tRecurse Sub Folders. Default is Off.\n");
sb.Append("--r\tRemove empty folders. Default is Off.\n");
sb.Append("--nr\tDo not remove empty root folder (if --r used). Default is Off.\n");
sb.Append("--w\tDelete newer (deletes files 'newer' than supplied timeframe).\n");
sb.Append("--q\tQuiet Mode (this suppresses any output).\n");
sb.Append("--n\tDo nothing - simulate only what 'would occur'.\n\n");
sb.Append("e.g. \tThe following deletes all files matching *.dat in c:\\temp older than \n\t(last modified) 10 days ago (based on current Date and Time).\n\n");
sb.Append("\tIt also recurses and removes any empty folders within the tree and pipes\toutput to a text file.\n\n");
sb.Append("DeleteOld -p \"C:\\temp\\dev\" -f *.dat -a 10 -t d --s --r >c:\\out.txt\n\n");
return sb.ToString();
}
#endregion
#region Private Methods
/// <summary>
/// Parse input arguments for completeness
/// </summary>
/// <returns></returns>
private bool ParseArguments()
{
//Help
if (String.Compare(arguments[ArgumentNames.Help], bool.TrueString, true) == 0)
{
showHelp = true;
return false;
}
//Quiet
if (String.Compare(arguments[ArgumentNames.Quiet], bool.TrueString, true) == 0)
quietMode = true;
//Simulate Only
if (String.Compare(arguments[ArgumentNames.Nothing], bool.TrueString, true) == 0)
simulateOnly = true;
//Mandatory Parameters
//Age
if (arguments[ArgumentNames.Age] != null)
{
try
{
age = int.Parse(arguments[ArgumentNames.Age]);
if (age < 0 || age == int.MaxValue)
return false;
}
catch (Exception)
{
if (! quietMode)
errorOutputStream.WriteLine("Age was invalid");
return false;
}
}
//Absolute Date
if (arguments[ArgumentNames.Date] != null)
{
if (!DateTime.TryParse(arguments[ArgumentNames.Date], out absoluteDate))
{
errorOutputStream.WriteLine("Absolute Date '{0}' was invalid", arguments[ArgumentNames.Date]);
return false;
}
}
if (arguments[ArgumentNames.Date] == null && arguments[ArgumentNames.Age] == null)
{
if (!quietMode)
errorOutputStream.WriteLine("Age or Absolute Date must be supplied");
return false;
}
//Optional Parameters
//Path
if (arguments[ArgumentNames.Path] != null)
path = arguments[ArgumentNames.Path];
if (!Directory.Exists(path))
{
errorOutputStream.WriteLine("Directory does not exist : {0}", path);
return false;
}
//Filter
if (arguments[ArgumentNames.Filter] != null)
filter = arguments[ArgumentNames.Filter];
//TimeFrame
if (arguments[ArgumentNames.AgeIncrement] != null)
{
if (Regex.IsMatch(arguments[ArgumentNames.AgeIncrement], @"[s|n|h|d|m|y]{1}"))
timeFrame = arguments[ArgumentNames.AgeIncrement];
else
{
if (! quietMode)
errorOutputStream.WriteLine("Time Frame was invalid");
return false;
}
}
//Recurse SubFolders
if (String.Compare(arguments[ArgumentNames.Recurse], bool.TrueString, true) == 0)
recurseSubFolders = true;
//Remove Empty Folders
if (String.Compare(arguments[ArgumentNames.RemoveEmptyFolders], bool.TrueString, true) == 0)
removeEmptyFolders = true;
//Remove Empty Root Folder
if (String.Compare(arguments[ArgumentNames.RemoveEmptyRootFolder], bool.TrueString, true) == 0)
dontRemoveEmptyRootFolder = true;
//Delete Newer (rather than older than)
if (String.Compare(arguments[ArgumentNames.DeleteNewer], bool.TrueString, true) == 0)
deleteNewer = true;
//Check for 'any' invalid arguments
foreach(string arg in arguments.InnerStringDictionary.Keys)
{
if (! Regex.IsMatch(arg, String.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}|{7}|{8}|{9}|{10}|{11}]", ArgumentNames.Path,
ArgumentNames.Age,
ArgumentNames.AgeIncrement,
ArgumentNames.Filter,
ArgumentNames.Help,
ArgumentNames.Nothing,
ArgumentNames.Quiet,
ArgumentNames.Recurse,
ArgumentNames.RemoveEmptyFolders,
ArgumentNames.RemoveEmptyRootFolder,
ArgumentNames.Date,
ArgumentNames.DeleteNewer)))
{
errorOutputStream.WriteLine("Invalid argument specified : {0}", arg);
return false;
}
}
return true;
}
/// <summary>
/// Sets the delete time benchmark.
/// </summary>
private void SetDeleteTimeBenchmark()
{
if (age != int.MaxValue)
{
if (timeFrame == AgeIncrement.Second)
deleteBaselineDate = runTime.AddSeconds(-age);
if (timeFrame == AgeIncrement.Minute)
deleteBaselineDate = runTime.AddMinutes(-age);
if (timeFrame == AgeIncrement.Hour)
deleteBaselineDate = runTime.AddHours(-age);
if (timeFrame == AgeIncrement.Day)
deleteBaselineDate = runTime.AddDays(-age);
if (timeFrame == AgeIncrement.Month)
deleteBaselineDate = runTime.AddMonths(-age);
if (timeFrame == AgeIncrement.Year)
deleteBaselineDate = runTime.AddYears(-age);
}
//If a date has been provided then this takes precedence
if (absoluteDate != null && absoluteDate != DateTime.MinValue)
deleteBaselineDate = absoluteDate;
}
/// <summary>
/// Deletes the files (potentially recursively) from given folder.
/// </summary>
/// <param name="folder">Folder.</param>
/// <returns></returns>
private int DeleteFilesFromFolder(DirectoryInfo folder, bool simulateOnly)
{
foreach(FileInfo file in folder.GetFiles(filter))
{
if (!deleteNewer ? file.LastWriteTime < deleteBaselineDate : file.LastWriteTime > deleteBaselineDate)
{
try
{
if (! quietMode)
outputStream.WriteLine("Deleting {0}", file.FullName);
if (! simulateOnly)
file.Delete();
}
catch (Exception ex)
{
if (! quietMode)
errorOutputStream.WriteLine("Could not delete file {0} : Reason {1}", file.FullName, ex.Message);
}
}
}
if (recurseSubFolders)
{
foreach(DirectoryInfo subFolder in folder.GetDirectories())
{
DeleteFilesFromFolder(subFolder, simulateOnly);
}
}
if (removeEmptyFolders)
{
if (folder.FullName == path && dontRemoveEmptyRootFolder)
{
outputStream.WriteLine("Leaving empty root folder {0}", folder.FullName);
}
else if (folder.GetFiles().Length == 0 && folder.GetDirectories().Length == 0)
{
if (! quietMode)
outputStream.WriteLine("Deleting empty folder {0}", folder.FullName);
try
{
if (! simulateOnly)
folder.Delete(true);
}
catch (Exception ex)
{
if (! quietMode)
errorOutputStream.WriteLine("Could not delete folder {0} : Reason {1}", folder.FullName, ex.Message);
}
}
}
return 0;
}
/// <summary>
/// Shows the usage string.
/// </summary>
private void ShowUsage()
{
outputStream.Write(Usage());
}
#endregion
#region Public Methods
/// <summary>
/// Process the console command
/// </summary>
/// <returns></returns>
public int ProcessCommand()
{
if (Directory.Exists(path))
{
string deleteType = !deleteNewer ? "older" : "newer";
runTime = DateTime.Now;
SetDeleteTimeBenchmark();
if (! quietMode)
{
if (simulateOnly)
outputStream.WriteLine("SIMULATION ONLY:\nDeleting {0} from {1} {3} than {2}", filter, path, deleteBaselineDate.ToString("F"), deleteType);
else
outputStream.WriteLine("Deleting {0} from {1} {3} than {2}", filter, path, deleteBaselineDate.ToString("F"), deleteType);
}
return DeleteFilesFromFolder(new DirectoryInfo(path), simulateOnly);
}
else
errorOutputStream.WriteLine("Directory does not exist : {0}", path);
ShowUsage();
return -1;
}
#endregion
#region Public Attributes
/// <summary>
/// Return whether the parameters validated OK
/// </summary>
public bool Validated
{
get {return validated;}
}
#endregion
}
#region Constants
/// <summary>
/// Argument Names constants
/// </summary>
public class ArgumentNames
{
private ArgumentNames() {}
public static readonly string Path = "p";
public static readonly string Recurse = "s";
public static readonly string Help = "h";
public static readonly string RemoveEmptyFolders = "r";
public static readonly string Age = "a";
public static readonly string AgeIncrement = "t";
public static readonly string Filter = "f";
public static readonly string Quiet = "q";
public static readonly string Nothing = "n";
public static readonly string RemoveEmptyRootFolder = "nr";
public static readonly string Date = "d";
public static readonly string DeleteNewer = "w";
}
/// <summary>
/// Age Increment (TimeFrame) constants
/// </summary>
public class AgeIncrement
{
private AgeIncrement() {}
public static readonly string Second = "s";
public static readonly string Minute = "n";
public static readonly string Hour = "h";
public static readonly string Day = "d";
public static readonly string Month = "m";
public static readonly string Year = "y";
}
#endregion
}