9

Ich benutze ReadDirectoryChangesW, um ein bestimmtes Verzeichnis zu beobachten und Indexstrukturen zu aktualisieren, wenn eine Änderung erkannt wird. Ich verwende den folgenden Code (grob)Warum unterdrückt ReadDirectoryChangesW Ereignisse?

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; 

Filter ist

FFilter := FILE_NOTIFY_CHANGE_FILE_NAME or 
      FILE_NOTIFY_CHANGE_DIR_NAME or 
      FILE_NOTIFY_CHANGE_SIZE or 
      FILE_NOTIFY_CHANGE_LAST_WRITE; 

und das Verzeichnis-Handle wird wie folgt erhalten:

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);   

Wenn ich mehrere Dateien löschen erhalte ich nur ein Ereignis und NextOffset ist 0! Und wenn ich ein Verzeichnis lösche, bekomme ich nur ein Ereignis für das Verzeichnis. Was ist, wenn ich für jede Datei im Verzeichnis ein Ereignis haben möchte?

Jede Hilfe wäre willkommen.

Antwort

15

Es scheint mir, dass Sie die verschiedenen Möglichkeiten, mischen ReadDirectoryChangesW() verwenden, Sie geben sowohl die FILE _ FLAG _ Flagge ÜBERSCHNEIDUNG wenn das Verzeichnis zu öffnen und einen Zeiger bereitzustellen, um die lpOverlapped Parameter , dh Sie möchten auf das Ereignis in der Struktur warten und die asynchrone E/A verarbeiten; und zur gleichen Zeit rufen Sie ReadDirectoryChangesW() in einer Schleife in einem Worker-Thread. Ich würde zuerst wieder versuchen mit lpOverlapped auf nil eingestellt, da Sie einen dedizierten Thread haben und den synchronen Modus verwenden können. In der Dokumentation der API-Funktion ReadDirectoryChangesW() werden die verschiedenen Verwendungsmöglichkeiten beschrieben. Beachten Sie, dass es auch möglich ist, dass der Puffer überläuft, sodass Änderungsereignisse trotzdem verloren gehen können. Vielleicht sollten Sie Ihre Strategie, sich ausschließlich auf diese Funktion zu verlassen, überdenken, und der Vergleich von Snapshots von Verzeichnisinhalten könnte ebenfalls funktionieren.

Edit:

bearbeiteten Code sieht besser aus. In meinen Tests jedoch ReadDirectoryChangesW() funktionierte wie angekündigt, es gab entweder mehrere Dateneinträge im zurückgegebenen Puffer, oder es gab mehr als einen Puffer zu verarbeiten. Das hängt vom Timing ab, nachdem ich einen Haltepunkt in Delphi getroffen habe, bekomme ich mehrere Einträge in einem Puffer.

Der Vollständigkeit ich den Test-Code anhängen, umgesetzt mit 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; 

Verzeichnis löschen in der Tat nicht nur eine Änderung Rückkehr für sie, nichts für die darin enthaltenen Dateien. Aber es macht Sinn, da Sie nur den Handle des übergeordneten Verzeichnisses beobachten. Wenn Sie Benachrichtigungen für Unterverzeichnisse benötigen, müssen Sie sie wahrscheinlich ebenfalls ansehen.

+0

Entschuldigung für meinen verspäteten Kommentar.Ich habe vorher die synchrone Version benutzt (mit genau den gleichen Problemen), bin aber zur asynchronen Version gewechselt, weil ich keine Möglichkeit gefunden habe, den Thread sauber zu beenden. Ich verpasste jedoch eine wichtige Zeile im Beispielcode (der blockierende Aufruf von WaitForMultipleObjects, der entweder durch ein Dateiänderungsereignis oder durch ein Terminierungsereignis terminiert werden kann). Ich bearbeite die Frage entsprechend. (...) – jpfollenius

+0

Was meinst du mit Snapshot? Wenn Sie das Iterieren über alle Dateien mit FindFirst, FindNext meinen: Früher habe ich einen solchen Ansatz verwendet, aber ich wollte (1) verzögerte Änderungserkennungszeiten vermeiden, wenn große Verzeichnisse verwendet werden und (2) konstante I/O-Last für den Indizierungsthread alles verlangsamt andere E/A-Vorgänge. – jpfollenius

+1

Vereinbart mit Ihrem zweiten Kommentar, aber wie die MSDN-Dokumentation besagt, müssen Sie auf Überläufe des internen Puffers vorbereitet sein, und in diesem Fall ist ein vollständiger (Neu-) Scan des Verzeichnisses erforderlich. – mghie

4

Wir hatten das gleiche Problem mit dem Verlieren von Ereignissen, vor allem, wenn viele Änderungen gleichzeitig passieren, dh. 500 Dateien werden in das überwachte Verzeichnis kopiert.

Am Ende fanden wir Cromis und verwenden Sie die Directory watch. Wir haben uns nie wieder angesehen.

+0

Verzeichnisüberwachung ist in der Tat gut. Für 64-Bit-Kompatibilität müssen Sie getWindowLong durch getWindowLongPtr ersetzen (auch für set ...) – Ampere