Моніторинг змін в директоріях і файлах засобами delphi

При розробці додатків Delphi для синхронізації чого-небудь, наприклад, файлів на різних комп'ютерах, так чи інакше доводиться розробляти алгоритм за допомогою якого можна однозначно визначати які з файлів необхідно видалити з певної директорії, які перемістити, перейменувати і т.д. Подібні алгоритми і приклади їх використання на практиці не є рідкістю - в Мережі Ви можете знайти масу всіляких варіацій Delphi-коду за допомогою якого можна відстежити зміни в директоріях і файлах Windows.

Не так давно і мені довелося зіткнутися з подібним завданням - відстежити зміни в певній директорії і сформувати список завдань для синхронізації файлів з сервером. Так як до цього моменту мені не доводилося розробляти подібні алгоритми, то довелося пошерстити простори Інтернету і зібрати якомога більше інформації на задану тему. Ну, а результати моїх пошуків я вирішив оформити у вигляді окремої статті в блозі. Отже, сьогоднішня тема - моніторинг змін в директоріях і файлів засобами Delphi.

Найпростішим і доступним навіть для новачків в програмуванні способом стеження за змінами в директорії є робота по таймеру. Сенс роботи полягає в тому, що на старті роботи програми створюється список файлів і піддиректорій в цільової директорії. Потім, в момент спрацювання таймера, створюється новий список і порівнюється з попереднім - визначається які файли були додані, які віддалені / переміщені і т.д. і за тим самим певних змін проводяться операції синхронізації. Як в даному випадку визначити, що, скажімо, файл Test.txt був змінений? Наприклад, можна розраховувати кожен раз CRC файлу і порівнювати цю суму з попереднім значенням. Ось исходник функції з www.delphisources.ru. для розрахунку CRC файлу:

Приклад використання функції. Створимо новий додаток в Delphi з наступними компонентами на формі:


При відкритті файлу будемо визначати його розмір і розраховувати CRC за допомогою наведеної вище функції:

Тепер візьмемо створимо текстовий файлик і запишемо в нього рядок, скажімо "Hello World!", Збережемо його, запустимо програму і розрахуємо CRC. Ось, що вийшло в програмі:

Тепер знову відкриємо файл і замінимо великі літери на прописні, тобто рядок набуде вигляду "hello world!". Знову розрахуємо CRC:

Зверніть увагу, що розмір файлу залишився колишнім, а вміст файлу змінилося. Це зайвий приклад того, що використання в якості критерію зміни файлу тільки його розміру - це дуже ненадійний варіант і, напевно, навіть неправильний.

Що можна сказати з приводу запропонованого вище варіанту моніторингу зміни в директорії за допомогою таймера?

Перевагою цього методу можна назвати його простота. Не важливо, який таймер буде використовуватися в роботі - стандартний TTimer або власноруч створений високоточний таймер. Повісити обробник на спрацьовування таймера зможе хто завгодно. Але поряд з простотою цього варіанту він також має і масу недоліків. І найголовніше з недоліків - ненадійність.

Ніхто не дасть Вам гарантій того, що заданий інтервал спрацьовування таймера буде достатнім для виконання процедури обробника. Як говоритися, комп'ютер користувача - темний ліс. Можна, звичайно, ставити великий інтервал часу і сподіватися на те, що обробник таймера відпрацює на 100%, але це всього-лише "милиця", але ніяк не вирішення проблем надійності алгоритму.

Крім того, кожен раз перебирати велику кількість файлів - зайва трата ресурсів. Звичайно, в наше століття багатоядерних процесорів, ресурси комп'ютера дуже великі, але і розкидатися ними з приводу і без - не варто, тим більше, якщо робота ведеться над створенням серйозного ресурсоемкого проекту, де на рахунку кожен байт.

І, тому, більш раціональним способом моніторингу змін в файлах і директоріях є використання функцій Windows. Тут можна виділити два варіанти роботи:

  1. Моніторинг змін в директорії без виведення інформації про зміни, тобто проста констатація факту - була зміна, а що саме було змінено не визначається. Для цього способу використовується трійка функцій: FindFirstChangeNotification. FindNextChangeNotification. FindCloseChangeNotification.
  2. Моніторинг змін в директорії з виведенням інформації за зміненими елементами. Для цього способу використовується пара функцій: CreateFile і ReadDirectoryChangesW.

Обидва ці способи в рівній мірі зручні, але який з цих способів використовувати в конкретній ситуації вирішувати тільки Вам. Розглянемо приклади використання функцій Windows для моніторингу змін в директоріях.

Використання функцій FindFirstChangeNotification, FindNextChangeNotification, FindCloseChangeNotification

Перш, ніж приступимо до вивчення функцій, створимо модуль-заготовку для подальшої роботи. Стежити за змінами ми будемо в потоці (TThread):

Тепер розглянемо призначення функцій Windows.

FindFirstChangeNotification - створює дескриптор повідомлення про зміни і встановлює початкові умови відправки повідомлення. Функція повертає дескриптор (THandle) або INVALID_HANDLE_VALUE в разі помилки:

lpPathName: PChar - повний шлях до директорії за якою проводиться спостереження. Значення цього параметра не може містити відносний шлях або порожній рядок.
bWatchSubtree: boolean - True - вказує на те, що також в результат моніторингу будуть потрапляти зміни в піддиректоріях.
dwNotifyFilter: DWORD - набір прапорів, що визначають настройки фільтра. Прапори можуть бути наступними:

  • FILE_NOTIFY_CHANGE_FILE_NAME (0x00000001) - будь-яка зміна імені файлу в каталозі або підкаталозі. Зміни включають в себе перейменування, створення або видалення файлу.
  • FILE_NOTIFY_CHANGE_DIR_NAME (0x00000002) - будь-яка зміна імені директорії в каталозі або підкаталозі. Зміни включають в себе перейменування, створення або видалення директорії.
  • FILE_NOTIFY_CHANGE_ATTRIBUTES (0x00000004) - будь-яка зміна атрибутів в переглядається директорії і піддиректоріях.
  • FILE_NOTIFY_CHANGE_SIZE (0x00000008) - зміна розміру файлу в директорії або піддиректорії. Зміна розміру виявляється тільки коли файл записується на диск.
  • FILE_NOTIFY_CHANGE_LAST_WRITE (0x00000010) - зміна часу останнього запису в файл.
  • FILE_NOTIFY_CHANGE_SECURITY (0x00000100) - зміна параметрів безпеки в каталозі або підкаталозі.

FindNextChangeNotification - вказує, щоб операційна система повернула звукове попередження про зміну THandle в наступний раз, коли виявляються зміни, згідно з фільтру, встановленому функцією FindFirstChangeNotification.

hChangeHandle: THandle - дескриптор, отриманий за допомогою функції FindFirstChangeNotification.

FindCloseChangeNotification - зупиняє моніторинг змін в директорії.

hChangeHandle: THandle - дескриптор, отриманий за допомогою функції FindFirstChangeNotification.

Призначення функцій тепер більш-менш стали нам зрозумілі - залишилося закріпити отримані знання на досвіді. Отже, перш за все наш потік повинен отримувати два значення: шлях до директорії за якою ми будемо стежити і прапор, який вказує чи слід моніторити підкаталоги. Пишемо конструктор потоку:

Тепер створимо наступний Execute:

У наведеному вище обработчике Execute ми проводимо моніторинг зміни імені файлу / директорії або розміру файлу. При цьому ми очікуємо будь-якого із заданих в фільтрі подій і виводимо повідомлення. До речі, створимо подія для виведення повідомлення про зміни, наприклад, таке:

Тепер все готово для перевірки працездатності нашого потоку. Створимо новий проект Delphi і на головну форму покладемо наступні компоненти:

У uses підключимо модуль з нашим потоком і оголосимо наступну змінну:

Тепер напишемо необхідні обробники для подій:

Запускаємо програму, вибираємо директорію за якою необхідно стежити і натискаємо кнопку "Стежити". Тепер спробуємо скопіювати / видалити який-небудь файл і побачимо в Memo відповідне повідомлення. Можете накачати електронні книги безкоштовно і злити в директорію відразу купу текстових файлів - потік коректно повідомить Вам про зміни каталогу рівно стільки раз скільки файлів Ви скинете в директорію.
Наведений вище приклад є, напевно, самим простим, коли ми відстежуємо всього лише одна подія за яким просто констатуємо факт - відбулися зміни в директорії. А які зміни - про це ми нічого не повідомляємо. Ми навіть не можемо в цьому випадку сказати, що саме сталося. Можна злегка підкоригувати Наведений вище обробник і, використовуючи наведені вище функції Windows визначити різні події на кожен варіант змін, використовуючи при цьому замість функції WaitForSingleObject функцію WaitForMultipleObjects. Але про це, а також про використання методів CreateFile і ReadDirectoryChangesW ми поговоримо наступного разу.