CnFileSystemWatcher.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. {******************************************************************************}
  2. { CnPack For Delphi/C++Builder }
  3. { 中国人自己的开放源码第三方开发包 }
  4. { (C)Copyright 2001-2018 CnPack 开发组 }
  5. { ------------------------------------ }
  6. { }
  7. { 本开发包是开源的自由软件,您可以遵照 CnPack 的发布协议来修 }
  8. { 改和重新发布这一程序。 }
  9. { }
  10. { 发布这一开发包的目的是希望它有用,但没有任何担保。甚至没有 }
  11. { 适合特定目的而隐含的担保。更详细的情况请参阅 CnPack 发布协议。 }
  12. { }
  13. { 您应该已经和开发包一起收到一份 CnPack 发布协议的副本。如果 }
  14. { 还没有,可访问我们的网站: }
  15. { }
  16. { 网站地址:http://www.cnpack.org }
  17. { 电子邮件:master@cnpack.org }
  18. { }
  19. {******************************************************************************}
  20. unit CnFileSystemWatcher;
  21. {* |<PRE>
  22. ================================================================================
  23. * 软件名称:不可视工具组件包
  24. * 单元名称:CnFileSystemWatcher 实现单元
  25. * 单元作者:CnPack 开发组 Solokey
  26. * 备 注:该单元实现了文件/目录变化的监视组件,可以监视到某目录(可包括子目录)
  27. * 下的文件、目录发生的名称修改、大小改变、访问时间改变等、事件的发生。
  28. * 监视选项可自行选择并组合。
  29. * 开发平台:PWinXP + Delphi 5.0
  30. * 兼容测试:PWin9X/2000/XP + Delphi 5/6
  31. * 本 地 化:该单元中的字符串均符合本地化处理方式
  32. * 单元标识:$Id$
  33. * 修改记录:2008.10.24 V1.2
  34. * 修正在受限用户模式下无法获取目录句柄的错误
  35. * 2008.05.09 V1.1
  36. * 增加通配符的处理
  37. * 2007.11.03 V1.0
  38. * 创建单元
  39. ================================================================================
  40. |</PRE>}
  41. interface
  42. {$I CnPack.inc}
  43. uses
  44. Windows, Classes, SysUtils,
  45. CnNativeDecl, CnCommon, CnClasses, CnConsts, CnCompConsts;
  46. type
  47. TFileOperation = (foAdded, foRemoved, foModified, foRenamed);
  48. TFileDealMethod = procedure(Sender: TObject; FileOperation: TFileOperation; const FileName1,
  49. FileName2: string) of object;
  50. TNotifyFilter = (nfFileNameChange, nfDirNameChange, nfAttributeChange,
  51. nfSizeChange, nfWriteChange, nfAccessChange, nfCreationDateChange,
  52. nfSecurityChange);
  53. TNotifyFilters = set of TNotifyFilter;
  54. TNotificationBuffer = array[0..4095] of Byte;
  55. PFileNotifyInformation = ^TFileNotifyInformation;
  56. TFileNotifyInformation = record
  57. NextEntryOffset: DWORD;
  58. Action: DWORD;
  59. FileNameLength: DWORD;
  60. FileName: array[0..0] of WideChar;
  61. end;
  62. TCnFileSystemWatcher = class;
  63. TCnShellChangeThread = class(TThread)
  64. private
  65. FParent: TCnFileSystemWatcher;
  66. FActive: Boolean;
  67. FDirectoryHandle: Cardinal;
  68. FCS: TRTLCriticalSection;
  69. FChangeEvent: TFileDealMethod;
  70. FDirectory: string;
  71. FWatchSubTree: Boolean;
  72. FFileMasks: TStringList;
  73. FTmpFileMasks: TStringList;
  74. FIncludePath: Boolean;
  75. FCompletionPort: Cardinal;
  76. FOverlapped: TOverlapped;
  77. FNotifyOptionFlags: DWORD;
  78. FBytesWritten: DWORD;
  79. FNotificationBuffer: TNotificationBuffer;
  80. protected
  81. procedure Execute; override;
  82. procedure DoIOCompletionEvent;
  83. function ResetReadDirctory: Boolean;
  84. procedure Lock;
  85. procedure Unlock;
  86. public
  87. constructor Create(AParent: TCnFileSystemWatcher; ChangeEvent: TFileDealMethod); virtual;
  88. destructor Destroy; override;
  89. procedure SetDirectoryOptions(Directory : String; Active: Boolean; WatchSubTree : Boolean;
  90. NotifyOptionFlags : DWORD);
  91. procedure SetFileMasks(FileMasks: TStringList);
  92. procedure SetIncludePath(IncludePath: Boolean);
  93. property ChangeEvent : TFileDealMethod read FChangeEvent write FChangeEvent;
  94. end;
  95. TCnFileSystemWatcher = class(TCnComponent)
  96. private
  97. FActive: Boolean;
  98. FWatchedDir: string;
  99. FThread: TCnShellChangeThread;
  100. FOnChange: TFileDealMethod;
  101. FWatchSubTree: Boolean;
  102. FFilters: TNotifyFilters;
  103. FFileMasks: TStringList;
  104. FIncludePath: Boolean;
  105. procedure SetWatchedDir(const Value: string);
  106. procedure SetWatchSubTree(const Value: Boolean);
  107. procedure SetOnChange(const Value: TFileDealMethod);
  108. procedure SetFilters(const Value: TNotifyFilters);
  109. function NotifyOptionFlags: DWORD;
  110. procedure SetActive(const Value: Boolean);
  111. procedure SetFileMasks(const Value: TStringList);
  112. procedure SetIncludePath(const Value: Boolean);
  113. protected
  114. procedure Change;
  115. procedure Start;
  116. procedure Stop;
  117. procedure OnFileMasksChange(Sender: TObject);
  118. procedure GetComponentInfo(var AName, Author, Email, Comment: string); override;
  119. public
  120. constructor Create(AOwner : TComponent); override;
  121. destructor Destroy; override;
  122. published
  123. property Active: Boolean read FActive write SetActive;
  124. property IncludePath: Boolean read FIncludePath write SetIncludePath;
  125. property FileMasks: TStringList read FFileMasks write SetFileMasks;
  126. property WatchedDir: string read FWatchedDir write SetWatchedDir;
  127. property WatchSubTree: Boolean read FWatchSubTree write SetWatchSubTree;
  128. property NotifyFilters: TNotifyFilters read FFilters write SetFilters;
  129. property OnChange: TFileDealMethod read FOnChange write SetOnChange;
  130. end;
  131. implementation
  132. { TCnShellChangeThread }
  133. constructor TCnShellChangeThread.Create(AParent: TCnFileSystemWatcher;
  134. ChangeEvent: TFileDealMethod);
  135. begin
  136. FParent := AParent;
  137. FreeOnTerminate := True;
  138. FChangeEvent := ChangeEvent;
  139. InitializeCriticalSection(FCS);
  140. FDirectoryHandle := 0;
  141. FCompletionPort := 0;
  142. FFileMasks := TStringList.Create;
  143. FTmpFileMasks := TStringList.Create;
  144. inherited Create(True);
  145. end;
  146. destructor TCnShellChangeThread.Destroy;
  147. begin
  148. FFileMasks.Free;
  149. FTmpFileMasks.Free;
  150. CloseHandle(FDirectoryHandle);
  151. CloseHandle(FCompletionPort);
  152. DeleteCriticalSection(FCS);
  153. inherited Destroy;
  154. end;
  155. procedure TCnShellChangeThread.DoIOCompletionEvent;
  156. var
  157. TempBuffer: TNotificationBuffer;
  158. FileOpNotification: PFileNotifyInformation;
  159. Offset: Longint;
  160. FileName1, FileName2: string;
  161. FileOperation: TFileOperation;
  162. procedure DoDirChangeEvent;
  163. var
  164. IsInFileMasks: Boolean;
  165. begin
  166. if Assigned(ChangeEvent) and FActive then
  167. begin
  168. if FTmpFileMasks.Count > 0 then
  169. IsInFileMasks := FileMatchesMasks(FileName1, FTmpFileMasks)
  170. else
  171. IsInFileMasks := FileMatchesMasks(FileName1, '*.*', False);
  172. if IsInFileMasks then
  173. begin
  174. if FIncludePath then
  175. begin
  176. FileName1 := GetTrueFileName(FDirectory + FileName1);
  177. if FileOperation = foRenamed then
  178. FileName2 := GetTrueFileName(FDirectory + FileName2);
  179. end;
  180. ChangeEvent(FParent, FileOperation, FileName1, FileName2);
  181. end;
  182. end;
  183. end;
  184. function GetFileName(const FileName: PWideChar; FileNameLength: DWORD):string;
  185. begin
  186. Result := '';
  187. if Trim(FileName) <> '' then
  188. Result := WideCharLenToString(FileName, FileNameLength div SizeOf(WideChar));
  189. end;
  190. begin
  191. Lock;
  192. TempBuffer := FNotificationBuffer;
  193. FTmpFileMasks.Assign(FFileMasks);
  194. FillChar(FNotificationBuffer, SizeOf(FNotificationBuffer), 0);
  195. Unlock;
  196. Pointer(FileOpNotification) := @TempBuffer[0];
  197. repeat
  198. with FileOpNotification^ do
  199. begin
  200. Offset := NextEntryOffset;
  201. FileName2 := '';
  202. case Action of
  203. FILE_ACTION_ADDED..FILE_ACTION_RENAMED_OLD_NAME:
  204. begin
  205. FileName1 := GetFileName(FileName, FileNameLength);
  206. FileOperation := TFileOperation(Action - 1);
  207. if Action <> FILE_ACTION_RENAMED_OLD_NAME then
  208. DoDirChangeEvent;
  209. end;
  210. FILE_ACTION_RENAMED_NEW_NAME:
  211. begin
  212. if FileOperation = foRenamed then
  213. begin
  214. FileName2 := GetFileName(FileName, FileNameLength);
  215. DoDirChangeEvent;
  216. end;
  217. end;
  218. end;
  219. end;
  220. Pointer(FileOpNotification) := Pointer(Integer(FileOpNotification) + OffSet);
  221. until Offset = 0;
  222. end;
  223. procedure TCnShellChangeThread.Execute;
  224. var
  225. numBytes: DWORD;
  226. CompletionKey: TCnNativePointer;
  227. PFOverlapped: POverlapped;
  228. TempDirectoryHandle: Cardinal;
  229. TempCompletionPort: Cardinal;
  230. begin
  231. TempCompletionPort := FCompletionPort;
  232. while not Terminated do
  233. begin
  234. Lock;
  235. TempDirectoryHandle := FDirectoryHandle;
  236. TempCompletionPort := FCompletionPort;
  237. Unlock;
  238. if TempDirectoryHandle > 0 then
  239. begin
  240. PFOverlapped := @FOverlapped;
  241. GetQueuedCompletionStatus(TempCompletionPort, numBytes, CompletionKey,
  242. PFOverlapped, INFINITE);
  243. if CompletionKey = Handle then
  244. begin
  245. Synchronize(DoIOCompletionEvent);
  246. FBytesWritten := 0;
  247. FillChar(FNotificationBuffer, SizeOf(FNotificationBuffer), 0);
  248. ReadDirectoryChanges(FDirectoryHandle, @FNotificationBuffer,
  249. SizeOf(FNotificationBuffer), FWatchSubTree, FNotifyOptionFlags,
  250. @FBytesWritten, @FOverlapped, nil);
  251. end;
  252. end;
  253. end;
  254. PostQueuedCompletionStatus(TempCompletionPort, numBytes, CompletionKey, PFOverlapped);
  255. end;
  256. procedure TCnShellChangeThread.Lock;
  257. begin
  258. EnterCriticalSection(FCS);
  259. end;
  260. function TCnShellChangeThread.ResetReadDirctory: Boolean;
  261. var
  262. TempHandle: Cardinal;
  263. TempCompletionPort: Cardinal;
  264. begin
  265. Result := False;
  266. CloseHandle(FDirectoryHandle);
  267. PostQueuedCompletionStatus(FCompletionPort, 0, 0, nil);
  268. CloseHandle(FCompletionPort);
  269. TempHandle := CreateFile(PChar(FDirectory), GENERIC_READ,
  270. FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
  271. nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS
  272. or FILE_FLAG_OVERLAPPED, 0);
  273. Lock;
  274. FDirectoryHandle := TempHandle;
  275. Unlock;
  276. if (TempHandle = INVALID_HANDLE_VALUE) or
  277. (GetLastError in [ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND, ERROR_ACCESS_DENIED]) then
  278. begin
  279. Lock;
  280. FDirectoryHandle := 0;
  281. FCompletionPort := 0;
  282. Unlock;
  283. Exit;
  284. end;
  285. TempCompletionPort := CreateIoCompletionPort(FDirectoryHandle, 0, Handle, 0);
  286. Lock;
  287. FCompletionPort := TempCompletionPort;
  288. Unlock;
  289. FBytesWritten := 0;
  290. FillChar(FNotificationBuffer, SizeOf(FNotificationBuffer), 0);
  291. Result := ReadDirectoryChanges(FDirectoryHandle, @FNotificationBuffer,
  292. SizeOf(FNotificationBuffer), FWatchSubTree, FNotifyOptionFlags, @FBytesWritten,
  293. @FOverlapped, nil);
  294. end;
  295. procedure TCnShellChangeThread.SetDirectoryOptions(Directory: String; Active: Boolean;
  296. WatchSubTree: Boolean; NotifyOptionFlags : DWORD);
  297. begin
  298. FWatchSubTree := WatchSubTree;
  299. FNotifyOptionFlags := NotifyOptionFlags;
  300. FDirectory := IncludeTrailingBackslash(Directory);
  301. FActive := Active;
  302. ResetReadDirctory;
  303. end;
  304. procedure TCnShellChangeThread.SetFileMasks(FileMasks: TStringList);
  305. begin
  306. if Assigned(FileMasks) then
  307. begin
  308. FFileMasks.Assign(FileMasks);
  309. end else
  310. FFileMasks.Text := '*.*';
  311. end;
  312. procedure TCnShellChangeThread.SetIncludePath(IncludePath: Boolean);
  313. begin
  314. FIncludePath := IncludePath;
  315. end;
  316. procedure TCnShellChangeThread.Unlock;
  317. begin
  318. LeaveCriticalSection(FCS);
  319. end;
  320. { TCnFileSystemWatcher }
  321. procedure TCnFileSystemWatcher.Change;
  322. begin
  323. if csDesigning in ComponentState then
  324. Exit;
  325. if Assigned(FThread) then
  326. begin
  327. FThread.SetDirectoryOptions(FWatchedDir, FActive, LongBool(FWatchSubTree), NotifyOptionFlags);
  328. end;
  329. end;
  330. constructor TCnFileSystemWatcher.Create(AOwner: TComponent);
  331. begin
  332. inherited Create(AOwner);
  333. FActive := False;
  334. FWatchedDir := 'C:\';
  335. FFilters := [nfFilenameChange, nfDirNameChange];
  336. FWatchSubTree := True;
  337. FFileMasks := TStringList.Create;
  338. FFileMasks.OnChange := OnFileMasksChange;
  339. FFileMasks.Text := '*.*';
  340. FIncludePath := False;
  341. FOnChange := nil;
  342. end;
  343. destructor TCnFileSystemWatcher.Destroy;
  344. begin
  345. FFileMasks.Free;
  346. if Assigned(FThread) then
  347. FThread.Terminate;
  348. inherited Destroy;
  349. end;
  350. procedure TCnFileSystemWatcher.GetComponentInfo(var AName, Author, Email,
  351. Comment: string);
  352. begin
  353. AName := SCnFileSystemWatcherName;
  354. Author := SCnPack_solokey;
  355. Email := SCnPack_solokeyEmail;
  356. Comment := SCnFileSystemWatcherComment;
  357. end;
  358. function TCnFileSystemWatcher.NotifyOptionFlags: DWORD;
  359. begin
  360. Result := 0;
  361. if nfFileNameChange in FFilters then
  362. Result := Result or FILE_NOTIFY_CHANGE_FILE_NAME;
  363. if nfDirNameChange in FFilters then
  364. Result := Result or FILE_NOTIFY_CHANGE_DIR_NAME;
  365. if nfSizeChange in FFilters then
  366. Result := Result or FILE_NOTIFY_CHANGE_SIZE;
  367. if nfAttributeChange in FFilters then
  368. Result := Result or FILE_NOTIFY_CHANGE_ATTRIBUTES;
  369. if nfWriteChange in FFilters then
  370. Result := Result or FILE_NOTIFY_CHANGE_LAST_WRITE;
  371. if nfAccessChange in FFilters then
  372. Result := Result or FILE_NOTIFY_CHANGE_LAST_ACCESS;
  373. if nfCreationDateChange in FFilters then
  374. Result := Result or FILE_NOTIFY_CHANGE_CREATION;
  375. if nfSecurityChange in FFilters then
  376. Result := Result or FILE_NOTIFY_CHANGE_SECURITY;
  377. end;
  378. procedure TCnFileSystemWatcher.OnFileMasksChange(Sender: TObject);
  379. begin
  380. if Assigned(FThread) then
  381. FThread.SetFileMasks(FFileMasks);
  382. end;
  383. procedure TCnFileSystemWatcher.SetActive(const Value: Boolean);
  384. begin
  385. if FActive <> Value then
  386. begin
  387. FActive := Value;
  388. Change;
  389. if FActive then
  390. Start
  391. else
  392. Stop;
  393. end;
  394. end;
  395. procedure TCnFileSystemWatcher.SetFileMasks(const Value: TStringList);
  396. begin
  397. if Assigned(Value) then
  398. FFileMasks.Assign(Value);
  399. if Assigned(FThread) then
  400. FThread.SetFileMasks(FFileMasks);
  401. end;
  402. procedure TCnFileSystemWatcher.SetFilters(const Value: TNotifyFilters);
  403. begin
  404. if FFilters <> Value then
  405. begin
  406. FFilters := Value;
  407. Change;
  408. end;
  409. end;
  410. procedure TCnFileSystemWatcher.SetIncludePath(const Value: Boolean);
  411. begin
  412. FIncludePath := Value;
  413. if Assigned(FThread) then
  414. FThread.SetIncludePath(FIncludePath);
  415. end;
  416. procedure TCnFileSystemWatcher.SetOnChange(const Value: TFileDealMethod);
  417. begin
  418. FOnChange := Value;
  419. if Assigned(FOnChange) and FActive then
  420. Start
  421. else
  422. Stop;
  423. Change;
  424. end;
  425. procedure TCnFileSystemWatcher.SetWatchedDir(const Value: string);
  426. begin
  427. if not SameText(FWatchedDir, Value) then
  428. begin
  429. FWatchedDir := Value;
  430. Change;
  431. end;
  432. end;
  433. procedure TCnFileSystemWatcher.SetWatchSubTree(const Value: Boolean);
  434. begin
  435. if FWatchSubTree <> Value then
  436. begin
  437. FWatchSubTree := Value;
  438. Change;
  439. end;
  440. end;
  441. procedure TCnFileSystemWatcher.Start;
  442. begin
  443. if csDesigning in ComponentState then
  444. Exit;
  445. if Assigned(FOnChange) then
  446. begin
  447. FThread := TCnShellChangeThread.Create(Self, FOnChange);
  448. FThread.SetDirectoryOptions(FWatchedDir, FActive, LongBool(FWatchSubTree), NotifyOptionFlags);
  449. FThread.SetFileMasks(FFileMasks);
  450. FThread.SetIncludePath(FIncludePath);
  451. FThread.Resume;
  452. end;
  453. end;
  454. procedure TCnFileSystemWatcher.Stop;
  455. begin
  456. if csDesigning in ComponentState then
  457. Exit;
  458. if Assigned(FThread) then
  459. begin
  460. FThread.Terminate;
  461. FThread := nil;
  462. end;
  463. end;
  464. end.