9

J'utilise ReadDirectoryChangesW pour regarder un répertoire spécifié et mettre à jour les structures d'indexation chaque fois qu'une modification est détectée. J'utilise le code suivant (environ)Pourquoi ReadDirectoryChangesW omet-il des événements?

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 est

FFilter := FILE_NOTIFY_CHANGE_FILE_NAME or 
      FILE_NOTIFY_CHANGE_DIR_NAME or 
      FILE_NOTIFY_CHANGE_SIZE or 
      FILE_NOTIFY_CHANGE_LAST_WRITE; 

et la poignée de répertoire est obtenu comme ceci:

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

Lorsque je supprime plusieurs fichiers je reçois un seul événement et NextOffset est 0! Et quand je supprime un répertoire, je n'ai qu'un seul événement pour le répertoire. Que faire si je veux un événement pour chaque fichier dans le répertoire?

Toute aide serait appréciée.

Répondre

15

Il me semble que vous mélangez les différentes façons d'utiliser ReadDirectoryChangesW(), vous faites les deux spécifier le FICHIER _ FLAG _ CHEVAUCHENT drapeau lors de l'ouverture du répertoire et de fournir un pointeur sur le lpOverlapped paramètre , ce qui signifie que vous voulez attendre l'événement dans la structure et gérer les E/S asynchrones; et en même temps vous appelez ReadDirectoryChangesW() dans une boucle dans un thread de travail. Je voudrais d'abord essayer à nouveau avec lpOverlapped mis à nul, comme vous avez un thread dédié et pouvez utiliser le mode synchrone.

La documentation de la fonction API ReadDirectoryChangesW() décrit les différentes manières de l'utiliser. Notez qu'il est également possible que le tampon déborde, donc les événements de changement peuvent être perdus de toute façon. Peut-être que vous devriez repenser votre stratégie de ne compter que sur cette fonction, en comparant les instantanés du contenu du répertoire pourrait fonctionner aussi bien.

Edit:

Votre code modifié semble mieux. Dans mes tests cependant ReadDirectoryChangesW() a fonctionné comme annoncé, il y avait plusieurs entrées de données dans le tampon retourné, ou il y avait plus d'un tampon à traiter. Cela dépend du timing, après avoir frappé un point d'arrêt dans Delphi, j'ai plusieurs entrées dans un tampon.

Pour être complet, je joins le code de test, mis en œuvre en utilisant 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; 

Suppression d'un répertoire ne fait revenir un seul changement pour elle, rien pour les fichiers qu'il contient. Mais cela a du sens, car vous ne regardez que la poignée du répertoire parent. Si vous avez besoin de notifications pour les sous-répertoires, vous devez probablement les regarder également.

+0

Désolé pour mon commentaire différé.J'ai utilisé la version synchrone avant (avec exactement les mêmes problèmes) mais je suis passé à la version asynchrone parce que je n'ai pas trouvé un moyen de terminer proprement le thread. J'ai cependant manqué une ligne importante dans le code d'exemple (l'appel bloquant à WaitForMultipleObjects, qui peut soit être terminé par un événement de changement de fichier ou par un événement de fin). Je modifie la question en conséquence. (...) – jpfollenius

+0

Qu'entendez-vous par instantané? Si vous voulez répéter sur tous les fichiers avec FindFirst, FindNext: J'ai précédemment utilisé une telle approche mais je voulais éviter (1) les temps de détection des changements retardés lors de l'utilisation de gros répertoires et (2) la charge constante des E/S pour le thread d'indexation autres opérations d'E/S. – jpfollenius

+1

D'accord avec votre deuxième commentaire, mais comme la documentation de MSDN indique que vous devez être préparé pour les débordements de la mémoire tampon interne, et dans ce cas, une (re) analyse complète du répertoire est nécessaire. – mghie

4

Nous avons eu le même problème avec la perte d'événements, surtout si beaucoup de changements se produisent en même temps, c'est-à-dire. 500 fichiers sont copiés dans le répertoire surveillé.

En fin de compte, nous avons trouvé Cromis et utilisez le Directory watch. Nous n'avons jamais regardé en arrière.

+0

regarder le répertoire est bon en effet. pour la compatibilité 64 bits, vous devez remplacer getWindowLong par getWindowLongPtr (également pour set ...) – Ampere

Questions connexes