dirwatch.pp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. unit dirwatch;
  2. {$IFDEF LINUX}
  3. {$DEFINE USEINOTIFY}
  4. {$ELSE}
  5. {$DEFINE USEGENERIC}
  6. {$ENDIF}
  7. {$mode objfpc}{$H+}
  8. interface
  9. uses
  10. Classes, SysUtils,
  11. {$IFDEF UNIX}
  12. baseunix,
  13. {$IFDEF USEINOTIFY}
  14. ctypes,
  15. linux,
  16. {$ENDIF}
  17. {$ENDIF}
  18. contnrs;
  19. Type
  20. TFileEvent = (feModify,feAttrib,feCreate,feDelete);
  21. TFileEvents = set of TFileEvent;
  22. { TDirectoryEntry }
  23. TDirectoryEntry = Class(TCollectionItem)
  24. private
  25. FEvents: TFileEvents;
  26. FName: String;
  27. FAttributes: Integer;
  28. {$IFDEF UNIX}
  29. FGroup: gid_t;
  30. FMode: mode_t;
  31. FOwner: uid_t;
  32. {$ENDIF}
  33. FSize: Int64;
  34. FTimeStamp: TDateTime;
  35. Protected
  36. {$IFDEF USEGENERIC}
  37. procedure InitWatch(ABaseDir: String; AList: TFPStringHashTable);
  38. {$ENDIF}
  39. Public
  40. Property TimeStamp : TDateTime Read FTimeStamp Write FTimeStamp;
  41. Property Size : Int64 Read FSize Write FSize;
  42. Property Attributes : Integer Read FAttributes Write FAttributes;
  43. {$IFDEF UNIX}
  44. Property Mode : mode_t Read FMode Write FMode;
  45. Property Owner : uid_t Read FOwner Write FOwner;
  46. Property Group : gid_t Read FGroup Write FGroup;
  47. {$ENDIF}
  48. Published
  49. Property Name : String Read FName Write FName;
  50. Property Events : TFileEvents Read FEvents Write FEvents;
  51. end;
  52. { TDirectoryEntries }
  53. TDirectoryEntries = Class(TCollection)
  54. private
  55. function GetE(AIndex : Integer): TDirectoryEntry;
  56. procedure SetE(AIndex : Integer; AValue: TDirectoryEntry);
  57. Public
  58. Function IndexOfEntry(Const AName : String) : Integer;
  59. Function EntryByName(Const AName : String) : TDirectoryEntry;
  60. Function AddEntry(Const AName : String) : TDirectoryEntry;
  61. Property Entries[AIndex : Integer] : TDirectoryEntry Read GetE Write SetE; default;
  62. end;
  63. TFileEventHandler = procedure (Sender : TObject; aEntry : TDirectoryEntry; AEvents : TFileEvents) of Object;
  64. { TDirwatch }
  65. TDirwatch = Class(TComponent)
  66. private
  67. FIdleInterval: Cardinal;
  68. FOnIdle: TNotifyEvent;
  69. FOnIdleNotify: TNotifyEvent;
  70. FTerminated: Boolean;
  71. FThreaded: Boolean;
  72. FWatches: TDirectoryEntries;
  73. FBaseDir: String;
  74. FOnChange: TFileEventHandler;
  75. {$IFDEF USEGENERIC}
  76. FReference : TFPStringHashTable;
  77. FOldReference : TFPStringHashTable;
  78. procedure DoCheckItem(Item: String; const Key: string; var Continue: Boolean);
  79. procedure DoDeletedItem(Item: String; const Key: string; var Continue: Boolean);
  80. {$ENDIF}
  81. {$IFDEF USEINOTIFY}
  82. FINotifyFD : Cint;
  83. {$ENDIF}
  84. function DirectoryEntryForFileName(S: String): TDirectoryEntry;
  85. procedure DoChangeEvent(Entry: TDirectoryEntry; Events: TFileEvents);
  86. procedure SetBaseDir(AValue: String);
  87. Protected
  88. procedure DoIdle; virtual;
  89. procedure Check; virtual;
  90. procedure DoneWatch; virtual;
  91. procedure DoStartWatch; virtual;
  92. procedure InitWatch;virtual;
  93. Public
  94. Constructor Create(AOWner : TComponent); override;
  95. Destructor Destroy; override;
  96. Procedure StartWatch;
  97. Procedure AddWatch(const aFileName : string; aEvents : TFileEvents);
  98. Procedure Terminate;
  99. Property Terminated : Boolean Read FTerminated;
  100. Published
  101. Property BaseDir : String read FBaseDir Write SetBaseDir;
  102. Property OnChange : TFileEventHandler Read FOnChange Write FOnChange;
  103. Property Threaded : Boolean Read FThreaded Write FThreaded;
  104. Property Watches : TDirectoryEntries Read FWatches Write FWatches;
  105. Property OnIdle : TNotifyEvent Read FOnIdle Write FOnIdleNotify;
  106. Property IdleInterval : Cardinal Read FIdleInterval Write FIdleInterval;
  107. end;
  108. Const
  109. EventNames : Array[TFileEvent] of string = ('Modify','Attrib','Create','Delete');
  110. AllEvents = [feModify,feAttrib,feCreate,feDelete];
  111. Function FileEventsToStr(Events : TFileEvents) : String;
  112. implementation
  113. Function FileEventsToStr(Events : TFileEvents) : String;
  114. Var
  115. E : TFileEvent;
  116. begin
  117. Result:='';
  118. for E in Events do
  119. begin
  120. if Result<>'' then
  121. Result:=Result+',';
  122. Result:=Result+EventNames[E];
  123. end;
  124. end;
  125. { TDirwatch }
  126. Type
  127. { TDirwatchThread }
  128. TDirwatchThread = class(TThread)
  129. Private
  130. FDir:TDirWatch;
  131. Public
  132. Constructor Create(ADirwatch : TDirWatch);
  133. Procedure Execute; override;
  134. end;
  135. { TDirectoryEntry }
  136. Function SearchRecToString(Info : TSearchRec; AEvents : TFileEvents) : String;
  137. begin
  138. if feAttrib in AEvents then
  139. Result:=IntToStr(Info.Attr)
  140. else
  141. Result:='';
  142. Result:=Result+';'+IntToStr(Info.Size)+';'+IntToStr(Info.Time);
  143. end;
  144. {$IFDEF USEGENERIC}
  145. procedure TDirectoryEntry.InitWatch(ABaseDir: String; AList: TFPStringHashTable);
  146. Var
  147. Info : TSearchRec;
  148. FN : String;
  149. begin
  150. if (ABaseDir<>'') then
  151. FN:=IncludeTrailingPathDelimiter(ABaseDir)+Name
  152. else
  153. FN:=Name;
  154. if FindFirst(FN,faAnyFile,Info)=0 then
  155. begin
  156. if (faDirectory and Info.Attr) = 0 then
  157. begin
  158. AList.Add(FN,SearchRecToString(Info,Self.Events))
  159. end
  160. else
  161. begin
  162. FindClose(Info);
  163. FN:=IncludeTrailingPathDelimiter(FN);
  164. if FindFirst(FN+AllFilesMask,0,Info)=0 then
  165. Repeat
  166. if (info.Name<>'.') and (Info.Name<>'..') then
  167. AList.Add(FN+Info.Name,SearchRecToString(Info,Self.Events));
  168. until (FindNext(Info)<>0)
  169. end;
  170. FindClose(Info);
  171. end
  172. end;
  173. {$ENDIF}
  174. {$IFDEF USEINOTIFY}
  175. {$ENDIF}
  176. { TDirwatchThread }
  177. constructor TDirwatchThread.Create(ADirwatch: TDirWatch);
  178. begin
  179. FDir:=ADirWatch;
  180. FreeOnTerminate:=True;
  181. inherited create(False);
  182. end;
  183. procedure TDirwatchThread.Execute;
  184. begin
  185. FDir.DoStartWatch;
  186. end;
  187. procedure TDirwatch.SetBaseDir(AValue: String);
  188. begin
  189. if FBaseDir=AValue then Exit;
  190. FBaseDir:=AValue;
  191. FWatches.Clear;
  192. end;
  193. constructor TDirwatch.Create(AOWner: TComponent);
  194. begin
  195. inherited Create(AOWner);
  196. FWatches:=TDirectoryEntries.Create(TDirectoryEntry);
  197. FidleInterval:=100;
  198. end;
  199. destructor TDirwatch.Destroy;
  200. begin
  201. FreeAndNil(FWatches);
  202. inherited Destroy;
  203. end;
  204. Type
  205. { TDirwatchChange }
  206. TDirwatchChange = Class
  207. FEntry : TDirectoryEntry;
  208. FEvents : TFileEvents;
  209. FDirWatch : TDirWatch;
  210. Constructor Create(AEntry : TDirectoryEntry;aEvents : TFileEvents;ADirWatch : TDirWatch);
  211. Procedure DoEvent;
  212. end;
  213. { TDirwatchChange }
  214. constructor TDirwatchChange.Create(AEntry: TDirectoryEntry; aEvents: TFileEvents; ADirWatch: TDirWatch);
  215. begin
  216. FEntry:=AEntry;
  217. FEvents:=AEvents;
  218. FDirWatch:=ADirWatch;
  219. end;
  220. procedure TDirwatchChange.DoEvent;
  221. begin
  222. FDirwatch.FonChange(FDirwatch,FEntry,FEvents);
  223. end;
  224. Procedure TDirwatch.DoChangeEvent(Entry : TDirectoryEntry; Events : TFileEvents);
  225. Var
  226. W : TDirWatchChange;
  227. begin
  228. try
  229. if Assigned(FOnChange) then
  230. if Not Threaded then
  231. FonChange(Self,Entry,Events)
  232. else
  233. begin
  234. W:=TDirWatchChange.Create(Entry,Events,Self);
  235. try
  236. TThread.Synchronize(TThread.CurrentThread,@W.DoEvent)
  237. finally
  238. W.Free;
  239. end;
  240. end
  241. Finally
  242. // Specially created
  243. if Entry.Collection=Nil then
  244. FreeAndNil(Entry);
  245. end;
  246. end;
  247. procedure TDirwatch.DoIdle;
  248. begin
  249. if Assigned(FOnIdle) then
  250. FOnIdle(Self);
  251. end;
  252. Function TDirwatch.DirectoryEntryForFileName(S : String) : TDirectoryEntry;
  253. begin
  254. Result:=FWatches.EntryByName(S);
  255. if (Result=Nil) then
  256. Result:=FWatches.EntryByName(ExtractFilePath(S));
  257. if (Result=Nil) then
  258. begin
  259. Result:=TDirectoryEntry.Create(Nil);
  260. Result.Name:=S;
  261. end;
  262. end;
  263. {$IFDEF USEGENERIC}
  264. procedure TDirwatch.DoneWatch;
  265. begin
  266. FreeAndNil(FReference);
  267. end;
  268. procedure TDirwatch.InitWatch;
  269. Var
  270. I : Integer;
  271. begin
  272. FReference:=TFPStringHashTable.Create;
  273. For I:=0 to FWatches.Count-1 do
  274. FWatches[i].InitWatch(BaseDir,FReference);
  275. end;
  276. procedure TDirwatch.DoDeletedItem(Item: String; const Key: string; var Continue: Boolean);
  277. Var
  278. DE : TDirectoryEntry;
  279. begin
  280. DE:=FWatches.EntryByName(Key);
  281. if (DE=Nil) then
  282. DE:=FWatches.EntryByName(ExtractFilePath(Key));
  283. if (DE=Nil) then
  284. begin
  285. DE:=TDirectoryEntry.Create(Nil);
  286. DE.Name:=Key;
  287. end;
  288. DoChangeEvent(DE,[feDelete]);
  289. Continue:=False;
  290. end;
  291. procedure TDirwatch.DoCheckItem(Item: String; const Key: string; var Continue: Boolean);
  292. Var
  293. S : String;
  294. E : TFileEvents;
  295. DE : TDirectoryEntry;
  296. begin
  297. // Writeln('check file: ',key,' attrs : ',Item);
  298. E:=[];
  299. S:=FOldReference[Key];
  300. if (S='') then
  301. E:=[feCreate]
  302. else
  303. begin
  304. FOldReference.Delete(Key);
  305. if (S<>Item) then
  306. E:=[feAttrib];
  307. end;
  308. if E<>[] then
  309. begin
  310. DE:=DirectoryEntryForFileName(Key);
  311. DoChangeEvent(DE,E);
  312. Continue:=False;
  313. end;
  314. end;
  315. procedure TDirwatch.Check;
  316. begin
  317. FOldReference:=FReference;
  318. try
  319. FReference:=TFPStringHashTable.Create;
  320. InitWatch;
  321. FReference.Iterate(@doCheckItem);
  322. if FoldReference.Count>0 then
  323. FReference.Iterate(@doDeletedItem);
  324. // Deleted files
  325. Sleep(IdleInterval);
  326. finally
  327. FreeAndNil(FoldReference);
  328. end;
  329. end;
  330. {$ENDIF}
  331. {$IFDEF USEINOTIFY}
  332. Procedure WatchDirectory(d : string);
  333. Const
  334. Events = IN_MODIFY or IN_ATTRIB or IN_CREATE or IN_DELETE;
  335. Var
  336. fd, wd,fnl,len : cint;
  337. fds : tfdset;
  338. e : ^inotify_event;
  339. buf : Array[0..1023*4] of Byte; // 4K Buffer
  340. fn : string;
  341. p : pchar;
  342. begin
  343. fd:=inotify_init;
  344. try
  345. wd:=inotify_add_watch(fd,pchar(d),Events);
  346. fpFD_Zero(fds);
  347. fpFD_SET(fd,fds);
  348. While (fpSelect(fd+1,@fds,nil,nil,nil)>=0) do
  349. begin
  350. len:=fpRead(fd,buf,sizeof(buf));
  351. e:=@buf;
  352. While ((pchar(e)-@buf)<len) do
  353. begin
  354. fnl:=e^.len;
  355. if (fnl>0) then
  356. begin
  357. p:=@e^.name+fnl-1;
  358. While (p^=#0) do
  359. begin
  360. dec(p);
  361. dec(fnl);
  362. end;
  363. end;
  364. setlength(fn,fnl);
  365. if (fnl>0) then
  366. move(e^.name,fn[1],fnl);
  367. {$ifdef VerboseDirWatch}
  368. Writeln('Change ',e^.mask,' (',
  369. // InotifyEventsToString(e^.mask),
  370. ') detected for file "',fn,'"');
  371. {$endif}
  372. ptrint(e):=ptrint(e)+sizeof(inotify_event)+e^.len-1;
  373. end;
  374. end;
  375. finally
  376. fpClose(fd);
  377. end;
  378. end;
  379. procedure TDirwatch.DoneWatch;
  380. begin
  381. fpClose(FInotifyFD);
  382. end;
  383. procedure TDirwatch.InitWatch;
  384. Const
  385. NativeEvents : Array[TFileEvent] of cint = (IN_Modify,IN_Attrib,IN_Create,IN_Delete);
  386. Var
  387. WD,I,NEvents : Integer;
  388. E : TFileEvent;
  389. BD,FN : String;
  390. begin
  391. BD:=BaseDir;
  392. if BD<>'' then
  393. BD:=IncludeTrailingPathDelimiter(BD);
  394. FINotifyFD:=inotify_init;
  395. For I:=0 to FWatches.Count-1 do
  396. begin
  397. NEvents:=0;
  398. for E in FWatches[i].Events do
  399. NEvents:=NEvents OR NativeEvents[E];
  400. FN:=BD+FWatches[i].Name;
  401. wd:=inotify_add_watch(FINotifyFD,PChar(FN),NEvents);
  402. end;
  403. end;
  404. Function NativeEventsToEvents(Native : cint) : TFileEvents;
  405. Procedure MA(C : cint; AEvent : TFileEvent);
  406. begin
  407. if (Native and C)<>0 then
  408. Include(Result,AEvent);
  409. end;
  410. begin
  411. Result:=[];
  412. MA(IN_ACCESS,feAttrib);
  413. MA(IN_MODIFY,feModify);
  414. MA(IN_ATTRIB,feAttrib);
  415. MA(IN_CLOSE_WRITE,feAttrib);
  416. MA(IN_CLOSE_NOWRITE,feAttrib);
  417. MA(IN_OPEN,feAttrib);
  418. MA(IN_MOVED_FROM,feCreate);
  419. MA(IN_MOVED_TO,feDelete);
  420. MA(IN_CREATE,feCreate);
  421. Ma(IN_DELETE,feDelete);
  422. Ma(IN_DELETE_SELF,feDelete);
  423. Ma(IN_MOVE_SELF,feDelete);
  424. Ma(IN_UNMOUNT,feDelete);
  425. // IN_Q_OVERFLOW
  426. // IN_IGNORED
  427. end;
  428. procedure TDirwatch.Check;
  429. Var
  430. fnl,len : cint;
  431. e : ^inotify_event;
  432. buf : Array[0..1023*4] of Byte; // 4K Buffer
  433. fn : string;
  434. p : pchar;
  435. fds : tfdset;
  436. Timeout : ttimeval;
  437. begin
  438. fpFD_Zero(fds);
  439. fpFD_SET(FINotifyFD,fds);
  440. timeout.tv_sec:=FIdleInterval div 1000;
  441. timeout.tv_usec:=(FIdleInterval mod 1000)*1000;
  442. if (fpSelect(FINotifyFD+1,@fds,nil,nil,@Timeout)<=0) then
  443. exit;
  444. len:=fpRead(FINotifyFD,buf,sizeof(buf));
  445. e:=@buf;
  446. While ((pchar(e)-@buf)<len) do
  447. begin
  448. fnl:=e^.len;
  449. if (fnl>0) then
  450. begin
  451. p:=@e^.name+fnl-1;
  452. While (p^=#0) do
  453. begin
  454. dec(p);
  455. dec(fnl);
  456. end;
  457. end;
  458. setlength(fn,fnl);
  459. if (fnl>0) then
  460. move(e^.name,fn[1],fnl);
  461. DoChangeEvent(DirectoryEntryForFileName(FN),NativeEventsToEvents(E^ .mask));
  462. ptrint(e):=ptrint(e)+sizeof(inotify_event)+e^.len-1;
  463. end;
  464. end;
  465. {$ENDIF}
  466. procedure TDirwatch.DoStartWatch;
  467. begin
  468. InitWatch;
  469. try
  470. While not Terminated do
  471. begin
  472. Check;
  473. if Threaded then
  474. TThread.Synchronize(TThread.CurrentThread,@DoIdle)
  475. else
  476. DoIdle;
  477. end;
  478. Finally
  479. DoneWatch;
  480. end;
  481. end;
  482. procedure TDirwatch.StartWatch;
  483. begin
  484. If Threaded then
  485. TDirwatchThread.Create(Self).WaitFor
  486. else
  487. DoStartWatch;
  488. end;
  489. procedure TDirwatch.AddWatch(const aFileName: string; aEvents: TFileEvents);
  490. begin
  491. FWatches.AddEntry(AFileName).Events:=AEvents;
  492. end;
  493. procedure TDirwatch.Terminate;
  494. begin
  495. FTerminated:=True;
  496. end;
  497. { TDirectoryEntries }
  498. function TDirectoryEntries.GetE(AIndex : Integer): TDirectoryEntry;
  499. begin
  500. Result:=TDirectoryEntry(Items[AIndex]);
  501. end;
  502. procedure TDirectoryEntries.SetE(AIndex : Integer; AValue: TDirectoryEntry);
  503. begin
  504. Items[AIndex]:=AValue;
  505. end;
  506. function TDirectoryEntries.IndexOfEntry(const AName: String): Integer;
  507. begin
  508. Result:=Count-1;
  509. While (Result>=0) and (GetE(Result).Name<>AName) do
  510. Dec(Result);
  511. end;
  512. function TDirectoryEntries.EntryByName(const AName: String): TDirectoryEntry;
  513. Var
  514. I : Integer;
  515. begin
  516. I:=IndexOfEntry(AName);
  517. If (I=-1) then
  518. Result:=Nil
  519. else
  520. Result:=GetE(I);
  521. end;
  522. function TDirectoryEntries.AddEntry(Const AName: String): TDirectoryEntry;
  523. begin
  524. Result:=Add as TDirectoryEntry;
  525. Result.Name:=AName;
  526. end;
  527. end.