ADD: FileSystemWatcher: use fsevents instead of kqueue/kevent on MacOS (#777)

This commit is contained in:
rich2014 2023-01-28 17:36:53 +08:00 committed by GitHub
commit 89a67cb0d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 513 additions and 10 deletions

View file

@ -307,7 +307,7 @@ end;"/>
<PackageName Value="Image32"/>
</Item13>
</RequiredPackages>
<Units Count="268">
<Units Count="269">
<Unit0>
<Filename Value="doublecmd.lpr"/>
<IsPartOfProject Value="True"/>
@ -1967,6 +1967,11 @@ end;"/>
<IsPartOfProject Value="True"/>
<UnitName Value="uTrashFileSource"/>
</Unit267>
<Unit268>
<Filename Value="platform\unix\darwin\udarwinfswatch.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="uDarwinFSWatch"/>
</Unit268>
</Units>
</ProjectOptions>
<CompilerOptions>

View file

@ -83,6 +83,8 @@ uses
DCConvertEncoding
{$ELSEIF DEFINED(LINUX)}
, inotify, BaseUnix, FileUtil, DCConvertEncoding, DCUnix
{$ELSEIF DEFINED(DARWIN)}
, uDarwinFSWatch
{$ELSEIF DEFINED(BSD)}
, BSD, Unix, BaseUnix, UnixType, FileUtil, DCOSUtils
{$ELSEIF DEFINED(HAIKU)}
@ -94,6 +96,10 @@ uses
{$ENDIF}
{$ENDIF};
{$IF DEFINED(UNIX) AND not DEFINED(DARWIN)}
{$DEFINE UNIX_butnot_DARWIN}
{$ENDIF}
{$IF DEFINED(HAIKU) AND (DEFINED(LCLQT5) OR DEFINED(LCLQT6))}
{$DEFINE HAIKUQT}
{$ENDIF}
@ -103,7 +109,7 @@ uses
{$define SameMethod:= CompareMethods}
{$endif}
{$IF DEFINED(UNIX)}
{$IF DEFINED(UNIX_butnot_DARWIN)}
type
{$IF DEFINED(HAIKUQT)}
TNotifyHandle = QFileSystemWatcherH;
@ -157,7 +163,9 @@ type
TOSWatch = class
private
{$IF NOT DEFINED(DARWIN)}
FHandle: THandle;
{$ENDIF}
FObservers: TOSWatchObservers;
FWatchFilter: TFSWatchFilter;
FWatchPath: String;
@ -168,11 +176,13 @@ type
FReferenceCount: LongInt;
FOldFileName: String; // for FILE_ACTION_RENAMED_OLD_NAME action
{$ENDIF}
{$IF DEFINED(UNIX)}
{$IF DEFINED(UNIX_butnot_DARWIN)}
FNotifyHandle: TNotifyHandle;
{$ENDIF}
{$IF NOT DEFINED(DARWIN)}
procedure CreateHandle;
procedure DestroyHandle;
{$ENDIF}
{$IF DEFINED(MSWINDOWS)}
procedure QueueCancelRead;
procedure QueueRead;
@ -180,14 +190,18 @@ type
{$ENDIF}
public
constructor Create(const aWatchPath: String
{$IFDEF UNIX}; aNotifyHandle: TNotifyHandle{$ENDIF}); reintroduce;
{$IFDEF UNIX_butnot_DARWIN}; aNotifyHandle: TNotifyHandle{$ENDIF}); reintroduce;
destructor Destroy; override;
{$IF not DEFINED(DARWIN)}
procedure UpdateFilter;
{$ENDIF}
{$IF DEFINED(MSWINDOWS)}
procedure Reference{$IFDEF DEBUG_WATCHER}(s: String){$ENDIF};
procedure Dereference{$IFDEF DEBUG_WATCHER}(s: String){$ENDIF};
{$ENDIF}
{$IF not DEFINED(DARWIN)}
property Handle: THandle read FHandle;
{$ENDIF}
property Observers: TOSWatchObservers read FObservers;
property WatchPath: String read FWatchPath;
end;
@ -199,9 +213,12 @@ type
private
FWatcherLock: syncobjs.TCriticalSection;
FOSWatchers: TOSWatchs;
{$IF DEFINED(UNIX)}
{$IF DEFINED(UNIX_butnot_DARWIN)}
FNotifyHandle: TNotifyHandle;
{$ENDIF}
{$IF DEFINED(DARWIN)}
FDarwinFSWatcher: TDarwinFSWatcher;
{$ENDIF}
{$IF DEFINED(LINUX)}
FEventPipe: TFilDes;
{$ENDIF}
@ -212,6 +229,9 @@ type
FHook: QFileSystemWatcher_hookH;
procedure DirectoryChanged(Path: PWideString); cdecl;
{$ENDIF}
{$IF DEFINED(DARWIN)}
procedure handleFSEvent(event:TDarwinFSWatchEvent);
{$ENDIF}
procedure DoWatcherEvent;
function GetWatchersCount: Integer;
function GetWatchPath(var aWatchPath: String): Boolean;
@ -797,6 +817,10 @@ begin
FreeMem(buf);
end; { try - finally }
end;
{$ELSEIF DEFINED(DARWIN)}
begin
FDarwinFSWatcher.start;
end;
{$ELSEIF DEFINED(BSD)}
var
ret: cint;
@ -851,6 +875,21 @@ begin
end;
{$ENDIF}
{$IF DEFINED(DARWIN)}
procedure TFileSystemWatcherImpl.handleFSEvent(event:TDarwinFSWatchEvent);
begin
FCurrentEventData.Path := event.watchPath;
FCurrentEventData.EventType := fswUnknownChange;
FCurrentEventData.FileName := EmptyStr;
FCurrentEventData.NewFileName := EmptyStr;
{$IFDEF DEBUG_WATCHER}
DCDebug('FSWatcher: Send event, Path %s', [FCurrentEventData.Path]);
{$ENDIF};
Synchronize(@DoWatcherEvent);
end;
{$ENDIF}
{$IF DEFINED(HAIKUQT)}
procedure TFileSystemWatcherImpl.DirectoryChanged(Path: PWideString); cdecl;
begin
@ -1010,6 +1049,8 @@ begin
end
else
ShowError('pipe() failed');
{$ELSEIF DEFINED(DARWIN)}
FDarwinFSWatcher := TDarwinFSWatcher.create(@handleFSEvent);
{$ELSEIF DEFINED(BSD)}
FNotifyHandle := kqueue();
if FNotifyHandle = feInvalidHandle then
@ -1047,6 +1088,8 @@ begin
FileClose(FNotifyHandle);
FNotifyHandle := feInvalidHandle;
end;
{$ELSEIF DEFINED(DARWIN)}
FreeAndNil(FDarwinFSWatcher);
{$ELSEIF DEFINED(BSD)}
if FNotifyHandle <> feInvalidHandle then
begin
@ -1134,9 +1177,11 @@ begin
if not Assigned(OSWatcher) then
begin
OSWatcher := TOSWatch.Create(aWatchPath {$IFDEF UNIX}, FNotifyHandle {$ENDIF});
OSWatcher := TOSWatch.Create(aWatchPath {$IFDEF UNIX_butnot_DARWIN}, FNotifyHandle {$ENDIF});
{$IF DEFINED(MSWINDOWS)}
OSWatcher.Reference{$IFDEF DEBUG_WATCHER}('AddWatch'){$ENDIF}; // For usage by FileSystemWatcher (main thread)
{$ELSEIF DEFINED(DARWIN)}
FDarwinFSWatcher.addPath(aWatchPath);
{$ENDIF}
OSWatcherCreated := True;
end;
@ -1159,9 +1204,12 @@ begin
WatcherIndex := FOSWatchers.Add(OSWatcher);
OSWatcher.Observers.Add(Observer);
{$IF DEFINED(DARWIN)}
Result:= true;
{$ELSE}
OSWatcher.UpdateFilter; // This creates or recreates handle.
Result := OSWatcher.Handle <> feInvalidHandle;
{$ENDIF}
// Remove watcher if could not create notification handle.
if not Result then
@ -1226,8 +1274,10 @@ begin
if FOSWatchers[OSWatcherIndex].Observers.Count = 0 then
RemoveOSWatchLocked(OSWatcherIndex)
{$IF NOT DEFINED(DARWIN)}
else
FOSWatchers[OSWatcherIndex].UpdateFilter;
FOSWatchers[OSWatcherIndex].UpdateFilter
{$ENDIF};
Break;
end;
@ -1243,6 +1293,9 @@ begin
Dereference{$IFDEF DEBUG_WATCHER}('RemoveOSWatchLocked'){$ENDIF}; // Not using anymore by FileSystemWatcher from main thread
end;
{$ENDIF}
{$IF DEFINED(DARWIN)}
FDarwinFSWatcher.removePath(FOSWatchers[Index].WatchPath);
{$ENDIF}
FOSWatchers.Delete(Index);
end;
@ -1281,6 +1334,10 @@ begin
FileWrite(FEventPipe[1], buf, 1);
end; { if }
end;
{$ELSEIF DEFINED(DARWIN)}
begin
FDarwinFSWatcher.terminate;
end;
{$ELSEIF DEFINED(BSD)}
var
ke: TKEvent;
@ -1310,24 +1367,28 @@ end;
{ TOSWatch }
constructor TOSWatch.Create(const aWatchPath: String
{$IFDEF UNIX}; aNotifyHandle: TNotifyHandle{$ENDIF});
{$IFDEF UNIX_butnot_DARWIN}; aNotifyHandle: TNotifyHandle{$ENDIF});
begin
FObservers := TOSWatchObservers.Create(True);
FWatchFilter := [];
FWatchPath := aWatchPath;
{$IFDEF UNIX}
{$IFDEF UNIX_butnot_DARWIN}
FNotifyHandle := aNotifyHandle;
{$ENDIF}
{$IF DEFINED(MSWINDOWS)}
FReferenceCount := 0;
FBuffer := GetMem(VAR_READDIRECTORYCHANGESW_BUFFERSIZE);
{$ENDIF}
{$IF not DEFINED(DARWIN)}
FHandle := feInvalidHandle;
{$ENDIF}
end;
destructor TOSWatch.Destroy;
begin
{$IF not DEFINED(DARWIN)}
DestroyHandle;
{$ENDIF}
inherited;
{$IFDEF DEBUG_WATCHER}
DCDebug(['FSWatcher: Destroying watch ', hexStr(Self)]);
@ -1338,6 +1399,7 @@ begin
{$ENDIF}
end;
{$IF not DEFINED(DARWIN)}
procedure TOSWatch.UpdateFilter;
var
i: Integer;
@ -1363,6 +1425,7 @@ begin
{$ENDIF}
end;
end;
{$ENDIF}
{$IF DEFINED(MSWINDOWS)}
procedure TOSWatch.Reference{$IFDEF DEBUG_WATCHER}(s: String){$ENDIF};
@ -1397,6 +1460,7 @@ begin
end;
{$ENDIF}
{$IF not DEFINED(DARWIN)}
procedure TOSWatch.CreateHandle;
{$IF DEFINED(MSWINDOWS)}
begin
@ -1532,6 +1596,7 @@ begin
{$ENDIF}
end;
end;
{$ENDIF}
{$IF DEFINED(MSWINDOWS)}
procedure TOSWatch.QueueCancelRead;

View file

@ -0,0 +1,433 @@
{
Double Commander
-------------------------------------------------------------------------
This unit contains specific DARWIN FSEvent functions.
Copyright (C) 2023 Alexander Koblov (alexx2000@mail.ru)
Copyright (C) 2023 Rich Chang (rich2014.git@outlook.com)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Notes:
1. multiple directories can be monitored at the same time.
DC generally monitors more than 2 directories (possibly much more than 2),
just one TDarwinFSWatcher needed.
2. subdirectories monitor supported. currently in DC, only one level needed.
3. file attributes monitoring is supported, and monitoring of adding files,
renaming files, deleting files is also supported.
for comparison, file attributes monitoring is missing with kqueue/kevent.
4. CFRunLoop is used in TDarwinFSWatcher. because in DC a separate thread
has been opened (in uFileSystemWatcher), it is more appropriate to use
CFRunLoop than DispatchQueue.
}
unit uDarwinFSWatch;
{$mode delphi}
{$modeswitch objectivec2}
interface
uses
Classes, SysUtils, SyncObjs,
MacOSAll, CocoaAll;
type TDarwinFSWatchEventCategory = (
ecUnknown,
ecAttributesChanged,
ecStructChanged, ecCreated, ecRemoved, ecRenamed,
ecRootChanged, ecChildChanged );
type TDarwinFSWatchEvent = class
private
_category: TDarwinFSWatchEventCategory;
_watchPath: String;
_fullPath: String;
_rawEventFlags: UInt32;
public
constructor create( const aWatchPath:String; const aFullPath:String; const aFlags:UInt32 );
function rawEventflagsToStr(): String;
public
property category: TDarwinFSWatchEventCategory read _category;
property watchPath: String read _watchPath;
property fullPath: String read _fullPath;
property rawEventFlags: UInt32 read _rawEventFlags;
end;
type TDarwinFSWatchCallBack = Procedure( event:TDarwinFSWatchEvent ) of object;
type TDarwinFSWatcher = class
private
_watchPaths: NSMutableArray;
_streamPaths: NSArray;
_watchSubtree: Boolean;
_callback: TDarwinFSWatchCallBack;
_latency: Integer;
_stream: FSEventStreamRef;
_streamContext: FSEventStreamContext;
_lastEventId: FSEventStreamEventId;
_running: Boolean;
_runLoop: CFRunLoopRef;
_thread: TThread;
_lockObject: TCriticalSection;
_pathsSyncObject: TEventObject;
private
procedure handleEvents( const amount:size_t; paths:PPChar; flags:FSEventStreamEventFlagsPtr; ids:FSEventStreamEventIdPtr );
procedure doCallback( const path:String; const flags:FSEventStreamEventFlags);
procedure updateStream;
procedure closeStream;
procedure waitPath;
procedure notifyPath;
procedure interrupt;
public
constructor create( const callback:TDarwinFSWatchCallBack; const watchSubtree:Boolean=false; const latency:Integer=1000 );
destructor destroy; override;
procedure start;
procedure terminate;
procedure addPath( path:String );
procedure removePath( path:String );
procedure clearPath;
end;
implementation
function StringToNSString(const S: String): NSString;
begin
Result:= NSString(NSString.stringWithUTF8String(PAnsiChar(S)));
end;
constructor TDarwinFSWatchEvent.create( const aWatchPath:String; const aFullPath:String; const aFlags:UInt32 );
begin
_category:= TDarwinFSWatchEventCategory.ecUnknown;
_watchPath:= aWatchPath;
_fullPath:= aFullPath;
_rawEventFlags:= aFlags;
end;
function TDarwinFSWatchEvent.rawEventflagsToStr(): String;
begin
Result:= EmptyStr;
if (_rawEventFlags and kFSEventStreamEventFlagItemModified)<>0 then
Result:= Result + '|Modified';
if (_rawEventFlags and kFSEventStreamEventFlagItemCreated)<>0 then
Result:= Result + '|Created';
if (_rawEventFlags and kFSEventStreamEventFlagItemRemoved)<>0 then
Result:= Result + '|Removed';
if (_rawEventFlags and kFSEventStreamEventFlagItemRenamed)<>0 then
Result:= Result + '|Renamed';
if (_rawEventFlags and kFSEventStreamEventFlagItemChangeOwner)<>0 then
Result:= Result + '|ChangeOwner';
if (_rawEventFlags and kFSEventStreamEventFlagItemInodeMetaMod)<>0 then
Result:= Result + '|InodeMetaMod';
if (_rawEventFlags and kFSEventStreamEventFlagItemFinderInfoMod)<>0 then
Result:= Result + '|FinderInfoMod';
if (_rawEventFlags and kFSEventStreamEventFlagItemXattrMod)<>0 then
Result:= Result + '|XattrMod';
if (_rawEventFlags and kFSEventStreamEventFlagItemIsFile)<>0 then
Result:= Result + '|IsFile';
if (_rawEventFlags and kFSEventStreamEventFlagItemIsDir)<>0 then
Result:= Result + '|IsDir';
if (_rawEventFlags and kFSEventStreamEventFlagItemIsSymlink)<>0 then
Result:= Result + '|IsSymlink';
if (_rawEventFlags and $00100000)<>0 then
Result:= Result + '|IsHardLink';
if (_rawEventFlags and $00200000)<>0 then
Result:= Result + '|IsLastHardLink';
if (_rawEventFlags and kFSEventStreamEventFlagRootChanged)<>0 then
Result:= Result + '|RootChanged';
if (_rawEventFlags and kFSEventStreamEventFlagMustScanSubDirs)<>0 then
Result:= Result + '|ScanSubDirs';
if (_rawEventFlags and kFSEventStreamEventFlagUserDropped)<>0 then
Result:= Result + '|UserDropped';
if (_rawEventFlags and kFSEventStreamEventFlagKernelDropped)<>0 then
Result:= Result + '|KernelDropped';
if (_rawEventFlags and kFSEventStreamEventFlagEventIdsWrapped)<>0 then
Result:= Result + '|IdsWrapped';
if (_rawEventFlags and kFSEventStreamEventFlagHistoryDone)<>0 then
Result:= Result + '|HistoryDone';
if (_rawEventFlags and kFSEventStreamEventFlagMount)<>0 then
Result:= Result + '|Mount';
if (_rawEventFlags and kFSEventStreamEventFlagUnmount)<>0 then
Result:= Result + '|Unmount';
if (_rawEventFlags and kFSEventStreamEventFlagOwnEvent)<>0 then
Result:= Result + '|OwnEvent';
if (_rawEventFlags and $00400000)<>0 then
Result:= Result + '|Cloned';
if (_rawEventFlags and $FF800000)<>0 then
Result:= '|*UnkownFlags:' + IntToHex(_rawEventFlags) + '*' + Result;
if Result.IsEmpty then
Result:= 'NoneFlag'
else
Result:= Result.TrimLeft( '|' );
end;
constructor TDarwinFSWatcher.create( const callback:TDarwinFSWatchCallBack; const watchSubtree:Boolean; const latency:Integer );
begin
Inherited Create;
_watchPaths:= NSMutableArray.alloc.initWithCapacity( 16 );
_watchSubtree:= watchSubtree;
_callback:= callback;
_latency:= latency;
_streamContext.info:= self;
_lastEventId:= FSEventStreamEventId(kFSEventStreamEventIdSinceNow);
_running:= false;
_lockObject:= TCriticalSection.Create;
_pathsSyncObject:= TSimpleEvent.Create;
end;
destructor TDarwinFSWatcher.destroy;
begin
_pathsSyncObject.SetEvent;
FreeAndNil( _lockObject );
FreeAndNil( _pathsSyncObject );
_watchPaths.release;
_watchPaths:= nil;
_streamPaths.release;
_streamPaths:= nil;
Inherited;
end;
procedure cdeclFSEventsCallback( {%H-}streamRef: ConstFSEventStreamRef;
clientCallBackInfo: UnivPtr;
numEvents: size_t;
eventPaths: UnivPtr;
eventFlags: FSEventStreamEventFlagsPtr;
eventIds: FSEventStreamEventIdPtr ); cdecl;
var
watcher: TDarwinFSWatcher absolute clientCallBackInfo;
begin
watcher.handleEvents( NumEvents, PPChar(EventPaths), EventFlags, EventIds );
end;
procedure TDarwinFSWatcher.handleEvents( const amount:size_t; paths:PPChar; flags:FSEventStreamEventFlagsPtr; ids:FSEventStreamEventIdPtr );
var
i: size_t;
begin
for i:=amount downto 1 do
begin
doCallback( paths^, flags^ );
inc(paths);
inc(flags);
end;
end;
// Note: try to avoid string copy
function isMatchWatchPath(
const fullPath:String; const watchPath:String;
const flags:FSEventStreamEventFlags; const watchSubtree:Boolean ): Boolean;
var
fullPathDeep: Integer;
watchPathDeep: Integer;
begin
// detect if fullPath=watchPath
if (flags and (kFSEventStreamEventFlagItemIsDir or kFSEventStreamEventFlagRootChanged)) <> 0 then
begin
Result:= watchPath.Equals( fullPath );
if Result then exit; // fullPath=watchPath, matched
end;
// detect if fullPath startsWith watchPath
Result:= fullPath.StartsWith(watchPath);
if watchSubtree then exit;
// not watchSubtree
// not startsWith watchPath, not match
if not Result then exit;
// not watchSubtree, and startsWith watchPath
// detect if fullPath and watchPath in the same level
fullPathDeep:= fullPath.CountChar(PathDelim);
watchPathDeep:= watchPath.CountChar(PathDelim)+1;
Result:= fullPathDeep=watchPathDeep;
end;
procedure TDarwinFSWatcher.doCallback( const path:String; const flags:FSEventStreamEventFlags );
var
watchPath: NSString;
event: TDarwinFSWatchEvent;
begin
for watchPath in _streamPaths do
begin
if isMatchWatchPath(path, watchPath.UTF8String, flags, _watchSubtree) then
begin
event:= TDarwinFSWatchEvent.create( watchPath.UTF8String, path, flags );
_callback( event );
event.Free;
end;
end;
end;
procedure TDarwinFSWatcher.updateStream;
var
flags: FSEventStreamCreateFlags;
begin
if _watchPaths.isEqualToArray(_streamPaths) then exit;
closeStream;
_streamPaths.release;
_streamPaths:= NSArray.alloc.initWithArray( _watchPaths );
if _watchPaths.count = 0 then
begin
_lastEventId:= FSEventStreamEventId(kFSEventStreamEventIdSinceNow);
exit;
end;
flags:= kFSEventStreamCreateFlagFileEvents or kFSEventStreamCreateFlagWatchRoot or kFSEventStreamCreateFlagNoDefer;
_stream:= FSEventStreamCreate( nil,
@cdeclFSEventsCallback,
@_streamContext,
CFArrayRef(_watchPaths),
_lastEventId,
_latency/1000,
flags );
FSEventStreamScheduleWithRunLoop( _stream, _runLoop, kCFRunLoopDefaultMode );
FSEventStreamStart( _stream );
end;
procedure TDarwinFSWatcher.closeStream;
begin
if Assigned(_stream) then
begin
FSEventStreamFlushSync( _stream );
FSEventStreamStop( _stream );
_lastEventId:= FSEventStreamGetLatestEventId( _stream );
FSEventStreamInvalidate( _stream );
FSEventStreamRelease( _stream );
_stream:= nil;
end;
end;
procedure TDarwinFSWatcher.start;
begin
_running:= true;
_runLoop:= CFRunLoopGetCurrent();
_thread:= TThread.CurrentThread;
repeat
_lockObject.Acquire;
try
updateStream;
finally
_lockObject.Release;
end;
if Assigned(_stream) then
CFRunLoopRun
else
waitPath;
until not _running;
end;
procedure TDarwinFSWatcher.terminate;
begin
_lockObject.Acquire;
try
_running:= false;
interrupt;
finally
_lockObject.Release;
end;
if Assigned(_thread) then _thread.WaitFor;
closeStream;
end;
procedure TDarwinFSWatcher.interrupt;
begin
if Assigned(_stream) then
CFRunLoopStop( _runLoop )
else
notifyPath;
end;
procedure TDarwinFSWatcher.addPath( path:String );
var
nsPath: NSString;
begin
_lockObject.Acquire;
try
if path<>PathDelim then
path:= ExcludeTrailingPathDelimiter(path);
nsPath:= StringToNSString( path );
if _watchPaths.containsObject(nsPath) then exit;
_watchPaths.addObject( nsPath );
interrupt;
finally
_lockObject.Release;
end;
end;
procedure TDarwinFSWatcher.removePath( path:String );
var
nsPath: NSString;
begin
_lockObject.Acquire;
try
if path<>PathDelim then
path:= ExcludeTrailingPathDelimiter(path);
nsPath:= StringToNSString( path );
if not _watchPaths.containsObject(nsPath) then exit;
_watchPaths.removeObject( nsPath );
interrupt;
finally
_lockObject.Release;
end;
end;
procedure TDarwinFSWatcher.clearPath;
begin
_lockObject.Acquire;
try
if _watchPaths.count = 0 then exit;
_watchPaths.removeAllObjects;
interrupt;
finally
_lockObject.Release;
end;
end;
procedure TDarwinFSWatcher.waitPath;
begin
_pathsSyncObject.WaitFor( INFINITE );
_pathsSyncObject.ResetEvent;
end;
procedure TDarwinFSWatcher.notifyPath;
begin
_pathsSyncObject.SetEvent;
end;
end.