Detectar un cambio en los ficheros de un directorio  

Enviado Por: Radikal (Q3 Team)
Web : http://www.q3.nu
Email: radikal@q3.nu
Fecha: 01/02/00

Truco accedido 87 veces

 


A veces es interesante monitorizar un subdirectorio, y saber si se ha borrado, añadido o renombrado algún fichero dentro de él.
Por ejemplo, si estás haciendo algún componente que muestre ficheros y quieres saber cuándo debes refrescarlo.
O tambien, para monitorizar un directorio de red comun a varios ordenadores y saber así si otro ordenador a grabado algun fichero, pudiendo usar esta ventaja para pasar mensajes, datos o lo que sea de un ordenador a otro...


Una vez planteado el problema, la primera solución que se nos ocurre es la de comparar cada x tiempo (mediante un Timer, por ejemplo) los directorios que hay en el disco con una lista de los ficheros que previamente hayamos realizado.

Como verás, no es una solución muy elegante, ya que, en primer lugar, tendríamos que almacenar en algún sitio la lista de ficheros que contiene el directorio (y pueden ser muuuchos), y por otra parte fíjate, esto nos obligaría a recorrer dicha lista cada x tiempo para comparar con el contenido actual del subdirectorio, sin olvidarnos de la tarea más tediosa: leer todo el subdirectorio cada x tiempo.

Si asignamos mucho tiempo entre comparación y comparación, sin duda nos perderemos alguno de los cambios que se produzcan, y, si por el contrario lo mustreamos continuamente... estaremos haciendo accesos a discos casi continuamente, lo cual nos absorverá una gran cantidad de tiempo de CPU.

Visto el problema, vayamos con la solución:

Windows nos proporciona un invento para esta clase de monitorizaciones: Los notificadores de cambios.

Se trata de crear un Notificador de cambio que apunte al directorio que queremos monitorizar, y, a través de este notificador, cada X tiempo podremos consultarlo para ver si se produjo o no un cambio en el directorio (borrar, añadir o renombrar un fichero).

La ventaja radikal de este método es que aunque tardemos en consultar al notificador de cambios, éste recuerda que un cambio se produjo, lo cual nos dá cierta libertad en nuestra aplicación para consultarlo en un tiempo prudencial, descargando así el consumo de CPU utilizado para esta tarea.

Usaré las siguientes funciones del API de Windows:


 FindFirstChangeNotification
 WaitForSingleObject
 FindNextChangeNotification
 FindCloseChangeNotification



El siguiente ejemplo monitorizará el directorio del escritorio de windows, que yo tengo situado en:


 c:\windows\escritorio



Aunque podrias usar el truco:

[202] - Obtener el path de carpetas del sistema

para obtenerlo en cualquier otro Windows, de tal forma que mostrará un mensaje cuando hagas algún cambio en los ficheros del escritorio, como por ejemplo, renombrar un acceso directo.

El ejemplo (por fin!) :

  • Pon un TTimer en tu form (Timer1), asignale, por ejemplo 1 segundo de intervalo y ponlo desactivado (Enabled=FALSE)
  • Define el handle que apuntará al notificador de cambio que vamos a crear. Como lo vamos a usar en varios métodos de la form, lo definiremos global a la form, es decir, en el var de la propia form:


     var
       Form1              : TForm1;
       NotificationHandle : THandle; {Esta linea/This line}
    
     implementation
    



  • Ahora vamos a montar el invento de vigilancia. Lo haremos en el evento OnCreate de la form:


     procedure TForm1.FormCreate(Sender: TObject);
     begin
       {Creamos un Notificador de cambio para el escritorio}
       {Create the Changes Notificator}
       NotificationHandle:=
         FindFirstChangeNotification( PChar('c:\windows\Escritorio'),
                                      FALSE,
                                      FILE_NOTIFY_CHANGE_FILE_NAME);
    
       if NotificationHandle = INVALID_HANDLE_VALUE
       then
         raise Exception.create( 'Error al establecer el notificador...'+
                                 '/Error in FindFirstChangeNotification...')
       else
         Timer1.Enabled:=TRUE;
     end;
    



    Lo que hacemos es crear el notificador de cambio apuntando al directorio del escritorio, generando una excepción si no se pudo crear (por ejemplo, si especificamos un directorio que no existe) o bien activando el Timer1 que es quien ejecutará el código de vigilancia.

  • Ahora, vamos con el código de vigilancia, que irá en el evento OnTimer del Timer1:


     procedure TForm1.Timer1Timer(Sender: TObject);
     var
        Palabra : DWORD;
     begin
         {Esperamos el cambio durante 100 milisegundos}
         {We expect the change during 100 milisegundos}
         Palabra:=WaitForSingleObject(NotificationHandle,100);
         {Si hubo un cambio...}
         {If there was a change...}
         if Palabra = WAIT_OBJECT_0 then
         begin
           FindNextChangeNotification(NotificationHandle);
           FindNextChangeNotification(NotificationHandle);
           ShowMessage('Detectado cambio...');
         end;
     end;
    



    Aqui, testeamos si se produjo un cambio mediante WaitForSingleObject, y si es así, mostramos un mensaje y ejecutamos FindNextChangeNotification para 'resetear' el notificador de cambio.
    Si te fijas, lo ejecuto dos veces, ya que no se por qué, el cambio es detectado dos veces... y así nos deshacemos del segundo.

  • Por último, la fase de limpieza... hay que deshacerse del notificador de cambio. Lo haremos en el evento OnDestroy de la form:


     procedure TForm1.FormDestroy(Sender: TObject);
     begin
       {Cerramos el notificador de cambios}
       {Close the Changes notificator}
       If NotificationHandle <> INVALID_HANDLE_VALUE then
         FindCloseChangeNotification(NotificationHandle);
     end;
    



    Por supuesto, si hubo un fallo a la hora de crearlo... pues no lo liberamos.

    Si has seguido todo este rollo, enhorabuena por tu paciencia (y por la mía). Estás ya listo para probarlo.
    Simplemente ejecuta la aplicacion y cambia el nombre a algún icono de tu escritorio.

    Para aprender más sobre este tema, consulta la ayuda de estas funciones del Api en el fichero Win32.hlp que tendrás por tu disco duro.

    Ah, y lo siento, pero mi inglés no dá para más... así que si me echas una manita y me lo envias correctamente traducido... pues te lo agradecería mucho.


    Actualizado el 01/02/2000 ('wondows'---'windows')