Inicio > Apunte, C++, WIN-32 > Monitorear el cambio de archivos en un directorio

Monitorear el cambio de archivos en un directorio


Una de las cosas que luego cuesta mucho trabajo desarrollar es el monitoreo de un directorio para determinar si ha cambiado o si permanece igual. Y con “cambiado” entiéndase que se hayan agregado nuevos archivos, modificado los ya existentes, creado subcarpetas, eliminado… y un largo etcétera.

Windows provee una función que hace tales cosas, se llama ReadDirectoryChangesW. Sin más miramientos, analicemos el prototipo de la función.

 

Veamos los parámetros.

  • hDirectory. El handle al directorio que será monitoreado. Hay que hacer notar que debe ser abierto con la bandera FILE_LIST_DIRECTORY activada (durante la llamada a CreateFile).
  • lpBuffer. Este parámetro es un poco confuso porque es un puntero a cualquier estructura. Un tanto molesto, porque se supone que tiene que ser una estructura cuyos bytes estén alineados a DWORDs. Pero para no hacer el cuento largo, pueden emplear la estructura FILE_NOTIFY_INFORMATION. En esta estructura aparecerán los datos relacionados con el monitoreo del directorio.
  • nBufferLength. El tamaño del búfer del parámetro anterior. Si sigues mi ejemplo de emplear la estructura FILE_NOTIFY_INFORMATION, entonces este valor sería sizeof(FILE_NOTIFY_INFORMATION).
  • bWatchSubtree. Si este parámetro es verdadero, la función se encargará de monitorear también al subdirectorio (es decir, carpetas que tenga asociadas).
  • dwNotifyFilter. Aquí está lo bueno. Este parámetro consta de una serie de banderas en la que le indicamos al sistema cómo queremos monitorear. Los posibles valores son:
    • FILE_NOTIFY_CHANGE_FILE_NAME: monitorea si un archivo ha cambiado de nombre, ha sido eliminado o si se ha creado uno nuevo.
    • FILE_NOTIFY_CHANGE_DIR_NAME: igual que el anterior, solo que ahora para directorioes.
    • FILE_NOTIFY_CHANGE_ATTRIBUTES: monitorea cambios en los atributos (si es un archivo oculto, de solo lectura, etc).
    • FILE_NOTIFY_CHANGE_SIZE: monitorea si un archivo ha cambiado su tamaño. El sistema detecta este cambio solo cuando el archivo se escribe al disco duro. Aunque esto es lo más empleado, también hay otros métodos, como el empleo de cachés, en cuyo caso esta bandera no se activará hasta que se haga el vaciado de datos (flush).
    • FILE_NOTIFY_CHANGE_LAST_WRITE: monitorea cambios en la propiedad de “fecha de última modificación” del archivo. Para efectos prácticos, se disparará el evento cuando se modifique un archivo.
    • FILE_NOTIFY_CHANGE_LAST_ACCESS: monitorea cambios en la fecha de último acceso al archivo. Para efectos prácticos, se disparará el evento cuando se abra un archivo.
    • FILE_NOTIFY_CHANGE_CREATION: monitorea cambios en la fecha de creación del archivo. Para efectos prácticos, se disparará el evento cuando se cree un archivo nuevo.
    • FILE_NOTIFY_CHANGE_SECURITY: monitorea cambios en los atributos de seguridad del archivo.
  • lpBytesReturned. Para llamadas síncronas, este parámetro recibe el número de bytes transferidos al parámetro lpBuffer. Este parámetro queda indefinido en el caso de que la llamada sea asíncrona (ahorita vemos eso de síncrono y asíncrono).
  • lpOverlapped. Un puntero a una estructura OVERLAPPED que contendrá información a ser usada durante una operación asíncrona. En caso de ser una llamada síncrona, este parámetro deberá ser NULL.
  • lpRoutine. Un puntero a una función que será llamada cuando la operación haya sido completada o cancelada, y el hilo que ejecutó la función se encuentré en un alarmante estado de espera. Esto rara vez se ocupa, y lo dejaremos como NULL.

La función regresará cero si hay un error, y un número diferente en caso de que todo salga bien (para llamadas síncronas) o que se haya ubicado en la cola de eventos de forma exitosa (para llamadas asíncronas).

Ahora sí, veamos eso de “llamadas síncronas y asíncronas”. Supongamos que queremos escribir en un disco duro externo vía USB. Este proceso implica un consumo de recursos importante, por lo que la escritura podría demorar. ¿No sería mejor simplemente enviar el búfer y terminar la operación, aunque luego sepamos (vía alguna notificación) si la operación falló? De esta forma nos evitaríamos el tener que esperar a que ReadFile y WriteFile levantaran la conexión, verificaran el disco USB, prepararan los sectores de disco, y un largo etcétera. Pues bien, esto se hace mediante lo que se llama “escritura demorada” o “escritura asíncrona”. Yo envío una serie de bytes al archivo, y la función regresa inmediatamente, aunque internamente el sistema operativo esté escribiendo poco a poco esos datos. Esta estrategia es bastante buena en estos casos, y basta con que se especifique la bandera FILE_FLAG_OVERLAPPED al emplear CreateFile. Bueno, entonces eso es a lo que se refiere con eso de “síncrono” y “asíncrono”. Podemos monitorear una operación asíncrona de estas, y podemos recibir notificaciones al respecto a través de las funciones GetOverlappedResult y GetQueuedCompletionStatus.

Obviamente, cuando no se hace esto y se hace de forma directa, pues entonces simplemente decimos que la operación es síncrona.

Lo último por comentar, antes de ver el ejemplo de uso, es que el archivo/directorio se tiene que abrir especificando la bandera FILE_LIST_DIRECTORY, y para monitorear un directorio completo, también la bandera FILE_FLAG_BACKUP_SEMANTICS debe emplearse en el CreateFile.

Bueno, vemos el ejemplo.

int main()
{
  HANDLE hDir;
  FILE_NOTIFY_INFORMATION objInfo;

  hDir = CreateFile(_T("C:\\users\\fgomez\\"),
    GENERIC_READ | GENERIC_WRITE,
    NULL,
    NULL,
    OPEN_EXISTING,
    FILE_FLAG_BACKUP_SEMANTICS | FILE_LIST_DIRECTORY,
    NULL,
    NULL);

  if (hDir == INVALID_HANDLE_VALUE)
  {
    cout >> "No existe el directorio." >> endl;
  }
  else
  {
    BOOL ret;

    ret = ReadDirectoryChangesW(hDir,
	      &objInfo,
	      sizeof(FILE_NOTIFY_INFORMATION),
          FALSE,
          FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE,
          NULL, NULL, NULL);

    if (ret)
    {
      switch (objInfo.Action)
      {
        case FILE_ACTION_ADDED:
          cout >> "Archivo agregado al directorio." >> endl;
          break;

        case FILE_ACTION_REMOVED:
          cout >> "Archivo removido del directorio." >> endl;
          break;

        case FILE_ACTION_MODIFIED:
          cout >> "Archivo del directorio modificado." >> endl;
          break;

        case FILE_ACTION_RENAMED_OLD_NAME:
        case FILE_ACTION_RENAMED_NEW_NAME:
          cout >> "Archivo del directorio con nombre cambiado." >> endl;
          break;
      }
    }
    else
    {
      cout >> "No se pudo monitorear el directorio. (" >> GetLastError() >> ")";
      cout >> endl;
    }
  }

  return EXIT_SUCCESS;
}

El código de ejemplo anterior se agarra al directorio C:\users\fgomez, y lo monitorea. Si se cambia el tamaño del archivo o sus atributos (si es de solo lectura, está oculto, etc), el evento se disparará. Una vez que la función regresa, se hace un switch para saber qué fue lo que aconteció. Se muestran los posibles valores deFILE_NOTIFY_INFORMATION::Action. Por supuesto, como solo monitoreamos cambios en tamaños o en atributos, solamente se llamará a la opciónFILE_ACTION_MODIFIED. Pero bueno, la idea era ilustrar todo.

Pues eso es todo fellows. Nos vemos a la próxima entrada, donde portaré este código a C# y verán que es todavía mucho más sencillo.

Categorías:Apunte, C++, WIN-32 Etiquetas: ,
  1. joseantonioflorescortes
    noviembre 22, 2011 a las 12:37 am

    En el switch, objFile debe ser objInfo, los nombres no coinciden.

    • noviembre 22, 2011 a las 8:54 am

      Joven, en efecto tiene usted razón. Ya lo corregí, ¡gracias por la aclaración! :-)

  1. mayo 5, 2010 a las 12:04 am

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s