mirror of
https://github.com/winscp/winscp.git
synced 2026-10-24 02:28:55 +03:00
(FileName is not null-terminated + Next entry offset was incorrectly processed as PChar is two-byte in Unicode) Source commit: 17c446e1ce05d7c25555d1f5f5ee54522dc61071
254 lines
7.3 KiB
ObjectPascal
254 lines
7.3 KiB
ObjectPascal
unit DirectoryMonitor;
|
|
|
|
// Based on DirMon by phaeteon and LoLa
|
|
|
|
interface
|
|
|
|
uses
|
|
Windows, Messages, SysUtils, Classes, CompThread;
|
|
|
|
type
|
|
EDirectoryMonitorError = class(Exception);
|
|
|
|
TFileChangedEvent = procedure(Sender: TObject; const FileName: string) of Object;
|
|
TFileRenamedEvent = procedure(Sender: TObject; const FromFileName: string; const ToFileName: string) of Object;
|
|
|
|
TDirectoryMonitor = class(TComponent)
|
|
private
|
|
FDirectoryHandle: THandle;
|
|
FNotificationBuffer: array[0..4096] of Byte;
|
|
FWatchThread: TCompThread;
|
|
FWatchFilters: DWord;
|
|
FOverlapped: TOverlapped;
|
|
FPOverlapped: POverlapped;
|
|
FBytesWritten: DWORD;
|
|
FCompletionPort: THandle;
|
|
FPath: string;
|
|
FActive: Boolean;
|
|
FOnCreated: TFileChangedEvent;
|
|
FOnDeleted: TFileChangedEvent;
|
|
FOnModified: TFileChangedEvent;
|
|
FOnRenamed: TFileRenamedEvent;
|
|
FWatchSubTree: Boolean;
|
|
|
|
procedure SetActive(AActive: Boolean);
|
|
procedure SetPath(aPath: string);
|
|
|
|
protected
|
|
procedure Start;
|
|
procedure Stop;
|
|
procedure DoCreated(Sender: TObject; FileName: string);
|
|
procedure DoDeleted(Sender: TObject; FileName: string);
|
|
procedure DoModified(Sender: TObject; FileName: string);
|
|
procedure DoRenamed(Sender: TObject; FromFileName: string; ToFileName: string);
|
|
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
|
|
published
|
|
property Active: Boolean read FActive write SetActive;
|
|
property Path: string read FPath write SetPath;
|
|
property OnCreated: TFileChangedEvent read FOnCreated write FOnCreated;
|
|
property OnDeleted: TFileChangedEvent read FOnDeleted write FOnDeleted;
|
|
property OnModified: TFileChangedEvent read FOnModified write FOnModified;
|
|
property OnRenamed: TFileRenamedEvent read FOnRenamed write FOnRenamed;
|
|
property WatchSubtree: Boolean read FWatchSubTree write FWatchSubtree;
|
|
property WatchFilters: DWord read FWatchFilters write FWatchFilters;
|
|
end;
|
|
|
|
implementation
|
|
|
|
type
|
|
// See Windows API help
|
|
PFileNotifyInformation = ^TFileNotifyInformation;
|
|
TFileNotifyInformation = record
|
|
NextEntryOffset: DWord;
|
|
Action: DWord;
|
|
FileNameLength: DWord;
|
|
FileName: array[0..0] of WideChar;
|
|
end;
|
|
|
|
const
|
|
FILE_LIST_DIRECTORY = $0001;
|
|
|
|
type
|
|
TDirectoryMonitorThread = class(TCompThread)
|
|
private
|
|
FParent: TDirectoryMonitor;
|
|
FRenamedFrom: string;
|
|
procedure HandleEvent;
|
|
|
|
protected
|
|
procedure Execute; override;
|
|
|
|
public
|
|
constructor Create(AParent: TDirectoryMonitor);
|
|
end;
|
|
|
|
constructor TDirectoryMonitorThread.Create(AParent: TDirectoryMonitor);
|
|
begin
|
|
inherited Create(True);
|
|
FreeOnTerminate := False;
|
|
FParent := AParent;
|
|
end;
|
|
|
|
procedure TDirectoryMonitorThread.HandleEvent;
|
|
var
|
|
FileOpNotification: PFileNotifyInformation;
|
|
Offset: DWord;
|
|
FileName: string;
|
|
begin
|
|
FileOpNotification := PFileNotifyInformation(@FParent.FNotificationBuffer[0]);
|
|
repeat
|
|
Offset := FileOpNotification^.NextEntryOffset;
|
|
FileName := WideCharLenToString(@(FileOpNotification^.FileName), FileOpNotification^.FileNameLength div SizeOf(Char));
|
|
case FileOpNotification^.Action of
|
|
1: FParent.DoCreated(FParent, FileName);
|
|
2: FParent.DoDeleted(FParent, FileName);
|
|
3: FParent.DoModified(FParent, FileName);
|
|
4: FRenamedFrom := FileName;
|
|
5: FParent.DoRenamed(FParent, FRenamedFrom, FileName);
|
|
end;
|
|
FileOpNotification := PFileNotifyInformation(IntPtr(FileOpNotification) + IntPtr(Offset));
|
|
until (Offset = 0);
|
|
end;
|
|
|
|
procedure TDirectoryMonitorThread.Execute;
|
|
var
|
|
NumBytes: DWord;
|
|
CompletionKey: ULONG_PTR;
|
|
begin
|
|
while (not Terminated) do
|
|
begin
|
|
GetQueuedCompletionStatus(FParent.FCompletionPort, NumBytes, CompletionKey, FParent.FPOverlapped, INFINITE);
|
|
if CompletionKey <> 0 then
|
|
begin
|
|
Synchronize(HandleEvent);
|
|
with FParent do
|
|
begin
|
|
FBytesWritten := 0;
|
|
ZeroMemory(@FNotificationBuffer, SizeOf(FNotificationBuffer));
|
|
ReadDirectoryChanges(FDirectoryHandle, @FNotificationBuffer, SizeOf(FNotificationBuffer), FParent.WatchSubtree, WatchFilters, @FBytesWritten, @FOverlapped, nil);
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
Terminate;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ TDirectoryMonitor }
|
|
|
|
constructor TDirectoryMonitor.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
FCompletionPort := 0;
|
|
FDirectoryHandle := 0;
|
|
FPOverlapped := @FOverlapped;
|
|
ZeroMemory(@FOverlapped, SizeOf(FOverlapped));
|
|
FWatchFilters := FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME or FILE_NOTIFY_CHANGE_LAST_WRITE or FILE_NOTIFY_CHANGE_CREATION;
|
|
end;
|
|
|
|
destructor TDirectoryMonitor.Destroy;
|
|
begin
|
|
if FActive then Stop;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TDirectoryMonitor.SetActive(AActive: Boolean);
|
|
begin
|
|
if csDesigning in ComponentState then Exit;
|
|
|
|
if AActive <> FActive then
|
|
begin
|
|
if AActive then Start
|
|
else Stop;
|
|
end;
|
|
end;
|
|
|
|
procedure TDirectoryMonitor.Start;
|
|
begin
|
|
FDirectoryHandle :=
|
|
CreateFile(
|
|
PChar(FPath),
|
|
FILE_LIST_DIRECTORY,
|
|
FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
|
|
nil,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED,
|
|
0);
|
|
|
|
if FDirectoryHandle = INVALID_HANDLE_VALUE then
|
|
begin
|
|
FDirectoryHandle := 0;
|
|
raise EDirectoryMonitorError.Create(SysErrorMessage(GetLastError));
|
|
exit;
|
|
end;
|
|
|
|
FCompletionPort := CreateIoCompletionPort(FDirectoryHandle, 0, LongInt(Pointer(Self)), 0);
|
|
ZeroMemory(@FNotificationBuffer, SizeOf(FNotificationBuffer));
|
|
FBytesWritten := 0;
|
|
if not ReadDirectoryChanges(FDirectoryHandle, @FNotificationBuffer, SizeOf(FNotificationBuffer), FWatchSubTree, WatchFilters, @FBytesWritten, @FOverlapped, nil) then
|
|
begin
|
|
CloseHandle(FDirectoryHandle);
|
|
FDirectoryHandle := 0;
|
|
CloseHandle(FCompletionPort);
|
|
FCompletionPort := 0;
|
|
raise EDirectoryMonitorError.Create(SysErrorMessage(GetLastError));
|
|
exit;
|
|
end;
|
|
FWatchThread := TDirectoryMonitorThread.Create(Self);
|
|
TDirectoryMonitorThread(FWatchThread).Resume;
|
|
FActive := True;
|
|
end;
|
|
|
|
procedure TDirectoryMonitor.Stop;
|
|
begin
|
|
PostQueuedCompletionStatus(FCompletionPort, 0, 0, nil);
|
|
FWatchThread.WaitFor;
|
|
FWatchThread.Free;
|
|
CloseHandle(FDirectoryHandle);
|
|
FDirectoryHandle := 0;
|
|
CloseHandle(FCompletionPort);
|
|
FCompletionPort := 0;
|
|
FActive := False;
|
|
end;
|
|
|
|
procedure TDirectoryMonitor.DoCreated(Sender: TObject; FileName: string);
|
|
begin
|
|
if Assigned(FOnCreated) then FOnCreated(Sender, FPath + FileName);
|
|
end;
|
|
|
|
procedure TDirectoryMonitor.DoDeleted(Sender: TObject; FileName: string);
|
|
begin
|
|
if Assigned(FOnDeleted) then FOnDeleted(Sender, FPath + FileName);
|
|
end;
|
|
|
|
procedure TDirectoryMonitor.DoModified(Sender: TObject; FileName: string);
|
|
begin
|
|
if Assigned(FOnModified) then FOnModified(Sender, FPath + FileName);
|
|
end;
|
|
|
|
procedure TDirectoryMonitor.DoRenamed(Sender: TObject; FromFileName: string; ToFileName: string);
|
|
begin
|
|
if Assigned(FOnRenamed) then FOnRenamed(Sender, FPath + FromFileName, FPath + ToFileName);
|
|
end;
|
|
|
|
procedure TDirectoryMonitor.SetPath(APath: string);
|
|
begin
|
|
APath := IncludeTrailingPathDelimiter(APath);
|
|
if APath <> FPath then
|
|
begin
|
|
FPath := APath;
|
|
if FActive then
|
|
begin
|
|
Stop;
|
|
Start;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
end.
|