-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathUMonitor.pas
347 lines (305 loc) · 9.54 KB
/
UMonitor.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
unit UMonitor;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
Classes, Windows, SysUtils;
const
MONITOR_FLAGS = FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME;
var
Critical: TRTLCriticalSection;
type
PInfoCallback = ^TInfoCallback;
TInfoCallback = record
fAction : Integer; // FILE_ACTION_XXX constants
fDrive : String; // Drive, where change occured
fOldFileName : String; // File name before rename
fNewFileName : String; // File name after rename
fFolder : String; // Folder assigned to thread
end;
TWatchFileSystemCallback = procedure (pInfo: TInfoCallback);
PFileNotifyInformation = ^TFileNotifyInformation;
TFileNotifyInformation = record
NextEntryOffset: DWORD;
Action : DWORD;
FileNameLength : DWORD;
FileName : Pointer;
end;
TWFS = class(TThread)
private
fName : String;
fFilter : Cardinal;
fSubTree : Boolean;
fInfoCallback : TWatchFileSystemCallback;
fWatchHandle : THandle;
fWatchBuf : array[0..4096] of Byte;
fOverLapp : TOverlapped;
fPOverLapp : POverlapped;
fBytesWrite : DWORD;
fCompletionPort: THandle;
fNumBytes : Cardinal;
fOldFileName : String;
fLockCounter : Integer;
function CreateDirHandle(ADir: String): THandle;
procedure WatchEvent;
procedure HandleEvent;
protected
procedure Execute; override;
public
constructor Create(pName: String; pFilter: Cardinal; pSubTree: Boolean;
pInfoCallback: TWatchFileSystemCallback);
destructor Destroy; override;
procedure IncLockCounter;
procedure DecLockCounter;
property Directory: String read fName write fName;
property LockCounter: Integer read fLockCounter write fLockCounter;
end;
procedure StartWatch(pName: String; pFilter: Cardinal; pSubTree: Boolean;
pInfoCallback: TWatchFileSystemCallback);
procedure StopWatch(pName: String; AForce: Boolean = False); overload;
procedure StopWatch; overload;
procedure RenameWatch(const pOldName, pNewName: String);
procedure RealingArray(const Item: TWFS);
implementation
const
FILE_LIST_DIRECTORY = $0001;
var
WFSCount: Integer = 0;
WFS: array of TWFS;
// -----------------------------------------------------------------------------
// Start monitoring of specified folder:
// -------------------------------------
// pName: path to folder
// pFilter: actions to monitor (FILE_NOTIFY_XXX)
// pSubTree: true, if recursively
// pInfoCallback: callback address
procedure StartWatch(pName: String; pFilter: Cardinal; pSubTree: Boolean;
pInfoCallback: TWatchFileSystemCallback);
var
I: Integer;
begin
if DirectoryExists(pName) then
begin
{ Check if already watching }
for I := 0 to WFSCount - 1 do
if WFS[I].Directory = pName then
begin
WFS[I].IncLockCounter; // Multiple requests to monitor the same folder
Exit;
end;
SetLength(WFS, WFSCount + 1);
WFS[WFSCount] := TWFS.Create(pName, pFilter, pSubTree, pInfoCallback);
Inc(WFSCount);
end;
end;
// -----------------------------------------------------------------------------
// Stop monitoring a folder
procedure StopWatch(pName: String; AForce: Boolean = False);
var
I: Integer;
begin
if pName = '' then
Exit;
for I := 0 to WFSCount - 1 do
if Assigned(WFS[I]) and (WFS[I].Directory = pName) then
begin
if AForce or (WFS[I].LockCounter <= 0) then
begin
PostQueuedCompletionStatus(WFS[I].fCompletionPort, 0, 0, nil);
WFS[I].Terminate;
end
else
WFS[I].DecLockCounter;
end;
end;
// -----------------------------------------------------------------------------
// Ultimately stop watch everything
procedure StopWatch;
var
I: Integer;
begin
for I := 0 to WFSCount - 1 do
if Assigned(WFS[I]) then
begin
PostQueuedCompletionStatus(WFS[I].fCompletionPort, 0, 0, nil);
try
WFS[I].Terminate;
except
end;
end;
end;
// -----------------------------------------------------------------------------
// Rename directory in watch pool if it's name gets changed
procedure RenameWatch(const pOldName, pNewName: String);
var
I: Integer;
begin
for I := 0 to WFSCount - 1 do
if WFS[I].Directory = pOldName then
begin
WFS[I].Directory := pNewName;
Break;
end;
end;
// -----------------------------------------------------------------------------
// Realings array after deletion of thread
procedure RealingArray(const Item: TWFS);
var
I, P, J: Integer;
begin
P := -1;
for I := 0 to WFSCount - 1 do
begin
if WFS[I] = Item then
begin
P := I;
Break;
end;
end;
if P > -1 then
begin
if P < WFSCount - 1 then
for J := P + 1 to WFSCount - 1 do
WFS[J - 1] := WFS[J];
Dec(WFSCount);
SetLength(WFS, WFSCount);
end;
end;
// -----------------------------------------------------------------------------
{ TWatchFileSystem }
// -----------------------------------------------------------------------------
constructor TWFS.Create(pName: String; pFilter: Cardinal; pSubTree: Boolean;
pInfoCallback: TWatchFileSystemCallback);
begin
inherited Create(True);
fWatchHandle := 0;
fCompletionPort := 0;
fLockCounter := 0;
FreeOnTerminate := True;
fName := pName;
fFilter := pFilter;
fSubTree := pSubTree;
fOldFileName := EmptyStr;
ZeroMemory(@fOverLapp, SizeOf(TOverLapped));
fPOverLapp := @fOverLapp;
ZeroMemory(@fWatchBuf, SizeOf(fWatchBuf));
fInfoCallback := pInfoCallback;
Resume;
end;
destructor TWFS.Destroy;
begin
PostQueuedCompletionStatus(fCompletionPort, 0, 0, nil);
CloseHandle(fWatchHandle);
fWatchHandle := 0;
CloseHandle(fCompletionPort);
fCompletionPort := 0;
EnterCriticalSection(Critical);
try
RealingArray(Self);
finally
LeaveCriticalSection(Critical);
end;
inherited Destroy;
end;
procedure TWFS.IncLockCounter;
begin
Inc(fLockCounter);
end;
procedure TWFS.DecLockCounter;
begin
if fLockCounter > 0 then
Dec(fLockCounter);
end;
function TWFS.CreateDirHandle(ADir: string): THandle;
begin
Result := CreateFile(PChar(ADir), FILE_LIST_DIRECTORY, FILE_SHARE_READ or FILE_SHARE_DELETE or FILE_SHARE_WRITE,
nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED, 0);
end;
procedure TWFS.Execute;
begin
fWatchHandle := CreateDirHandle(fName);
if (fWatchHandle = 0) or (fWatchHandle = ERROR_INVALID_HANDLE) then
Terminate
else
WatchEvent;
end;
procedure TWFS.HandleEvent;
var
FileNotifyInfo: PFileNotifyInformation;
InfoCallback : TInfoCallback;
Offset : Longint;
begin
Pointer(FileNotifyInfo) := @fWatchBuf[0];
repeat
{ Find position of next entry }
Offset := FileNotifyInfo^.NextEntryOffset;
{ Get file name }
SetLength(InfoCallback.FNewFileName, FileNotifyInfo^.FileNameLength shr 1);
Move(FileNotifyInfo^.FileName, InfoCallback.FNewFileName[1],
FileNotifyInfo^.FileNameLength);
InfoCallback.FNewFileName := Trim(InfoCallback.FNewFileName);
{ Get drive name }
InfoCallback.fDrive := ExtractFileDrive(InfoCallback.FNewFileName);
{ Get performed action }
InfoCallback.FAction := FileNotifyInfo^.Action;
case InfoCallback.FAction of
FILE_ACTION_RENAMED_OLD_NAME: fOldFileName := InfoCallback.FNewFileName;
FILE_ACTION_RENAMED_NEW_NAME: InfoCallback.FOldFileName := fOldFileName;
end;
{ Set folder which caused callback }
InfoCallback.fFolder := fName;
{ Call processing routine }
if InfoCallback.fAction >= FILE_ACTION_RENAMED_OLD_NAME then
if (InfoCallback.fOldFileName <> '') and (InfoCallback.fNewFileName <> '') then
begin
if DirectoryExists(InfoCallback.fFolder + InfoCallback.fNewFileName) then
begin
EnterCriticalSection(Critical);
try
RenameWatch(IncludeTrailingBackslash(InfoCallback.fFolder + InfoCallback.fOldFileName),
IncludeTrailingBackslash(InfoCallback.fFolder + InfoCallback.fNewFileName));
finally
LeaveCriticalSection(Critical);
end;
end;
fInfoCallback(InfoCallback);
end
else
else
fInfoCallback(InfoCallback);
{ Proceed to next entry }
PByte(FileNotifyInfo) := PByte(FileNotifyInfo) + Offset;
until
(Offset = 0) or Terminated;
end;
procedure TWFS.WatchEvent;
var
CompletionKey: Cardinal;
begin
fCompletionPort := CreateIoCompletionPort(fWatchHandle, 0, Longint(Pointer(Self)), 0);
ZeroMemory(@fWatchBuf, SizeOf(fWatchBuf));
if not ReadDirectoryChanges(fWatchHandle, @fWatchBuf[0], SizeOf(fWatchBuf), fSubTree,
fFilter, @fBytesWrite, @fOverLapp, nil)
then
Terminate
else
while not Terminated do
begin
GetQueuedCompletionStatus(fCompletionPort, fNumBytes, CompletionKey,
fPOverLapp, INFINITE);
if CompletionKey <> 0 then
begin
Synchronize(HandleEvent);
ZeroMemory(@fWatchBuf, SizeOf(fWatchBuf));
fBytesWrite := 0;
ReadDirectoryChanges(fWatchHandle, @fWatchBuf, SizeOf(fWatchBuf), fSubTree, fFilter,
@fBytesWrite, @fOverLapp, nil);
end
else
Terminate;
end;
end;
initialization
InitializeCriticalSection(Critical);
finalization
DeleteCriticalSection(Critical);
end.