Следим за папками и файлами
Решил я взяться за простую задачу: отслеживать любые изменения в определенной папке. Методом проб и ошибок вроде как получилось добиться сносного результата. И так начнем :)
Первое , что надо сделать: это открыть объект (в данном случае папку) для возможности отслеживания. В этом поможет функция CreateFile.
hDir = CreateFile(_
"C:\22",_
FILE_LIST_DIRECTORY,_
FILE_SHARE_READ+FILE_SHARE_WRITE+FILE_SHARE_DELETE,_
0,_
OPEN_EXISTING,_
FILE_FLAG_BACKUP_SEMANTICS,_
0 _
)
В данном случае:
- В первом параметре передаем путь до папки
- Во втором уровень доступа (для перечисления)
- В третьем флаги (для отслеживания чтения, записи, удаления)
- Четвертый параметр нам не нужен, обнуляем
- Пятый параметр определяет только существующую папку. Если папки не окажется, функция завершится с ошибкой
- В шестом передаем флаг для установки нужных привилегий
- Последний параметр не нужен, обнуляем
Функция вернет нам хедл, который будем использовать в функции ReadDirectoryChanges. По идее это функция ReadDirectoryChangesW, но в freebasic при декларации почему-то решили отрезать последнюю букву :)
Данная функция при успешном вызове, будет возвращать значение отличное от нуля. В коде это будет выглядеть так:
If ReadDirectoryChanges(_ hDir,_ buf,_ 10000,_ TRUE,_ &h17F,_ ' совокупность флагов 0,_ @ov,_ 0_ )=0 Then Exit While
Здесь:
- В первом параметре передаем хендл, возвращенный CreateFile
- Во втором буфер, в который будет возвращена нужная нам информация
- В третьем параметре передаем размер буфера
- В четвертом устанавливаем возможность следить за подкаталогами
- В пятом совокупность флагов, которые определяют слежение за папками, файлами и их различными изменениями. Если нужно следить за чем-то определенным (например за размерами файлов), то советую просмотреть эту страницу и определиться с выбором самим.
- Шестой параметр определяет размер возвращаемых данных, но при ассинхронном вызове он не используется, обнуляем
- Седьмой параметр - указатель на структуру OVERLAPPED для ассинхронного чтения
- Восьмой не используется, обнуляем
Буфер, который используется в функции по сути указатель на структуру или множество структур FILE_NOTIFY_INFORMATION, которые располагаются друг за другом.
В начале кода мы определим данную структуру сами, поскольку объявленная в заголовке нам не годится...
#Undef FILE_NOTIFY_INFORMATION Type FILE_NOTIFY_INFORMATION NextEntryOffset As DWORD Action As DWORD FileNameLength As DWORD FileName As Wstring * 255 End Type
В ней:
- NextEntryOffset - смещение в байтах для каждой структуры по отношению к предыдущей. Выудив информацию из структуры по смещению ноль, мы получим следущую, прибавив к имеющемуся смещению значение в NextEntryOffset. И так будем проделывать, пока NextEntryOffset не будет равно нулю.
- Action - тип изменения, которое произошло. Расписывать не буду, их всего пять и в коде ниже интуитивно понятно, которое для чего.
- FileNameLength - длина в байтах имени файла или папки, в котором произошли изменения
- FileName - имя файла или папки в юникоде, в котором произошли изменения. Я поставил длину этого буфера 255 символов
Поскольку указатель buf у нас будет меняться, мы в самом начале его адрес сохраним в указатель BufBackup, а перед использованием функции ReadDirectoryChanges восстановим его. И так получив информацию с помощью ReadDirectoryChanges мы в цикле начинаем вытаскивать информацию из совокупности структур. Сначала создадим временный указатель на структуру FILE_NOTIFY_INFORMATION и присвоим ему адрес буфера. Далее узнаем следущее смещение другой структуры и установим для буфера новый адрес:
Dim As FILE_NOTIFY_INFORMATION Ptr fni fni = Cast(FILE_NOTIFY_INFORMATION Ptr,buf) iOffset = fni->NextEntryOffset buf+=iOffset
Далее выделяем достаточный буфер для имени файла и копируем в него путь до файла или папки с длиной, указанной в поле структуры FileNameLength, естественно делая акцент на то, что у нас строки в юникоде:
wsFileName = Left(fni->FileName,fni->FileNameLength\2)
Следущий этап: узнаем какой тип изменения произошел (тут по моему все очевидно):
Select Case fni->Action Case FILE_ACTION_ADDED ? "Создан файл: " & wsFileName Case FILE_ACTION_REMOVED ? "Удален файл: " & wsFileName Case FILE_ACTION_RENAMED_OLD_NAME ? "Старое имя файла: " & wsFileName Case FILE_ACTION_RENAMED_NEW_NAME ? "Новое имя файла: " & wsFileName Case FILE_ACTION_MODIFIED ? "Изменен файл: " & wsFileName End Select
И конечно очищаем структуру каждый раз перед использованием на всякий
случай с помощью ZeroMemory.
Все это хозяйство мы определяем в процедуру и выносим ее в отдельный поток:
Threadcreate(@detect) Do Sleep(10) Loop Until GetAsyncKeyState(VK_ESCAPE)<0
И конечно нужно помнить, что исходный код должен быть сохранен в юникоде,
а вначале листинга указать это директивой:
#DEFINE Unicode
А вот и полный код:
' Для завершения программы, ' просто нажмите ESCAPE #DEFINE Unicode #INCLUDE "windows.bi" #Undef FILE_NOTIFY_INFORMATION Type FILE_NOTIFY_INFORMATION NextEntryOffset As DWORD Action As DWORD FileNameLength As DWORD FileName As Wstring * 255 End Type Dim Shared As Handle hDir hDir = CreateFile(_ "C:\22",_ FILE_LIST_DIRECTORY,_ FILE_SHARE_READ+FILE_SHARE_WRITE+FILE_SHARE_DELETE,_ 0,_ OPEN_EXISTING,_ FILE_FLAG_BACKUP_SEMANTICS,_ 0 _ ) If hDir = INVALID_HANDLE_VALUE Then MessageBox(0,"По каким-то причинам открыть данный каталог нельзя","",0) Endif Sub detect(p As Any Ptr) Dim As Byte Ptr buf = Callocate(10000), BufBackup Dim As OVERLAPPED ov Dim As Integer iOffset BufBackup = buf While TRUE buf = BufBackup If ReadDirectoryChanges(_ hDir,_ buf,_ 10000,_ TRUE,_ &h17F,_ ' совокупность флагов 0,_ @ov,_ 0 _ )=0 Then Exit While Do Dim As FILE_NOTIFY_INFORMATION Ptr fni fni = Cast(FILE_NOTIFY_INFORMATION Ptr,buf) iOffset = fni->NextEntryOffset buf+=iOffset If fni->FileNameLength<>0 Then Dim As WString*256 wsFileName wsFileName = Left(fni->FileName,fni->FileNameLength\2) Select Case fni->Action Case FILE_ACTION_ADDED ? "Создан файл: " & wsFileName Case FILE_ACTION_REMOVED ? "Удален файл: " & wsFileName Case FILE_ACTION_RENAMED_OLD_NAME ? "Старое имя файла: " & wsFileName Case FILE_ACTION_RENAMED_NEW_NAME ? "Новое имя файла: " & wsFileName Case FILE_ACTION_MODIFIED ? "Изменен файл: " & wsFileName End Select Endif Loop While iOffset<>0 ZeroMemory(BufBackup,10000) Wend End Sub Threadcreate(@detect) Do Sleep(10) Loop Until GetAsyncKeyState(VK_ESCAPE)<0