mirror of
https://gitlab.com/freepascal.org/fpc/pas2js.git
synced 2025-04-06 21:07:47 +02:00
623 lines
13 KiB
ObjectPascal
623 lines
13 KiB
ObjectPascal
unit dirwatch;
|
|
{$IFDEF LINUX}
|
|
{$DEFINE USEINOTIFY}
|
|
{$ELSE}
|
|
{$DEFINE USEGENERIC}
|
|
{$ENDIF}
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils,
|
|
{$IFDEF UNIX}
|
|
baseunix,
|
|
{$IFDEF USEINOTIFY}
|
|
ctypes,
|
|
linux,
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
contnrs;
|
|
|
|
|
|
|
|
Type
|
|
TFileEvent = (feModify,feAttrib,feCreate,feDelete);
|
|
TFileEvents = set of TFileEvent;
|
|
|
|
{ TDirectoryEntry }
|
|
TDirectoryEntry = Class(TCollectionItem)
|
|
private
|
|
FEvents: TFileEvents;
|
|
FName: String;
|
|
FAttributes: Integer;
|
|
FGroup: gid_t;
|
|
FMode: mode_t;
|
|
FOwner: uid_t;
|
|
FSize: Int64;
|
|
FTimeStamp: TDateTime;
|
|
Protected
|
|
{$IFDEF USEGENERIC}
|
|
procedure InitWatch(ABaseDir: String; AList: TFPStringHashTable);
|
|
{$ENDIF}
|
|
Public
|
|
Property TimeStamp : TDateTime Read FTimeStamp Write FTimeStamp;
|
|
Property Size : Int64 Read FSize Write FSize;
|
|
Property Attributes : Integer Read FAttributes Write FAttributes;
|
|
{$IFDEF UNIX}
|
|
Property Mode : mode_t Read FMode Write FMode;
|
|
Property Owner : uid_t Read FOwner Write FOwner;
|
|
Property Group : gid_t Read FGroup Write FGroup;
|
|
{$ENDIF}
|
|
Published
|
|
Property Name : String Read FName Write FName;
|
|
Property Events : TFileEvents Read FEvents Write FEvents;
|
|
end;
|
|
|
|
{ TDirectoryEntries }
|
|
|
|
TDirectoryEntries = Class(TCollection)
|
|
private
|
|
function GetE(AIndex : Integer): TDirectoryEntry;
|
|
procedure SetE(AIndex : Integer; AValue: TDirectoryEntry);
|
|
Public
|
|
Function IndexOfEntry(Const AName : String) : Integer;
|
|
Function EntryByName(Const AName : String) : TDirectoryEntry;
|
|
Function AddEntry(Const AName : String) : TDirectoryEntry;
|
|
Property Entries[AIndex : Integer] : TDirectoryEntry Read GetE Write SetE; default;
|
|
end;
|
|
|
|
TFileEventHandler = procedure (Sender : TObject; aEntry : TDirectoryEntry; AEvents : TFileEvents) of Object;
|
|
|
|
{ TDirwatch }
|
|
|
|
TDirwatch = Class(TComponent)
|
|
private
|
|
FIdleInterval: Cardinal;
|
|
FOnIdle: TNotifyEvent;
|
|
FOnIdleNotify: TNotifyEvent;
|
|
FTerminated: Boolean;
|
|
FThreaded: Boolean;
|
|
FWatches: TDirectoryEntries;
|
|
FBaseDir: String;
|
|
FOnChange: TFileEventHandler;
|
|
{$IFDEF USEGENERIC}
|
|
FReference : TFPStringHashTable;
|
|
FOldReference : TFPStringHashTable;
|
|
procedure DoCheckItem(Item: String; const Key: string; var Continue: Boolean);
|
|
procedure DoDeletedItem(Item: String; const Key: string; var Continue: Boolean);
|
|
{$ENDIF}
|
|
{$IFDEF USEINOTIFY}
|
|
FINotifyFD : Cint;
|
|
{$ENDIF}
|
|
function DirectoryEntryForFileName(S: String): TDirectoryEntry;
|
|
procedure DoChangeEvent(Entry: TDirectoryEntry; Events: TFileEvents);
|
|
procedure SetBaseDir(AValue: String);
|
|
Protected
|
|
procedure DoIdle; virtual;
|
|
procedure Check; virtual;
|
|
procedure DoneWatch; virtual;
|
|
procedure DoStartWatch; virtual;
|
|
procedure InitWatch;virtual;
|
|
Public
|
|
Constructor Create(AOWner : TComponent); override;
|
|
Destructor Destroy; override;
|
|
Procedure StartWatch;
|
|
Procedure AddWatch(const aFileName : string; aEvents : TFileEvents);
|
|
Procedure Terminate;
|
|
Property Terminated : Boolean Read FTerminated;
|
|
Published
|
|
Property BaseDir : String read FBaseDir Write SetBaseDir;
|
|
Property OnChange : TFileEventHandler Read FOnChange Write FOnChange;
|
|
Property Threaded : Boolean Read FThreaded Write FThreaded;
|
|
Property Watches : TDirectoryEntries Read FWatches Write FWatches;
|
|
Property OnIdle : TNotifyEvent Read FOnIdle Write FOnIdleNotify;
|
|
Property IdleInterval : Cardinal Read FIdleInterval Write FIdleInterval;
|
|
end;
|
|
|
|
Const
|
|
EventNames : Array[TFileEvent] of string = ('Modify','Attrib','Create','Delete');
|
|
AllEvents = [feModify,feAttrib,feCreate,feDelete];
|
|
|
|
Function FileEventsToStr(Events : TFileEvents) : String;
|
|
|
|
implementation
|
|
|
|
|
|
Function FileEventsToStr(Events : TFileEvents) : String;
|
|
|
|
Var
|
|
E : TFileEvent;
|
|
|
|
begin
|
|
Result:='';
|
|
for E in Events do
|
|
begin
|
|
if Result<>'' then
|
|
Result:=Result+',';
|
|
Result:=Result+EventNames[E];
|
|
end;
|
|
|
|
end;
|
|
|
|
{ TDirwatch }
|
|
Type
|
|
|
|
{ TDirwatchThread }
|
|
|
|
TDirwatchThread = class(TThread)
|
|
Private
|
|
FDir:TDirWatch;
|
|
Public
|
|
Constructor Create(ADirwatch : TDirWatch);
|
|
Procedure Execute; override;
|
|
end;
|
|
|
|
{ TDirectoryEntry }
|
|
|
|
Function SearchRecToString(Info : TSearchRec; AEvents : TFileEvents) : String;
|
|
|
|
begin
|
|
if feAttrib in AEvents then
|
|
Result:=IntToStr(Info.Attr)
|
|
else
|
|
Result:='';
|
|
Result:=Result+';'+IntToStr(Info.Size)+';'+IntToStr(Info.Time);
|
|
end;
|
|
|
|
{$IFDEF USEGENERIC}
|
|
procedure TDirectoryEntry.InitWatch(ABaseDir: String; AList: TFPStringHashTable);
|
|
|
|
Var
|
|
Info : TSearchRec;
|
|
FN : String;
|
|
|
|
begin
|
|
if (ABaseDir<>'') then
|
|
FN:=IncludeTrailingPathDelimiter(ABaseDir)+Name
|
|
else
|
|
FN:=Name;
|
|
if FindFirst(FN,faAnyFile,Info)=0 then
|
|
begin
|
|
if (faDirectory and Info.Attr) = 0 then
|
|
begin
|
|
AList.Add(FN,SearchRecToString(Info,Self.Events))
|
|
end
|
|
else
|
|
begin
|
|
FindClose(Info);
|
|
FN:=IncludeTrailingPathDelimiter(FN);
|
|
if FindFirst(FN+AllFilesMask,0,Info)=0 then
|
|
Repeat
|
|
if (info.Name<>'.') and (Info.Name<>'..') then
|
|
AList.Add(FN+Info.Name,SearchRecToString(Info,Self.Events));
|
|
until (FindNext(Info)<>0)
|
|
end;
|
|
FindClose(Info);
|
|
end
|
|
end;
|
|
|
|
{$ENDIF}
|
|
{$IFDEF USEINOTIFY}
|
|
|
|
{$ENDIF}
|
|
{ TDirwatchThread }
|
|
|
|
constructor TDirwatchThread.Create(ADirwatch: TDirWatch);
|
|
|
|
begin
|
|
FDir:=ADirWatch;
|
|
FreeOnTerminate:=True;
|
|
inherited create(False);
|
|
end;
|
|
|
|
procedure TDirwatchThread.Execute;
|
|
begin
|
|
FDir.DoStartWatch;
|
|
end;
|
|
|
|
|
|
procedure TDirwatch.SetBaseDir(AValue: String);
|
|
begin
|
|
if FBaseDir=AValue then Exit;
|
|
FBaseDir:=AValue;
|
|
FWatches.Clear;
|
|
end;
|
|
|
|
constructor TDirwatch.Create(AOWner: TComponent);
|
|
begin
|
|
inherited Create(AOWner);
|
|
FWatches:=TDirectoryEntries.Create(TDirectoryEntry);
|
|
FidleInterval:=100;
|
|
end;
|
|
|
|
destructor TDirwatch.Destroy;
|
|
begin
|
|
FreeAndNil(FWatches);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
Type
|
|
{ TDirwatchChange }
|
|
TDirwatchChange = Class
|
|
FEntry : TDirectoryEntry;
|
|
FEvents : TFileEvents;
|
|
FDirWatch : TDirWatch;
|
|
Constructor Create(AEntry : TDirectoryEntry;aEvents : TFileEvents;ADirWatch : TDirWatch);
|
|
Procedure DoEvent;
|
|
end;
|
|
|
|
{ TDirwatchChange }
|
|
|
|
constructor TDirwatchChange.Create(AEntry: TDirectoryEntry; aEvents: TFileEvents; ADirWatch: TDirWatch);
|
|
|
|
begin
|
|
FEntry:=AEntry;
|
|
FEvents:=AEvents;
|
|
FDirWatch:=ADirWatch;
|
|
end;
|
|
|
|
procedure TDirwatchChange.DoEvent;
|
|
begin
|
|
FDirwatch.FonChange(FDirwatch,FEntry,FEvents);
|
|
end;
|
|
|
|
Procedure TDirwatch.DoChangeEvent(Entry : TDirectoryEntry; Events : TFileEvents);
|
|
|
|
Var
|
|
W : TDirWatchChange;
|
|
|
|
begin
|
|
try
|
|
if Assigned(FOnChange) then
|
|
if Not Threaded then
|
|
FonChange(Self,Entry,Events)
|
|
else
|
|
begin
|
|
W:=TDirWatchChange.Create(Entry,Events,Self);
|
|
try
|
|
TThread.Synchronize(TThread.CurrentThread,@W.DoEvent)
|
|
finally
|
|
W.Free;
|
|
end;
|
|
end
|
|
Finally
|
|
// Specially created
|
|
if Entry.Collection=Nil then
|
|
FreeAndNil(Entry);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TDirwatch.DoIdle;
|
|
|
|
begin
|
|
if Assigned(FOnIdle) then
|
|
FOnIdle(Self);
|
|
end;
|
|
|
|
Function TDirwatch.DirectoryEntryForFileName(S : String) : TDirectoryEntry;
|
|
|
|
begin
|
|
Result:=FWatches.EntryByName(S);
|
|
if (Result=Nil) then
|
|
Result:=FWatches.EntryByName(ExtractFilePath(S));
|
|
if (Result=Nil) then
|
|
begin
|
|
Result:=TDirectoryEntry.Create(Nil);
|
|
Result.Name:=S;
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF USEGENERIC}
|
|
procedure TDirwatch.DoneWatch;
|
|
|
|
begin
|
|
FreeAndNil(FReference);
|
|
end;
|
|
|
|
procedure TDirwatch.InitWatch;
|
|
|
|
Var
|
|
I : Integer;
|
|
|
|
begin
|
|
FReference:=TFPStringHashTable.Create;
|
|
For I:=0 to FWatches.Count-1 do
|
|
FWatches[i].InitWatch(BaseDir,FReference);
|
|
end;
|
|
|
|
procedure TDirwatch.DoDeletedItem(Item: String; const Key: string; var Continue: Boolean);
|
|
|
|
Var
|
|
DE : TDirectoryEntry;
|
|
|
|
begin
|
|
DE:=FWatches.EntryByName(Key);
|
|
if (DE=Nil) then
|
|
DE:=FWatches.EntryByName(ExtractFilePath(Key));
|
|
if (DE=Nil) then
|
|
begin
|
|
DE:=TDirectoryEntry.Create(Nil);
|
|
DE.Name:=Key;
|
|
end;
|
|
DoChangeEvent(DE,[feDelete]);
|
|
Continue:=False;
|
|
end;
|
|
|
|
procedure TDirwatch.DoCheckItem(Item: String; const Key: string; var Continue: Boolean);
|
|
|
|
Var
|
|
S : String;
|
|
E : TFileEvents;
|
|
DE : TDirectoryEntry;
|
|
|
|
begin
|
|
// Writeln('check file: ',key,' attrs : ',Item);
|
|
E:=[];
|
|
S:=FOldReference[Key];
|
|
if (S='') then
|
|
E:=[feCreate]
|
|
else
|
|
begin
|
|
FOldReference.Delete(Key);
|
|
if (S<>Item) then
|
|
E:=[feAttrib];
|
|
end;
|
|
if E<>[] then
|
|
begin
|
|
DE:=DirectoryEntryForFileName(Key);
|
|
DoChangeEvent(DE,E);
|
|
Continue:=False;
|
|
end;
|
|
end;
|
|
|
|
procedure TDirwatch.Check;
|
|
|
|
begin
|
|
FOldReference:=FReference;
|
|
try
|
|
FReference:=TFPStringHashTable.Create;
|
|
InitWatch;
|
|
FReference.Iterate(@doCheckItem);
|
|
if FoldReference.Count>0 then
|
|
FReference.Iterate(@doDeletedItem);
|
|
// Deleted files
|
|
Sleep(IdleInterval);
|
|
finally
|
|
FreeAndNil(FoldReference);
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
{$IFDEF USEINOTIFY}
|
|
Procedure WatchDirectory(d : string);
|
|
|
|
Const
|
|
Events = IN_MODIFY or IN_ATTRIB or IN_CREATE or IN_DELETE;
|
|
|
|
Var
|
|
fd, wd,fnl,len : cint;
|
|
fds : tfdset;
|
|
e : ^inotify_event;
|
|
buf : Array[0..1023*4] of Byte; // 4K Buffer
|
|
fn : string;
|
|
p : pchar;
|
|
|
|
begin
|
|
fd:=inotify_init;
|
|
try
|
|
wd:=inotify_add_watch(fd,pchar(d),Events);
|
|
fpFD_Zero(fds);
|
|
fpFD_SET(fd,fds);
|
|
While (fpSelect(fd+1,@fds,nil,nil,nil)>=0) do
|
|
begin
|
|
len:=fpRead(fd,buf,sizeof(buf));
|
|
e:=@buf;
|
|
While ((pchar(e)-@buf)<len) do
|
|
begin
|
|
fnl:=e^.len;
|
|
if (fnl>0) then
|
|
begin
|
|
p:=@e^.name+fnl-1;
|
|
While (p^=#0) do
|
|
begin
|
|
dec(p);
|
|
dec(fnl);
|
|
end;
|
|
end;
|
|
setlength(fn,fnl);
|
|
if (fnl>0) then
|
|
move(e^.name,fn[1],fnl);
|
|
Writeln('Change ',e^.mask,' (',
|
|
// InotifyEventsToString(e^.mask),
|
|
') detected for file "',fn,'"');
|
|
ptrint(e):=ptrint(e)+sizeof(inotify_event)+e^.len-1;
|
|
end;
|
|
end;
|
|
finally
|
|
fpClose(fd);
|
|
end;
|
|
end;
|
|
|
|
procedure TDirwatch.DoneWatch;
|
|
|
|
begin
|
|
fpClose(FInotifyFD);
|
|
end;
|
|
|
|
procedure TDirwatch.InitWatch;
|
|
|
|
Const
|
|
NativeEvents : Array[TFileEvent] of cint = (IN_Modify,IN_Attrib,IN_Create,IN_Delete);
|
|
|
|
Var
|
|
WD,I,NEvents : Integer;
|
|
E : TFileEvent;
|
|
BD,FN : String;
|
|
|
|
begin
|
|
BD:=BaseDir;
|
|
if BD<>'' then
|
|
BD:=IncludeTrailingPathDelimiter(BD);
|
|
FINotifyFD:=inotify_init;
|
|
For I:=0 to FWatches.Count-1 do
|
|
begin
|
|
NEvents:=0;
|
|
for E in FWatches[i].Events do
|
|
NEvents:=NEvents OR NativeEvents[E];
|
|
FN:=BD+FWatches[i].Name;
|
|
wd:=inotify_add_watch(FINotifyFD,PChar(FN),NEvents);
|
|
end;
|
|
end;
|
|
|
|
Function NativeEventsToEvents(Native : cint) : TFileEvents;
|
|
|
|
Procedure MA(C : cint; AEvent : TFileEvent);
|
|
|
|
begin
|
|
if (Native and C)<>0 then
|
|
Include(Result,AEvent);
|
|
end;
|
|
|
|
begin
|
|
Result:=[];
|
|
MA(IN_ACCESS,feAttrib);
|
|
MA(IN_MODIFY,feModify);
|
|
MA(IN_ATTRIB,feAttrib);
|
|
MA(IN_CLOSE_WRITE,feAttrib);
|
|
MA(IN_CLOSE_NOWRITE,feAttrib);
|
|
MA(IN_OPEN,feAttrib);
|
|
MA(IN_MOVED_FROM,feCreate);
|
|
MA(IN_MOVED_TO,feDelete);
|
|
MA(IN_CREATE,feCreate);
|
|
Ma(IN_DELETE,feDelete);
|
|
Ma(IN_DELETE_SELF,feDelete);
|
|
Ma(IN_MOVE_SELF,feDelete);
|
|
Ma(IN_UNMOUNT,feDelete);
|
|
// IN_Q_OVERFLOW
|
|
// IN_IGNORED
|
|
|
|
end;
|
|
|
|
procedure TDirwatch.Check;
|
|
|
|
Var
|
|
fnl,len : cint;
|
|
e : ^inotify_event;
|
|
buf : Array[0..1023*4] of Byte; // 4K Buffer
|
|
fn : string;
|
|
p : pchar;
|
|
fds : tfdset;
|
|
Timeout : ttimeval;
|
|
|
|
begin
|
|
fpFD_Zero(fds);
|
|
fpFD_SET(FINotifyFD,fds);
|
|
timeout.tv_sec:=FIdleInterval div 1000;
|
|
timeout.tv_usec:=(FIdleInterval mod 1000)*1000;
|
|
if (fpSelect(FINotifyFD+1,@fds,nil,nil,@Timeout)<=0) then
|
|
exit;
|
|
len:=fpRead(FINotifyFD,buf,sizeof(buf));
|
|
e:=@buf;
|
|
While ((pchar(e)-@buf)<len) do
|
|
begin
|
|
fnl:=e^.len;
|
|
if (fnl>0) then
|
|
begin
|
|
p:=@e^.name+fnl-1;
|
|
While (p^=#0) do
|
|
begin
|
|
dec(p);
|
|
dec(fnl);
|
|
end;
|
|
end;
|
|
setlength(fn,fnl);
|
|
if (fnl>0) then
|
|
move(e^.name,fn[1],fnl);
|
|
DoChangeEvent(DirectoryEntryForFileName(FN),NativeEventsToEvents(E^ .mask));
|
|
ptrint(e):=ptrint(e)+sizeof(inotify_event)+e^.len-1;
|
|
end;
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure TDirwatch.DoStartWatch;
|
|
|
|
begin
|
|
InitWatch;
|
|
try
|
|
While not Terminated do
|
|
begin
|
|
Check;
|
|
if Threaded then
|
|
TThread.Synchronize(TThread.CurrentThread,@DoIdle)
|
|
else
|
|
DoIdle;
|
|
end;
|
|
Finally
|
|
DoneWatch;
|
|
end;
|
|
end;
|
|
|
|
procedure TDirwatch.StartWatch;
|
|
|
|
begin
|
|
If Threaded then
|
|
TDirwatchThread.Create(Self).WaitFor
|
|
else
|
|
DoStartWatch;
|
|
end;
|
|
|
|
procedure TDirwatch.AddWatch(const aFileName: string; aEvents: TFileEvents);
|
|
begin
|
|
FWatches.AddEntry(AFileName).Events:=AEvents;
|
|
end;
|
|
|
|
procedure TDirwatch.Terminate;
|
|
begin
|
|
FTerminated:=True;
|
|
end;
|
|
|
|
{ TDirectoryEntries }
|
|
|
|
function TDirectoryEntries.GetE(AIndex : Integer): TDirectoryEntry;
|
|
begin
|
|
Result:=TDirectoryEntry(Items[AIndex]);
|
|
end;
|
|
|
|
procedure TDirectoryEntries.SetE(AIndex : Integer; AValue: TDirectoryEntry);
|
|
begin
|
|
Items[AIndex]:=AValue;
|
|
end;
|
|
|
|
function TDirectoryEntries.IndexOfEntry(const AName: String): Integer;
|
|
|
|
begin
|
|
Result:=Count-1;
|
|
While (Result>=0) and (GetE(Result).Name<>AName) do
|
|
Dec(Result);
|
|
end;
|
|
|
|
function TDirectoryEntries.EntryByName(const AName: String): TDirectoryEntry;
|
|
|
|
Var
|
|
I : Integer;
|
|
|
|
begin
|
|
I:=IndexOfEntry(AName);
|
|
If (I=-1) then
|
|
Result:=Nil
|
|
else
|
|
Result:=GetE(I);
|
|
end;
|
|
|
|
function TDirectoryEntries.AddEntry(Const AName: String): TDirectoryEntry;
|
|
begin
|
|
Result:=Add as TDirectoryEntry;
|
|
Result.Name:=AName;
|
|
end;
|
|
|
|
end.
|
|
|