Заметки
Отслеживание изменений в каталоге

:: Меню ::
:: На главную ::
:: FAQ ::
:: Заметки ::
:: Практика ::
:: Win API ::
:: Проекты ::
:: Скачать ::
:: Секреты ::
:: Ссылки ::

:: Сервис ::
:: Написать ::

:: MVP ::

:: RSS ::

Яндекс.Метрика


Для одного из проектов мне потребовалось реализовать функционал по мониторингу изменений в каталоге. Разумеется, первым делом я полез в интернет за решением в виде готового компонента. И, как это не покажется странно, я его не нашел! Не то, чтобы их нет совсем, скорее не то, что мне было нужно. Такие компоненты как ShellChangeNotifier или ATFileNotification в своей основе используют функции FindFirstChangeNotification/FindNextChangeNotification/FindCloseChangeNotification, позволяет узнать лишь о факте какого либо изменения в наблюдаемой директории. Если этого достаточно, данные компоненты вполне подойдут, если же нужно точно знать что произошло, нужно пойти другим путем.

Мне нужно было решение на базе функции ReadDirectoryChanges, которое я и представляю вашему вниманию. Посмотрим на основную функцию потока:

procedure TSpyDirectoryThread.Execute;
const
  SizeBuff = High( Word );
var
  hDir: THandle;
  Buf: array [0..SizeBuff] of Byte;
  WaitResult, SizeRet: Cardinal;
  lpFNI: PFileNotifyInformation;
  fName, s: string;
begin
   FillChar( FOverlapped, SizeOf( TOverlapped ), 0 );
   FOverlapped.hEvent := CreateEvent( nil, False, False, nil );
   hDir := CreateFile( PWideChar( FRootDir ), FILE_LIST_DIRECTORY {или GENERIC_READ},
                       FILE_SHARE_READ or FILE_SHARE_DELETE or FILE_SHARE_WRITE,
                       nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED, 0 );
   Win32Check( hDir <> INVALID_HANDLE_VALUE );
   try
      while not Terminated do
      begin
         ReadDirectoryChanges( hDir, @Buf, SizeOf( Buf ), FWatchSubTree,
                               FFileNotifyChanges, nil, @FOverlapped, nil );
         repeat
            WaitResult := WaitForSingleObject( FOverlapped.hEvent, 500 );
         until Terminated or ( WaitResult <> WAIT_TIMEOUT );

         if WaitResult = WAIT_OBJECT_0 then
         begin
            if not GetOverlappedResult( hDir, FOverlapped, SizeRet, False ) then
               Continue;
         end;

         lpFNI := @Buf[0];

         while True do
         begin
            SetLength( fName, lpFNI^.FileNameLength div SizeOf( WideChar ) );
            Move( lpFNI^.FileName, fName[1], lpFNI^.FileNameLength );

            case lpFNI^.Action of
               FILE_ACTION_ADDED:
                  if TFileAction.fsAdded in FFileActions then
                  begin
                     FFileName := fName;
                     Synchronize( Added );
                  end;
               FILE_ACTION_REMOVED:
                  if TFileAction.faRemoved in FFileActions then
                  begin
                     FFileName := fName;
                     Synchronize( Removed );
                  end;
               FILE_ACTION_MODIFIED:
                  if TFileAction.faModified in FFileActions then
                  begin
                     FFileName := fName;
                     Synchronize( Modified );
                  end;
               FILE_ACTION_RENAMED_OLD_NAME:
                  if TFileAction.faRenamed in FFileActions then
                     FOldFileName := fName;
               FILE_ACTION_RENAMED_NEW_NAME:
                  if TFileAction.faRenamed in FFileActions then
                  begin
                     FNewFileName := fName;
                     Synchronize( Renamed );
                  end;
            end;
            lpFNI^.Action := 0;

            if lpFNI^.NextEntryOffset > 0 then
               lpFNI := Pointer( Cardinal( lpFNI ) + lpFNI^.NextEntryOffset )
            else
               Break;
         end;
      end;
   finally
      CloseHandle( hDir );
      CloseHandle( FOverlapped.hEvent );
   end;
end;

Несколько слов о происходящем. Функция ReadDirectoryChanges будет вызываться асинхронно (FILE_FLAG_OVERLAPPED), поэтому нам понадобится структура TOverlapped, в одно из полей которой записывается Handle события на получение данных об изменениях. После этого ожидаем наступления события, анализируем и сообщаем пользователю.

Чтобы понять, какие события мы можем отслеживать (и, следовательно, как можно настраивать компонент), рассмотрим функцию ReadDirectoryChanges поподробнее.

function ReadDirectoryChanges(hDirectory: THandle; lpBuffer: Pointer;
  nBufferLength: DWORD; bWatchSubtree: Bool; dwNotifyFilter: DWORD;
  lpBytesReturned: LPDWORD; lpOverlapped: POverlapped;
  lpCompletionRoutine: FARPROC): BOOL; stdcall;
  • hDirectory – описатель каталога, за которым надо следить (результат работы функции CreateFile);
  • lpBuffer – указатель на буфер, в который будут записаны обнаруженные изменения. Структура записей в буфере соответствует структуре FILE_NOTIFY_INFORMATION (см. описание ниже). Буфер может записываться как синхронно, так и асинхронно (в зависимости от параметров, заданных в CreateFile);
  • nBufferLength – размер буфера (в байтах);
  • bWatchSubtree – определяет, нужно ли следить за подкаталогами;
  • dwNotifyFilter – фильтр событий, на которые нужно реагировать:
    • FILE_NOTIFY_CHANGE_FILE_NAME – создание, удаление, переименование файла (переименование касается и подкаталогов);
    • FILE_NOTIFY_CHANGE_DIR_NAME – любое изменение имени подкаталога, включая добавление и удаление;
    • FILE_NOTIFY_CHANGE_ATTRIBUTES – изменение атрибутов файла или каталога;
    • FILE_NOTIFY_CHANGE_SIZE – изменение размера файла (вызывается в момент реальной записи файла на диск);
    • FILE_NOTIFY_CHANGE_LAST_WRITE – изменение времени последней записи в файл или каталог (вызывается в момент реальной записи файла на диск);
    • FILE_NOTIFY_CHANGE_LAST_ACCESS – изменение времени последнего доступа к файлу или каталогу;
    • FILE_NOTIFY_CHANGE_CREATION – изменение времени создания файла или каталога;
    • FILE_NOTIFY_CHANGE_SECURITY – изменение параметров безопасности файла или каталога (прав доступа и т.д.);
  • lpBytesReturned – в случае синхронного вызова функции этот параметр будет содержать количество байт информации, записанной в буфер. Для асинхронных вызовов значение этого параметра остается неопределенным;
  • lpOverlapped – указатель на структуру OVERLAPPED , которая поставляет данные, которые будут использоваться во время асинхронной операции. Это значение может быть равным nil;
  • lpCompletionRoutine – указатель на callback-функцию, которая будет вызвана при окончании операции. Этот параметр может устанавливаться в значение nil.
Если параметр bWatchSubtree установлен в False, мы все равно будем получать сообщения об изменениях в подкаталогах, правда с небольшим уровнем вложенности. Так, если мы следим за каталогом C:\1\, то при создании в нем подкаталогов C:\1\2\ и c:\1\2\3\ мы об этом узнаем, а вот изменения на более глубоких уровнях вложенности пройдут мимо нас не замеченными. Если параметр bWatchSubtree установлен в True, уровень вложенности значения не имеет, мы обо всем узнаем.

Формат буфера, в котором мы получаем данные об изменениях, имеет следующую структуру:

FILE_NOTIFY_INFORMATION = record
  NextEntryOffset: DWORD;
  Action: DWORD;
  FileNameLength: DWORD;
  FileName: array [0..0] of WCHAR;
end;
  • NextEntryOffset – смещение (в байтах) до следующей записи, если значение равно 0, то это запись последняя;
  • Action – идентификатор события:
    • FILE_ACTION_ADDED – в директорию был добавлен файл (или подкаталог), мы получаем его имя;
    • FILE_ACTION_REMOVED – файл (или подкаталог) был удален из директории, мы получаем его имя;
    • FILE_ACTION_MODIFIED – файл (или подкаталог) был изменен (это может быть изменение размера файла, его атрибутов и т.п.), мы получаем его имя;
    • FILE_ACTION_RENAMED_OLD_NAME – файл (или подкаталог) был переименован, мы получаем его старое имя;
    • FILE_ACTION_RENAMED_NEW_NAME – файл (или подкаталог) был переименован, мы получаем его новое имя;
  • FileNameLength – длина имени файла (подкаталога) без учета завершающего нуль-символа;
  • FileName – имя файла (подкаталога).

Весь остальной код компонента особого интереса не представляет и является “обвесом” для взаимодействия с пользователем и приложением во время настройки. Компонент SpyDirectory можно скачать в разделе мои компоненты.

.: Компонент к данной заметке :.


При использовании материала - ссылка на сайт обязательна