8
Classes, SysUtils, LazDbgLog, AVL_Tree, LazFileUtils;
11
TFileStateCacheItemFlag = (
12
fsciExists, // file or directory exists
13
fsciDirectory, // file exists and is directory
14
fsciReadable, // file is readable
15
fsciWritable, // file is writable
16
fsciDirectoryReadable, // file is directory and can be searched
17
fsciDirectoryWritable, // file is directory and new files can be created
18
fsciText, // file is text file (not binary)
19
fsciExecutable,// file is executable
20
fsciAge // file age is valid
22
TFileStateCacheItemFlags = set of TFileStateCacheItemFlag;
24
{ TFileStateCacheItem }
26
TFileStateCacheItem = class
30
FFlags: TFileStateCacheItemFlags;
31
FTestedFlags: TFileStateCacheItemFlags;
34
constructor Create(const TheFilename: string; NewTimeStamp: int64);
35
function CalcMemSize: PtrUint;
37
property Filename: string read FFilename;
38
property Flags: TFileStateCacheItemFlags read FFlags;
39
property TestedFlags: TFileStateCacheItemFlags read FTestedFlags;
40
property TimeStamp: int64 read FTimeStamp;
41
property Age: longint read FAge;
44
TOnChangeFileStateTimeStamp = procedure(Sender: TObject;
45
const AFilename: string) of object;
49
TFileStateCache = class
51
FFiles: TAVLTree; // tree of TFileStateCacheItem
54
FChangeTimeStampHandler: array of TOnChangeFileStateTimeStamp;
55
procedure SetFlag(AFile: TFileStateCacheItem;
56
AFlag: TFileStateCacheItemFlag; NewValue: boolean);
59
destructor Destroy; override;
62
function Locked: boolean;
63
procedure IncreaseTimeStamp(const AFilename: string);
64
function FileExistsCached(const AFilename: string): boolean;
65
function DirPathExistsCached(const AFilename: string): boolean;
66
function DirectoryIsWritableCached(const DirectoryName: string): boolean;
67
function FileIsExecutableCached(const AFilename: string): boolean;
68
function FileIsReadableCached(const AFilename: string): boolean;
69
function FileIsWritableCached(const AFilename: string): boolean;
70
function FileIsTextCached(const AFilename: string): boolean;
71
function FileAgeCached(const AFileName: string): Longint;
72
function FindFile(const Filename: string;
73
CreateIfNotExists: boolean): TFileStateCacheItem;
74
function Check(const Filename: string; AFlag: TFileStateCacheItemFlag;
75
out AFile: TFileStateCacheItem; var FlagIsSet: boolean): boolean;
76
procedure AddChangeTimeStampHandler(const Handler: TOnChangeFileStateTimeStamp);
77
procedure RemoveChangeTimeStampHandler(const Handler: TOnChangeFileStateTimeStamp);
78
function CalcMemSize: PtrUint;
80
property TimeStamp: int64 read FTimeStamp;
84
FileStateCache: TFileStateCache = nil;
86
function FileExistsCached(const AFilename: string): boolean;
87
function DirPathExistsCached(const AFilename: string): boolean;
88
function DirectoryIsWritableCached(const ADirectoryName: string): boolean;
89
function FileIsExecutableCached(const AFilename: string): boolean;
90
function FileIsReadableCached(const AFilename: string): boolean;
91
function FileIsWritableCached(const AFilename: string): boolean;
92
function FileIsTextCached(const AFilename: string): boolean;
93
function FileAgeCached(const AFileName: string): Longint;
95
procedure InvalidateFileStateCache(const Filename: string = ''); inline;
96
function CompareFileStateItems(Data1, Data2: Pointer): integer;
97
function CompareFilenameWithFileStateCacheItem(Key, Data: Pointer): integer;
100
LUInvalidChangeStamp = Low(integer);
101
LUInvalidChangeStamp64 = Low(int64); // using a value outside integer to spot wrong types early
102
procedure LUIncreaseChangeStamp(var ChangeStamp: integer); inline;
103
procedure LUIncreaseChangeStamp64(var ChangeStamp: int64); inline;
106
TOnFileExistsCached = function(Filename: string): boolean of object;
107
TOnFileAgeCached = function(Filename: string): longint of object;
109
OnFileExistsCached: TOnFileExistsCached = nil;
110
OnFileAgeCached: TOnFileAgeCached = nil;
115
function FileExistsCached(const AFilename: string): boolean;
117
if OnFileExistsCached<>nil then
118
Result:=OnFileExistsCached(AFilename)
119
else if FileStateCache<>nil then
120
Result:=FileStateCache.FileExistsCached(AFilename)
122
Result:=FileExistsUTF8(AFilename);
125
function DirPathExistsCached(const AFilename: string): boolean;
127
if FileStateCache<>nil then
128
Result:=FileStateCache.DirPathExistsCached(AFilename)
130
Result:=DirPathExists(AFilename);
133
function DirectoryIsWritableCached(const ADirectoryName: string): boolean;
135
if FileStateCache<>nil then
136
Result:=FileStateCache.DirectoryIsWritableCached(ADirectoryName)
138
Result:=DirectoryIsWritable(ADirectoryName);
141
function FileIsExecutableCached(const AFilename: string): boolean;
143
if FileStateCache<>nil then
144
Result:=FileStateCache.FileIsExecutableCached(AFilename)
146
Result:=FileIsExecutable(AFilename);
149
function FileIsReadableCached(const AFilename: string): boolean;
151
if FileStateCache<>nil then
152
Result:=FileStateCache.FileIsReadableCached(AFilename)
154
Result:=FileIsReadable(AFilename);
157
function FileIsWritableCached(const AFilename: string): boolean;
159
if FileStateCache<>nil then
160
Result:=FileStateCache.FileIsWritableCached(AFilename)
162
Result:=FileIsWritable(AFilename);
165
function FileIsTextCached(const AFilename: string): boolean;
167
if FileStateCache<>nil then
168
Result:=FileStateCache.FileIsTextCached(AFilename)
170
Result:=FileIsText(AFilename);
173
function FileAgeCached(const AFileName: string): Longint;
175
if OnFileAgeCached<>nil then
176
Result:=OnFileAgeCached(AFilename)
177
else if FileStateCache<>nil then
178
Result:=FileStateCache.FileAgeCached(AFilename)
180
Result:=FileAgeUTF8(AFileName);
183
procedure InvalidateFileStateCache(const Filename: string);
185
FileStateCache.IncreaseTimeStamp(Filename);
188
function CompareFileStateItems(Data1, Data2: Pointer): integer;
190
Result:=CompareFilenames(TFileStateCacheItem(Data1).FFilename,
191
TFileStateCacheItem(Data2).FFilename);
194
function CompareFilenameWithFileStateCacheItem(Key, Data: Pointer): integer;
196
Result:=CompareFilenames(AnsiString(Key),TFileStateCacheItem(Data).FFilename);
197
//debugln('CompareFilenameWithFileStateCacheItem Key=',AnsiString(Key),' Data=',TFileStateCacheItem(Data).FFilename,' Result=',dbgs(Result));
200
procedure LUIncreaseChangeStamp(var ChangeStamp: integer);
202
if ChangeStamp<High(ChangeStamp) then
205
ChangeStamp:=LUInvalidChangeStamp+1;
208
procedure LUIncreaseChangeStamp64(var ChangeStamp: int64);
210
if ChangeStamp<High(ChangeStamp) then
213
ChangeStamp:=LUInvalidChangeStamp64+1;
216
{ TFileStateCacheItem }
218
constructor TFileStateCacheItem.Create(const TheFilename: string;
219
NewTimeStamp: int64);
221
FFilename:=TheFilename;
222
FTimeStamp:=NewTimeStamp;
225
function TFileStateCacheItem.CalcMemSize: PtrUint;
227
Result:=PtrUInt(InstanceSize)
228
+MemSizeString(FFilename);
233
procedure TFileStateCache.SetFlag(AFile: TFileStateCacheItem;
234
AFlag: TFileStateCacheItemFlag; NewValue: boolean);
236
if AFile.FTimeStamp<>FTimeStamp then begin
237
AFile.FTestedFlags:=[];
238
AFile.FTimeStamp:=FTimeStamp;
240
Include(AFile.FTestedFlags,AFlag);
242
Include(AFile.FFlags,AFlag)
244
Exclude(AFile.FFlags,AFlag);
245
//WriteStr(s, AFlag);
246
//debugln('TFileStateCache.SetFlag AFile.Filename=',AFile.Filename,' ',s,'=',dbgs(AFlag in AFile.FFlags),' Valid=',dbgs(AFlag in AFile.FTestedFlags));
249
constructor TFileStateCache.Create;
251
FFiles:=TAVLTree.Create(@CompareFileStateItems);
252
LUIncreaseChangeStamp64(FTimeStamp); // one higher than default for new files
255
destructor TFileStateCache.Destroy;
259
SetLength(FChangeTimeStampHandler,0);
263
procedure TFileStateCache.Lock;
268
procedure TFileStateCache.Unlock;
270
procedure RaiseTooManyUnlocks;
272
raise Exception.Create('TFileStateCache.Unlock');
276
if FLockCount<=0 then RaiseTooManyUnlocks;
280
function TFileStateCache.Locked: boolean;
282
Result:=FLockCount>0;
285
procedure TFileStateCache.IncreaseTimeStamp(const AFilename: string);
288
AFile: TFileStateCacheItem;
290
if Self=nil then exit;
291
if AFilename='' then begin
293
LUIncreaseChangeStamp64(FTimeStamp);
295
// invalidate single file
296
AFile:=FindFile(AFilename,false);
298
AFile.FTestedFlags:=[];
300
for i:=0 to length(FChangeTimeStampHandler)-1 do
301
FChangeTimeStampHandler[i](Self,AFilename);
302
//debugln('TFileStateCache.IncreaseTimeStamp FTimeStamp=',dbgs(FTimeStamp));
305
function TFileStateCache.FileExistsCached(const AFilename: string): boolean;
307
AFile: TFileStateCacheItem;
310
if Check(AFilename,fsciExists,AFile,Result) then exit;
311
Result:=FileExistsUTF8(AFile.Filename);
312
SetFlag(AFile,fsciExists,Result);
313
{if not Check(Filename,fsciExists,AFile,Result) then begin
315
raise Exception.Create('');
319
function TFileStateCache.DirPathExistsCached(const AFilename: string): boolean;
321
AFile: TFileStateCacheItem;
324
if Check(AFilename,fsciDirectory,AFile,Result) then exit;
325
Result:=DirPathExists(AFile.Filename);
326
SetFlag(AFile,fsciDirectory,Result);
329
function TFileStateCache.DirectoryIsWritableCached(const DirectoryName: string
332
AFile: TFileStateCacheItem;
335
if Check(DirectoryName,fsciDirectoryWritable,AFile,Result) then exit;
336
Result:=DirectoryIsWritable(AFile.Filename);
337
SetFlag(AFile,fsciDirectoryWritable,Result);
340
function TFileStateCache.FileIsExecutableCached(
341
const AFilename: string): boolean;
343
AFile: TFileStateCacheItem;
346
if Check(AFilename,fsciExecutable,AFile,Result) then exit;
347
Result:=FileIsExecutable(AFile.Filename);
348
SetFlag(AFile,fsciExecutable,Result);
351
function TFileStateCache.FileIsReadableCached(const AFilename: string): boolean;
353
AFile: TFileStateCacheItem;
356
if Check(AFilename,fsciReadable,AFile,Result) then exit;
357
Result:=FileIsReadable(AFile.Filename);
358
SetFlag(AFile,fsciReadable,Result);
361
function TFileStateCache.FileIsWritableCached(const AFilename: string): boolean;
363
AFile: TFileStateCacheItem;
366
if Check(AFilename,fsciWritable,AFile,Result) then exit;
367
Result:=FileIsWritable(AFile.Filename);
368
SetFlag(AFile,fsciWritable,Result);
371
function TFileStateCache.FileIsTextCached(const AFilename: string): boolean;
373
AFile: TFileStateCacheItem;
376
if Check(AFilename,fsciText,AFile,Result) then exit;
377
Result:=FileIsText(AFile.Filename);
378
SetFlag(AFile,fsciText,Result);
381
function TFileStateCache.FileAgeCached(const AFileName: string): Longint;
383
AFile: TFileStateCacheItem;
387
if Check(AFilename,fsciAge,AFile,Dummy) then begin
391
Result:=FileAge(AFile.Filename);
393
Include(AFile.FTestedFlags,fsciAge);
396
function TFileStateCache.FindFile(const Filename: string;
397
CreateIfNotExists: boolean): TFileStateCacheItem;
399
TrimmedFilename: String;
402
// make filename unique
403
TrimmedFilename:=ChompPathDelim(TrimFilename(Filename));
404
ANode:=FFiles.FindKey(Pointer(TrimmedFilename),
405
@CompareFilenameWithFileStateCacheItem);
407
Result:=TFileStateCacheItem(ANode.Data)
408
else if CreateIfNotExists then begin
409
Result:=TFileStateCacheItem.Create(TrimmedFilename,FTimeStamp);
411
if FFiles.FindKey(Pointer(TrimmedFilename),
412
@CompareFilenameWithFileStateCacheItem)=nil
414
//DebugLn(format('FileStateCache.FindFile: "%s"',[FileName]));
415
raise Exception.Create('');
421
function TFileStateCache.Check(const Filename: string;
422
AFlag: TFileStateCacheItemFlag; out AFile: TFileStateCacheItem;
423
var FlagIsSet: boolean): boolean;
425
AFile:=FindFile(Filename,true);
426
if FTimeStamp=AFile.FTimeStamp then begin
427
Result:=AFlag in AFile.FTestedFlags;
428
FlagIsSet:=AFlag in AFile.FFlags;
430
AFile.FTestedFlags:=[];
431
AFile.FTimeStamp:=FTimeStamp;
435
//WriteStr(s, AFlag);
436
//debugln('TFileStateCache.Check Filename=',Filename,' AFile.Filename=',AFile.Filename,' ',s,'=',dbgs(FlagIsSet),' Valid=',dbgs(Result));
439
procedure TFileStateCache.AddChangeTimeStampHandler(
440
const Handler: TOnChangeFileStateTimeStamp);
442
SetLength(FChangeTimeStampHandler,length(FChangeTimeStampHandler)+1);
443
FChangeTimeStampHandler[length(FChangeTimeStampHandler)-1]:=Handler;
446
procedure TFileStateCache.RemoveChangeTimeStampHandler(
447
const Handler: TOnChangeFileStateTimeStamp);
451
for i:=length(FChangeTimeStampHandler)-1 downto 0 do begin
452
if Handler=FChangeTimeStampHandler[i] then begin
453
if i<length(FChangeTimeStampHandler)-1 then
454
System.Move(FChangeTimeStampHandler[i+1],FChangeTimeStampHandler[i],
455
SizeOf(TNotifyEvent)*(length(FChangeTimeStampHandler)-i-1));
456
SetLength(FChangeTimeStampHandler,length(FChangeTimeStampHandler)-1);
461
function TFileStateCache.CalcMemSize: PtrUint;
465
Result:=PtrUInt(InstanceSize)
466
+PtrUInt(length(FChangeTimeStampHandler))*SizeOf(TNotifyEvent);
467
if FFiles<>nil then begin
468
inc(Result,PtrUInt(FFiles.InstanceSize)
469
+PtrUInt(FFiles.Count)*PtrUInt(TAVLTreeNode.InstanceSize));
470
Node:=FFiles.FindLowest;
471
while Node<>nil do begin
472
inc(Result,TFileStateCacheItem(Node.Data).CalcMemSize);
473
Node:=FFiles.FindSuccessor(Node);
479
OnInvalidateFileStateCache:=@InvalidateFileStateCache;