dirwatch.pp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  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. Writeln('Change ',e^.mask,' (',
  368. // InotifyEventsToString(e^.mask),
  369. ') detected for file "',fn,'"');
  370. ptrint(e):=ptrint(e)+sizeof(inotify_event)+e^.len-1;
  371. end;
  372. end;
  373. finally
  374. fpClose(fd);
  375. end;
  376. end;
  377. procedure TDirwatch.DoneWatch;
  378. begin
  379. fpClose(FInotifyFD);
  380. end;
  381. procedure TDirwatch.InitWatch;
  382. Const
  383. NativeEvents : Array[TFileEvent] of cint = (IN_Modify,IN_Attrib,IN_Create,IN_Delete);
  384. Var
  385. WD,I,NEvents : Integer;
  386. E : TFileEvent;
  387. BD,FN : String;
  388. begin
  389. BD:=BaseDir;
  390. if BD<>'' then
  391. BD:=IncludeTrailingPathDelimiter(BD);
  392. FINotifyFD:=inotify_init;
  393. For I:=0 to FWatches.Count-1 do
  394. begin
  395. NEvents:=0;
  396. for E in FWatches[i].Events do
  397. NEvents:=NEvents OR NativeEvents[E];
  398. FN:=BD+FWatches[i].Name;
  399. wd:=inotify_add_watch(FINotifyFD,PChar(FN),NEvents);
  400. end;
  401. end;
  402. Function NativeEventsToEvents(Native : cint) : TFileEvents;
  403. Procedure MA(C : cint; AEvent : TFileEvent);
  404. begin
  405. if (Native and C)<>0 then
  406. Include(Result,AEvent);
  407. end;
  408. begin
  409. Result:=[];
  410. MA(IN_ACCESS,feAttrib);
  411. MA(IN_MODIFY,feModify);
  412. MA(IN_ATTRIB,feAttrib);
  413. MA(IN_CLOSE_WRITE,feAttrib);
  414. MA(IN_CLOSE_NOWRITE,feAttrib);
  415. MA(IN_OPEN,feAttrib);
  416. MA(IN_MOVED_FROM,feCreate);
  417. MA(IN_MOVED_TO,feDelete);
  418. MA(IN_CREATE,feCreate);
  419. Ma(IN_DELETE,feDelete);
  420. Ma(IN_DELETE_SELF,feDelete);
  421. Ma(IN_MOVE_SELF,feDelete);
  422. Ma(IN_UNMOUNT,feDelete);
  423. // IN_Q_OVERFLOW
  424. // IN_IGNORED
  425. end;
  426. procedure TDirwatch.Check;
  427. Var
  428. fnl,len : cint;
  429. e : ^inotify_event;
  430. buf : Array[0..1023*4] of Byte; // 4K Buffer
  431. fn : string;
  432. p : pchar;
  433. fds : tfdset;
  434. Timeout : ttimeval;
  435. begin
  436. fpFD_Zero(fds);
  437. fpFD_SET(FINotifyFD,fds);
  438. timeout.tv_sec:=FIdleInterval div 1000;
  439. timeout.tv_usec:=(FIdleInterval mod 1000)*1000;
  440. if (fpSelect(FINotifyFD+1,@fds,nil,nil,@Timeout)<=0) then
  441. exit;
  442. len:=fpRead(FINotifyFD,buf,sizeof(buf));
  443. e:=@buf;
  444. While ((pchar(e)-@buf)<len) do
  445. begin
  446. fnl:=e^.len;
  447. if (fnl>0) then
  448. begin
  449. p:=@e^.name+fnl-1;
  450. While (p^=#0) do
  451. begin
  452. dec(p);
  453. dec(fnl);
  454. end;
  455. end;
  456. setlength(fn,fnl);
  457. if (fnl>0) then
  458. move(e^.name,fn[1],fnl);
  459. DoChangeEvent(DirectoryEntryForFileName(FN),NativeEventsToEvents(E^ .mask));
  460. ptrint(e):=ptrint(e)+sizeof(inotify_event)+e^.len-1;
  461. end;
  462. end;
  463. {$ENDIF}
  464. procedure TDirwatch.DoStartWatch;
  465. begin
  466. InitWatch;
  467. try
  468. While not Terminated do
  469. begin
  470. Check;
  471. if Threaded then
  472. TThread.Synchronize(TThread.CurrentThread,@DoIdle)
  473. else
  474. DoIdle;
  475. end;
  476. Finally
  477. DoneWatch;
  478. end;
  479. end;
  480. procedure TDirwatch.StartWatch;
  481. begin
  482. If Threaded then
  483. TDirwatchThread.Create(Self).WaitFor
  484. else
  485. DoStartWatch;
  486. end;
  487. procedure TDirwatch.AddWatch(const aFileName: string; aEvents: TFileEvents);
  488. begin
  489. FWatches.AddEntry(AFileName).Events:=AEvents;
  490. end;
  491. procedure TDirwatch.Terminate;
  492. begin
  493. FTerminated:=True;
  494. end;
  495. { TDirectoryEntries }
  496. function TDirectoryEntries.GetE(AIndex : Integer): TDirectoryEntry;
  497. begin
  498. Result:=TDirectoryEntry(Items[AIndex]);
  499. end;
  500. procedure TDirectoryEntries.SetE(AIndex : Integer; AValue: TDirectoryEntry);
  501. begin
  502. Items[AIndex]:=AValue;
  503. end;
  504. function TDirectoryEntries.IndexOfEntry(const AName: String): Integer;
  505. begin
  506. Result:=Count-1;
  507. While (Result>=0) and (GetE(Result).Name<>AName) do
  508. Dec(Result);
  509. end;
  510. function TDirectoryEntries.EntryByName(const AName: String): TDirectoryEntry;
  511. Var
  512. I : Integer;
  513. begin
  514. I:=IndexOfEntry(AName);
  515. If (I=-1) then
  516. Result:=Nil
  517. else
  518. Result:=GetE(I);
  519. end;
  520. function TDirectoryEntries.AddEntry(Const AName: String): TDirectoryEntry;
  521. begin
  522. Result:=Add as TDirectoryEntry;
  523. Result.Name:=AName;
  524. end;
  525. end.