706 lines
22 KiB
ObjectPascal
706 lines
22 KiB
ObjectPascal
unit Myc.DirectoryMonitor experimental;
|
|
|
|
interface
|
|
|
|
uses
|
|
System.SysUtils,
|
|
Myc.Core.Notifier,
|
|
Myc.TaskManager,
|
|
Myc.Signals;
|
|
|
|
type
|
|
// What kind of changes to monitor
|
|
TFileChangeKind = (
|
|
FileNameChange, // A file name has been created, renamed, or deleted.
|
|
DirNameChange, // A directory name has been created, renamed, or deleted.
|
|
AttributeChange, // File or directory attributes have changed.
|
|
SizeChange, // File size has changed.
|
|
LastWriteChange, // Last write time of a file or directory has changed.
|
|
SecurityChange // Security descriptor of a file or directory has changed.
|
|
);
|
|
TFileChangeKinds = set of TFileChangeKind;
|
|
|
|
TFileState = (
|
|
Unchanged, // The file exists and has not changed since the last check.
|
|
Deleted, // The file has been deleted since the last check.
|
|
Changed, // The file has been modified (e.g., size, write time) since the last check.
|
|
Added // The file is new since the last check.
|
|
);
|
|
|
|
// Represents a single file and its properties.
|
|
IFileInfo = interface
|
|
{$region 'private'}
|
|
function GetFullPath: string;
|
|
function GetFileName: string;
|
|
function GetExtension: string;
|
|
function GetCreationTime: TDateTime;
|
|
function GetLastWriteTime: TDateTime;
|
|
function GetSize: Int64;
|
|
{$endregion}
|
|
|
|
// The full path of the file.
|
|
property FullPath: string read GetFullPath;
|
|
// The name of the file including the extension.
|
|
property FileName: string read GetFileName;
|
|
// The extension of the file.
|
|
property Extension: string read GetExtension;
|
|
// The creation timestamp of the file.
|
|
property CreationTime: TDateTime read GetCreationTime;
|
|
// The last modification timestamp of the file.
|
|
property LastWriteTime: TDateTime read GetLastWriteTime;
|
|
// The size of the file in bytes.
|
|
property Size: Int64 read GetSize;
|
|
|
|
// Client may hook in here for change notifications.
|
|
// If ChangeNotification returns false, it will be removed from the notify list.
|
|
procedure AddChangeNotification(const ChangeNotification: TFunc<IFileInfo, TFileState, Boolean>);
|
|
end;
|
|
|
|
TSubDirectoryHandling = (
|
|
Ignore, // Only monitor the specified directory.
|
|
Recursive // Monitor the specified directory and all its subdirectories.
|
|
);
|
|
|
|
// Monitors directories for file changes.
|
|
IDirectoryMonitor = interface
|
|
{$region 'private'}
|
|
function GetFiles(const Name: String): IFileInfo;
|
|
function GetFileCount: Integer;
|
|
{$endregion}
|
|
|
|
// Retrieves a snapshot of the currently known files.
|
|
property Files[const Name: String]: IFileInfo read GetFiles;
|
|
property FileCount: Integer read GetFileCount;
|
|
|
|
// Checks the current state of a tracked file against the file system.
|
|
function CheckState(const FileInfo: IFileInfo): TFileState;
|
|
|
|
// Attempts to find an updated version of a given file info object.
|
|
function TryFindNewVersion(const OldFile: IFileInfo; out NewFile: IFileInfo): TFileState;
|
|
end;
|
|
|
|
// Factory for creating directory monitors.
|
|
IDirectoryMonitorFactory = interface
|
|
// Adds a directory to the watch list.
|
|
procedure AddDirectory(const DirPath: string; SubDirHandling: TSubDirectoryHandling);
|
|
// Adds a file extension to filter by (e.g., '.txt'). Use '*' for all files.
|
|
procedure AddExtension(const Extension: string);
|
|
// Sets the kinds of changes to monitor.
|
|
procedure SetChangeKinds(const ChangeKinds: TFileChangeKinds);
|
|
|
|
// Creates the monitor instance based on the provided configuration.
|
|
function CreateMonitor: IDirectoryMonitor;
|
|
end;
|
|
|
|
function CreateDirectoryMonitorFactory: IDirectoryMonitorFactory;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Winapi.Windows,
|
|
System.Classes,
|
|
System.IOUtils,
|
|
System.Generics.Collections,
|
|
System.SyncObjs,
|
|
System.StrUtils;
|
|
|
|
const
|
|
// Buffer size for ReadDirectoryChangesW notifications
|
|
NOTIFY_BUFFER_SIZE = 65536; // 64 KB
|
|
|
|
type
|
|
// Manual definition of FILE_NOTIFY_INFORMATION as it seems to be missing
|
|
// from the project's Winapi.Windows unit.
|
|
PFileNotifyInformation = ^TFileNotifyInformation;
|
|
TFileNotifyInformation = packed record
|
|
NextEntryOffset: DWORD;
|
|
Action: DWORD;
|
|
FileNameLength: DWORD;
|
|
FileName: array[0..0] of WideChar;
|
|
end;
|
|
|
|
TDirectoryWatch = record
|
|
Directory: string;
|
|
WatchSubtree: Boolean;
|
|
end;
|
|
|
|
// Private record to hold all necessary information for monitoring a single directory.
|
|
TWatchInfo = record
|
|
Directory: string;
|
|
Handle: THandle;
|
|
Subtree: Boolean;
|
|
Overlapped: TOverlapped;
|
|
Buffer: TArray<Byte>;
|
|
end;
|
|
|
|
// Concrete implementation of IFileInfo
|
|
TFileInfo = class(TInterfacedObject, IFileInfo)
|
|
private
|
|
FFullPath: string;
|
|
FFileName: string;
|
|
FExtension: string;
|
|
FCreationTime: TDateTime;
|
|
FLastWriteTime: TDateTime;
|
|
FSize: Int64;
|
|
FNotifyList: TMycNotifyList<TFunc<IFileInfo, TFileState, Boolean>>;
|
|
|
|
function GetFullPath: string;
|
|
function GetFileName: string;
|
|
function GetExtension: string;
|
|
function GetCreationTime: TDateTime;
|
|
function GetLastWriteTime: TDateTime;
|
|
function GetSize: Int64;
|
|
public
|
|
constructor Create(const AFullPath: string; const SearchRec: TSearchRec);
|
|
procedure AddChangeNotification(const ChangeNotification: TFunc<IFileInfo, TFileState, Boolean>);
|
|
procedure NotifyChanges(NewState: TFileState);
|
|
end;
|
|
|
|
// Concrete implementation of IDirectoryMonitor
|
|
TDirectoryMonitor = class(TInterfacedObject, IDirectoryMonitor)
|
|
private
|
|
FExtensions: TArray<string>;
|
|
FChangeKinds: TFileChangeKinds;
|
|
FWatchInfos: TArray<TWatchInfo>;
|
|
|
|
FFiles: TDictionary<string, IFileInfo>;
|
|
FLock: TCriticalSection;
|
|
FThreadState: TState;
|
|
FStopEvent: THandle;
|
|
|
|
procedure InitialScan(const WatchInfo: TWatchInfo);
|
|
procedure MonitorLoop;
|
|
procedure ProcessChangeBuffer(const WatchInfo: TWatchInfo; BytesWritten: DWORD);
|
|
procedure HandleFileChange(Action: DWORD; const BasePath, FileName: string);
|
|
function TranslateChangeKinds: DWORD;
|
|
class function FileMatchesExtensions(const FileName: string; const Extensions: TArray<string>): Boolean; static;
|
|
|
|
function GetFiles(const Name: String): IFileInfo;
|
|
function GetFileCount: Integer;
|
|
public
|
|
constructor Create(const Directories: TArray<TDirectoryWatch>; const Extensions: TArray<string>; ChangeKinds: TFileChangeKinds);
|
|
destructor Destroy; override;
|
|
|
|
function CheckState(const FileInfo: IFileInfo): TFileState;
|
|
function TryFindNewVersion(const OldFile: IFileInfo; out NewFile: IFileInfo): TFileState;
|
|
end;
|
|
|
|
// Concrete implementation of IDirectoryMonitorFactory
|
|
TDirectoryMonitorFactory = class(TInterfacedObject, IDirectoryMonitorFactory)
|
|
private
|
|
FDirectories: TList<TDirectoryWatch>;
|
|
FExtensions: TList<string>;
|
|
FChangeKinds: TFileChangeKinds;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
|
|
procedure AddDirectory(const DirPath: string; SubDirHandling: TSubDirectoryHandling);
|
|
procedure AddExtension(const Extension: string);
|
|
procedure SetChangeKinds(const ChangeKinds: TFileChangeKinds);
|
|
function CreateMonitor: IDirectoryMonitor;
|
|
end;
|
|
|
|
function CreateDirectoryMonitorFactory: IDirectoryMonitorFactory;
|
|
begin
|
|
Result := TDirectoryMonitorFactory.Create;
|
|
end;
|
|
|
|
{ TFileInfo }
|
|
|
|
constructor TFileInfo.Create(const AFullPath: string; const SearchRec: TSearchRec);
|
|
begin
|
|
inherited Create;
|
|
FFullPath := AFullPath;
|
|
FFileName := TPath.GetFileName(AFullPath);
|
|
FExtension := TPath.GetExtension(AFullPath);
|
|
FCreationTime := SearchRec.CreationTime;
|
|
FLastWriteTime := SearchRec.TimeStamp;
|
|
FSize := SearchRec.Size;
|
|
end;
|
|
|
|
procedure TFileInfo.AddChangeNotification(const ChangeNotification: TFunc<IFileInfo, TFileState, Boolean>);
|
|
begin
|
|
FNotifyList.Lock;
|
|
try
|
|
FNotifyList.Advise(ChangeNotification);
|
|
finally
|
|
FNotifyList.Release;
|
|
end;
|
|
end;
|
|
|
|
procedure TFileInfo.NotifyChanges(NewState: TFileState);
|
|
begin
|
|
FNotifyList.Lock;
|
|
try
|
|
FNotifyList.Notify(function(const Func: TFunc<IFileInfo, TFileState, Boolean>): Boolean begin Result := Func(Self, NewState); end);
|
|
finally
|
|
FNotifyList.Release;
|
|
end;
|
|
end;
|
|
|
|
function TFileInfo.GetCreationTime: TDateTime;
|
|
begin
|
|
Result := FCreationTime;
|
|
end;
|
|
|
|
function TFileInfo.GetExtension: string;
|
|
begin
|
|
Result := FExtension;
|
|
end;
|
|
|
|
function TFileInfo.GetFileName: string;
|
|
begin
|
|
Result := FFileName;
|
|
end;
|
|
|
|
function TFileInfo.GetFullPath: string;
|
|
begin
|
|
Result := FFullPath;
|
|
end;
|
|
|
|
function TFileInfo.GetLastWriteTime: TDateTime;
|
|
begin
|
|
Result := FLastWriteTime;
|
|
end;
|
|
|
|
function TFileInfo.GetSize: Int64;
|
|
begin
|
|
Result := FSize;
|
|
end;
|
|
|
|
{ TDirectoryMonitor }
|
|
|
|
constructor TDirectoryMonitor.Create(
|
|
const Directories: TArray<TDirectoryWatch>;
|
|
const Extensions: TArray<string>;
|
|
ChangeKinds: TFileChangeKinds
|
|
);
|
|
var
|
|
i: Integer;
|
|
watch: TDirectoryWatch;
|
|
begin
|
|
inherited Create;
|
|
FExtensions := Extensions;
|
|
FChangeKinds := ChangeKinds;
|
|
FLock := TCriticalSection.Create;
|
|
FFiles := TDictionary<string, IFileInfo>.Create;
|
|
FStopEvent := CreateEvent(nil, TRUE, FALSE, nil);
|
|
|
|
SetLength(FWatchInfos, Length(Directories));
|
|
for i := 0 to High(Directories) do
|
|
begin
|
|
watch := Directories[i];
|
|
FWatchInfos[i].Directory := watch.Directory;
|
|
FWatchInfos[i].Subtree := watch.WatchSubtree;
|
|
FWatchInfos[i].Handle :=
|
|
CreateFile(
|
|
PChar(watch.Directory),
|
|
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
|
|
);
|
|
FWatchInfos[i].Overlapped.hEvent := CreateEvent(nil, TRUE, FALSE, nil);
|
|
SetLength(FWatchInfos[i].Buffer, NOTIFY_BUFFER_SIZE);
|
|
|
|
if FWatchInfos[i].Handle <> INVALID_HANDLE_VALUE then
|
|
InitialScan(FWatchInfos[i]);
|
|
end;
|
|
|
|
FThreadState := TaskManager.CreateThread(MonitorLoop);
|
|
end;
|
|
|
|
destructor TDirectoryMonitor.Destroy;
|
|
var
|
|
info: TWatchInfo;
|
|
begin
|
|
// Signal the monitoring thread to stop.
|
|
SetEvent(FStopEvent);
|
|
|
|
// Cancel all pending IO operations for the handle to ensure the thread is unblocked.
|
|
for info in FWatchInfos do
|
|
begin
|
|
if info.Handle <> INVALID_HANDLE_VALUE then
|
|
CancelIoEx(info.Handle, nil);
|
|
end;
|
|
|
|
// Wait for the thread to terminate gracefully.
|
|
TaskManager.WaitFor(FThreadState);
|
|
|
|
// After the thread has terminated, clean up all handles and resources.
|
|
for info in FWatchInfos do
|
|
begin
|
|
if info.Handle <> INVALID_HANDLE_VALUE then
|
|
CloseHandle(info.Handle);
|
|
if info.Overlapped.hEvent <> 0 then
|
|
CloseHandle(info.Overlapped.hEvent);
|
|
end;
|
|
|
|
CloseHandle(FStopEvent);
|
|
FFiles.Free;
|
|
FLock.Free;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TDirectoryMonitor.InitialScan(const WatchInfo: TWatchInfo);
|
|
|
|
procedure Scan(const Dir: string);
|
|
var
|
|
filePath, subDirPath: string;
|
|
sr: TSearchRec;
|
|
begin
|
|
try
|
|
// 1. Process all files in the current directory using the modern, more robust API.
|
|
for filePath in TDirectory.GetFiles(Dir) do
|
|
begin
|
|
if FileMatchesExtensions(filePath, FExtensions) then
|
|
begin
|
|
// We still need FindFirst here, but only on a specific file,
|
|
// not for enumeration, which avoids the bug.
|
|
if System.SysUtils.FindFirst(filePath, faAnyFile, sr) = 0 then
|
|
try
|
|
FFiles.AddOrSetValue(filePath, TFileInfo.Create(filePath, sr));
|
|
finally
|
|
System.SysUtils.FindClose(sr);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// 2. If recursive, process all subdirectories.
|
|
if WatchInfo.Subtree then
|
|
begin
|
|
for subDirPath in TDirectory.GetDirectories(Dir) do
|
|
begin
|
|
Scan(subDirPath); // Recurse into subdirectory
|
|
end;
|
|
end;
|
|
except
|
|
// Ignore exceptions from inaccessible directories (e.g., access denied)
|
|
on E: EInOutError do
|
|
begin
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
FLock.Enter;
|
|
try
|
|
Scan(WatchInfo.Directory);
|
|
finally
|
|
FLock.Leave;
|
|
end;
|
|
end;
|
|
|
|
procedure TDirectoryMonitor.MonitorLoop;
|
|
var
|
|
handles: TArray<THandle>;
|
|
i: Integer;
|
|
notifyFilter: DWORD;
|
|
waitResult: DWORD;
|
|
bytesRead: DWORD;
|
|
changedIndex: Integer;
|
|
success: Boolean;
|
|
begin
|
|
notifyFilter := TranslateChangeKinds();
|
|
|
|
for i := 0 to High(FWatchInfos) do
|
|
begin
|
|
if FWatchInfos[i].Handle <> INVALID_HANDLE_VALUE then
|
|
begin
|
|
ReadDirectoryChangesW(
|
|
FWatchInfos[i].Handle,
|
|
@FWatchInfos[i].Buffer[0],
|
|
Length(FWatchInfos[i].Buffer),
|
|
FWatchInfos[i].Subtree,
|
|
notifyFilter,
|
|
nil,
|
|
@FWatchInfos[i].Overlapped,
|
|
nil
|
|
);
|
|
end;
|
|
end;
|
|
|
|
SetLength(handles, Length(FWatchInfos) + 1);
|
|
handles[0] := FStopEvent;
|
|
for i := 0 to High(FWatchInfos) do
|
|
handles[i + 1] := FWatchInfos[i].Overlapped.hEvent;
|
|
|
|
while True do
|
|
begin
|
|
waitResult := WaitForMultipleObjects(Length(handles), @handles[0], FALSE, INFINITE);
|
|
|
|
if waitResult = WAIT_OBJECT_0 then
|
|
break;
|
|
|
|
if (waitResult >= WAIT_OBJECT_0 + 1) and (waitResult < WAIT_OBJECT_0 + Length(handles)) then
|
|
begin
|
|
changedIndex := waitResult - (WAIT_OBJECT_0 + 1);
|
|
|
|
success := GetOverlappedResult(FWatchInfos[changedIndex].Handle, FWatchInfos[changedIndex].Overlapped, bytesRead, FALSE);
|
|
|
|
if success then
|
|
begin
|
|
if bytesRead > 0 then
|
|
begin
|
|
ProcessChangeBuffer(FWatchInfos[changedIndex], bytesRead)
|
|
end
|
|
else
|
|
begin
|
|
InitialScan(FWatchInfos[changedIndex]);
|
|
end;
|
|
end;
|
|
|
|
ReadDirectoryChangesW(
|
|
FWatchInfos[changedIndex].Handle,
|
|
@FWatchInfos[changedIndex].Buffer[0],
|
|
Length(FWatchInfos[changedIndex].Buffer),
|
|
FWatchInfos[changedIndex].Subtree,
|
|
notifyFilter,
|
|
nil,
|
|
@FWatchInfos[changedIndex].Overlapped,
|
|
nil
|
|
);
|
|
end
|
|
else
|
|
begin
|
|
break;
|
|
end;
|
|
end;
|
|
// No cleanup logic here; the destructor is responsible.
|
|
end;
|
|
|
|
procedure TDirectoryMonitor.ProcessChangeBuffer(const WatchInfo: TWatchInfo; BytesWritten: DWORD);
|
|
var
|
|
pInfo: PFileNotifyInformation;
|
|
offset: DWORD;
|
|
fileName: string;
|
|
begin
|
|
pInfo := PFileNotifyInformation(@WatchInfo.Buffer[0]);
|
|
while True do
|
|
begin
|
|
SetString(fileName, PWideChar(@pInfo.FileName), pInfo.FileNameLength div SizeOf(WideChar));
|
|
|
|
HandleFileChange(pInfo.Action, WatchInfo.Directory, fileName);
|
|
|
|
if pInfo.NextEntryOffset = 0 then
|
|
break;
|
|
|
|
offset := pInfo.NextEntryOffset;
|
|
pInfo := PFileNotifyInformation(PByte(pInfo) + offset);
|
|
end;
|
|
end;
|
|
|
|
procedure TDirectoryMonitor.HandleFileChange(Action: DWORD; const BasePath, FileName: string);
|
|
var
|
|
fullPath: string;
|
|
sr: TSearchRec;
|
|
oldFileInfo, newFileInfo: IFileInfo;
|
|
begin
|
|
fullPath := TPath.Combine(BasePath, FileName);
|
|
|
|
if not FileMatchesExtensions(fullPath, FExtensions) then
|
|
exit;
|
|
|
|
case Action of
|
|
FILE_ACTION_ADDED, FILE_ACTION_MODIFIED, FILE_ACTION_RENAMED_NEW_NAME:
|
|
begin
|
|
if TFile.Exists(fullPath) and (System.SysUtils.FindFirst(fullPath, faAnyFile, sr) = 0) then
|
|
try
|
|
FLock.Enter;
|
|
try
|
|
newFileInfo := TFileInfo.Create(fullPath, sr);
|
|
|
|
if FFiles.TryGetValue(fullPath, oldFileInfo) then
|
|
begin
|
|
(oldFileInfo as TFileInfo).NotifyChanges(TFileState.Changed);
|
|
end
|
|
else
|
|
begin
|
|
(newFileInfo as TFileInfo).NotifyChanges(TFileState.Added);
|
|
end;
|
|
|
|
FFiles.AddOrSetValue(fullPath, newFileInfo);
|
|
finally
|
|
FLock.Leave;
|
|
end;
|
|
finally
|
|
System.SysUtils.FindClose(sr);
|
|
end;
|
|
end;
|
|
|
|
FILE_ACTION_REMOVED, FILE_ACTION_RENAMED_OLD_NAME:
|
|
begin
|
|
FLock.Enter;
|
|
try
|
|
if FFiles.TryGetValue(fullPath, oldFileInfo) then
|
|
begin
|
|
(oldFileInfo as TFileInfo).NotifyChanges(TFileState.Deleted);
|
|
FFiles.Remove(fullPath);
|
|
end;
|
|
finally
|
|
FLock.Leave;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TDirectoryMonitor.TranslateChangeKinds: DWORD;
|
|
begin
|
|
Result := 0;
|
|
if TFileChangeKind.FileNameChange in FChangeKinds then
|
|
Result := Result or FILE_NOTIFY_CHANGE_FILE_NAME;
|
|
if TFileChangeKind.DirNameChange in FChangeKinds then
|
|
Result := Result or FILE_NOTIFY_CHANGE_DIR_NAME;
|
|
if TFileChangeKind.AttributeChange in FChangeKinds then
|
|
Result := Result or FILE_NOTIFY_CHANGE_ATTRIBUTES;
|
|
if TFileChangeKind.SizeChange in FChangeKinds then
|
|
Result := Result or FILE_NOTIFY_CHANGE_SIZE;
|
|
if TFileChangeKind.LastWriteChange in FChangeKinds then
|
|
Result := Result or FILE_NOTIFY_CHANGE_LAST_WRITE;
|
|
if TFileChangeKind.SecurityChange in FChangeKinds then
|
|
Result := Result or FILE_NOTIFY_CHANGE_SECURITY;
|
|
end;
|
|
|
|
class function TDirectoryMonitor.FileMatchesExtensions(const FileName: string; const Extensions: TArray<string>): Boolean;
|
|
var
|
|
ext: string;
|
|
fileExt: string;
|
|
begin
|
|
if (Length(Extensions) = 0) or ((Length(Extensions) = 1) and (Extensions[0] = '*')) then
|
|
exit(true);
|
|
|
|
fileExt := TPath.GetExtension(FileName);
|
|
for ext in Extensions do
|
|
begin
|
|
if SameText(fileExt, ext) then
|
|
exit(true);
|
|
end;
|
|
Result := false;
|
|
end;
|
|
|
|
function TDirectoryMonitor.GetFiles(const Name: String): IFileInfo;
|
|
begin
|
|
FLock.Enter;
|
|
try
|
|
if not FFiles.TryGetValue(Name, Result) then
|
|
Result := nil;
|
|
finally
|
|
FLock.Leave;
|
|
end;
|
|
end;
|
|
|
|
function TDirectoryMonitor.GetFileCount: Integer;
|
|
begin
|
|
FLock.Enter;
|
|
try
|
|
Result := FFiles.Count;
|
|
finally
|
|
FLock.Leave;
|
|
end;
|
|
end;
|
|
|
|
function TDirectoryMonitor.CheckState(const FileInfo: IFileInfo): TFileState;
|
|
var
|
|
currentFileInfo: IFileInfo;
|
|
begin
|
|
if not Assigned(FileInfo) then
|
|
begin
|
|
Result := TFileState.Deleted;
|
|
exit;
|
|
end;
|
|
|
|
FLock.Enter;
|
|
try
|
|
if not FFiles.TryGetValue(FileInfo.FullPath, currentFileInfo) then
|
|
begin
|
|
Result := TFileState.Deleted;
|
|
end
|
|
else if currentFileInfo = FileInfo then
|
|
begin
|
|
Result := TFileState.Unchanged;
|
|
end
|
|
else
|
|
begin
|
|
Result := TFileState.Changed;
|
|
end;
|
|
finally
|
|
FLock.Leave;
|
|
end;
|
|
end;
|
|
|
|
function TDirectoryMonitor.TryFindNewVersion(const OldFile: IFileInfo; out NewFile: IFileInfo): TFileState;
|
|
begin
|
|
NewFile := nil;
|
|
if not Assigned(OldFile) then
|
|
begin
|
|
Result := TFileState.Deleted;
|
|
exit;
|
|
end;
|
|
|
|
FLock.Enter;
|
|
try
|
|
if FFiles.TryGetValue(OldFile.FullPath, NewFile) then
|
|
begin
|
|
if NewFile = OldFile then
|
|
Result := TFileState.Unchanged
|
|
else
|
|
Result := TFileState.Changed;
|
|
end
|
|
else
|
|
begin
|
|
Result := TFileState.Deleted;
|
|
end;
|
|
finally
|
|
FLock.Leave;
|
|
end;
|
|
end;
|
|
|
|
{ TDirectoryMonitorFactory }
|
|
|
|
constructor TDirectoryMonitorFactory.Create;
|
|
begin
|
|
inherited;
|
|
FDirectories := TList<TDirectoryWatch>.Create;
|
|
FExtensions := TList<string>.Create;
|
|
FChangeKinds :=
|
|
[TFileChangeKind.FileNameChange, TFileChangeKind.LastWriteChange, TFileChangeKind.SizeChange, TFileChangeKind.DirNameChange];
|
|
end;
|
|
|
|
destructor TDirectoryMonitorFactory.Destroy;
|
|
begin
|
|
FreeAndNil(FDirectories);
|
|
FreeAndNil(FExtensions);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TDirectoryMonitorFactory.AddDirectory(const DirPath: string; SubDirHandling: TSubDirectoryHandling);
|
|
var
|
|
watch: TDirectoryWatch;
|
|
begin
|
|
watch.Directory := TPath.GetFullPath(DirPath);
|
|
watch.WatchSubtree := (SubDirHandling = TSubDirectoryHandling.Recursive);
|
|
FDirectories.Add(watch);
|
|
end;
|
|
|
|
procedure TDirectoryMonitorFactory.AddExtension(const Extension: string);
|
|
begin
|
|
FExtensions.Add(Extension);
|
|
end;
|
|
|
|
procedure TDirectoryMonitorFactory.SetChangeKinds(const ChangeKinds: TFileChangeKinds);
|
|
begin
|
|
FChangeKinds := ChangeKinds;
|
|
end;
|
|
|
|
function TDirectoryMonitorFactory.CreateMonitor: IDirectoryMonitor;
|
|
begin
|
|
Result := TDirectoryMonitor.Create(FDirectories.ToArray, FExtensions.ToArray, FChangeKinds);
|
|
FDirectories.Clear;
|
|
FExtensions.Clear;
|
|
end;
|
|
|
|
end.
|