Delphi World - Следить за изменениями в каталоге
Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Следить за изменениями в каталоге

Автор: 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)... но в исходниках этого пока нет :)

Проект Delphi World © Выпуск 2002 - 2017
Автор проекта: Эксклюзивные курсы программирования