Следить за изменениями в каталоге
Автор: FliNT
Использование ReadDirectoryChangesW
Очень хорошая функция, только " Windows 95/98/Me: Unsupported" . Т.е. в Win9x этой функции нет.
Но мы же пишем для NT, а там с этой функцией все в порядке (если 3-ий сервис пак для NT3.1 поставили :)))
Кратко пройдемся по описанию функции (взято из windows
function ReadDirectoryChangesW(
hDirectory: THandle; // описатель каталога, за которым надо следить
lpBuffer: Pointer; // Указатель на буфер, в который будет записана информация
nBufferLength: DWORD; // Размер буфера
bWatchSubtree: Bool; // Следить ли за подкаталогами
dwNotifyFilter: DWORD; // Фильтр действий
lpBytesReturned: LPDWORD; // Сколько было записано в буфер
lpOverlapped: POverlapped; // Для асинхронной работы
lpCompletionRoutine: FARPROC // Функция, которая будет вызвана при окончании операции
): BOOL; stdcall;
|
Ну а теперь пример работы этой функции (исходник этого примера (пока без комментариев!! и на Delphi6)
можно скачать здесь)
Чтобы программа могла нормально работать во время ожидания очередного изменения,
мы функции мониторинга выделим отдельный поток. Поток " сделан" на WinAPI (функция WorkThread).
При нажатии на одну кнопку он будет создаваться, а на другую - жестоко уничтожаться.
Вся полезная информация будет выводиться в TListView.
Функция потока будет описана так:
procedure WorkThread(LV: TListView); stdcall;
|
LV - это то, во что мы будем выводить инфу. И не забывайте stdcall;
А вот ее текст:
procedure WorkThread(LV : TListView);stdcall;
var
hDir : THandle;
lpBuf : Pointer;
Ptr : Pointer;
cbReturn : Cardinal;
FileName : PWideChar;
Item : TListItem;
sTime : _SYSTEMTIME;
begin
// Сначала нам надо получить описатель каталога, за которым мы будем следить
// В данном примере это будет весь диск C:
hDir := CreateFile ('C:\',GENERIC_READ,FILE_SHARE_READ or FILE_SHARE_WRITE
or FILE_SHARE_DELETE,nil,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,0);
// Если ошиблись...
if hDir = INVALID_HANDLE_VALUE
then begin ShowMessage(SysErrorMessage(GetLastError)); exit; end;
// Выделяем память под буфер
// const BUF_SIZE = 2048 - думаю вполне достаточно
GetMem(lpBuf,BUF_SIZE);
repeat
// очищаем память перед записью в нее (на всякий случай)
ZeroMemory(lpBuf,BUF_SIZE);
// Теперь мы будем ждать пока чего-нибудь в интересующем нас каталоге
// изменится или произойдет ошибка (и мы выйдем из цикла)
// FILE_NOTIFY_CHANGE - это список флагов - о них ниже.
if not ReadDirectoryChangesW(hDir,lpBuf,BUF_SIZE,true,
FILE_NOTIFY_CHANGE,@cbReturn,nil,nil)
then Break;
// Сюда мы попадаем, если функция выполнилась успешно
// и lpBuf указывает на одну или несколько структур FILE_NOTIFY_INFORMATION
Ptr:=lpBuf;
|
Отойдем пока от исходного кода и рассмотрим, что у нас появится в буфере.
В данный момент lpBuf и Ptr указывают на первую структуру FILE_NOTIFY_INFORMATION.
Вторым полем этой структуры является - Action -тип действия, которое было совершено.
Четвертым - FileName - первый символ имени файла. Имя файла не заканчивается нулем #0 и
для определения его длины используется 3 параметр - FileNameLength.
При этом надо учесть, что имя файла в формате Unicode т.е. каждый символ занимает 2 байта,
а FileNameLength дается в байтах. Придется эту длину делить на 2, чтобы узнать кол-во символов.
Но возникает вопрос - как узнать, сколько таких структур было записано в буфер.
Для этого используется 1 параметр структуры - NextEntryOffset. Если он не равен нулю,
то в нем будет кол-во байт, через которые находится следующая запись и нам надо сдвинуть
указатель на это кол-во байт, чтобы " получить" следующую структуру.
И так далее, пока NextEntryOffset не будет равен 0 (т.е. эта запись была последней).
repeat
// Добавляем новый элемент в TListView (ViewStyle = vsReport )
Item := LV.Items.Add;
// Выделяем память под имя файла
GetMem(FileName,PFileNotifyInformation(Ptr).FileNameLength+2);
// Очищаем память - чтобы последним символом после копирования
// был бы #0 нуль
ZeroMemory(FileName,PFileNotifyInformation(Ptr).FileNameLength+2);
// WinAPI функция для копирования Unicode строки
lstrcpynW(FileName,PFileNotifyInformation(Ptr).FileName,
PFileNotifyInformation(Ptr).FileNameLength div 2+1);
// Имя файла у нас дается относительно папки
// т.е.если изменится файл C:\File\test.dat, то FileName
// будет равно File\test.dat
Item.Caption:='C:\'+FileName;
// Имя файла нам больше не нужно - очищаем память
FreeMem(FileName);
// Определяем тип произошедшего действия
case PFileNotifyInformation(Ptr).Action of
FILE_ACTION_ADDED : Item.SubItems.Add('Файл был создан');
FILE_ACTION_REMOVED : Item.SubItems.Add('Файл был удален');
FILE_ACTION_MODIFIED : Item.SubItems.Add('Файл был изменен');
FILE_ACTION_RENAMED_OLD_NAME :
Item.SubItems.Add('Файл был переименован и в имени файла - предыдущее имя');
FILE_ACTION_RENAMED_NEW_NAME :
Item.SubItems.Add('новое имя после переименования');
else Item.SubItems.Add('Произошло что-то странное');
end;
// Время, когда произошло событие
GetLocalTime(sTime);
with sTime do
Item.SubItems.Add(Format('%.2d:%.2d:%.2d',[wHour,wMinute,wSecond])); // 13:54:20
// Если эта запись не последняя (NextEntryOffset < > 0), то...
if PFileNotifyInformation(Ptr).NextEntryOffset=0
then Break
else begin
// ... добавляем строку в примечания (если интересно посмотреть смещение)
Item.SubItems.Add('Offset : '+
IntToStr(PFileNotifyInformation(Ptr).NextEntryOffset));
//Передвигаем указатель на NextEntryOffset байт вперед
Inc(Cardinal(Ptr),PFileNotifyInformation(Ptr).NextEntryOffset);
// Теперь Ptr указывает на следующую запись
end;
// Передвигать надо именно Ptr, а не lpBuf
until false;
until false;
// Очищаем память
FreeMem(lpBuf);
end;
|
Параметр функции 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 - изменение параметров безопасности (прав доступа и т.д.)
У меня в примере используются FILE_NOTIFY_CHANGE_FILE_NAME,
FILE_NOTIFY_CHANGE_DIR_NAME и FILE_NOTIFY_CHANGE_LAST_WRITE.
А теперь надо только запустить поток.
procedure TForm1.Button1Click(Sender: TObject);
var
ThID : Cardinal;
begin
// hThread - THandle - глобальная переменная
// Создаем поток
// LV - TListView, WorkThread - функция выше
hThread:=CreateThread(nil,0,@WorkThread,LV,0,ThID);
// В случае неудачи выводим сообщение
if hThread=0 then ShowMessage(SysErrorMessage(GetLastError));
end;
|
У меня в исходниках поток останавливается функцией TerminateThread(hThread,Cardinal(-1)). Но при таком завершении не будут освобождены все ресурсы, занятые потоком (а это как минимум BUF_SIZE байт памяти. Вместо этой функции было бы лучше использовать SuspendThread(hThread), а при запуске проверять на существование потока WaitForSingleObject(hThread,0)= WAIT_TIMEOUT и если он существует - делать ResumeThread(hThread)... но в исходниках этого пока нет :)
|