Subversion Repositories delphiutils

Compare Revisions

Regard whitespace Rev 11 → Rev 12

/trunk/Units/HighPerfFileComparator.pas
0,0 → 1,605
unit HighPerfFileComparator;
 
(*
 
HighPerfFileComparator.pas
(C) 2010 ViaThinkSoft, Daniel Marschall
 
Last modified: January, 21th 2010
 
THighPerfFileComparator.compare(filenameA, filenameB: string): boolean;
 
Compares two files primary with size comparison and
secundary with MD5 hash comparison. All results will be cached.
 
Note: If you want to use the cache for every file, please do not
destroy the instance of THighPerfFileComparator after done your job.
Use in a field of your form class and free it when the application
closes.
 
Example of usage:
 
var
comparator: THighPerfFileComparator;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
comparator := THighPerfFileComparator.Create;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
// This deletes all cached file hashs, so that the result will be
// new calculated. Alternatively you can create a new
// THighPerfFileComparator at the beginning of every new job.
comparator.clearCache;
 
if comparator.Compare('C:\a.txt', 'C:\b.txt') then
ShowMessage('Files are equal')
else
ShowMessage('Files are not equal');
end;
 
procedure TForm1.FormDestroy(Sender: TObject);
begin
comparator.Free;
end;
 
Class hierarchie:
 
Exception
EFileNotFound
ENoRegisteredComparators
TObject
(TContainer)
(TStringContainer)
(TInteger64Container)
TCacheManager
TFilenameCacheManager
TInteger64CacheManager
TStringCacheManager
TInterfacedObject
TComparator
TFileComparator
THashMD5Comparator
TCachedHashMD5Comparator [ICachedComparator]
TSizeComparator
TCachedSizeComparator [ICachedComparator]
TMultipleFileComparators
TCachedSizeHashMD5FileComparator [ICachedComparator]
= THighPerfFileComparator
 
*)
 
interface
 
uses
SysUtils, Classes, Contnrs;
 
type
ICachedComparator = interface(IInterface)
// private
procedure SetCacheEnabled(Value: boolean);
function GetCacheEnabled: boolean;
// public
property CacheEnabled: boolean read getCacheEnabled write setCacheEnabled;
procedure ClearCache;
end;
 
EFileNotFound = class(Exception);
 
ENoRegisteredComparators = class(Exception);
 
TCacheManager = class(TObject)
private
FCache: TStringList;
public
procedure SetCache(identifier: string; cacheObject: TObject);
function GetCache(identifier: string): TObject;
function IsCached(identifier: string): boolean;
procedure Clear;
constructor Create;
destructor Destroy; override;
end;
 
// TFilenameCacheManager extends every filename to a unique identifier
TFilenameCacheManager = class(TCacheManager)
protected
function FullQualifiedFilename(filename: string): string;
public
procedure SetCache(filename: string; cacheObject: TObject);
function GetCache(filename: string): TObject;
function IsCached(filename: string): boolean;
end;
 
// Wäre eigentlich ein guter Ansatz für Mehrfachvererbung...
TInteger64CacheManager = class(TFilenameCacheManager)
public
procedure SetCache(filename: string; content: int64);
function GetCache(filename: string): int64;
end;
 
TStringCacheManager = class(TFilenameCacheManager)
public
procedure SetCache(filename: string; content: string);
function GetCache(filename: string): string;
end;
 
TComparator = class(TInterfacedObject) // abstract
public
function Compare(a, b: string): boolean; virtual; abstract;
end;
 
TFileComparator = class(TComparator) // abstract
protected
// Please call this method for both filenames at every Compare()
// call of your derivates.
procedure CheckFileExistence(filename: string);
public
// This is an abstract method since it only checks filenames and returns
// always false.
// function Compare(filenameA, filenameB: string): boolean; override;
end;
 
TSizeComparator = class(TFileComparator)
protected
function GetFileSize(filename: string): Int64; virtual;
public
function Compare(filenameA, filenameB: string): boolean; override;
end;
 
TCachedSizeComparator = class(TSizeComparator, ICachedComparator)
private
FCacheManager: TInteger64CacheManager;
FCacheEnabled: boolean;
procedure SetCacheEnabled(Value: boolean);
function GetCacheEnabled: boolean;
protected
function GetFileSize(filename: string): Int64; override;
public
property CacheEnabled: boolean read getCacheEnabled write setCacheEnabled;
procedure ClearCache;
constructor Create;
destructor Destroy; override;
end;
 
THashMD5Comparator = class(TFileComparator)
protected
function GetFileHashMD5(filename: string): String; virtual;
public
function Compare(filenameA, filenameB: string): boolean; override;
end;
 
TCachedHashMD5Comparator = class(THashMD5Comparator, ICachedComparator)
private
FCacheManager: TStringCacheManager;
FCacheEnabled: boolean;
procedure SetCacheEnabled(Value: boolean);
function GetCacheEnabled: boolean;
protected
function GetFileHashMD5(filename: string): String; override;
public
property CacheEnabled: boolean read getCacheEnabled write setCacheEnabled;
procedure ClearCache;
constructor Create;
destructor Destroy; override;
end;
 
TMultipleFileComparators = class(TFileComparator) // abstract
// This is an abstract class since no comparators are registered and so
// compare() will throw an ENoRegisteredComparators exception.
protected
// WARNING: DOES *NOT* OWNS ITS OBJECTS. PLEASE FREE THEM ON DESTROY.
FRegisteredComparators: TObjectList; // of TFileComparator
procedure RegisterComparator(comparator: TFileComparator);
public
function Compare(filenameA, filenameB: string): boolean; override;
constructor Create;
destructor Destroy; override;
end;
 
TCachedSizeHashMD5FileComparator = class(TMultipleFileComparators,
ICachedComparator)
private
FHashComparator: TCachedHashMD5Comparator;
FSizeComparator: TCachedSizeComparator;
procedure SetCacheEnabled(Value: boolean);
function GetCacheEnabled: boolean;
public
property CacheEnabled: boolean read getCacheEnabled write setCacheEnabled;
procedure ClearCache;
constructor Create;
destructor Destroy; override;
end;
 
THighPerfFileComparator = TCachedSizeHashMD5FileComparator;
 
implementation
 
// Please download MD5.pas from
// http://www.koders.com/delphi/fid1C4B47A76F8C7172FDCFE7B3A74863D6FB7FC2BA.aspx
 
uses
MD5;
 
resourcestring
LNG_E_NO_REGISTERED_COMPARATORS = 'No comparators registered. Please use ' +
'a derivate of the class TMultipleFileComparators which does register ' +
'comparators.';
LNG_E_FILE_NOT_FOUND = 'The file "%s" was not found.';
 
type
TContainer = class(TObject);
 
TStringContainer = class(TContainer)
public
Content: string;
constructor Create(AContent: string);
end;
 
TInteger64Container = class(TContainer)
public
Content: int64;
constructor Create(AContent: int64);
end;
 
{ Functions }
 
function _MD5File(filename: string): string;
begin
result := MD5Print(MD5File(filename));
end;
 
{ TStringContainer }
 
constructor TStringContainer.Create(AContent: string);
begin
inherited Create;
 
content := AContent;
end;
 
{ TInteger64Container }
 
constructor TInteger64Container.Create(AContent: int64);
begin
inherited Create;
 
content := AContent;
end;
 
{ TCacheManager }
 
procedure TCacheManager.SetCache(identifier: string; cacheObject: TObject);
begin
FCache.AddObject(identifier, cacheObject);
end;
 
function TCacheManager.GetCache(identifier: string): TObject;
begin
if isCached(identifier) then
result := FCache.Objects[FCache.IndexOf(identifier)] as TContainer
else
result := nil;
end;
 
function TCacheManager.IsCached(identifier: string): boolean;
begin
result := FCache.IndexOf(identifier) <> -1;
end;
 
procedure TCacheManager.Clear;
begin
FCache.Clear;
end;
 
constructor TCacheManager.Create;
begin
inherited Create;
 
FCache := TStringList.Create;
end;
 
destructor TCacheManager.Destroy;
begin
FCache.Free;
 
inherited Destroy;
end;
 
{ TFilenameCacheManager }
 
function TFilenameCacheManager.FullQualifiedFilename(filename: string): string;
begin
result := ExpandUNCFileName(filename);
end;
 
procedure TFilenameCacheManager.SetCache(filename: string;
cacheObject: TObject);
begin
inherited setCache(FullQualifiedFilename(filename), cacheObject);
end;
 
function TFilenameCacheManager.GetCache(filename: string): TObject;
begin
result := inherited getCache(FullQualifiedFilename(filename));
end;
 
function TFilenameCacheManager.IsCached(filename: string): boolean;
begin
result := inherited isCached(FullQualifiedFilename(filename));
end;
 
{ TInteger64CacheManager }
 
procedure TInteger64CacheManager.SetCache(filename: string; content: int64);
begin
inherited setCache(filename, TInteger64Container.Create(content));
end;
 
function TInteger64CacheManager.GetCache(filename: string): int64;
begin
result := (inherited getCache(filename) as TInteger64Container).content;
end;
 
{ TStringCacheManager }
 
procedure TStringCacheManager.SetCache(filename: string; content: string);
begin
inherited setCache(filename, TStringContainer.Create(content));
end;
 
function TStringCacheManager.GetCache(filename: string): string;
begin
result := (inherited getCache(filename) as TStringContainer).content;
end;
 
{ TFileComparator }
 
procedure TFileComparator.CheckFileExistence(filename: string);
begin
if not fileExists(filename) then
raise EFileNotFound.CreateFmt(LNG_E_FILE_NOT_FOUND, [filename]);
end;
 
(* function TFileComparator.Compare(filenameA, filenameB: string): boolean;
begin
if not fileExists(filenameA) then
raise EFileNotFound.CreateFmt(LNG_E_FILE_NOT_FOUND, [filenameA]);
 
if not fileExists(filenameB) then
raise EFileNotFound.CreateFmt(LNG_E_FILE_NOT_FOUND, [filenameB]);
 
// Leider keine Überprüfung, ob Methode überschrieben wurde
// (da sonst result immer false ist!)
if Self.ClassType = TFileComparator then
raise EDirectCall.CreateFmt(LNG_E_DIRECT_CALL, [Self.ClassName]);
 
result := false;
end; *)
 
{ TSizeComparator }
 
function TSizeComparator.GetFileSize(filename: string): Int64;
var
f: TFileStream;
begin
f := TFileStream.Create(filename, fmOpenRead);
try
result := f.Size
finally
f.Free;
end;
end;
 
function TSizeComparator.Compare(filenameA, filenameB: string): boolean;
begin
//inherited compare(filenameA, filenameB);
CheckFileExistence(filenameA);
CheckFileExistence(filenameB);
 
result := getFileSize(filenameA) = getFileSize(filenameB);
end;
 
{ TCachedSizeComparator }
 
procedure TCachedSizeComparator.SetCacheEnabled(Value: boolean);
begin
if FCacheEnabled <> Value then
FCacheEnabled := Value;
end;
 
function TCachedSizeComparator.GetCacheEnabled: boolean;
begin
result := FCacheEnabled;
end;
 
function TCachedSizeComparator.GetFileSize(filename: string): Int64;
begin
if FCacheEnabled then
begin
if FCacheManager.isCached(filename) then
begin
result := FCacheManager.getCache(filename);
end
else
begin
result := inherited getFileSize(filename);
FCacheManager.setCache(filename, result);
end;
end
else
result := inherited getFileSize(filename);
end;
 
procedure TCachedSizeComparator.ClearCache;
begin
FCacheManager.clear;
end;
 
constructor TCachedSizeComparator.Create;
begin
inherited Create;
 
FCacheManager := TInteger64CacheManager.Create;
FCacheEnabled := true;
end;
 
destructor TCachedSizeComparator.Destroy;
begin
FCacheManager.Free;
 
inherited Destroy;
end;
 
{ THashMD5Comparator }
 
function THashMD5Comparator.GetFileHashMD5(filename: string): String;
begin
result := _MD5File(filename);
end;
 
function THashMD5Comparator.Compare(filenameA, filenameB: string): boolean;
begin
//inherited Compare(filenameA, filenameB);
CheckFileExistence(filenameA);
CheckFileExistence(filenameB);
 
result := GetFileHashMD5(filenameA) = GetFileHashMD5(filenameB);
end;
 
{ TCachedHashMD5Comparator }
 
procedure TCachedHashMD5Comparator.SetCacheEnabled(Value: boolean);
begin
if FCacheEnabled <> Value then
FCacheEnabled := Value;
end;
 
function TCachedHashMD5Comparator.GetCacheEnabled: boolean;
begin
result := FCacheEnabled;
end;
 
function TCachedHashMD5Comparator.GetFileHashMD5(filename: string): String;
begin
if FCacheEnabled then
begin
if FCacheManager.IsCached(filename) then
begin
result := FCacheManager.GetCache(filename);
end
else
begin
result := inherited GetFileHashMD5(filename);
FCacheManager.SetCache(filename, result);
end;
end
else
result := inherited GetFileHashMD5(filename);
end;
 
procedure TCachedHashMD5Comparator.ClearCache;
begin
FCacheManager.Clear;
end;
 
constructor TCachedHashMD5Comparator.Create;
begin
inherited Create;
 
FCacheManager := TStringCacheManager.Create;
FCacheEnabled := true;
end;
 
destructor TCachedHashMD5Comparator.Destroy;
begin
FCacheManager.Free;
 
inherited Destroy;
end;
 
{ TMultipleFileComparators }
 
procedure TMultipleFileComparators.RegisterComparator(comparator: TFileComparator);
begin
FRegisteredComparators.Add(comparator)
end;
 
function TMultipleFileComparators.Compare(filenameA,
filenameB: string): boolean;
var
i: integer;
begin
//inherited Compare(filenameA, filenameB);
CheckFileExistence(filenameA);
CheckFileExistence(filenameB);
 
if FRegisteredComparators.Count = 0 then
raise ENoRegisteredComparators.Create(LNG_E_NO_REGISTERED_COMPARATORS);
 
for i := 0 to FRegisteredComparators.Count - 1 do
begin
if not (FRegisteredComparators.Items[i] as TFileComparator).
Compare(filenameA, filenameB) then
begin
result := false;
exit;
end;
end;
result := true;
end;
 
constructor TMultipleFileComparators.Create;
begin
inherited Create;
 
FRegisteredComparators := TObjectList.Create(false);
end;
 
destructor TMultipleFileComparators.Destroy;
begin
FRegisteredComparators.Free;
 
inherited Destroy;
end;
 
{ TCachedSizeHashMD5FileComparator }
 
procedure TCachedSizeHashMD5FileComparator.SetCacheEnabled(Value: boolean);
begin
FSizeComparator.SetCacheEnabled(Value);
FHashComparator.SetCacheEnabled(Value);
end;
 
function TCachedSizeHashMD5FileComparator.getCacheEnabled: boolean;
begin
result := FSizeComparator.GetCacheEnabled and FHashComparator.GetCacheEnabled;
end;
 
procedure TCachedSizeHashMD5FileComparator.ClearCache;
begin
FSizeComparator.ClearCache;
FHashComparator.ClearCache;
end;
 
constructor TCachedSizeHashMD5FileComparator.Create;
begin
inherited Create;
 
FSizeComparator := TCachedSizeComparator.Create;
RegisterComparator(FSizeComparator);
 
FHashComparator := TCachedHashMD5Comparator.Create;
RegisterComparator(FHashComparator);
end;
 
destructor TCachedSizeHashMD5FileComparator.Destroy;
begin
FHashComparator.Free;
FSizeComparator.Free;
 
inherited Destroy;
end;
 
end.