Created
June 27, 2011 06:06
-
-
Save ducas/1048378 to your computer and use it in GitHub Desktop.
Hash - A quick file hasher that recursively generates and compares hash files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Security.Cryptography; | |
namespace Hash | |
{ | |
public class Program | |
{ | |
private static readonly Output output = new Output(); | |
private static readonly GenerateCommand generate = new GenerateCommand(); | |
private static readonly CompareCommand compare = new CompareCommand(); | |
private static readonly DiffCommand diff = new DiffCommand(); | |
public static void Main(string[] args) | |
{ | |
if (args.Length == 0 || args[0] == "?" || args[0] == "/?" || args[0] == "help") { output.Usage(); return; } | |
var command = args[0].ToLower(); | |
if (!(new[] { "generate", "compare", "diff" }).Contains(command)) { output.Usage(); return; } | |
if (args.Length == 1) { output.WriteError("Please specify a root directory."); output.Usage(); return; } | |
var root = args[1]; | |
if (!Directory.Exists(root)) { output.WriteError("Directory '" + root + "' does not exist."); return; } | |
switch (command) | |
{ | |
case "generate": | |
generate.Execute(new DirectoryInfo(root), args.Length == 3 ? args[2] : "*"); | |
break; | |
case "compare": | |
compare.Execute(new DirectoryInfo(root), args.Length == 3 ? args[2] : "*"); | |
break; | |
case "diff": | |
if (args.Length == 2) { output.WriteError("Please specify a second root directory to diff."); output.Usage(); return; } | |
var root2 = args[2]; | |
if (!Directory.Exists(root)) { output.WriteError("Directory '" + root2 + "' does not exist."); return; } | |
diff.Execute(new DirectoryInfo(root), new DirectoryInfo(root2)); | |
break; | |
default: | |
output.Usage(); | |
return; | |
} | |
output.Write("Done!"); | |
} | |
} | |
public class Command | |
{ | |
protected readonly HashGenerator generator = new HashGenerator(); | |
protected readonly HashDictionarySerializer serializer = new HashDictionarySerializer(); | |
protected readonly Output output = new Output(); | |
protected void CompareHashes(Dictionary<string, string> left, Dictionary<string, string> right) | |
{ | |
foreach (var item in left) | |
if (!right.ContainsKey(item.Key)) | |
output.WriteMissing(item.Key + " missing from previously generated hashes."); | |
else if (right[item.Key] == item.Value) | |
output.WriteSame(item.Key + " same"); | |
else output.WriteError(item.Key + " different"); | |
foreach (var item in right) | |
if (!left.ContainsKey(item.Key)) output.WriteMissing(item.Key + " missing from file system."); | |
} | |
} | |
public class GenerateCommand : Command | |
{ | |
public void Execute(DirectoryInfo directory, string filter) | |
{ | |
output.Write("Generating hashes for " + directory.FullName); | |
var hashes = generator.GetHashes(directory, filter); | |
if (hashes.Count > 0) | |
serializer.Serialize(hashes, Path.Combine(directory.FullName, ".hashes")); | |
foreach (var child in directory.GetDirectories()) | |
Execute(child, filter); | |
} | |
} | |
public class CompareCommand : Command | |
{ | |
public void Execute(DirectoryInfo directory, string filter) | |
{ | |
output.Write("Comparing " + directory.FullName); | |
var left = generator.GetHashes(directory, filter); | |
var right = serializer.Deserialize(Path.Combine(directory.FullName, ".hashes")); | |
if (left.Count > right.Count && right.Count == 0) | |
output.WriteMissing("No previously generated .hashes."); | |
else | |
CompareHashes(left, right); | |
foreach (var child in directory.GetDirectories()) | |
Execute(child, filter); | |
} | |
} | |
public class DiffCommand : Command | |
{ | |
public void Execute(DirectoryInfo directory, DirectoryInfo directory2) | |
{ | |
output.Write("Comparing " + directory.FullName + " to " + directory2.FullName); | |
var leftHashFiles = directory.GetFiles(".hashes", SearchOption.AllDirectories); | |
var rightHashFiles = directory2.GetFiles(".hashes", SearchOption.AllDirectories); | |
foreach (var left in leftHashFiles) | |
{ | |
var relativeFileName = left.FullName.Replace(directory.FullName, directory2.FullName); | |
var right = rightHashFiles.FirstOrDefault(f => f.FullName.Equals(relativeFileName, StringComparison.InvariantCultureIgnoreCase)); | |
if (right == null) | |
{ | |
output.WriteMissing(left.FullName + " - " + relativeFileName + " missing from file system."); | |
continue; | |
} | |
output.Write(left.FullName + " - comparing to " + right.FullName); | |
var leftHashes = serializer.Deserialize(left.FullName); | |
var rightHashes = serializer.Deserialize(right.FullName); | |
CompareHashes(leftHashes, rightHashes); | |
} | |
foreach (var right in rightHashFiles) | |
{ | |
var relativeFileName = right.FullName.Replace(directory2.FullName, directory.FullName); | |
if (!leftHashFiles.Any(f => f.FullName == relativeFileName)) output.WriteMissing(right.FullName + " - " + relativeFileName + " missing from file system."); | |
} | |
} | |
} | |
public class HashGenerator | |
{ | |
public Dictionary<string, string> GetHashes(DirectoryInfo directory, string filter) | |
{ | |
var hashes = new Dictionary<string, string>(); | |
foreach (var file in directory.GetFiles(filter).Where(f => f.Name != ".hashes")) | |
hashes.Add(file.Name, GetHash(file.FullName)); | |
return hashes; | |
} | |
public string GetHash(string filePath) | |
{ | |
using (var algorithm = new SHA1Managed()) | |
using (var file = new FileStream(filePath, FileMode.Open, FileAccess.Read)) | |
{ | |
byte[] hash = algorithm.ComputeHash(file); | |
return BitConverter.ToString(hash).Replace("-", ""); | |
} | |
} | |
} | |
public class HashDictionarySerializer | |
{ | |
public void Serialize(Dictionary<string, string> hashes, string filePath) | |
{ | |
using (var writer = File.CreateText(filePath)) | |
foreach (var item in hashes) | |
writer.WriteLine(item.Key + ":" + item.Value); | |
} | |
public Dictionary<string, string> Deserialize(string filePath) | |
{ | |
var result = new Dictionary<string, string>(); | |
if (!File.Exists(filePath)) return result; | |
using (var reader = File.OpenText(filePath)) | |
{ | |
var line = ""; | |
while ((line = reader.ReadLine()) != null) | |
{ | |
var parts = line.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); | |
if (parts.Length == 2) | |
{ | |
result.Add(parts[0], parts[1]); | |
} | |
} | |
} | |
return result; | |
} | |
} | |
public class Output | |
{ | |
public void Usage() | |
{ | |
Write(@" | |
hash - a simple hashing tool that recursively traverses a directory structure generating hash files (.hashes) for comparison. | |
hash <command> <options> | |
generate: | |
hash generate <root_directory> [filter] | |
recursively generates hash files in each directory of all files. | |
compare: | |
hash compare <root_directory> [filter] | |
recursively compares hash files to the current state of the file system. | |
diff: | |
hash diff <root_directory_1> <root_directory_2> | |
recursively compares all hash files under two root directories. | |
diff & compare: | |
search output for same, missing and different. | |
"); | |
} | |
public void Write(string message) | |
{ | |
Console.WriteLine(message); | |
} | |
public void WriteError(string message) | |
{ | |
Console.ForegroundColor = ConsoleColor.Red; | |
Console.WriteLine(message); | |
Console.ResetColor(); | |
} | |
public void WriteSame(string message) | |
{ | |
Console.ForegroundColor = ConsoleColor.Green; | |
Console.WriteLine(message); | |
Console.ResetColor(); | |
} | |
public void WriteMissing(string message) | |
{ | |
Console.ForegroundColor = ConsoleColor.Yellow; | |
Console.WriteLine(message); | |
Console.ResetColor(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment