1
0
mirror of https://github.com/winscp/winscp.git synced 2026-10-24 02:28:55 +03:00
Files
winscp/source/packages/my/DirectoryMonitor.pas
Martin Prikryl 941e1d6f7d Fixes for handling TFileNotifyInformation structure from dc855630a2
(FileName is not null-terminated + Next entry offset was incorrectly processed as PChar is two-byte in Unicode)

Source commit: 17c446e1ce05d7c25555d1f5f5ee54522dc61071
2018-05-04 02:38:10 +02:00

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.