1
0

DirectoryMonitor.pas 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. unit DirectoryMonitor;
  2. // Based on DirMon by phaeteon and LoLa
  3. interface
  4. uses
  5. Windows, Messages, SysUtils, Classes, CompThread;
  6. type
  7. EDirectoryMonitorError = class(Exception);
  8. TFileChangedEvent = procedure(Sender: TObject; const FileName: string) of Object;
  9. TFileRenamedEvent = procedure(Sender: TObject; const FromFileName: string; const ToFileName: string) of Object;
  10. TDirectoryMonitor = class(TComponent)
  11. private
  12. FDirectoryHandle: THandle;
  13. FNotificationBuffer: array[0..4096] of Byte;
  14. FWatchThread: TCompThread;
  15. FWatchFilters: DWord;
  16. FOverlapped: TOverlapped;
  17. FPOverlapped: POverlapped;
  18. FBytesWritten: DWORD;
  19. FCompletionPort: THandle;
  20. FPath: string;
  21. FActive: Boolean;
  22. FOnCreated: TFileChangedEvent;
  23. FOnDeleted: TFileChangedEvent;
  24. FOnModified: TFileChangedEvent;
  25. FOnRenamed: TFileRenamedEvent;
  26. FWatchSubTree: Boolean;
  27. procedure SetActive(AActive: Boolean);
  28. procedure SetPath(aPath: string);
  29. protected
  30. procedure Start;
  31. procedure Stop;
  32. procedure DoCreated(Sender: TObject; FileName: string);
  33. procedure DoDeleted(Sender: TObject; FileName: string);
  34. procedure DoModified(Sender: TObject; FileName: string);
  35. procedure DoRenamed(Sender: TObject; FromFileName: string; ToFileName: string);
  36. public
  37. constructor Create(AOwner: TComponent); override;
  38. destructor Destroy; override;
  39. published
  40. property Active: Boolean read FActive write SetActive;
  41. property Path: string read FPath write SetPath;
  42. property OnCreated: TFileChangedEvent read FOnCreated write FOnCreated;
  43. property OnDeleted: TFileChangedEvent read FOnDeleted write FOnDeleted;
  44. property OnModified: TFileChangedEvent read FOnModified write FOnModified;
  45. property OnRenamed: TFileRenamedEvent read FOnRenamed write FOnRenamed;
  46. property WatchSubtree: Boolean read FWatchSubTree write FWatchSubtree;
  47. property WatchFilters: DWord read FWatchFilters write FWatchFilters;
  48. end;
  49. implementation
  50. type
  51. // See Windows API help
  52. PFileNotifyInformation = ^TFileNotifyInformation;
  53. TFileNotifyInformation = record
  54. NextEntryOffset: DWord;
  55. Action: DWord;
  56. FileNameLength: DWord;
  57. FileName: array[0..0] of WideChar;
  58. end;
  59. const
  60. FILE_LIST_DIRECTORY = $0001;
  61. type
  62. TDirectoryMonitorThread = class(TCompThread)
  63. private
  64. FParent: TDirectoryMonitor;
  65. FRenamedFrom: string;
  66. procedure HandleEvent;
  67. protected
  68. procedure Execute; override;
  69. public
  70. constructor Create(AParent: TDirectoryMonitor);
  71. end;
  72. constructor TDirectoryMonitorThread.Create(AParent: TDirectoryMonitor);
  73. begin
  74. inherited Create(True);
  75. FreeOnTerminate := False;
  76. FParent := AParent;
  77. end;
  78. procedure TDirectoryMonitorThread.HandleEvent;
  79. var
  80. FileOpNotification: PFileNotifyInformation;
  81. Offset: DWord;
  82. FileName: string;
  83. begin
  84. FileOpNotification := PFileNotifyInformation(@FParent.FNotificationBuffer[0]);
  85. repeat
  86. Offset := FileOpNotification^.NextEntryOffset;
  87. FileName := WideCharLenToString(@(FileOpNotification^.FileName), FileOpNotification^.FileNameLength div SizeOf(Char));
  88. case FileOpNotification^.Action of
  89. 1: FParent.DoCreated(FParent, FileName);
  90. 2: FParent.DoDeleted(FParent, FileName);
  91. 3: FParent.DoModified(FParent, FileName);
  92. 4: FRenamedFrom := FileName;
  93. 5: FParent.DoRenamed(FParent, FRenamedFrom, FileName);
  94. end;
  95. FileOpNotification := PFileNotifyInformation(IntPtr(FileOpNotification) + IntPtr(Offset));
  96. until (Offset = 0);
  97. end;
  98. procedure TDirectoryMonitorThread.Execute;
  99. var
  100. NumBytes: DWord;
  101. CompletionKey: ULONG_PTR;
  102. begin
  103. while (not Terminated) do
  104. begin
  105. GetQueuedCompletionStatus(FParent.FCompletionPort, NumBytes, CompletionKey, FParent.FPOverlapped, INFINITE);
  106. if CompletionKey <> 0 then
  107. begin
  108. Synchronize(HandleEvent);
  109. with FParent do
  110. begin
  111. FBytesWritten := 0;
  112. ZeroMemory(@FNotificationBuffer, SizeOf(FNotificationBuffer));
  113. ReadDirectoryChanges(FDirectoryHandle, @FNotificationBuffer, SizeOf(FNotificationBuffer), FParent.WatchSubtree, WatchFilters, @FBytesWritten, @FOverlapped, nil);
  114. end;
  115. end
  116. else
  117. begin
  118. Terminate;
  119. end;
  120. end;
  121. end;
  122. { TDirectoryMonitor }
  123. constructor TDirectoryMonitor.Create(AOwner: TComponent);
  124. begin
  125. inherited;
  126. FCompletionPort := 0;
  127. FDirectoryHandle := 0;
  128. FPOverlapped := @FOverlapped;
  129. ZeroMemory(@FOverlapped, SizeOf(FOverlapped));
  130. FWatchFilters := FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME or FILE_NOTIFY_CHANGE_LAST_WRITE or FILE_NOTIFY_CHANGE_CREATION;
  131. end;
  132. destructor TDirectoryMonitor.Destroy;
  133. begin
  134. if FActive then Stop;
  135. inherited;
  136. end;
  137. procedure TDirectoryMonitor.SetActive(AActive: Boolean);
  138. begin
  139. if csDesigning in ComponentState then Exit;
  140. if AActive <> FActive then
  141. begin
  142. if AActive then Start
  143. else Stop;
  144. end;
  145. end;
  146. procedure TDirectoryMonitor.Start;
  147. begin
  148. FDirectoryHandle :=
  149. CreateFile(
  150. PChar(FPath),
  151. FILE_LIST_DIRECTORY,
  152. FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
  153. nil,
  154. OPEN_EXISTING,
  155. FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED,
  156. 0);
  157. if FDirectoryHandle = INVALID_HANDLE_VALUE then
  158. begin
  159. FDirectoryHandle := 0;
  160. raise EDirectoryMonitorError.Create(SysErrorMessage(GetLastError));
  161. exit;
  162. end;
  163. FCompletionPort := CreateIoCompletionPort(FDirectoryHandle, 0, LongInt(Pointer(Self)), 0);
  164. ZeroMemory(@FNotificationBuffer, SizeOf(FNotificationBuffer));
  165. FBytesWritten := 0;
  166. if not ReadDirectoryChanges(FDirectoryHandle, @FNotificationBuffer, SizeOf(FNotificationBuffer), FWatchSubTree, WatchFilters, @FBytesWritten, @FOverlapped, nil) then
  167. begin
  168. CloseHandle(FDirectoryHandle);
  169. FDirectoryHandle := 0;
  170. CloseHandle(FCompletionPort);
  171. FCompletionPort := 0;
  172. raise EDirectoryMonitorError.Create(SysErrorMessage(GetLastError));
  173. exit;
  174. end;
  175. FWatchThread := TDirectoryMonitorThread.Create(Self);
  176. TDirectoryMonitorThread(FWatchThread).Resume;
  177. FActive := True;
  178. end;
  179. procedure TDirectoryMonitor.Stop;
  180. begin
  181. PostQueuedCompletionStatus(FCompletionPort, 0, 0, nil);
  182. FWatchThread.WaitFor;
  183. FWatchThread.Free;
  184. CloseHandle(FDirectoryHandle);
  185. FDirectoryHandle := 0;
  186. CloseHandle(FCompletionPort);
  187. FCompletionPort := 0;
  188. FActive := False;
  189. end;
  190. procedure TDirectoryMonitor.DoCreated(Sender: TObject; FileName: string);
  191. begin
  192. if Assigned(FOnCreated) then FOnCreated(Sender, FPath + FileName);
  193. end;
  194. procedure TDirectoryMonitor.DoDeleted(Sender: TObject; FileName: string);
  195. begin
  196. if Assigned(FOnDeleted) then FOnDeleted(Sender, FPath + FileName);
  197. end;
  198. procedure TDirectoryMonitor.DoModified(Sender: TObject; FileName: string);
  199. begin
  200. if Assigned(FOnModified) then FOnModified(Sender, FPath + FileName);
  201. end;
  202. procedure TDirectoryMonitor.DoRenamed(Sender: TObject; FromFileName: string; ToFileName: string);
  203. begin
  204. if Assigned(FOnRenamed) then FOnRenamed(Sender, FPath + FromFileName, FPath + ToFileName);
  205. end;
  206. procedure TDirectoryMonitor.SetPath(APath: string);
  207. begin
  208. APath := IncludeTrailingPathDelimiter(APath);
  209. if APath <> FPath then
  210. begin
  211. FPath := APath;
  212. if FActive then
  213. begin
  214. Stop;
  215. Start;
  216. end;
  217. end;
  218. end;
  219. end.