mirror of
https://github.com/doublecmd/doublecmd.git
synced 2026-06-21 09:58:13 +00:00
ADD: FileSystemWatcher: use fsevents instead of kqueue/kevent on MacOS (#777)
This commit is contained in:
parent
cb9b79a26d
commit
89a67cb0d9
3 changed files with 513 additions and 10 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
433
src/platform/unix/darwin/udarwinfswatch.pas
Normal file
433
src/platform/unix/darwin/udarwinfswatch.pas
Normal 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.
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue