DiscMon.pas 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948
  1. {
  2. Modifications:
  3. ==============
  4. ie01: OnChange delayed for 500 ms.
  5. ie02: No usage of BrowseDirectory component
  6. ie03: Resume suspended thread when terminating the thread
  7. ie04: Martin Prikryl, option to watch multiple directories
  8. }
  9. // ==================== DISC DRIVE MONITOR =====================================
  10. //
  11. // Class and Component to encapsulate the FindXXXXChangeNotification API calls
  12. //
  13. // The FindXXXXChangeNotification API calls set up a disc contents change
  14. // notification handle. You can set a filter to control which change types
  15. // are notified, the directory which is monitored and set whether subdirectories
  16. // from the monitored directory are monitored as well.
  17. //
  18. //------------------------------------------------------------------------------
  19. // This file contains a class derived from TThread which undertakes the disc
  20. // monitoring and a simple component which encapsulates the thread to make
  21. // a non-visual VCL component. This component works at design time, monitoring
  22. // and notifying changes live if required.
  23. //
  24. // Version 1.00 - Grahame Marsh 14 January 1997
  25. // Version 1.01 - Grahame Marsh 30 December 1997
  26. // Bug fix - really a Win 95 bug but only surfaces in D3, not D2
  27. // - see notes in execute method
  28. // Version 1.02 - Grahame Marsh 30 January 1998
  29. // - adapted to work with version 2.30 TBrowseDirectoryDlg
  30. //
  31. // Freeware - you get it for free, I take nothing, I make no promises!
  32. //
  33. // Please feel free to contact me: [email protected]
  34. {$DEFINE BUGFIX}
  35. unit DiscMon;
  36. interface
  37. uses
  38. Windows, SysUtils, Classes, CompThread;
  39. //=== DISC MONITORING THREAD ===================================================
  40. // This thread will monitor a given directory and subdirectories (if required)
  41. // for defined filtered changes. When a change occurs the OnChange event will
  42. // be fired, if an invalid condition is found (eg non-existent path) then
  43. // the OnInvalid event is fired. Each event is called via the Sychronize method
  44. // and so are VCL thread safe.
  45. //
  46. // The thread is created suspended, so after setting the required properties
  47. // you must call the Resume method.
  48. type
  49. TDiscMonitorNotify = procedure(Sender: TObject; const Directory: string; var SubdirsChanged: Boolean) of object;
  50. TDiscMonitorInvalid = procedure(Sender: TObject; const Directory: string; const ErrorStr: string) of object;
  51. TDiscMonitorSynchronize = procedure(Sender: TObject; Method: TThreadMethod) of object;
  52. TDiscMonitorFilter = procedure(Sender: TObject; const DirectoryName: string; var Add: Boolean) of object;
  53. TDiscMonitorTooManyDirectories = procedure(Sender: TObject; var MaxDirectories: Integer) of object;
  54. TDiscMonitorDirectoriesChange = procedure(Sender: TObject; Directories: Integer) of object;
  55. TDiscMonitorThread = class(TCompThread)
  56. private
  57. FOnChange: TDiscMonitorNotify;
  58. FOnInvalid: TDiscMonitorInvalid;
  59. FOnSynchronize: TDiscMonitorSynchronize;
  60. FOnFilter: TDiscMonitorFilter;
  61. FOnDirectoriesChange: TDiscMonitorDirectoriesChange;
  62. FDirectories: TStrings;
  63. FFilters: DWORD;
  64. FDestroyEvent,
  65. FChangeEvent: THandle;
  66. FSubTree: Boolean;
  67. FChangeDelay: Integer; {ie01}
  68. FNotifiedDirectory: string;
  69. FSubdirsChanged: Boolean;
  70. FInvalidMessage: string;
  71. FNotifiedDirectories: Integer;
  72. FEnabled: Boolean;
  73. procedure InformChange;
  74. procedure InformInvalid;
  75. procedure InformDirectoriesChange;
  76. procedure SetDirectories(const Value: TStrings);
  77. procedure SetFilters(Value: DWORD);
  78. procedure SetSubTree(Value: Boolean);
  79. procedure SetEnabled(Value: Boolean);
  80. procedure SaveOSError;
  81. protected
  82. procedure Execute; override;
  83. procedure Update;
  84. procedure DoSynchronize(Method: TThreadMethod);
  85. public
  86. constructor Create;
  87. destructor Destroy; override;
  88. // The directory to monitor
  89. property Directories: TStrings read FDirectories write SetDirectories;
  90. // Filter condition, may be any of the FILE_NOTIFY_CHANGE_XXXXXXX constants
  91. // ORed together. Zero is invalid.
  92. property Filters: DWORD read FFilters write SetFilters;
  93. // Event called when change noted in directory
  94. property OnChange: TDiscMonitorNotify read FOnChange write FOnChange;
  95. // Event called for invalid parameters
  96. property OnInvalid: TDiscMonitorInvalid read FOnInvalid write FOnInvalid;
  97. property OnSynchronize: TDiscMonitorSynchronize read FOnSynchronize write FOnSynchronize;
  98. property OnFilter: TDiscMonitorFilter read FOnFilter write FOnFilter;
  99. property OnDirectoriesChange: TDiscMonitorDirectoriesChange read FOnDirectoriesChange write FOnDirectoriesChange;
  100. // Include subdirectories below specified directory.
  101. property SubTree: Boolean read FSubTree write SetSubTree;
  102. // specify, how long the thread should wait, before the event OnChange is fired:
  103. property ChangeDelay: Integer read FChangeDelay write FChangeDelay
  104. default 500;
  105. property Enabled: Boolean read FEnabled write SetEnabled;
  106. end;
  107. //===================== DISC MONITORING COMPONENT ==============================
  108. // enumerated type for filter conditions (not directly usable in thread class)
  109. // see the SetFilters procedure for the translation of these filter conditions
  110. // into FILE_NOTIFY_CHANGE_XXXXXX constants.
  111. TMonitorFilter = (moFilename, moDirName, moAttributes, moSize,
  112. moLastWrite, moSecurity);
  113. // set of filter conditions
  114. TMonitorFilters = set of TMonitorFilter;
  115. TDiscMonitor = class(TComponent)
  116. private
  117. FActive: Boolean;
  118. FMonitor: TDiscMonitorThread;
  119. FFilters: TMonitorFilters;
  120. FOnChange: TDiscMonitorNotify;
  121. FOnInvalid: TDiscMonitorInvalid;
  122. FOnSynchronize: TDiscMonitorSynchronize;
  123. FOnFilter: TDiscMonitorFilter;
  124. FOnTooManyDirectories: TDiscMonitorTooManyDirectories;
  125. FOnDirectoriesChange: TDiscMonitorDirectoriesChange;
  126. FShowMsg: Boolean;
  127. FPending: Boolean;
  128. FMaxDirectories: Integer;
  129. function GetDirectories: TStrings;
  130. function GetSubTree: Boolean;
  131. function GetEnabled: Boolean;
  132. procedure SetActive(Value: Boolean);
  133. procedure SetDirectories(Value: TStrings);
  134. procedure SetFilters(Value: TMonitorFilters);
  135. procedure SetSubTree(Value: Boolean);
  136. procedure SetEnabled(Value: Boolean);
  137. function GetChangeDelay: Integer;
  138. procedure SetChangeDelay(Value: Integer);
  139. protected
  140. procedure Change(Sender: TObject; const Directory: string;
  141. var SubdirsChanged: Boolean);
  142. procedure Invalid(Sender: TObject; const Directory: string; const ErrorStr: string);
  143. procedure Filter(Sender: TObject; const DirectoryName: string; var Add: Boolean);
  144. procedure DirectoriesChange(Sender: TObject; Directories: Integer);
  145. procedure DoSynchronize(Sender: TObject; Method: TThreadMethod);
  146. public
  147. constructor Create(AOwner : TComponent); override;
  148. destructor Destroy; override;
  149. // stop the monitoring thread running
  150. procedure Close;
  151. // start the monitoring thread running
  152. procedure Open;
  153. procedure AddDirectory(Directory: string; SubDirs: Boolean);
  154. procedure SetDirectory(Directory: string);
  155. // read-only property to access the thread directly
  156. property Thread: TDiscMonitorThread read FMonitor;
  157. property MaxDirectories: Integer read FMaxDirectories write FMaxDirectories;
  158. published
  159. // the directories to monitor
  160. property Directories: TStrings read GetDirectories;
  161. // control the appearance of information messages at design time (only)
  162. property ShowDesignMsg: Boolean read FShowMsg write FShowMsg default False;
  163. // event called when a change is notified
  164. property OnChange: TDiscMonitorNotify read FOnChange write FOnChange;
  165. // event called if an invalid condition is found
  166. property OnInvalid: TDiscMonitorInvalid read FOnInvalid write FOnInvalid;
  167. property OnSynchronize: TDiscMonitorSynchronize read FOnSynchronize write FOnSynchronize;
  168. property OnFilter: TDiscMonitorFilter read FOnFilter write FOnFilter;
  169. property OnTooManyDirectories: TDiscMonitorTooManyDirectories read FOnTooManyDirectories write FOnTooManyDirectories;
  170. property OnDirectoriesChange: TDiscMonitorDirectoriesChange read FOnDirectoriesChange write FOnDirectoriesChange;
  171. // notification filter conditions
  172. property Filters: TMonitorFilters read FFilters write SetFilters default [moFilename];
  173. // include subdirectories below the specified directory
  174. property SubTree: Boolean read GetSubTree write SetSubTree default True;
  175. // specify if the monitoring thread is active
  176. property Active: Boolean read FActive write SetActive default False;
  177. // specify, how long the thread should wait, before the event OnChange is fired:
  178. property ChangeDelay: Integer read GetChangeDelay write SetChangeDelay
  179. default 500;
  180. property Enabled: Boolean read GetEnabled write SetEnabled default True;
  181. end;
  182. procedure Register;
  183. implementation
  184. uses
  185. PasTools;
  186. {$WARN SYMBOL_PLATFORM OFF}
  187. {$IFDEF BUGFIX}
  188. {$Z4}
  189. type TWinBool = (winFalse, winTrue);
  190. function FixFindFirstChangeNotification(const lpPathName: PChar;
  191. bWatchSubtree: TWinBool; dwNotifyFilter: DWORD): THandle stdcall;
  192. external kernel32 name 'FindFirstChangeNotificationW';
  193. {$ENDIF}
  194. procedure AddDirectory(Dirs: TStrings; Directory: string;
  195. var MaxDirectories: Integer; OnFilter: TDiscMonitorFilter;
  196. OnTooManyDirectories: TDiscMonitorTooManyDirectories; Tag: Boolean);
  197. var
  198. Found: Boolean;
  199. SearchRec: TSearchRec;
  200. FileName: string;
  201. FindAttrs: Integer;
  202. Add: Boolean;
  203. Index: Integer;
  204. begin
  205. FindAttrs := faReadOnly or faHidden or faSysFile or faDirectory or faArchive;
  206. Directory := IncludeTrailingBackslash(Directory);
  207. Found := (FindFirst(ApiPath(Directory + '*.*'), FindAttrs, SearchRec) = 0);
  208. if Found then
  209. begin
  210. try
  211. while Found do
  212. begin
  213. if (SearchRec.Name <> '.') and (SearchRec.Name <> '..') and
  214. ((SearchRec.Attr and faDirectory) = faDirectory) then
  215. begin
  216. FileName := Directory + SearchRec.Name;
  217. Add := True;
  218. if Assigned(OnFilter) then OnFilter(nil, FileName, Add);
  219. if Add then
  220. begin
  221. if Tag then
  222. begin
  223. Index := Dirs.IndexOf(FileName);
  224. if Index >= 0 then
  225. begin
  226. Assert(Dirs.Objects[Index] = TObject(1));
  227. Dirs.Objects[Index] := TObject(0);
  228. end;
  229. end
  230. else Index := -1;
  231. if Index < 0 then
  232. begin
  233. if Tag then Dirs.AddObject(FileName, TObject(2))
  234. else Dirs.Add(FileName);
  235. if (MaxDirectories >= 0) and (Dirs.Count > MaxDirectories) and
  236. Assigned(OnTooManyDirectories) then
  237. OnTooManyDirectories(nil, MaxDirectories);
  238. end;
  239. // note that we are not re-scaning subdirectories of duplicate directory
  240. AddDirectory(Dirs, FileName, MaxDirectories, OnFilter, OnTooManyDirectories, Tag);
  241. end;
  242. end;
  243. Found := (FindNext(SearchRec) = 0);
  244. end;
  245. finally
  246. FindClose(SearchRec);
  247. end;
  248. end;
  249. end;
  250. //=== MONITOR THREAD ===========================================================
  251. // Create the thread suspended. Create two events, each are created using
  252. // standard security, in the non-signalled state, with auto-reset and without
  253. // names. The FDestroyEvent will be used to signal the thread that it is to close
  254. // down. The FChangeEvent will be used to signal the thread when the monitoring
  255. // conditions (directory, filters or sub-directory search) have changed.
  256. // OnTerminate is left as false, so the user must Free the thread.
  257. constructor TDiscMonitorThread.Create;
  258. begin
  259. inherited Create(True);
  260. FDirectories := TStringList.Create;
  261. FDestroyEvent := CreateEvent(nil, True, False, nil);
  262. FChangeEvent := CreateEvent(nil, False, False, nil);
  263. FOnFilter := nil;
  264. FOnDirectoriesChange := nil;
  265. FEnabled := True;
  266. end;
  267. // close OnXXXXX links, signal the thread that it is to close down
  268. destructor TDiscMonitorThread.Destroy;
  269. var
  270. D: TStrings;
  271. begin
  272. FOnChange := nil;
  273. FOnInvalid := nil;
  274. if Suspended then Resume;
  275. SetEvent(FDestroyEvent);
  276. D := FDirectories;
  277. inherited Destroy;
  278. // cannot free before destroy as the thread is using it
  279. D.Free;
  280. end;
  281. // called by the Execute procedure via Synchronize. So this is VCL thread safe
  282. procedure TDiscMonitorThread.InformChange;
  283. begin
  284. if Assigned(FOnChange) then FOnChange(Self, FNotifiedDirectory, FSubdirsChanged);
  285. end;
  286. // called by the Execute procedure via Synchronize. So this is VCL thread safe
  287. procedure TDiscMonitorThread.InformInvalid;
  288. begin
  289. if Assigned(FOnInvalid) then FOnInvalid(Self, FNotifiedDirectory, FInvalidMessage);
  290. end;
  291. procedure TDiscMonitorThread.InformDirectoriesChange;
  292. begin
  293. if Assigned(FOnDirectoriesChange) then FOnDirectoriesChange(Self, FNotifiedDirectories);
  294. end;
  295. procedure TDiscMonitorThread.SaveOSError;
  296. begin
  297. try
  298. RaiseLastOSError;
  299. except
  300. on E: Exception do FInvalidMessage := E.Message;
  301. end;
  302. end;
  303. procedure TDiscMonitorThread.SetDirectories(const Value: TStrings);
  304. begin
  305. if Value <> nil then
  306. begin
  307. FDirectories.Assign(Value);
  308. (FDirectories as TStringList).CaseSensitive := False;
  309. (FDirectories as TStringList).Sorted := True;
  310. end
  311. else FDirectories.Clear;
  312. Update;
  313. end;
  314. // Change the current filters
  315. procedure TDiscMonitorThread.SetFilters(Value: DWORD);
  316. begin
  317. if Value <> FFilters then
  318. begin
  319. FFilters := Value;
  320. Update;
  321. end
  322. end;
  323. // Change the current sub-tree condition
  324. procedure TDiscMonitorThread.SetSubTree(Value: Boolean);
  325. begin
  326. if Value <> FSubTree then
  327. begin
  328. FSubtree := Value;
  329. Update;
  330. end
  331. end;
  332. procedure TDiscMonitorThread.SetEnabled(Value: Boolean);
  333. begin
  334. if Value <> FEnabled then
  335. begin
  336. FEnabled := Value;
  337. Update;
  338. end
  339. end;
  340. function TDiscMonitor.GetChangeDelay: Integer; {ie01}
  341. begin
  342. Result := FMonitor.ChangeDelay;
  343. end;
  344. procedure TDiscMonitor.SetChangeDelay(Value: Integer); {ie01}
  345. begin
  346. FMonitor.ChangeDelay := Value;
  347. end;
  348. // On any of the above three changes, if the thread is running then
  349. // signal it that a change has occurred.
  350. procedure TDiscMonitorThread.Update;
  351. begin
  352. if not Suspended then
  353. begin
  354. SetEvent(FChangeEvent);
  355. end;
  356. end;
  357. procedure TDiscMonitorThread.DoSynchronize(Method: TThreadMethod);
  358. begin
  359. if Assigned(OnSynchronize) then OnSynchronize(Self, Method);
  360. end;
  361. // The EXECUTE procedure
  362. // -------
  363. // Execute needs to:
  364. // 1. Call FindFirstChangeNotification and use the Handle in a WaitFor...
  365. // to wait until the thread become signalled that a notification has occurred.
  366. // The OnChange event is called and then the FindNextChangeNotification is
  367. // the called and Execute loops back to the WaitFor
  368. // 2. If an invalid handle is obtained from the above call, the the OnInvalid
  369. // event is called and then Execute waits until valid conditions are set.
  370. // 3. If a ChangeEvent is signalled then FindCloseChangeNotification is called,
  371. // followed by a new FindFirstChangeNotification to use the altered
  372. // conditions.
  373. // 4. If a DestroyEvent is signalled then FindCloseChangeNotification is
  374. // called and the two events are closed and the thread terminates.
  375. //
  376. // In practice WaitForMultipleObjects is used to wait for any of the conditions
  377. // to be signalled, and the returned value used to determine which event occurred.
  378. procedure TDiscMonitorThread.Execute;
  379. var
  380. // used to give the handles to WaitFor...
  381. Handles: PWOHandleArray;
  382. BaseHandles: Word;
  383. SysHandles: Word;
  384. Count: Word;
  385. function StartMonitor(const Directory: string; ForceSubTree: Boolean): THandle;
  386. // There appears to be a bug in win 95 where the bWatchSubTree parameter
  387. // of FindFirstChangeNotification which is a BOOL only accepts values of
  388. // 0 and 1 as valid, rather than 0 and any non-0 value as it should. In D2
  389. // BOOL was defined as 0..1 so the code worked, in D3 it is 0..-1 so
  390. // fails. The result is FindF... produces and error message. This fix (bodge) is
  391. // needed to produce a 0,1 bool pair, rather that 0,-1 as declared in D3
  392. {$IFDEF BUGFIX}
  393. const R : array[False..True] of TWinBOOL = (WinFalse, WinTrue);
  394. {$ELSE}
  395. const R : array[False..True] of LongBool = (LongBool(0), LongBool(1));
  396. {$ENDIF}
  397. var
  398. Again: Boolean;
  399. SubTree: Boolean;
  400. begin
  401. repeat
  402. SubTree := FSubTree or ForceSubTree;
  403. {$IFDEF BUGFIX}
  404. Result := FixFindFirstChangeNotification(PChar(Directory), R[SubTree], FFilters);
  405. {$ELSE}
  406. Result := FindFirstChangeNotification(PChar(Directory), R[SubTree], FFilters);
  407. {$ENDIF}
  408. Again := (Result = INVALID_HANDLE_VALUE);
  409. if Again then
  410. begin
  411. // call the OnInvalid event
  412. FNotifiedDirectory := Directory;
  413. SaveOSError;
  414. DoSynchronize(InformInvalid);
  415. // wait until either DestroyEvent or the ChangeEvents are signalled
  416. Result := WaitForMultipleObjects(2, Handles, False, INFINITE);
  417. if Result = WAIT_FAILED then
  418. begin
  419. FNotifiedDirectory := '';
  420. SaveOSError;
  421. DoSynchronize(InformInvalid);
  422. Again := False;
  423. end
  424. else Again := (Result - WAIT_OBJECT_0 = 1);
  425. end
  426. until (not Again);
  427. end; {StartMonitor}
  428. function UpdateSubdirectories(Directory: Integer): Cardinal;
  429. var
  430. Path: string;
  431. NewHandles: PWOHandleArray;
  432. OrigDirectory: Integer;
  433. NewAlloc: Integer;
  434. MaxDirectories: Integer;
  435. Changed: Boolean;
  436. begin
  437. Assert(Directory >= 0);
  438. Path := ExcludeTrailingBackslash(FDirectories[Directory]);
  439. while ((Directory + 1 < FDirectories.Count) and
  440. SameText(Copy(FDirectories.Strings[Directory + 1], 1, Length(Path)), Path)) do
  441. begin
  442. FDirectories.Objects[Directory + 1] := TObject(1);
  443. Inc(Directory);
  444. end;
  445. MaxDirectories := -1;
  446. AddDirectory(FDirectories, Path, MaxDirectories, OnFilter, nil, True);
  447. Result := WAIT_OBJECT_0;
  448. // worst case limit
  449. NewAlloc := SysHandles + FDirectories.Count;
  450. GetMem(NewHandles, SizeOf(THandle) * NewAlloc);
  451. Move(Handles^, NewHandles^, SizeOf(THandle) * SysHandles);
  452. OrigDirectory := SysHandles;
  453. Directory := SysHandles;
  454. Changed := False;
  455. while Directory < SysHandles + FDirectories.Count do
  456. begin
  457. // removed directory
  458. if Integer(FDirectories.Objects[Directory - SysHandles]) = 1 then
  459. begin
  460. FDirectories.Delete(Directory - SysHandles);
  461. Assert(OrigDirectory < Count);
  462. FindCloseChangeNotification(Handles^[OrigDirectory]);
  463. Handles^[OrigDirectory] := INVALID_HANDLE_VALUE;
  464. Inc(OrigDirectory);
  465. Changed := True;
  466. end
  467. else
  468. // newly added
  469. if Integer(FDirectories.Objects[Directory - SysHandles]) = 2 then
  470. begin
  471. Assert(Directory < NewAlloc);
  472. NewHandles^[Directory] := StartMonitor(FDirectories[Directory - SysHandles], False);
  473. if NewHandles^[Directory] = INVALID_HANDLE_VALUE then
  474. begin
  475. // currently we resign on correct resource freeing
  476. Result := WAIT_FAILED;
  477. Break;
  478. end;
  479. FDirectories.Objects[Directory - SysHandles] := TObject(0);
  480. Inc(Directory);
  481. Changed := True;
  482. end
  483. else
  484. begin
  485. Assert(Integer(FDirectories.Objects[Directory - SysHandles]) = 0);
  486. Assert(Directory < NewAlloc);
  487. Assert(OrigDirectory < Count);
  488. NewHandles^[Directory] := Handles^[OrigDirectory];
  489. Inc(Directory);
  490. Inc(OrigDirectory);
  491. end;
  492. end;
  493. if Result <> WAIT_FAILED then
  494. begin
  495. Assert(Count = OrigDirectory);
  496. FreeMem(Handles);
  497. Handles := NewHandles;
  498. Count := SysHandles + FDirectories.Count;
  499. Assert(Count = Directory);
  500. Assert(Count <= NewAlloc);
  501. end;
  502. if Changed and Assigned(OnDirectoriesChange) then
  503. begin
  504. FNotifiedDirectories := FDirectories.Count;
  505. DoSynchronize(InformDirectoriesChange);
  506. end;
  507. end;
  508. function Notify(Directory: Integer; Handle: THandle): Cardinal;
  509. begin
  510. // Notification signalled, so fire the OnChange event and then FindNext..
  511. // loop back to re-WaitFor... the thread
  512. Sleep(FChangeDelay);
  513. Result := WAIT_TIMEOUT;
  514. // When deleting a tree, we may get notification about change in the directory
  515. // before notification about deleting the directory.
  516. // While this does not 100% protect against an attempt to synchronize the deleted directory,
  517. // it may greatly reduce the risk (as checked after the sleep above).
  518. // Though actually it may not even be possible to delete the directory as we have it locked.
  519. if DirectoryExists(FDirectories[Directory]) then
  520. begin
  521. FNotifiedDirectory := FDirectories[Directory];
  522. FSubdirsChanged := False;
  523. DoSynchronize(InformChange);
  524. if FSubdirsChanged then
  525. Result := UpdateSubDirectories(Directory)
  526. end;
  527. FindNextChangeNotification(Handle);
  528. end;
  529. function CheckAllObjects(Count: Integer; DirHandles: PWOHandleArray): Cardinal;
  530. const
  531. Offset = MAXIMUM_WAIT_OBJECTS;
  532. var
  533. C, Start, Directory: Cardinal;
  534. begin
  535. Result := WAIT_TIMEOUT;
  536. Start := 0;
  537. while Start < Cardinal(Count) do
  538. begin
  539. if Cardinal(Count) - Start > Offset then C := Offset
  540. else C := Cardinal(Count) - Start;
  541. Result := WaitForMultipleObjects(C, @DirHandles[Start], false, 0);
  542. Directory := Start + Result - WAIT_OBJECT_0;
  543. // (Result - WAIT_OBJECT_0 >= 0) is always true
  544. if Result - WAIT_OBJECT_0 < C then
  545. begin
  546. Result := Notify(Directory, DirHandles^[Directory]);
  547. // when new directory is found, restart,
  548. // if not check the same range again for possibly different notification
  549. if Result = WAIT_OBJECT_0 then Start := 0;
  550. end
  551. else Inc(Start, C);
  552. if Result <> WAIT_TIMEOUT then Break;
  553. Result := WaitForMultipleObjects(2, Handles, False, 0);
  554. if Result <> WAIT_TIMEOUT then Break;
  555. end;
  556. end;
  557. const
  558. DestroySlot = 0;
  559. ChangeSlot = 1;
  560. HierNotifySlot = 2;
  561. var
  562. HierMode: Boolean;
  563. WaitCount: Word;
  564. I: Integer;
  565. Result: Cardinal;
  566. WasEnabled: Boolean;
  567. begin {Execute}
  568. BaseHandles := 2;
  569. SysHandles := BaseHandles;
  570. Count := SysHandles + FDirectories.Count;
  571. HierMode := (Count > MAXIMUM_WAIT_OBJECTS);
  572. if HierMode then
  573. begin
  574. Inc(SysHandles);
  575. Inc(Count);
  576. end;
  577. GetMem(Handles, SizeOf(THandle) * Count);
  578. try
  579. Handles^[DestroySlot] := FDestroyEvent; // put DestroyEvent handle in slot 0
  580. Handles^[ChangeSlot] := FChangeEvent; // put ChangeEvent handle in slot 1
  581. repeat
  582. WasEnabled := Enabled;
  583. if WasEnabled then
  584. begin
  585. if HierMode then
  586. begin
  587. // expect that the first directory is the top level one
  588. Handles^[HierNotifySlot] := StartMonitor(FDirectories[0], True);
  589. if Handles^[HierNotifySlot] = INVALID_HANDLE_VALUE then Exit;
  590. end;
  591. for I := SysHandles to Count - 1 do
  592. begin
  593. Handles^[I] := StartMonitor(FDirectories[I - SysHandles], False);
  594. if Handles^[I] = INVALID_HANDLE_VALUE then Exit;
  595. end;
  596. end;
  597. repeat
  598. if WasEnabled then
  599. begin
  600. if HierMode then WaitCount := SysHandles
  601. else WaitCount := Count;
  602. end
  603. else WaitCount := BaseHandles;
  604. // wait for any of the change notification, destroy or
  605. // change events to be signalled
  606. Result := WaitForMultipleObjects(WaitCount, Handles, False, INFINITE);
  607. if Result = WAIT_FAILED then
  608. begin
  609. FNotifiedDirectory := '';
  610. SaveOSError;
  611. DoSynchronize(InformInvalid);
  612. end
  613. else
  614. if HierMode and (Result - WAIT_OBJECT_0 = HierNotifySlot) then
  615. begin
  616. FindNextChangeNotification(Handles[HierNotifySlot]);
  617. Result := CheckAllObjects(Count - SysHandles, @Handles[SysHandles]);
  618. // (Result >= WAIT_OBJECT_0) = always true
  619. if Result < Cardinal(WAIT_OBJECT_0 + (Count - SysHandles)) then
  620. begin
  621. Result := WAIT_OBJECT_0 + HierNotifySlot;
  622. end;
  623. end
  624. else
  625. if (Result >= WAIT_OBJECT_0 + SysHandles) and
  626. (Result < WAIT_OBJECT_0 + WaitCount) then
  627. begin
  628. Result := Notify(Result - WAIT_OBJECT_0 - SysHandles,
  629. Handles^[Result - WAIT_OBJECT_0]);
  630. if Result = WAIT_OBJECT_0 then Result := WAIT_TIMEOUT;
  631. end;
  632. // note that WaitCount can be different here than when
  633. // WaitForMultipleObjects was called, but it should not matter as it is
  634. until (Result = WAIT_FAILED) or (Result = WAIT_OBJECT_0 + DestroySlot) or
  635. (Result = WAIT_OBJECT_0 + ChangeSlot) or
  636. ((Result >= WAIT_ABANDONED_0) and (Result < WAIT_ABANDONED_0 + WaitCount));
  637. if WasEnabled then
  638. begin
  639. if HierMode then
  640. begin
  641. FindCloseChangeNotification(Handles^[HierNotifySlot]);
  642. end;
  643. for I := SysHandles to Count - 1 do
  644. begin
  645. FindCloseChangeNotification(Handles^[I]);
  646. end;
  647. end;
  648. // loop back to restart if ChangeEvent was signalled
  649. until (Result - WAIT_OBJECT_0 <> ChangeSlot) or Self.Terminated;
  650. // closing down so chuck the two events
  651. CloseHandle(FChangeEvent);
  652. CloseHandle(FDestroyEvent);
  653. finally
  654. FreeMem(Handles);
  655. end;
  656. end;
  657. //=== MONITOR COMPONENT ========================================================
  658. // This component encapsulates the above thread. It has properties for
  659. // directory, sub-directory conditions, filters, whether information messages
  660. // should be given at design time and if the thread is active.
  661. constructor TDiscMonitor.Create(AOwner: TComponent);
  662. begin
  663. inherited Create (AOwner);
  664. FOnFilter := nil;
  665. FOnTooManyDirectories := nil;
  666. FOnDirectoriesChange := nil;
  667. FMonitor := TDiscMonitorThread.Create; // create a monitor thread
  668. FMonitor.ChangeDelay := 500; {ie01}
  669. FMonitor.OnChange := Change; // hook into its event handlers
  670. FMonitor.OnInvalid := Invalid;
  671. FMonitor.OnSynchronize := DoSynchronize;
  672. FMonitor.OnFilter := Filter;
  673. FMonitor.OnDirectoriesChange := DirectoriesChange;
  674. Filters := [moFilename]; // default filters to moFilename
  675. SubTree := True; // default sub-tree search to on
  676. FPending := True;
  677. end;
  678. destructor TDiscMonitor.Destroy;
  679. begin
  680. FMonitor.Free; // chuck the thread
  681. inherited Destroy;
  682. end;
  683. // Change notification from the thread has occurred. Call the component's event
  684. // handler
  685. procedure TDiscMonitor.Change(Sender: TObject; const Directory: string;
  686. var SubdirsChanged: Boolean);
  687. begin
  688. if Assigned(FOnChange) then
  689. FOnChange(Self, Directory, SubdirsChanged)
  690. end;
  691. // Invalid notification from the thread has occurred. Call the component's event
  692. // handler
  693. procedure TDiscMonitor.Invalid(Sender: TObject; const Directory: string; const ErrorStr: string);
  694. begin
  695. if Assigned(FOnInvalid) then
  696. FOnInvalid(Self, Directory, ErrorStr)
  697. end;
  698. procedure TDiscMonitor.Filter(Sender: TObject; const DirectoryName: string; var Add: Boolean);
  699. begin
  700. if Assigned(FOnFilter) then
  701. FOnFilter(Self, DirectoryName, Add)
  702. end;
  703. procedure TDiscMonitor.DirectoriesChange(Sender: TObject; Directories: Integer);
  704. begin
  705. if Assigned(FOnDirectoriesChange) then
  706. FOnDirectoriesChange(Self, Directories)
  707. end;
  708. procedure TDiscMonitor.DoSynchronize(Sender: TObject; Method: TThreadMethod);
  709. begin
  710. if Assigned(FOnSynchronize) then FOnSynchronize(Self, Method)
  711. else FMonitor.Synchronize(Method);
  712. end;
  713. // Stop the monitor running
  714. procedure TDiscMonitor.Close;
  715. begin
  716. Active := False;
  717. end;
  718. // Run the monitor
  719. procedure TDiscMonitor.Open;
  720. begin
  721. Active := True
  722. end;
  723. // Control the thread by using it's resume and suspend methods
  724. procedure TDiscMonitor.SetActive(Value: Boolean);
  725. begin
  726. if Value <> FActive then
  727. begin
  728. FActive := Value;
  729. if Active then
  730. begin
  731. FMonitor.Resume;
  732. if not FPending then FMonitor.Update;
  733. FPending := False;
  734. end else
  735. FMonitor.Suspend;
  736. end
  737. end;
  738. // get the current directory from the thread
  739. function TDiscMonitor.GetDirectories: TStrings;
  740. begin
  741. Result := FMonitor.Directories;
  742. end;
  743. // get the current sub-tree status from the thread
  744. function TDiscMonitor.GetSubTree: Boolean;
  745. begin
  746. Result := FMonitor.SubTree;
  747. end;
  748. function TDiscMonitor.GetEnabled: Boolean;
  749. begin
  750. Result := FMonitor.Enabled;
  751. end;
  752. // set the directory to monitor
  753. procedure TDiscMonitor.SetDirectories(Value: TStrings);
  754. begin
  755. FMonitor.Directories := Value;
  756. end;
  757. procedure TDiscMonitor.AddDirectory(Directory: string; SubDirs: Boolean);
  758. var
  759. Dirs: TStringList;
  760. begin
  761. if Directory <> '' then
  762. begin
  763. Dirs := TStringList.Create;
  764. try
  765. Dirs.Assign(Directories);
  766. Dirs.Add(Directory);
  767. if SubDirs then
  768. DiscMon.AddDirectory(Dirs, Directory, FMaxDirectories, OnFilter,
  769. OnTooManyDirectories, False);
  770. SetDirectories(Dirs);
  771. finally
  772. Dirs.Free;
  773. end;
  774. end
  775. else
  776. begin
  777. SetDirectories(nil);
  778. end;
  779. end;
  780. procedure TDiscMonitor.SetDirectory(Directory: string);
  781. var
  782. Dirs: TStringList;
  783. begin
  784. if Directory <> '' then
  785. begin
  786. Dirs := TStringList.Create;
  787. try
  788. Dirs.Add(Directory);
  789. SetDirectories(Dirs);
  790. finally
  791. Dirs.Free;
  792. end;
  793. end
  794. else
  795. begin
  796. SetDirectories(nil);
  797. end;
  798. end;
  799. // Change the filter conditions. The thread uses the raw windows constants
  800. // (FILE_NOTIFY_CHANGE_XXXX) but the components uses a set of enumurated type.
  801. // It is therefore necessary to translate from the component format into
  802. // an integer value for the thread.
  803. procedure TDiscMonitor.SetFilters(Value: TMonitorFilters);
  804. const
  805. XlatFileNotify: array [moFilename..moSecurity] of DWORD =
  806. (FILE_NOTIFY_CHANGE_FILE_NAME, FILE_NOTIFY_CHANGE_DIR_NAME,
  807. FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_NOTIFY_CHANGE_SIZE,
  808. FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_NOTIFY_CHANGE_SECURITY);
  809. var
  810. L: TMonitorFilter;
  811. I: DWORD;
  812. begin
  813. if Value <> FFilters then
  814. if Value = [] then
  815. raise Exception.Create('Some filter condition must be set.')
  816. else begin
  817. FFilters := Value;
  818. I := 0;
  819. for L := moFilename to moSecurity do
  820. if L in Value then
  821. I := I or XlatFileNotify [L];
  822. FMonitor.Filters := I;
  823. end
  824. end;
  825. // set the sub-tree status in the thread
  826. procedure TDiscMonitor.SetSubTree(Value: Boolean);
  827. begin
  828. FMonitor.SubTree := Value;
  829. end;
  830. procedure TDiscMonitor.SetEnabled(Value: Boolean);
  831. begin
  832. FMonitor.Enabled := Value;
  833. end;
  834. procedure Register;
  835. begin
  836. RegisterComponents('Martin', [TDiscMonitor]);
  837. end;
  838. end.