mirror of
https://github.com/winscp/winscp.git
synced 2026-10-24 02:28:55 +03:00
949 lines
31 KiB
ObjectPascal
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.
|