1
0
mirror of https://github.com/winscp/winscp.git synced 2026-10-24 02:28:55 +03:00
Files
winscp/source/packages/my/DiscMon.pas
Martin Prikryl 8291073ae1 Typo in comment
Source commit: d1c59aca7452ffd2c6a4aae88ac3dd4a5c82014f
2018-01-28 05:24:25 +01:00

949 lines
31 KiB
ObjectPascal

{
Modifications:
==============
ie01: OnChange delayed for 500 ms.
ie02: No usage of BrowseDirectory component
ie03: Resume suspended thread when terminating the thread
ie04: Martin Prikryl, option to watch multiple directories
}
// ==================== DISC DRIVE MONITOR =====================================
//
// Class and Component to encapsulate the FindXXXXChangeNotification API calls
//
// The FindXXXXChangeNotification API calls set up a disc contents change
// notification handle. You can set a filter to control which change types
// are notified, the directory which is monitored and set whether subdirectories
// from the monitored directory are monitored as well.
//
//------------------------------------------------------------------------------
// This file contains a class derived from TThread which undertakes the disc
// monitoring and a simple component which encapsulates the thread to make
// a non-visual VCL component. This component works at design time, monitoring
// and notifying changes live if required.
//
// Version 1.00 - Grahame Marsh 14 January 1997
// Version 1.01 - Grahame Marsh 30 December 1997
// Bug fix - really a Win 95 bug but only surfaces in D3, not D2
// - see notes in execute method
// Version 1.02 - Grahame Marsh 30 January 1998
// - adapted to work with version 2.30 TBrowseDirectoryDlg
//
// Freeware - you get it for free, I take nothing, I make no promises!
//
// Please feel free to contact me: grahame.s.marsh@courtaulds.com
{$DEFINE BUGFIX}
unit DiscMon;
interface
uses
Windows, SysUtils, Classes, CompThread;
//=== DISC MONITORING THREAD ===================================================
// This thread will monitor a given directory and subdirectories (if required)
// for defined filtered changes. When a change occurs the OnChange event will
// be fired, if an invalid condition is found (eg non-existent path) then
// the OnInvalid event is fired. Each event is called via the Sychronize method
// and so are VCL thread safe.
//
// The thread is created suspended, so after setting the required properties
// you must call the Resume method.
type
TDiscMonitorNotify = procedure(Sender: TObject; const Directory: string; var SubdirsChanged: Boolean) of object;
TDiscMonitorInvalid = procedure(Sender: TObject; const Directory: string; const ErrorStr: string) of object;
TDiscMonitorSynchronize = procedure(Sender: TObject; Method: TThreadMethod) of object;
TDiscMonitorFilter = procedure(Sender: TObject; const DirectoryName: string; var Add: Boolean) of object;
TDiscMonitorTooManyDirectories = procedure(Sender: TObject; var MaxDirectories: Integer) of object;
TDiscMonitorDirectoriesChange = procedure(Sender: TObject; Directories: Integer) of object;
TDiscMonitorThread = class(TCompThread)
private
FOnChange: TDiscMonitorNotify;
FOnInvalid: TDiscMonitorInvalid;
FOnSynchronize: TDiscMonitorSynchronize;
FOnFilter: TDiscMonitorFilter;
FOnDirectoriesChange: TDiscMonitorDirectoriesChange;
FDirectories: TStrings;
FFilters: DWORD;
FDestroyEvent,
FChangeEvent: THandle;
FSubTree: Boolean;
FChangeDelay: Integer; {ie01}
FNotifiedDirectory: string;
FSubdirsChanged: Boolean;
FInvalidMessage: string;
FNotifiedDirectories: Integer;
FEnabled: Boolean;
procedure InformChange;
procedure InformInvalid;
procedure InformDirectoriesChange;
procedure SetDirectories(const Value: TStrings);
procedure SetFilters(Value: DWORD);
procedure SetSubTree(Value: Boolean);
procedure SetEnabled(Value: Boolean);
procedure SaveOSError;
protected
procedure Execute; override;
procedure Update;
procedure DoSynchronize(Method: TThreadMethod);
public
constructor Create;
destructor Destroy; override;
// The directory to monitor
property Directories: TStrings read FDirectories write SetDirectories;
// Filter condition, may be any of the FILE_NOTIFY_CHANGE_XXXXXXX constants
// ORed together. Zero is invalid.
property Filters: DWORD read FFilters write SetFilters;
// Event called when change noted in directory
property OnChange: TDiscMonitorNotify read FOnChange write FOnChange;
// Event called for invalid parameters
property OnInvalid: TDiscMonitorInvalid read FOnInvalid write FOnInvalid;
property OnSynchronize: TDiscMonitorSynchronize read FOnSynchronize write FOnSynchronize;
property OnFilter: TDiscMonitorFilter read FOnFilter write FOnFilter;
property OnDirectoriesChange: TDiscMonitorDirectoriesChange read FOnDirectoriesChange write FOnDirectoriesChange;
// Include subdirectories below specified directory.
property SubTree: Boolean read FSubTree write SetSubTree;
// specify, how long the thread should wait, before the event OnChange is fired:
property ChangeDelay: Integer read FChangeDelay write FChangeDelay
default 500;
property Enabled: Boolean read FEnabled write SetEnabled;
end;
//===================== DISC MONITORING COMPONENT ==============================
// enumerated type for filter conditions (not directly usable in thread class)
// see the SetFilters procedure for the translation of these filter conditions
// into FILE_NOTIFY_CHANGE_XXXXXX constants.
TMonitorFilter = (moFilename, moDirName, moAttributes, moSize,
moLastWrite, moSecurity);
// set of filter conditions
TMonitorFilters = set of TMonitorFilter;
TDiscMonitor = class(TComponent)
private
FActive: Boolean;
FMonitor: TDiscMonitorThread;
FFilters: TMonitorFilters;
FOnChange: TDiscMonitorNotify;
FOnInvalid: TDiscMonitorInvalid;
FOnSynchronize: TDiscMonitorSynchronize;
FOnFilter: TDiscMonitorFilter;
FOnTooManyDirectories: TDiscMonitorTooManyDirectories;
FOnDirectoriesChange: TDiscMonitorDirectoriesChange;
FShowMsg: Boolean;
FPending: Boolean;
FMaxDirectories: Integer;
function GetDirectories: TStrings;
function GetSubTree: Boolean;
function GetEnabled: Boolean;
procedure SetActive(Value: Boolean);
procedure SetDirectories(Value: TStrings);
procedure SetFilters(Value: TMonitorFilters);
procedure SetSubTree(Value: Boolean);
procedure SetEnabled(Value: Boolean);
function GetChangeDelay: Integer;
procedure SetChangeDelay(Value: Integer);
protected
procedure Change(Sender: TObject; const Directory: string;
var SubdirsChanged: Boolean);
procedure Invalid(Sender: TObject; const Directory: string; const ErrorStr: string);
procedure Filter(Sender: TObject; const DirectoryName: string; var Add: Boolean);
procedure DirectoriesChange(Sender: TObject; Directories: Integer);
procedure DoSynchronize(Sender: TObject; Method: TThreadMethod);
public
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
// stop the monitoring thread running
procedure Close;
// start the monitoring thread running
procedure Open;
procedure AddDirectory(Directory: string; SubDirs: Boolean);
procedure SetDirectory(Directory: string);
// read-only property to access the thread directly
property Thread: TDiscMonitorThread read FMonitor;
property MaxDirectories: Integer read FMaxDirectories write FMaxDirectories;
published
// the directories to monitor
property Directories: TStrings read GetDirectories;
// control the appearance of information messages at design time (only)
property ShowDesignMsg: Boolean read FShowMsg write FShowMsg default False;
// event called when a change is notified
property OnChange: TDiscMonitorNotify read FOnChange write FOnChange;
// event called if an invalid condition is found
property OnInvalid: TDiscMonitorInvalid read FOnInvalid write FOnInvalid;
property OnSynchronize: TDiscMonitorSynchronize read FOnSynchronize write FOnSynchronize;
property OnFilter: TDiscMonitorFilter read FOnFilter write FOnFilter;
property OnTooManyDirectories: TDiscMonitorTooManyDirectories read FOnTooManyDirectories write FOnTooManyDirectories;
property OnDirectoriesChange: TDiscMonitorDirectoriesChange read FOnDirectoriesChange write FOnDirectoriesChange;
// notification filter conditions
property Filters: TMonitorFilters read FFilters write SetFilters default [moFilename];
// include subdirectories below the specified directory
property SubTree: Boolean read GetSubTree write SetSubTree default True;
// specify if the monitoring thread is active
property Active: Boolean read FActive write SetActive default False;
// specify, how long the thread should wait, before the event OnChange is fired:
property ChangeDelay: Integer read GetChangeDelay write SetChangeDelay
default 500;
property Enabled: Boolean read GetEnabled write SetEnabled default True;
end;
procedure Register;
implementation
uses
PasTools;
{$WARN SYMBOL_PLATFORM OFF}
{$IFDEF BUGFIX}
{$Z4}
type TWinBool = (winFalse, winTrue);
function FixFindFirstChangeNotification(const lpPathName: PChar;
bWatchSubtree: TWinBool; dwNotifyFilter: DWORD): THandle stdcall;
external kernel32 name 'FindFirstChangeNotificationW';
{$ENDIF}
procedure AddDirectory(Dirs: TStrings; Directory: string;
var MaxDirectories: Integer; OnFilter: TDiscMonitorFilter;
OnTooManyDirectories: TDiscMonitorTooManyDirectories; Tag: Boolean);
var
Found: Boolean;
SearchRec: TSearchRec;
FileName: string;
FindAttrs: Integer;
Add: Boolean;
Index: Integer;
begin
FindAttrs := faReadOnly or faHidden or faSysFile or faDirectory or faArchive;
Directory := IncludeTrailingBackslash(Directory);
Found := (FindFirst(ApiPath(Directory + '*.*'), FindAttrs, SearchRec) = 0);
if Found then
begin
try
while Found do
begin
if (SearchRec.Name <> '.') and (SearchRec.Name <> '..') and
((SearchRec.Attr and faDirectory) = faDirectory) then
begin
FileName := Directory + SearchRec.Name;
Add := True;
if Assigned(OnFilter) then OnFilter(nil, FileName, Add);
if Add then
begin
if Tag then
begin
Index := Dirs.IndexOf(FileName);
if Index >= 0 then
begin
Assert(Dirs.Objects[Index] = TObject(1));
Dirs.Objects[Index] := TObject(0);
end;
end
else Index := -1;
if Index < 0 then
begin
if Tag then Dirs.AddObject(FileName, TObject(2))
else Dirs.Add(FileName);
if (MaxDirectories >= 0) and (Dirs.Count > MaxDirectories) and
Assigned(OnTooManyDirectories) then
OnTooManyDirectories(nil, MaxDirectories);
end;
// note that we are not re-scaning subdirectories of duplicate directory
AddDirectory(Dirs, FileName, MaxDirectories, OnFilter, OnTooManyDirectories, Tag);
end;
end;
Found := (FindNext(SearchRec) = 0);
end;
finally
FindClose(SearchRec);
end;
end;
end;
//=== MONITOR THREAD ===========================================================
// Create the thread suspended. Create two events, each are created using
// standard security, in the non-signalled state, with auto-reset and without
// names. The FDestroyEvent will be used to signal the thread that it is to close
// down. The FChangeEvent will be used to signal the thread when the monitoring
// conditions (directory, filters or sub-directory search) have changed.
// OnTerminate is left as false, so the user must Free the thread.
constructor TDiscMonitorThread.Create;
begin
inherited Create(True);
FDirectories := TStringList.Create;
FDestroyEvent := CreateEvent(nil, True, False, nil);
FChangeEvent := CreateEvent(nil, False, False, nil);
FOnFilter := nil;
FOnDirectoriesChange := nil;
FEnabled := True;
end;
// close OnXXXXX links, signal the thread that it is to close down
destructor TDiscMonitorThread.Destroy;
var
D: TStrings;
begin
FOnChange := nil;
FOnInvalid := nil;
if Suspended then Resume;
SetEvent(FDestroyEvent);
D := FDirectories;
inherited Destroy;
// cannot free before destroy as the thread is using it
D.Free;
end;
// called by the Execute procedure via Synchronize. So this is VCL thread safe
procedure TDiscMonitorThread.InformChange;
begin
if Assigned(FOnChange) then FOnChange(Self, FNotifiedDirectory, FSubdirsChanged);
end;
// called by the Execute procedure via Synchronize. So this is VCL thread safe
procedure TDiscMonitorThread.InformInvalid;
begin
if Assigned(FOnInvalid) then FOnInvalid(Self, FNotifiedDirectory, FInvalidMessage);
end;
procedure TDiscMonitorThread.InformDirectoriesChange;
begin
if Assigned(FOnDirectoriesChange) then FOnDirectoriesChange(Self, FNotifiedDirectories);
end;
procedure TDiscMonitorThread.SaveOSError;
begin
try
RaiseLastOSError;
except
on E: Exception do FInvalidMessage := E.Message;
end;
end;
procedure TDiscMonitorThread.SetDirectories(const Value: TStrings);
begin
if Value <> nil then
begin
FDirectories.Assign(Value);
(FDirectories as TStringList).CaseSensitive := False;
(FDirectories as TStringList).Sorted := True;
end
else FDirectories.Clear;
Update;
end;
// Change the current filters
procedure TDiscMonitorThread.SetFilters(Value: DWORD);
begin
if Value <> FFilters then
begin
FFilters := Value;
Update;
end
end;
// Change the current sub-tree condition
procedure TDiscMonitorThread.SetSubTree(Value: Boolean);
begin
if Value <> FSubTree then
begin
FSubtree := Value;
Update;
end
end;
procedure TDiscMonitorThread.SetEnabled(Value: Boolean);
begin
if Value <> FEnabled then
begin
FEnabled := Value;
Update;
end
end;
function TDiscMonitor.GetChangeDelay: Integer; {ie01}
begin
Result := FMonitor.ChangeDelay;
end;
procedure TDiscMonitor.SetChangeDelay(Value: Integer); {ie01}
begin
FMonitor.ChangeDelay := Value;
end;
// On any of the above three changes, if the thread is running then
// signal it that a change has occurred.
procedure TDiscMonitorThread.Update;
begin
if not Suspended then
begin
SetEvent(FChangeEvent);
end;
end;
procedure TDiscMonitorThread.DoSynchronize(Method: TThreadMethod);
begin
if Assigned(OnSynchronize) then OnSynchronize(Self, Method);
end;
// The EXECUTE procedure
// -------
// Execute needs to:
// 1. Call FindFirstChangeNotification and use the Handle in a WaitFor...
// to wait until the thread become signalled that a notification has occurred.
// The OnChange event is called and then the FindNextChangeNotification is
// the called and Execute loops back to the WaitFor
// 2. If an invalid handle is obtained from the above call, the the OnInvalid
// event is called and then Execute waits until valid conditions are set.
// 3. If a ChangeEvent is signalled then FindCloseChangeNotification is called,
// followed by a new FindFirstChangeNotification to use the altered
// conditions.
// 4. If a DestroyEvent is signalled then FindCloseChangeNotification is
// called and the two events are closed and the thread terminates.
//
// In practice WaitForMultipleObjects is used to wait for any of the conditions
// to be signalled, and the returned value used to determine which event occurred.
procedure TDiscMonitorThread.Execute;
var
// used to give the handles to WaitFor...
Handles: PWOHandleArray;
BaseHandles: Word;
SysHandles: Word;
Count: Word;
function StartMonitor(const Directory: string; ForceSubTree: Boolean): THandle;
// There appears to be a bug in win 95 where the bWatchSubTree parameter
// of FindFirstChangeNotification which is a BOOL only accepts values of
// 0 and 1 as valid, rather than 0 and any non-0 value as it should. In D2
// BOOL was defined as 0..1 so the code worked, in D3 it is 0..-1 so
// fails. The result is FindF... produces and error message. This fix (bodge) is
// needed to produce a 0,1 bool pair, rather that 0,-1 as declared in D3
{$IFDEF BUGFIX}
const R : array[False..True] of TWinBOOL = (WinFalse, WinTrue);
{$ELSE}
const R : array[False..True] of LongBool = (LongBool(0), LongBool(1));
{$ENDIF}
var
Again: Boolean;
SubTree: Boolean;
begin
repeat
SubTree := FSubTree or ForceSubTree;
{$IFDEF BUGFIX}
Result := FixFindFirstChangeNotification(PChar(Directory), R[SubTree], FFilters);
{$ELSE}
Result := FindFirstChangeNotification(PChar(Directory), R[SubTree], FFilters);
{$ENDIF}
Again := (Result = INVALID_HANDLE_VALUE);
if Again then
begin
// call the OnInvalid event
FNotifiedDirectory := Directory;
SaveOSError;
DoSynchronize(InformInvalid);
// wait until either DestroyEvent or the ChangeEvents are signalled
Result := WaitForMultipleObjects(2, Handles, False, INFINITE);
if Result = WAIT_FAILED then
begin
FNotifiedDirectory := '';
SaveOSError;
DoSynchronize(InformInvalid);
Again := False;
end
else Again := (Result - WAIT_OBJECT_0 = 1);
end
until (not Again);
end; {StartMonitor}
function UpdateSubdirectories(Directory: Integer): Cardinal;
var
Path: string;
NewHandles: PWOHandleArray;
OrigDirectory: Integer;
NewAlloc: Integer;
MaxDirectories: Integer;
Changed: Boolean;
begin
Assert(Directory >= 0);
Path := ExcludeTrailingBackslash(FDirectories[Directory]);
while ((Directory + 1 < FDirectories.Count) and
SameText(Copy(FDirectories.Strings[Directory + 1], 1, Length(Path)), Path)) do
begin
FDirectories.Objects[Directory + 1] := TObject(1);
Inc(Directory);
end;
MaxDirectories := -1;
AddDirectory(FDirectories, Path, MaxDirectories, OnFilter, nil, True);
Result := WAIT_OBJECT_0;
// worst case limit
NewAlloc := SysHandles + FDirectories.Count;
GetMem(NewHandles, SizeOf(THandle) * NewAlloc);
Move(Handles^, NewHandles^, SizeOf(THandle) * SysHandles);
OrigDirectory := SysHandles;
Directory := SysHandles;
Changed := False;
while Directory < SysHandles + FDirectories.Count do
begin
// removed directory
if Integer(FDirectories.Objects[Directory - SysHandles]) = 1 then
begin
FDirectories.Delete(Directory - SysHandles);
Assert(OrigDirectory < Count);
FindCloseChangeNotification(Handles^[OrigDirectory]);
Handles^[OrigDirectory] := INVALID_HANDLE_VALUE;
Inc(OrigDirectory);
Changed := True;
end
else
// newly added
if Integer(FDirectories.Objects[Directory - SysHandles]) = 2 then
begin
Assert(Directory < NewAlloc);
NewHandles^[Directory] := StartMonitor(FDirectories[Directory - SysHandles], False);
if NewHandles^[Directory] = INVALID_HANDLE_VALUE then
begin
// currently we resign on correct resource freeing
Result := WAIT_FAILED;
Break;
end;
FDirectories.Objects[Directory - SysHandles] := TObject(0);
Inc(Directory);
Changed := True;
end
else
begin
Assert(Integer(FDirectories.Objects[Directory - SysHandles]) = 0);
Assert(Directory < NewAlloc);
Assert(OrigDirectory < Count);
NewHandles^[Directory] := Handles^[OrigDirectory];
Inc(Directory);
Inc(OrigDirectory);
end;
end;
if Result <> WAIT_FAILED then
begin
Assert(Count = OrigDirectory);
FreeMem(Handles);
Handles := NewHandles;
Count := SysHandles + FDirectories.Count;
Assert(Count = Directory);
Assert(Count <= NewAlloc);
end;
if Changed and Assigned(OnDirectoriesChange) then
begin
FNotifiedDirectories := FDirectories.Count;
DoSynchronize(InformDirectoriesChange);
end;
end;
function Notify(Directory: Integer; Handle: THandle): Cardinal;
begin
// Notification signalled, so fire the OnChange event and then FindNext..
// loop back to re-WaitFor... the thread
Sleep(FChangeDelay);
Result := WAIT_TIMEOUT;
// When deleting a tree, we may get notification about change in the directory
// before notification about deleting the directory.
// While this does not 100% protect against an attempt to synchronize the deleted directory,
// it may greatly reduce the risk (as checked after the sleep above).
// Though actually it may not even be possible to delete the directory as we have it locked.
if DirectoryExists(FDirectories[Directory]) then
begin
FNotifiedDirectory := FDirectories[Directory];
FSubdirsChanged := False;
DoSynchronize(InformChange);
if FSubdirsChanged then
Result := UpdateSubDirectories(Directory)
end;
FindNextChangeNotification(Handle);
end;
function CheckAllObjects(Count: Integer; DirHandles: PWOHandleArray): Cardinal;
const
Offset = MAXIMUM_WAIT_OBJECTS;
var
C, Start, Directory: Cardinal;
begin
Result := WAIT_TIMEOUT;
Start := 0;
while Start < Cardinal(Count) do
begin
if Cardinal(Count) - Start > Offset then C := Offset
else C := Cardinal(Count) - Start;
Result := WaitForMultipleObjects(C, @DirHandles[Start], false, 0);
Directory := Start + Result - WAIT_OBJECT_0;
// (Result - WAIT_OBJECT_0 >= 0) is always true
if Result - WAIT_OBJECT_0 < C then
begin
Result := Notify(Directory, DirHandles^[Directory]);
// when new directory is found, restart,
// if not check the same range again for possibly different notification
if Result = WAIT_OBJECT_0 then Start := 0;
end
else Inc(Start, C);
if Result <> WAIT_TIMEOUT then Break;
Result := WaitForMultipleObjects(2, Handles, False, 0);
if Result <> WAIT_TIMEOUT then Break;
end;
end;
const
DestroySlot = 0;
ChangeSlot = 1;
HierNotifySlot = 2;
var
HierMode: Boolean;
WaitCount: Word;
I: Integer;
Result: Cardinal;
WasEnabled: Boolean;
begin {Execute}
BaseHandles := 2;
SysHandles := BaseHandles;
Count := SysHandles + FDirectories.Count;
HierMode := (Count > MAXIMUM_WAIT_OBJECTS);
if HierMode then
begin
Inc(SysHandles);
Inc(Count);
end;
GetMem(Handles, SizeOf(THandle) * Count);
try
Handles^[DestroySlot] := FDestroyEvent; // put DestroyEvent handle in slot 0
Handles^[ChangeSlot] := FChangeEvent; // put ChangeEvent handle in slot 1
repeat
WasEnabled := Enabled;
if WasEnabled then
begin
if HierMode then
begin
// expect that the first directory is the top level one
Handles^[HierNotifySlot] := StartMonitor(FDirectories[0], True);
if Handles^[HierNotifySlot] = INVALID_HANDLE_VALUE then Exit;
end;
for I := SysHandles to Count - 1 do
begin
Handles^[I] := StartMonitor(FDirectories[I - SysHandles], False);
if Handles^[I] = INVALID_HANDLE_VALUE then Exit;
end;
end;
repeat
if WasEnabled then
begin
if HierMode then WaitCount := SysHandles
else WaitCount := Count;
end
else WaitCount := BaseHandles;
// wait for any of the change notification, destroy or
// change events to be signalled
Result := WaitForMultipleObjects(WaitCount, Handles, False, INFINITE);
if Result = WAIT_FAILED then
begin
FNotifiedDirectory := '';
SaveOSError;
DoSynchronize(InformInvalid);
end
else
if HierMode and (Result - WAIT_OBJECT_0 = HierNotifySlot) then
begin
FindNextChangeNotification(Handles[HierNotifySlot]);
Result := CheckAllObjects(Count - SysHandles, @Handles[SysHandles]);
// (Result >= WAIT_OBJECT_0) = always true
if Result < Cardinal(WAIT_OBJECT_0 + (Count - SysHandles)) then
begin
Result := WAIT_OBJECT_0 + HierNotifySlot;
end;
end
else
if (Result >= WAIT_OBJECT_0 + SysHandles) and
(Result < WAIT_OBJECT_0 + WaitCount) then
begin
Result := Notify(Result - WAIT_OBJECT_0 - SysHandles,
Handles^[Result - WAIT_OBJECT_0]);
if Result = WAIT_OBJECT_0 then Result := WAIT_TIMEOUT;
end;
// note that WaitCount can be different here than when
// WaitForMultipleObjects was called, but it should not matter as it is
until (Result = WAIT_FAILED) or (Result = WAIT_OBJECT_0 + DestroySlot) or
(Result = WAIT_OBJECT_0 + ChangeSlot) or
((Result >= WAIT_ABANDONED_0) and (Result < WAIT_ABANDONED_0 + WaitCount));
if WasEnabled then
begin
if HierMode then
begin
FindCloseChangeNotification(Handles^[HierNotifySlot]);
end;
for I := SysHandles to Count - 1 do
begin
FindCloseChangeNotification(Handles^[I]);
end;
end;
// loop back to restart if ChangeEvent was signalled
until (Result - WAIT_OBJECT_0 <> ChangeSlot) or Self.Terminated;
// closing down so chuck the two events
CloseHandle(FChangeEvent);
CloseHandle(FDestroyEvent);
finally
FreeMem(Handles);
end;
end;
//=== MONITOR COMPONENT ========================================================
// This component encapsulates the above thread. It has properties for
// directory, sub-directory conditions, filters, whether information messages
// should be given at design time and if the thread is active.
constructor TDiscMonitor.Create(AOwner: TComponent);
begin
inherited Create (AOwner);
FOnFilter := nil;
FOnTooManyDirectories := nil;
FOnDirectoriesChange := nil;
FMonitor := TDiscMonitorThread.Create; // create a monitor thread
FMonitor.ChangeDelay := 500; {ie01}
FMonitor.OnChange := Change; // hook into its event handlers
FMonitor.OnInvalid := Invalid;
FMonitor.OnSynchronize := DoSynchronize;
FMonitor.OnFilter := Filter;
FMonitor.OnDirectoriesChange := DirectoriesChange;
Filters := [moFilename]; // default filters to moFilename
SubTree := True; // default sub-tree search to on
FPending := True;
end;
destructor TDiscMonitor.Destroy;
begin
FMonitor.Free; // chuck the thread
inherited Destroy;
end;
// Change notification from the thread has occurred. Call the component's event
// handler
procedure TDiscMonitor.Change(Sender: TObject; const Directory: string;
var SubdirsChanged: Boolean);
begin
if Assigned(FOnChange) then
FOnChange(Self, Directory, SubdirsChanged)
end;
// Invalid notification from the thread has occurred. Call the component's event
// handler
procedure TDiscMonitor.Invalid(Sender: TObject; const Directory: string; const ErrorStr: string);
begin
if Assigned(FOnInvalid) then
FOnInvalid(Self, Directory, ErrorStr)
end;
procedure TDiscMonitor.Filter(Sender: TObject; const DirectoryName: string; var Add: Boolean);
begin
if Assigned(FOnFilter) then
FOnFilter(Self, DirectoryName, Add)
end;
procedure TDiscMonitor.DirectoriesChange(Sender: TObject; Directories: Integer);
begin
if Assigned(FOnDirectoriesChange) then
FOnDirectoriesChange(Self, Directories)
end;
procedure TDiscMonitor.DoSynchronize(Sender: TObject; Method: TThreadMethod);
begin
if Assigned(FOnSynchronize) then FOnSynchronize(Self, Method)
else FMonitor.Synchronize(Method);
end;
// Stop the monitor running
procedure TDiscMonitor.Close;
begin
Active := False;
end;
// Run the monitor
procedure TDiscMonitor.Open;
begin
Active := True
end;
// Control the thread by using it's resume and suspend methods
procedure TDiscMonitor.SetActive(Value: Boolean);
begin
if Value <> FActive then
begin
FActive := Value;
if Active then
begin
FMonitor.Resume;
if not FPending then FMonitor.Update;
FPending := False;
end else
FMonitor.Suspend;
end
end;
// get the current directory from the thread
function TDiscMonitor.GetDirectories: TStrings;
begin
Result := FMonitor.Directories;
end;
// get the current sub-tree status from the thread
function TDiscMonitor.GetSubTree: Boolean;
begin
Result := FMonitor.SubTree;
end;
function TDiscMonitor.GetEnabled: Boolean;
begin
Result := FMonitor.Enabled;
end;
// set the directory to monitor
procedure TDiscMonitor.SetDirectories(Value: TStrings);
begin
FMonitor.Directories := Value;
end;
procedure TDiscMonitor.AddDirectory(Directory: string; SubDirs: Boolean);
var
Dirs: TStringList;
begin
if Directory <> '' then
begin
Dirs := TStringList.Create;
try
Dirs.Assign(Directories);
Dirs.Add(Directory);
if SubDirs then
DiscMon.AddDirectory(Dirs, Directory, FMaxDirectories, OnFilter,
OnTooManyDirectories, False);
SetDirectories(Dirs);
finally
Dirs.Free;
end;
end
else
begin
SetDirectories(nil);
end;
end;
procedure TDiscMonitor.SetDirectory(Directory: string);
var
Dirs: TStringList;
begin
if Directory <> '' then
begin
Dirs := TStringList.Create;
try
Dirs.Add(Directory);
SetDirectories(Dirs);
finally
Dirs.Free;
end;
end
else
begin
SetDirectories(nil);
end;
end;
// Change the filter conditions. The thread uses the raw windows constants
// (FILE_NOTIFY_CHANGE_XXXX) but the components uses a set of enumurated type.
// It is therefore necessary to translate from the component format into
// an integer value for the thread.
procedure TDiscMonitor.SetFilters(Value: TMonitorFilters);
const
XlatFileNotify: array [moFilename..moSecurity] of DWORD =
(FILE_NOTIFY_CHANGE_FILE_NAME, FILE_NOTIFY_CHANGE_DIR_NAME,
FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_NOTIFY_CHANGE_SIZE,
FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_NOTIFY_CHANGE_SECURITY);
var
L: TMonitorFilter;
I: DWORD;
begin
if Value <> FFilters then
if Value = [] then
raise Exception.Create('Some filter condition must be set.')
else begin
FFilters := Value;
I := 0;
for L := moFilename to moSecurity do
if L in Value then
I := I or XlatFileNotify [L];
FMonitor.Filters := I;
end
end;
// set the sub-tree status in the thread
procedure TDiscMonitor.SetSubTree(Value: Boolean);
begin
FMonitor.SubTree := Value;
end;
procedure TDiscMonitor.SetEnabled(Value: Boolean);
begin
FMonitor.Enabled := Value;
end;
procedure Register;
begin
RegisterComponents('Martin', [TDiscMonitor]);
end;
end.