:: 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 можно скачать в разделе мои компоненты.
.: Компонент к данной заметке :.
|
При использовании материала - ссылка на сайт обязательна
|
|