mirror of
https://github.com/doublecmd/doublecmd.git
synced 2026-06-21 09:58:13 +00:00
UPD: DARWIN: Renamed Event supported in fswatch on MacOS (#847)
This commit is contained in:
parent
615823d2e8
commit
409cdae2bf
2 changed files with 258 additions and 72 deletions
|
|
@ -907,11 +907,18 @@ begin
|
|||
([ecStructChanged, ecAttribChanged] * event.categories = [ecAttribChanged])
|
||||
then exit;
|
||||
|
||||
FCurrentEventData.FileName := ExtractFileName( event.fullPath );
|
||||
|
||||
if TDarwinFSWatchEventCategory.ecRemoved in event.categories then
|
||||
FCurrentEventData.EventType := fswFileDeleted
|
||||
else if TDarwinFSWatchEventCategory.ecRenamed in event.categories then
|
||||
FCurrentEventData.EventType := fswUnknownChange
|
||||
else if TDarwinFSWatchEventCategory.ecCreated in event.categories then
|
||||
else if TDarwinFSWatchEventCategory.ecRenamed in event.categories then begin
|
||||
if ExtractFilePath(event.fullPath)=ExtractFilePath(event.renamedPath) then begin
|
||||
// fswFileRenamed only when FileName and NewFileName in the same dir
|
||||
// otherwise keep fswUnknownChange
|
||||
FCurrentEventData.EventType := fswFileRenamed;
|
||||
FCurrentEventData.NewFileName := ExtractFileName( event.renamedPath );
|
||||
end;
|
||||
end else if TDarwinFSWatchEventCategory.ecCreated in event.categories then
|
||||
FCurrentEventData.EventType := fswFileCreated
|
||||
else if TDarwinFSWatchEventCategory.ecCoreAttribChanged in event.categories then
|
||||
FCurrentEventData.EventType := fswFileChanged
|
||||
|
|
@ -926,7 +933,6 @@ begin
|
|||
|
||||
FCurrentEventData.OriginalEvent := nil;
|
||||
end;
|
||||
|
||||
{$ENDIF}
|
||||
|
||||
{$IF DEFINED(HAIKUQT)}
|
||||
|
|
@ -984,7 +990,6 @@ begin
|
|||
{$IFDEF DARWIN}
|
||||
// FlatView Watch is supported on MacOS
|
||||
// FCurrentEventData.Path contains WatchPath
|
||||
// FCurrentEventData.FileName contains FullPath
|
||||
// so in FlatView Mode, Path need to be adjusted to the Real Path
|
||||
if TFileView(UserData).FlatView then begin
|
||||
FCurrentEventData.Path := ExcludeTrailingPathDelimiter(ExtractFilePath(FCurrentEventData.OriginalEvent.fullPath));
|
||||
|
|
@ -994,7 +999,6 @@ begin
|
|||
continue;
|
||||
FCurrentEventData.Path := AWatchPath;
|
||||
end;
|
||||
FCurrentEventData.FileName := ExtractFileName( FCurrentEventData.OriginalEvent.fullPath );
|
||||
{$ENDIF}
|
||||
WatcherEvent(FCurrentEventData);
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
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.
|
||||
2. subdirectories monitor supported.
|
||||
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.
|
||||
|
|
@ -40,8 +40,8 @@ unit uDarwinFSWatch;
|
|||
interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, SyncObjs,
|
||||
MacOSAll, CocoaAll;
|
||||
Classes, SysUtils, Contnrs, SyncObjs,
|
||||
MacOSAll, CocoaAll, BaseUnix;
|
||||
|
||||
type TDarwinFSWatchEventCategory = (
|
||||
ecAttribChanged, ecCoreAttribChanged, ecXattrChanged,
|
||||
|
|
@ -51,16 +51,46 @@ type TDarwinFSWatchEventCategory = (
|
|||
|
||||
type TDarwinFSWatchEventCategories = set of TDarwinFSWatchEventCategory;
|
||||
|
||||
type TInternalEvent = class
|
||||
private
|
||||
path: String;
|
||||
renamedPath: String; // only for Ranamed Event
|
||||
flags: FSEventStreamEventFlags;
|
||||
iNode: UInt64;
|
||||
private
|
||||
function deepCopy(): TInternalEvent;
|
||||
end;
|
||||
|
||||
type TDarwinFSWatchEventSession = class
|
||||
private
|
||||
_list: TObjectList;
|
||||
public
|
||||
constructor create; overload;
|
||||
constructor create( const amount:size_t; eventPaths:CFArrayRef; flags:FSEventStreamEventFlagsPtr );
|
||||
destructor destroy; override;
|
||||
private
|
||||
function deepCopy(): TDarwinFSWatchEventSession;
|
||||
|
||||
function count(): Integer;
|
||||
function getItem( index:Integer ): TInternalEvent;
|
||||
procedure adjustRenamedEventIfNecessary( index:Integer );
|
||||
function isRenamed( event:TInternalEvent ): Boolean;
|
||||
|
||||
property items[index: Integer]: TInternalEvent read getItem; default;
|
||||
end;
|
||||
|
||||
type TDarwinFSWatchEvent = class
|
||||
private
|
||||
_categories: TDarwinFSWatchEventCategories;
|
||||
_watchPath: String;
|
||||
_fullPath: String;
|
||||
_renamedPath: String;
|
||||
_rawEventFlags: UInt32;
|
||||
_rawINode: UInt64;
|
||||
private
|
||||
procedure createCategories;
|
||||
{%H-}constructor create( const aWatchPath:String; const internalEvent:TInternalEvent );
|
||||
public
|
||||
constructor create( const aWatchPath:String; const aFullPath:String; const aFlags:UInt32 );
|
||||
function isDropabled(): Boolean;
|
||||
function categoriesToStr(): String;
|
||||
function rawEventFlagsToStr(): String;
|
||||
|
|
@ -68,7 +98,9 @@ public
|
|||
property categories: TDarwinFSWatchEventCategories read _categories;
|
||||
property watchPath: String read _watchPath;
|
||||
property fullPath: String read _fullPath;
|
||||
property renamedPath: String read _renamedPath;
|
||||
property rawEventFlags: UInt32 read _rawEventFlags;
|
||||
property rawINode: UInt64 read _rawINode;
|
||||
end;
|
||||
|
||||
type TDarwinFSWatchCallBack = Procedure( event:TDarwinFSWatchEvent ) of object;
|
||||
|
|
@ -92,8 +124,8 @@ private
|
|||
_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 handleEvents( const originalSession:TDarwinFSWatchEventSession );
|
||||
procedure doCallback( const watchPath:String; const internalEvent:TInternalEvent );
|
||||
|
||||
procedure updateStream;
|
||||
procedure closeStream;
|
||||
|
|
@ -112,20 +144,31 @@ public
|
|||
procedure clearPath;
|
||||
end;
|
||||
|
||||
|
||||
implementation
|
||||
|
||||
const
|
||||
kFSEventStreamCreateFlagUseExtendedData = $00000040;
|
||||
kFSEventStreamEventFlagItemIsHardlink = $00100000;
|
||||
kFSEventStreamEventFlagItemIsLastHardlink = $00200000;
|
||||
kFSEventStreamEventFlagItemCloned = $00400000;
|
||||
|
||||
var
|
||||
kFSEventStreamEventExtendedDataPathKey: CFStringRef;
|
||||
kFSEventStreamEventExtendedFileIDKey: CFStringRef;
|
||||
|
||||
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 );
|
||||
constructor TDarwinFSWatchEvent.create( const aWatchPath:String; const internalEvent:TInternalEvent );
|
||||
begin
|
||||
Inherited Create;
|
||||
_watchPath:= aWatchPath;
|
||||
_fullPath:= aFullPath;
|
||||
_rawEventFlags:= aFlags;
|
||||
_fullPath:= internalEvent.path;
|
||||
_renamedPath:= internalEvent.renamedPath;
|
||||
_rawEventFlags:= internalEvent.flags;
|
||||
_rawINode:= internalEvent.iNode;
|
||||
createCategories;
|
||||
end;
|
||||
|
||||
|
|
@ -156,8 +199,10 @@ begin
|
|||
if (_rawEventFlags and kFSEventStreamEventFlagRootChanged)<>0 then begin
|
||||
_categories:= _categories + [ecRootChanged];
|
||||
end else begin
|
||||
if (_fullPath<>watchPath) and (_fullPath.CountChar(PathDelim)<>_watchPath.CountChar(PathDelim)+1) then
|
||||
_categories:= _categories + [ecChildChanged];
|
||||
if (_fullPath<>watchPath) and (_fullPath.CountChar(PathDelim)<>_watchPath.CountChar(PathDelim)+1) then begin
|
||||
if (_renamedPath.IsEmpty) or ((_renamedPath<>watchPath) and (_renamedPath.CountChar(PathDelim)<>_watchPath.CountChar(PathDelim)+1)) then
|
||||
_categories:= _categories + [ecChildChanged];
|
||||
end;
|
||||
end;
|
||||
|
||||
if _categories * [ecAttribChanged,ecStructChanged,ecRootChanged] = [] then
|
||||
|
|
@ -228,9 +273,9 @@ begin
|
|||
Result:= Result + '|IsDir';
|
||||
if (_rawEventFlags and kFSEventStreamEventFlagItemIsSymlink)<>0 then
|
||||
Result:= Result + '|IsSymlink';
|
||||
if (_rawEventFlags and $00100000)<>0 then
|
||||
if (_rawEventFlags and kFSEventStreamEventFlagItemIsHardlink)<>0 then
|
||||
Result:= Result + '|IsHardLink';
|
||||
if (_rawEventFlags and $00200000)<>0 then
|
||||
if (_rawEventFlags and kFSEventStreamEventFlagItemIsLastHardlink)<>0 then
|
||||
Result:= Result + '|IsLastHardLink';
|
||||
|
||||
if (_rawEventFlags and kFSEventStreamEventFlagMustScanSubDirs)<>0 then
|
||||
|
|
@ -249,7 +294,7 @@ begin
|
|||
Result:= Result + '|Unmount';
|
||||
if (_rawEventFlags and kFSEventStreamEventFlagOwnEvent)<>0 then
|
||||
Result:= Result + '|OwnEvent';
|
||||
if (_rawEventFlags and $00400000)<>0 then
|
||||
if (_rawEventFlags and kFSEventStreamEventFlagItemCloned)<>0 then
|
||||
Result:= Result + '|Cloned';
|
||||
|
||||
if (_rawEventFlags and $FF800000)<>0 then
|
||||
|
|
@ -261,6 +306,153 @@ begin
|
|||
Result:= Result.TrimLeft( '|' );
|
||||
end;
|
||||
|
||||
// Note: try to avoid string copy
|
||||
function isMatchWatchPath(
|
||||
const internalEvent:TInternalEvent;
|
||||
const watchPath:String;
|
||||
const watchSubtree:Boolean ): Boolean;
|
||||
var
|
||||
fullPath: String;
|
||||
fullPathDeep: Integer;
|
||||
watchPathDeep: Integer;
|
||||
begin
|
||||
fullPath:= internalEvent.path;;
|
||||
|
||||
// detect if fullPath=watchPath
|
||||
if (internalEvent.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;
|
||||
|
||||
function TInternalEvent.deepCopy(): TInternalEvent;
|
||||
begin
|
||||
Result:= TInternalEvent.Create;
|
||||
Result.path:= self.path;
|
||||
Result.renamedPath:= self.renamedPath;
|
||||
Result.flags:= self.flags;
|
||||
Result.iNode:= self.iNode;
|
||||
end;
|
||||
|
||||
function TDarwinFSWatchEventSession.deepCopy(): TDarwinFSWatchEventSession;
|
||||
var
|
||||
list: TObjectList;
|
||||
i: Integer;
|
||||
begin
|
||||
list:= TObjectList.Create;
|
||||
for i:=0 to count-1 do
|
||||
begin
|
||||
list.Add( Items[i].deepCopy() );
|
||||
end;
|
||||
|
||||
Result:= TDarwinFSWatchEventSession.create;
|
||||
Result._list:= list;
|
||||
end;
|
||||
|
||||
constructor TDarwinFSWatchEventSession.create();
|
||||
begin
|
||||
Inherited;
|
||||
end;
|
||||
|
||||
constructor TDarwinFSWatchEventSession.create( const amount:size_t; eventPaths:CFArrayRef; flags:FSEventStreamEventFlagsPtr );
|
||||
var
|
||||
i: size_t;
|
||||
event: TInternalEvent;
|
||||
infoDict: CFDictionaryRef;
|
||||
nsPath: NSString;
|
||||
nsNode: CFNumberRef;
|
||||
begin
|
||||
_list:= TObjectList.Create;
|
||||
for i:=0 to amount-1 do
|
||||
begin
|
||||
infoDict:= CFArrayGetValueAtIndex( eventPaths, i );
|
||||
nsPath:= CFDictionaryGetValue( infoDict, kFSEventStreamEventExtendedDataPathKey );
|
||||
nsNode:= CFDictionaryGetValue( infoDict, kFSEventStreamEventExtendedFileIDKey );
|
||||
|
||||
event:= TInternalEvent.Create;
|
||||
event.path:= nsPath.UTF8String;
|
||||
event.flags:= flags^;
|
||||
if Assigned(nsNode) then
|
||||
CFNumberGetValue( nsNode, kCFNumberLongLongType, @(event.iNode) );
|
||||
_list.Add( event );
|
||||
|
||||
inc(flags);
|
||||
end;
|
||||
end;
|
||||
|
||||
destructor TDarwinFSWatchEventSession.destroy;
|
||||
begin
|
||||
FreeAndNil( _list );
|
||||
end;
|
||||
|
||||
function TDarwinFSWatchEventSession.count: Integer;
|
||||
begin
|
||||
Result:= _list.Count;
|
||||
end;
|
||||
|
||||
function TDarwinFSWatchEventSession.getItem( index:Integer ): TInternalEvent;
|
||||
begin
|
||||
Result:= TInternalEvent( _list[index] );
|
||||
end;
|
||||
|
||||
function TDarwinFSWatchEventSession.isRenamed( event:TInternalEvent ): Boolean;
|
||||
begin
|
||||
Result:= event.flags
|
||||
and (kFSEventStreamEventFlagItemRenamed or kFSEventStreamEventFlagItemCreated or kFSEventStreamEventFlagItemRemoved)
|
||||
= kFSEventStreamEventFlagItemRenamed;
|
||||
end;
|
||||
|
||||
procedure TDarwinFSWatchEventSession.adjustRenamedEventIfNecessary( index:Integer );
|
||||
var
|
||||
currentEvent: TInternalEvent;
|
||||
nextEvent: TInternalEvent;
|
||||
i: Integer;
|
||||
begin
|
||||
currentEvent:= Items[index];
|
||||
if not isRenamed(currentEvent)
|
||||
then exit;
|
||||
|
||||
// find all related Renamed Event, and try to build a complete Renamed Event with NewPath
|
||||
i:= index + 1;
|
||||
while i < count do
|
||||
begin
|
||||
nextEvent:= Items[i];
|
||||
if isRenamed(nextEvent) and (nextEvent.iNode=currentEvent.iNode) then begin
|
||||
if currentEvent.path<>nextEvent.path then currentEvent.renamedPath:= nextEvent.path;
|
||||
_list.Delete( i );
|
||||
end else begin
|
||||
inc( i );
|
||||
end;
|
||||
end;
|
||||
|
||||
// got the complete Renamed Event, then exit
|
||||
if not currentEvent.renamedPath.IsEmpty then
|
||||
exit;
|
||||
|
||||
// got the incomplete Renamed Event, change to Created or Removed Event
|
||||
currentEvent.flags:= currentEvent.flags and (not kFSEventStreamEventFlagItemRenamed);
|
||||
if FpAccess(currentEvent.path, F_OK) = 0 then begin
|
||||
currentEvent.flags:= currentEvent.flags or kFSEventStreamEventFlagItemCreated;
|
||||
end else begin
|
||||
currentEvent.flags:= currentEvent.flags or kFSEventStreamEventFlagItemRemoved;
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TDarwinFSWatcher.create( const callback:TDarwinFSWatchCallBack; const watchSubtree:Boolean; const latency:Integer );
|
||||
begin
|
||||
Inherited Create;
|
||||
|
|
@ -289,74 +481,56 @@ begin
|
|||
Inherited;
|
||||
end;
|
||||
|
||||
procedure cdeclFSEventsCallback( {%H-}streamRef: ConstFSEventStreamRef;
|
||||
procedure cdeclFSEventsCallback(
|
||||
{%H-}streamRef: ConstFSEventStreamRef;
|
||||
clientCallBackInfo: UnivPtr;
|
||||
numEvents: size_t;
|
||||
eventPaths: UnivPtr;
|
||||
eventFlags: FSEventStreamEventFlagsPtr;
|
||||
eventIds: FSEventStreamEventIdPtr ); cdecl;
|
||||
{%H-}eventIds: FSEventStreamEventIdPtr ); cdecl;
|
||||
var
|
||||
watcher: TDarwinFSWatcher absolute clientCallBackInfo;
|
||||
session: TDarwinFSWatchEventSession;
|
||||
begin
|
||||
watcher.handleEvents( NumEvents, PPChar(EventPaths), EventFlags, EventIds );
|
||||
session:= TDarwinFSWatchEventSession.create( numEvents, eventPaths, eventFlags );
|
||||
watcher.handleEvents( session );
|
||||
FreeAndNil( session );
|
||||
end;
|
||||
|
||||
procedure TDarwinFSWatcher.handleEvents( const amount:size_t; paths:PPChar; flags:FSEventStreamEventFlagsPtr; ids:FSEventStreamEventIdPtr );
|
||||
procedure TDarwinFSWatcher.handleEvents( const originalSession:TDarwinFSWatchEventSession );
|
||||
var
|
||||
i: size_t;
|
||||
nsWatchPath: NSString;
|
||||
watchPath: String;
|
||||
event: TInternalEvent;
|
||||
i: Integer;
|
||||
session: TDarwinFSWatchEventSession;
|
||||
begin
|
||||
for i:=amount downto 1 do
|
||||
for nsWatchPath in _streamPaths do
|
||||
begin
|
||||
doCallback( paths^, flags^ );
|
||||
inc(paths);
|
||||
inc(flags);
|
||||
watchPath:= nsWatchPath.UTF8String;
|
||||
session:= originalSession.deepCopy();
|
||||
i:= 0;
|
||||
while i < session.count do
|
||||
begin
|
||||
event:= session[i];
|
||||
if isMatchWatchPath(event, watchPath, _watchSubtree) then
|
||||
begin
|
||||
session.adjustRenamedEventIfNecessary( i );
|
||||
doCallback( watchPath, event );
|
||||
end;
|
||||
inc( i );
|
||||
end;
|
||||
session.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Note: try to avoid string copy
|
||||
function isMatchWatchPath(
|
||||
const fullPath:String; const watchPath:String;
|
||||
const flags:FSEventStreamEventFlags; const watchSubtree:Boolean ): Boolean;
|
||||
procedure TDarwinFSWatcher.doCallback( const watchPath:String; const internalEvent:TInternalEvent );
|
||||
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;
|
||||
event:= TDarwinFSWatchEvent.create( watchPath, internalEvent );
|
||||
_callback( event );
|
||||
event.Free;
|
||||
end;
|
||||
|
||||
procedure TDarwinFSWatcher.updateStream;
|
||||
|
|
@ -376,7 +550,10 @@ begin
|
|||
exit;
|
||||
end;
|
||||
|
||||
flags:= kFSEventStreamCreateFlagFileEvents or kFSEventStreamCreateFlagWatchRoot;
|
||||
flags:= kFSEventStreamCreateFlagFileEvents
|
||||
or kFSEventStreamCreateFlagWatchRoot
|
||||
or kFSEventStreamCreateFlagUseCFTypes
|
||||
or kFSEventStreamCreateFlagUseExtendedData;
|
||||
_stream:= FSEventStreamCreate( nil,
|
||||
@cdeclFSEventsCallback,
|
||||
@_streamContext,
|
||||
|
|
@ -439,6 +616,7 @@ end;
|
|||
|
||||
procedure TDarwinFSWatcher.interrupt;
|
||||
begin
|
||||
Sleep( 20 );
|
||||
if Assigned(_stream) then
|
||||
CFRunLoopStop( _runLoop )
|
||||
else
|
||||
|
|
@ -502,5 +680,9 @@ begin
|
|||
_pathsSyncObject.SetEvent;
|
||||
end;
|
||||
|
||||
initialization
|
||||
kFSEventStreamEventExtendedDataPathKey:= CFSTR('path');
|
||||
kFSEventStreamEventExtendedFileIDKey:= CFSTR('fileID');
|
||||
|
||||
end.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue