123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626 |
- 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;
- {$IFDEF UNIX}
- FGroup: gid_t;
- FMode: mode_t;
- FOwner: uid_t;
- {$ENDIF}
- 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);
- {$ifdef VerboseDirWatch}
- Writeln('Change ',e^.mask,' (',
- // InotifyEventsToString(e^.mask),
- ') detected for file "',fn,'"');
- {$endif}
- 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.
|