HoShiMin
11/13/2015 - 9:45 PM

Модуль для отслеживания изменений файлов в папках

Модуль для отслеживания изменений файлов в папках

unit FilesNotifier;

interface

uses
  Windows, SysUtils, Classes, ShlwAPI;

const
  FULL_NOTIFY_FILTER = FILE_NOTIFY_CHANGE_FILE_NAME  or
                       FILE_NOTIFY_CHANGE_DIR_NAME   or
                       FILE_NOTIFY_CHANGE_ATTRIBUTES or
                       FILE_NOTIFY_CHANGE_SIZE       or
                       FILE_NOTIFY_CHANGE_LAST_WRITE or
                       FILE_NOTIFY_CHANGE_SECURITY;

type
  FILE_ACTION = (
    FILE_ACTION_ADDED = 1,
    FILE_ACTION_REMOVED,
    FILE_ACTION_MODIFIED,
    FILE_ACTION_RENAMED_OLD_NAME, // В FileName - старое имя файла
    FILE_ACTION_RENAMED_NEW_NAME  // В FileName - новое имя файла
  );

  TFileChangesInfo = record
    FileName : string;
    Action   : FILE_ACTION;
  end;

  TNotifyStruct = record
    ChangesCount : LongWord;
    Changes      : array of TFileChangesInfo;
  end;

  TFilesNotifier = class
    private type
      TOnDirChange = reference to procedure(const FilesNotifier: TFilesNotifier; const NotifyStruct: TNotifyStruct);
    private const
      BufferSize = 8192;
    private
      FBaseFolder: string;
      FFilesTypes: array of string;
      FFilesTypesCount: Integer;
      FDirHandle: THandle;
      FWatcherHandle: THandle;
      FOnDirChange: TOnDirChange;
      procedure ParseFilesTypes(const FilesTypes: string);
      function OpenFolder(const Directory: string): THandle;
    public
      property OnDirChange: TOnDirChange read FOnDirChange write FOnDirChange;
      property BaseFolder: string read FBaseFolder;

      constructor Create(const Directory: string; const FilesTypes: string = '');
      destructor Destroy; override;

      procedure StartWatching(WatchSubfolders: Boolean = True; NotifyFilter: LongWord = FULL_NOTIFY_FILTER);
      procedure StopWatching;
  end;

implementation

type
  FILE_NOTIFY_INFORMATION = record
    NextEntryOffset : LongWord;
    Action          : LongWord;
    FileNameLength  : LongWord;
    FileName        : WideChar;
  end;
  PFILE_NOTIFY_INFORMATION = ^FILE_NOTIFY_INFORMATION;

function CancelIoEx(hFile: THandle; lpOverlapped: POverlapped): LongBool; stdcall; external 'kernel32.dll';

{ TFilesNotifier }

procedure TFilesNotifier.ParseFilesTypes(const FilesTypes: string);
var
  StrLength: Integer;
  StartPos, EndPos: Integer;
  TypesCount: Integer;
begin
  FFilesTypesCount := 0;
  SetLength(FFilesTypes, FFilesTypesCount);

  StrLength := Length(FilesTypes);
  if StrLength = 0 then Exit;

  TypesCount := 0;
  StartPos   := 1;
  EndPos     := Pos(',', FilesTypes, StartPos);
  while EndPos <> 0 do
  begin
    Inc(TypesCount);
    SetLength(FFilesTypes, TypesCount);
    FFilesTypes[TypesCount - 1] := Copy(FilesTypes, StartPos, EndPos - StartPos);
    StartPos := EndPos + 1;
    if StartPos > StrLength then Break;
    EndPos := Pos(',', FilesTypes, StartPos);
  end;

  if StartPos < StrLength then
  begin
    Inc(TypesCount);
    SetLength(FFilesTypes, TypesCount);
    FFilesTypes[TypesCount - 1] := Copy(FilesTypes, StartPos, StrLength - StartPos + 1);
  end;

  FFilesTypesCount := TypesCount;
end;

constructor TFilesNotifier.Create(const Directory: string; const FilesTypes: string = '');
begin
  FWatcherHandle := 0;
  SetLength(FFilesTypes, 0);
  FFilesTypesCount := 0;
  FOnDirChange := nil;

  FBaseFolder := Directory;
  FDirHandle := OpenFolder(FBaseFolder);
  ParseFilesTypes(FilesTypes);
end;

destructor TFilesNotifier.Destroy;
begin
  StopWatching;
  if (FDirHandle <> 0) and (FDirHandle <> INVALID_HANDLE_VALUE) then CloseHandle(FDirHandle);
  inherited;
end;


function TFilesNotifier.OpenFolder(const Directory: string): THandle;
begin
  Result := CreateFile(
                        PChar('\\?\' + Directory),
                        FILE_LIST_DIRECTORY,
                        FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
                        nil,
                        OPEN_EXISTING,
                        FILE_FLAG_BACKUP_SEMANTICS,
                        0
                       );
end;



procedure TFilesNotifier.StartWatching(WatchSubfolders: Boolean = True; NotifyFilter: LongWord = FULL_NOTIFY_FILTER);
var
  WatcherThread: TThread;
begin
  StopWatching;

  WatcherThread := TThread.CreateAnonymousThread(procedure
  var
    Buffer: Pointer;
    BytesReturned: LongWord;
    FileNotifyInformation: PFILE_NOTIFY_INFORMATION;
    NotifyStruct: TNotifyStruct;
    FileName: string;
    I: Integer;
    NeedToAdd: Boolean;
  begin
    GetMem(Buffer, BufferSize);
    FillChar(Buffer^, BufferSize, #0);
    FileNotifyInformation := Buffer;

    while ReadDirectoryChanges(FDirHandle, Buffer, BufferSize, WatchSubfolders, NotifyFilter, @BytesReturned, nil, nil) do
    begin
      NotifyStruct.ChangesCount := 0;
      SetLength(NotifyStruct.Changes, 0);

      if BytesReturned <> 0 then
      repeat
        if FileNotifyInformation.FileNameLength > 0 then
        begin
          SetLength(FileName, FileNotifyInformation.FileNameLength div SizeOf(WideChar));
          Move(FileNotifyInformation.FileName, FileName[1], FileNotifyInformation.FileNameLength);

          NeedToAdd := True;
          if FFilesTypesCount > 0 then
          begin
            NeedToAdd := False;
            for I := 0 to FFilesTypesCount - 1 do if PathMatchSpec(PChar(FileName), PChar(FFilesTypes[I])) then
            begin
              NeedToAdd := True;
              Break;
            end;
          end;

          if NeedToAdd then
          begin
            Inc(NotifyStruct.ChangesCount);
            SetLength(NotifyStruct.Changes, NotifyStruct.ChangesCount);
            NotifyStruct.Changes[NotifyStruct.ChangesCount - 1].FileName := FileName;
            NotifyStruct.Changes[NotifyStruct.ChangesCount - 1].Action := FILE_ACTION(FileNotifyInformation.Action);
          end;
        end;

        FileNotifyInformation := Pointer(NativeUInt(FileNotifyInformation) + FileNotifyInformation.NextEntryOffset);
      until FileNotifyInformation.NextEntryOffset = 0;

      if (NotifyStruct.ChangesCount > 0) and Assigned(FOnDirChange) then
        TThread.Synchronize(TThread.CurrentThread, procedure
        begin
          FOnDirChange(Self, NotifyStruct);
        end);

      FillChar(Buffer^, BufferSize, #0);
    end;

    SetLength(NotifyStruct.Changes, 0);
    FreeMem(Buffer);
  end);
  WatcherThread.FreeOnTerminate := True;
  WatcherThread.Priority := tpLower;
  FWatcherHandle := WatcherThread.Handle;
  WatcherThread.Start;
end;

procedure TFilesNotifier.StopWatching;
begin
  if (FDirHandle <> 0) and (FDirHandle <> INVALID_HANDLE_VALUE) then CancelIoEx(FDirHandle, nil);
  WaitForSingleObject(FWatcherHandle, INFINITE);
  FWatcherHandle := 0;
end;

end.