Subversion Repositories delphiutils

Rev

Rev 9 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
9 daniel-mar 1
unit HighPerfFileComparator;
2
 
3
(*
4
 
5
  HighPerfFileComparator.pas
6
  (C) 2010 ViaThinkSoft, Daniel Marschall
7
 
8
  Last modified: January, 21th 2010
9
 
10
  THighPerfFileComparator.compare(filenameA, filenameB: string): boolean;
11
 
12
  Compares two files primary with size comparison and
13
  secundary with MD5 hash comparison. All results will be cached.
14
 
15
  Note: If you want to use the cache for every file, please do not
16
  destroy the instance of THighPerfFileComparator after done your job.
17
  Use in a field of your form class and free it when the application
18
  closes.
19
 
20
  Example of usage:
21
 
22
      var
23
        comparator: THighPerfFileComparator;
24
 
25
      procedure TForm1.FormCreate(Sender: TObject);
26
      begin
27
        comparator := THighPerfFileComparator.Create;
28
      end;
29
 
30
      procedure TForm1.Button1Click(Sender: TObject);
31
      begin
32
        // This deletes all cached file hashs, so that the result will be
33
        // new calculated. Alternatively you can create a new
34
        // THighPerfFileComparator at the beginning of every new job.
35
        comparator.clearCache;
36
 
37
        if comparator.Compare('C:\a.txt', 'C:\b.txt') then
38
          ShowMessage('Files are equal')
39
        else
40
          ShowMessage('Files are not equal');
41
      end;
42
 
43
      procedure TForm1.FormDestroy(Sender: TObject);
44
      begin
45
        comparator.Free;
46
      end;
47
 
48
  Class hierarchie:
49
 
50
      Exception
51
          EFileNotFound
52
          ENoRegisteredComparators
53
      TObject
54
          (TContainer)
55
              (TStringContainer)
56
              (TInteger64Container)
57
          TCacheManager
58
              TFilenameCacheManager
59
                  TInteger64CacheManager
60
                  TStringCacheManager
61
      TInterfacedObject
62
          TComparator
63
              TFileComparator
64
                  THashMD5Comparator
65
                      TCachedHashMD5Comparator [ICachedComparator]
66
                  TSizeComparator
67
                      TCachedSizeComparator [ICachedComparator]
68
                  TMultipleFileComparators
69
                      TCachedSizeHashMD5FileComparator [ICachedComparator]
70
                      = THighPerfFileComparator
71
 
72
*)
73
 
74
interface
75
 
76
uses
77
  SysUtils, Classes, Contnrs;
78
 
79
type
80
  ICachedComparator = interface(IInterface)
81
  // private
82
    procedure SetCacheEnabled(Value: boolean);
83
    function GetCacheEnabled: boolean;
84
  // public
85
    property CacheEnabled: boolean read getCacheEnabled write setCacheEnabled;
86
    procedure ClearCache;
87
  end;
88
 
89
  EFileNotFound = class(Exception);
90
 
91
  ENoRegisteredComparators = class(Exception);
92
 
93
  TCacheManager = class(TObject)
94
  private
95
    FCache: TStringList;
96
  public
97
    procedure SetCache(identifier: string; cacheObject: TObject);
98
    function GetCache(identifier: string): TObject;
99
    function IsCached(identifier: string): boolean;
100
    procedure Clear;
101
    constructor Create;
102
    destructor Destroy; override;
103
  end;
104
 
105
  // TFilenameCacheManager extends every filename to a unique identifier
106
  TFilenameCacheManager = class(TCacheManager)
107
  protected
108
    function FullQualifiedFilename(filename: string): string;
109
  public
110
    procedure SetCache(filename: string; cacheObject: TObject);
111
    function GetCache(filename: string): TObject;
112
    function IsCached(filename: string): boolean;
113
  end;
114
 
115
  // Wäre eigentlich ein guter Ansatz für Mehrfachvererbung...
116
  TInteger64CacheManager = class(TFilenameCacheManager)
117
  public
118
    procedure SetCache(filename: string; content: int64);
119
    function GetCache(filename: string): int64;
120
  end;
121
 
122
  TStringCacheManager = class(TFilenameCacheManager)
123
  public
124
    procedure SetCache(filename: string; content: string);
125
    function GetCache(filename: string): string;
126
  end;
127
 
128
  TComparator = class(TInterfacedObject) // abstract
129
  public
130
    function Compare(a, b: string): boolean; virtual; abstract;
131
  end;
132
 
133
  TFileComparator = class(TComparator) // abstract
134
  protected
135
    // Please call this method for both filenames at every Compare()
136
    // call of your derivates.
137
    procedure CheckFileExistence(filename: string);
138
  public
139
    // This is an abstract method since it only checks filenames and returns
140
    // always false.
141
    // function Compare(filenameA, filenameB: string): boolean; override;
142
  end;
143
 
144
  TSizeComparator = class(TFileComparator)
145
  protected
146
    function GetFileSize(filename: string): Int64; virtual;
147
  public
148
    function Compare(filenameA, filenameB: string): boolean; override;
149
  end;
150
 
151
  TCachedSizeComparator = class(TSizeComparator, ICachedComparator)
152
  private
153
    FCacheManager: TInteger64CacheManager;
154
    FCacheEnabled: boolean;
155
    procedure SetCacheEnabled(Value: boolean);
156
    function GetCacheEnabled: boolean;
157
  protected
158
    function GetFileSize(filename: string): Int64; override;
159
  public
160
    property CacheEnabled: boolean read getCacheEnabled write setCacheEnabled;
161
    procedure ClearCache;
162
    constructor Create;
163
    destructor Destroy; override;
164
  end;
165
 
166
  THashMD5Comparator = class(TFileComparator)
167
  protected
168
    function GetFileHashMD5(filename: string): String; virtual;
169
  public
170
    function Compare(filenameA, filenameB: string): boolean; override;
171
  end;
172
 
173
  TCachedHashMD5Comparator = class(THashMD5Comparator, ICachedComparator)
174
  private
175
    FCacheManager: TStringCacheManager;
176
    FCacheEnabled: boolean;
177
    procedure SetCacheEnabled(Value: boolean);
178
    function GetCacheEnabled: boolean;
179
  protected
180
    function GetFileHashMD5(filename: string): String; override;
181
  public
182
    property CacheEnabled: boolean read getCacheEnabled write setCacheEnabled;
183
    procedure ClearCache;
184
    constructor Create;
185
    destructor Destroy; override;
186
  end;
187
 
188
  TMultipleFileComparators = class(TFileComparator) // abstract
189
  // This is an abstract class since no comparators are registered and so
190
  // compare() will throw an ENoRegisteredComparators exception.
191
  protected
192
    // WARNING: DOES *NOT* OWNS ITS OBJECTS. PLEASE FREE THEM ON DESTROY.
193
    FRegisteredComparators: TObjectList; // of TFileComparator
194
    procedure RegisterComparator(comparator: TFileComparator);
195
  public
196
    function Compare(filenameA, filenameB: string): boolean; override;
197
    constructor Create;
198
    destructor Destroy; override;
199
  end;
200
 
201
  TCachedSizeHashMD5FileComparator = class(TMultipleFileComparators,
202
    ICachedComparator)
203
  private
204
    FHashComparator: TCachedHashMD5Comparator;
205
    FSizeComparator: TCachedSizeComparator;
206
    procedure SetCacheEnabled(Value: boolean);
207
    function GetCacheEnabled: boolean;
208
  public
209
    property CacheEnabled: boolean read getCacheEnabled write setCacheEnabled;
210
    procedure ClearCache;
211
    constructor Create;
212
    destructor Destroy; override;
213
  end;
214
 
215
  THighPerfFileComparator = TCachedSizeHashMD5FileComparator;
216
 
217
implementation
218
 
219
// Please download MD5.pas from
220
// http://www.koders.com/delphi/fid1C4B47A76F8C7172FDCFE7B3A74863D6FB7FC2BA.aspx
221
 
222
uses
223
  MD5;
224
 
225
resourcestring
226
  LNG_E_NO_REGISTERED_COMPARATORS = 'No comparators registered. Please use ' +
227
    'a derivate of the class TMultipleFileComparators which does register ' +
228
    'comparators.';
229
  LNG_E_FILE_NOT_FOUND = 'The file "%s" was not found.';
230
 
231
type
232
  TContainer = class(TObject);
233
 
234
  TStringContainer = class(TContainer)
235
  public
236
    Content: string;
237
    constructor Create(AContent: string);
238
  end;
239
 
240
  TInteger64Container = class(TContainer)
241
  public
242
    Content: int64;
243
    constructor Create(AContent: int64);
244
  end;
245
 
246
{ Functions }
247
 
248
function _MD5File(filename: string): string;
249
begin
250
  result := MD5Print(MD5File(filename));
251
end;
252
 
253
{ TStringContainer }
254
 
255
constructor TStringContainer.Create(AContent: string);
256
begin
257
  inherited Create;
258
 
259
  content := AContent;
260
end;
261
 
262
{ TInteger64Container }
263
 
264
constructor TInteger64Container.Create(AContent: int64);
265
begin
266
  inherited Create;
267
 
268
  content := AContent;
269
end;
270
 
271
{ TCacheManager }
272
 
273
procedure TCacheManager.SetCache(identifier: string; cacheObject: TObject);
274
begin
275
  FCache.AddObject(identifier, cacheObject);
276
end;
277
 
278
function TCacheManager.GetCache(identifier: string): TObject;
279
begin
280
  if isCached(identifier) then
281
    result := FCache.Objects[FCache.IndexOf(identifier)] as TContainer
282
  else
283
    result := nil;
284
end;
285
 
286
function TCacheManager.IsCached(identifier: string): boolean;
287
begin
288
  result := FCache.IndexOf(identifier) <> -1;
289
end;
290
 
291
procedure TCacheManager.Clear;
292
begin
293
  FCache.Clear;
294
end;
295
 
296
constructor TCacheManager.Create;
297
begin
298
  inherited Create;
299
 
300
  FCache := TStringList.Create;
301
end;
302
 
303
destructor TCacheManager.Destroy;
304
begin
305
  FCache.Free;
306
 
307
  inherited Destroy;
308
end;
309
 
310
{ TFilenameCacheManager }
311
 
312
function TFilenameCacheManager.FullQualifiedFilename(filename: string): string;
313
begin
314
  result := ExpandUNCFileName(filename);
315
end;
316
 
317
procedure TFilenameCacheManager.SetCache(filename: string;
318
  cacheObject: TObject);
319
begin
320
  inherited setCache(FullQualifiedFilename(filename), cacheObject);
321
end;
322
 
323
function TFilenameCacheManager.GetCache(filename: string): TObject;
324
begin
325
  result := inherited getCache(FullQualifiedFilename(filename));
326
end;
327
 
328
function TFilenameCacheManager.IsCached(filename: string): boolean;
329
begin
330
  result := inherited isCached(FullQualifiedFilename(filename));
331
end;
332
 
333
{ TInteger64CacheManager }
334
 
335
procedure TInteger64CacheManager.SetCache(filename: string; content: int64);
336
begin
337
  inherited setCache(filename, TInteger64Container.Create(content));
338
end;
339
 
340
function TInteger64CacheManager.GetCache(filename: string): int64;
341
begin
342
  result := (inherited getCache(filename) as TInteger64Container).content;
343
end;
344
 
345
{ TStringCacheManager }
346
 
347
procedure TStringCacheManager.SetCache(filename: string; content: string);
348
begin
349
  inherited setCache(filename, TStringContainer.Create(content));
350
end;
351
 
352
function TStringCacheManager.GetCache(filename: string): string;
353
begin
354
  result := (inherited getCache(filename) as TStringContainer).content;
355
end;
356
 
357
{ TFileComparator }
358
 
359
procedure TFileComparator.CheckFileExistence(filename: string);
360
begin
361
  if not fileExists(filename) then
362
    raise EFileNotFound.CreateFmt(LNG_E_FILE_NOT_FOUND, [filename]);
363
end;
364
 
365
(* function TFileComparator.Compare(filenameA, filenameB: string): boolean;
366
begin
367
  if not fileExists(filenameA) then
368
    raise EFileNotFound.CreateFmt(LNG_E_FILE_NOT_FOUND, [filenameA]);
369
 
370
  if not fileExists(filenameB) then
371
    raise EFileNotFound.CreateFmt(LNG_E_FILE_NOT_FOUND, [filenameB]);
372
 
373
  // Leider keine Überprüfung, ob Methode überschrieben wurde
374
  // (da sonst result immer false ist!)
375
  if Self.ClassType = TFileComparator then
376
    raise EDirectCall.CreateFmt(LNG_E_DIRECT_CALL, [Self.ClassName]);
377
 
378
  result := false;
379
end; *)
380
 
381
{ TSizeComparator }
382
 
383
function TSizeComparator.GetFileSize(filename: string): Int64;
384
var
385
  f: TFileStream;
386
begin
387
  f := TFileStream.Create(filename, fmOpenRead);
388
  try
389
    result := f.Size
390
  finally
391
    f.Free;
392
  end;
393
end;
394
 
395
function TSizeComparator.Compare(filenameA, filenameB: string): boolean;
396
begin
397
  //inherited compare(filenameA, filenameB);
398
  CheckFileExistence(filenameA);
399
  CheckFileExistence(filenameB);
400
 
401
  result := getFileSize(filenameA) = getFileSize(filenameB);
402
end;
403
 
404
{ TCachedSizeComparator }
405
 
406
procedure TCachedSizeComparator.SetCacheEnabled(Value: boolean);
407
begin
408
  if FCacheEnabled <> Value then
409
    FCacheEnabled := Value;
410
end;
411
 
412
function TCachedSizeComparator.GetCacheEnabled: boolean;
413
begin
414
  result := FCacheEnabled;
415
end;
416
 
417
function TCachedSizeComparator.GetFileSize(filename: string): Int64;
418
begin
419
  if FCacheEnabled then
420
  begin
421
    if FCacheManager.isCached(filename) then
422
    begin
423
      result := FCacheManager.getCache(filename);
424
    end
425
    else
426
    begin
427
      result := inherited getFileSize(filename);
428
      FCacheManager.setCache(filename, result);
429
    end;
430
  end
431
  else
432
    result := inherited getFileSize(filename);
433
end;
434
 
435
procedure TCachedSizeComparator.ClearCache;
436
begin
437
  FCacheManager.clear;
438
end;
439
 
440
constructor TCachedSizeComparator.Create;
441
begin
442
  inherited Create;
443
 
444
  FCacheManager := TInteger64CacheManager.Create;
445
  FCacheEnabled := true;
446
end;
447
 
448
destructor TCachedSizeComparator.Destroy;
449
begin
450
  FCacheManager.Free;
451
 
452
  inherited Destroy;
453
end;
454
 
455
{ THashMD5Comparator }
456
 
457
function THashMD5Comparator.GetFileHashMD5(filename: string): String;
458
begin
459
  result := _MD5File(filename);
460
end;
461
 
462
function THashMD5Comparator.Compare(filenameA, filenameB: string): boolean;
463
begin
464
  //inherited Compare(filenameA, filenameB);
465
  CheckFileExistence(filenameA);
466
  CheckFileExistence(filenameB);
467
 
468
  result := GetFileHashMD5(filenameA) = GetFileHashMD5(filenameB);
469
end;
470
 
471
{ TCachedHashMD5Comparator }
472
 
473
procedure TCachedHashMD5Comparator.SetCacheEnabled(Value: boolean);
474
begin
475
  if FCacheEnabled <> Value then
476
    FCacheEnabled := Value;
477
end;
478
 
479
function TCachedHashMD5Comparator.GetCacheEnabled: boolean;
480
begin
481
  result := FCacheEnabled;
482
end;
483
 
484
function TCachedHashMD5Comparator.GetFileHashMD5(filename: string): String;
485
begin
486
  if FCacheEnabled then
487
  begin
488
    if FCacheManager.IsCached(filename) then
489
    begin
490
      result := FCacheManager.GetCache(filename);
491
    end
492
    else
493
    begin
494
      result := inherited GetFileHashMD5(filename);
495
      FCacheManager.SetCache(filename, result);
496
    end;
497
  end
498
  else
499
    result := inherited GetFileHashMD5(filename);
500
end;
501
 
502
procedure TCachedHashMD5Comparator.ClearCache;
503
begin
504
  FCacheManager.Clear;
505
end;
506
 
507
constructor TCachedHashMD5Comparator.Create;
508
begin
509
  inherited Create;
510
 
511
  FCacheManager := TStringCacheManager.Create;
512
  FCacheEnabled := true;
513
end;
514
 
515
destructor TCachedHashMD5Comparator.Destroy;
516
begin
517
  FCacheManager.Free;
518
 
519
  inherited Destroy;
520
end;
521
 
522
{ TMultipleFileComparators }
523
 
524
procedure TMultipleFileComparators.RegisterComparator(comparator: TFileComparator);
525
begin
526
  FRegisteredComparators.Add(comparator)
527
end;
528
 
529
function TMultipleFileComparators.Compare(filenameA,
530
  filenameB: string): boolean;
531
var
532
  i: integer;
533
begin
534
  //inherited Compare(filenameA, filenameB);
535
  CheckFileExistence(filenameA);
536
  CheckFileExistence(filenameB);
537
 
538
  if FRegisteredComparators.Count = 0 then
539
    raise ENoRegisteredComparators.Create(LNG_E_NO_REGISTERED_COMPARATORS);
540
 
541
  for i := 0 to FRegisteredComparators.Count - 1 do
542
  begin
543
    if not (FRegisteredComparators.Items[i] as TFileComparator).
544
      Compare(filenameA, filenameB) then
545
    begin
546
      result := false;
547
      exit;
548
    end;
549
  end;
550
  result := true;
551
end;
552
 
553
constructor TMultipleFileComparators.Create;
554
begin
555
  inherited Create;
556
 
557
  FRegisteredComparators := TObjectList.Create(false);
558
end;
559
 
560
destructor TMultipleFileComparators.Destroy;
561
begin
562
  FRegisteredComparators.Free;
563
 
564
  inherited Destroy;
565
end;
566
 
567
{ TCachedSizeHashMD5FileComparator }
568
 
569
procedure TCachedSizeHashMD5FileComparator.SetCacheEnabled(Value: boolean);
570
begin
571
  FSizeComparator.SetCacheEnabled(Value);
572
  FHashComparator.SetCacheEnabled(Value);
573
end;
574
 
575
function TCachedSizeHashMD5FileComparator.getCacheEnabled: boolean;
576
begin
577
  result := FSizeComparator.GetCacheEnabled and FHashComparator.GetCacheEnabled;
578
end;
579
 
580
procedure TCachedSizeHashMD5FileComparator.ClearCache;
581
begin
582
  FSizeComparator.ClearCache;
583
  FHashComparator.ClearCache;
584
end;
585
 
586
constructor TCachedSizeHashMD5FileComparator.Create;
587
begin
588
  inherited Create;
589
 
590
  FSizeComparator := TCachedSizeComparator.Create;
591
  RegisterComparator(FSizeComparator);
592
 
593
  FHashComparator := TCachedHashMD5Comparator.Create;
594
  RegisterComparator(FHashComparator);
595
end;
596
 
597
destructor TCachedSizeHashMD5FileComparator.Destroy;
598
begin
599
  FHashComparator.Free;
600
  FSizeComparator.Free;
601
 
602
  inherited Destroy;
603
end;
604
 
605
end.