dirwatch.pp 13 KB

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