9

Uso ReadDirectoryChangesW para ver un directorio específico y actualizar las estructuras de indexación cada vez que se detecta un cambio. Yo uso el siguiente código (más o menos)¿Por qué ReadDirectoryChangesW omite eventos?

var 
    InfoPointer : PFileNotifyInformation; 
    NextOffset : DWORD; 
... 
while (not Terminated) do begin 
    if ReadDirectoryChangesW (FDirHandle, FBuffer, FBufferLength, True, 
          FFilter, @BytesRead, @FOverlap, nil) then 
    begin 
    WaitResult := WaitForMultipleObjects (2, @FEventArray, False, INFINITE); 
    if (WaitResult = waitFileChange) then 
     begin 
     InfoPointer := FBuffer; 
     repeat 
     NextOffset := InfoPointer.NextEntryOffset; 
     ... 
     PByte (InfoPointer) := PByte (InfoPointer) + NextOffset; 
     until NextOffset = 0; 
     end; 
    end; 
end; 

filtro es

FFilter := FILE_NOTIFY_CHANGE_FILE_NAME or 
      FILE_NOTIFY_CHANGE_DIR_NAME or 
      FILE_NOTIFY_CHANGE_SIZE or 
      FILE_NOTIFY_CHANGE_LAST_WRITE; 

y se obtiene el gestor de directorio de esta manera:

FDirHandle := CreateFile (PChar (FDirectoryWatch.WatchedDirectory), 
          FILE_LIST_DIRECTORY or GENERIC_READ, 
          FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, 
          nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS or 
          FILE_FLAG_OVERLAPPED, 0);   

Al eliminar varios archivos me sale un solo evento y NextOffset es 0! Y cuando elimino un directorio obtengo solo un evento para el directorio. ¿Qué pasa si quiero un evento para cada archivo en el directorio?

Cualquier ayuda sería apreciada.

Respuesta

15

Me parece que usted está mezclando las diversas maneras de utilizar ReadDirectoryChangesW(), no tanto especificar el indicador ARCHIVO _ _ COMPROMETIDOS bandera al abrir el directorio y proporciona un puntero a la lpOverlapped parámetro , lo que significa que desea esperar el evento en la estructura y manejar la E/S asíncrona; y al mismo tiempo llama al ReadDirectoryChangesW() en un bucle en un hilo de trabajo. Primero probaría de nuevo con lpOverlapped establecido en nil, ya que tiene un hilo dedicado y puede usar el modo síncrono.

En la documentación de la función de API ReadDirectoryChangesW() se describen las diferentes formas de usarlo. Tenga en cuenta que también es posible que el búfer se desborde, por lo que los eventos de cambio se pueden perder de todos modos. Tal vez debería reconsiderar su estrategia de confiar únicamente en esta función, la comparación de instantáneas de los contenidos del directorio podría funcionar también.

Editar:

Su código editado ve mejor. En mis pruebas, sin embargo, ReadDirectoryChangesW() funcionaba como se anunciaba, había varias entradas de datos en el búfer devuelto, o había más de un búfer para procesar. Esto depende del tiempo, después de llegar a un punto de interrupción en Delphi, obtengo varias entradas en un búfer.

Para completar hay que adjuntar el código de prueba, implementado usando Delphi 5:

type 
    TWatcherThread = class(TThread) 
    private 
    fChangeHandle: THandle; 
    fDirHandle: THandle; 
    fShutdownHandle: THandle; 
    protected 
    procedure Execute; override; 
    public 
    constructor Create(ADirectoryToWatch: string); 
    destructor Destroy; override; 

    procedure Shutdown; 
    end; 

constructor TWatcherThread.Create(ADirectoryToWatch: string); 
const 
    FILE_LIST_DIRECTORY = 1; 
begin 
    inherited Create(TRUE); 
    fChangeHandle := CreateEvent(nil, FALSE, FALSE, nil); 
    fDirHandle := CreateFile(PChar(ADirectoryToWatch), 
    FILE_LIST_DIRECTORY or GENERIC_READ, 
    FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE, 
    nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED, 0); 
    fShutdownHandle := CreateEvent(nil, FALSE, FALSE, nil); 
    Resume; 
end; 

destructor TWatcherThread.Destroy; 
begin 
    if fDirHandle <> INVALID_HANDLE_VALUE then 
    CloseHandle(fDirHandle); 
    if fChangeHandle <> 0 then 
    CloseHandle(fChangeHandle); 
    if fShutdownHandle <> 0 then 
    CloseHandle(fShutdownHandle); 
    inherited Destroy; 
end; 

procedure TWatcherThread.Execute; 
type 
    PFileNotifyInformation = ^TFileNotifyInformation; 
    TFileNotifyInformation = record 
    NextEntryOffset: DWORD; 
    Action: DWORD; 
    FileNameLength: DWORD; 
    FileName: WideChar; 
    end; 
const 
    BufferLength = 65536; 
var 
    Filter, BytesRead: DWORD; 
    InfoPointer: PFileNotifyInformation; 
    Offset, NextOffset: DWORD; 
    Buffer: array[0..BufferLength - 1] of byte; 
    Overlap: TOverlapped; 
    Events: array[0..1] of THandle; 
    WaitResult: DWORD; 
    FileName, s: string; 
begin 
    if fDirHandle <> INVALID_HANDLE_VALUE then begin 
    Filter := FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME 
     or FILE_NOTIFY_CHANGE_SIZE or FILE_NOTIFY_CHANGE_LAST_WRITE; 

    FillChar(Overlap, SizeOf(TOverlapped), 0); 
    Overlap.hEvent := fChangeHandle; 

    Events[0] := fChangeHandle; 
    Events[1] := fShutdownHandle; 

    while not Terminated do begin 
     if ReadDirectoryChangesW (fDirHandle, @Buffer[0], BufferLength, TRUE, 
     Filter, @BytesRead, @Overlap, nil) 
     then begin 
     WaitResult := WaitForMultipleObjects(2, @Events[0], FALSE, INFINITE); 
     if WaitResult = WAIT_OBJECT_0 then begin 
      InfoPointer := @Buffer[0]; 
      Offset := 0; 
      repeat 
      NextOffset := InfoPointer.NextEntryOffset; 
      FileName := WideCharLenToString(@InfoPointer.FileName, 
       InfoPointer.FileNameLength); 
      SetLength(FileName, StrLen(PChar(FileName))); 
      s := Format('[%d] Action: %.8xh, File: "%s"', 
       [Offset, InfoPointer.Action, FileName]); 
      OutputDebugString(PChar(s)); 
      PByte(InfoPointer) := PByte(DWORD(InfoPointer) + NextOffset); 
      Offset := Offset + NextOffset; 
      until NextOffset = 0; 
     end; 
     end; 
    end; 
    end; 
end; 

procedure TWatcherThread.Shutdown; 
begin 
    Terminate; 
    if fShutdownHandle <> 0 then 
    SetEvent(fShutdownHandle); 
end; 

//////////////////////////////////////////////////////////////////////////////// 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    fThread := TWatcherThread.Create('D:\Temp'); 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
    if fThread <> nil then begin 
    TWatcherThread(fThread).Shutdown; 
    fThread.Free; 
    end; 
end; 

La eliminación de un directorio en efecto sólo se volverá un cambio para él, nada de los archivos contenidos en ella. Pero tiene sentido, ya que solo está viendo el manejo del directorio principal. Si necesita notificaciones para subdirectorios, probablemente necesite verlas también.

+0

Lo siento por mi comentario demorado.Utilicé la versión sincrónica antes (con exactamente los mismos problemas) pero cambié a la versión asíncrona porque no encontré la manera de terminar el hilo de forma limpia. Sin embargo, perdí una línea importante en el código de ejemplo (la llamada de bloqueo a WaitForMultipleObjects, que puede ser termine por un evento de cambio de archivo o por un evento de terminación). Edito la pregunta en consecuencia. (...) – jpfollenius

+0

¿Qué quiere decir con instantánea? Si quiere decir iterar sobre todos los archivos usando FindFirst, FindNext: Anteriormente utilicé dicho enfoque, pero quería evitar (1) tiempos de detección de cambios retrasados ​​cuando utilizaba directorios grandes y (2) carga de E/S constante para el hilo de indexación que ralentizaba todo otras operaciones de E/S. – jpfollenius

+1

Está de acuerdo con su segundo comentario, pero como la documentación de MSDN indica que debe estar preparado para los desbordamientos del búfer interno, y en ese caso, es necesario un escaneo (re) completo del directorio. – mghie

4

Hemos tenido el mismo problema con la pérdida de eventos, especialmente si se producen muchos cambios al mismo tiempo, es decir. Se copian 500 archivos en el directorio supervisado.

Al final encontramos Cromis y usamos el Directory watch. Nunca hemos mirado hacia atrás otra vez.

+0

reloj de directorio es bueno de hecho. para la compatibilidad de 64 bits necesita reemplazar getWindowLong con getWindowLongPtr (también para establecer ...) – Ampere

Cuestiones relacionadas