win32file.ReadDirectoryChangesW no encuentra todos los archivos movidos

Buenos días,

He encontrado un problema peculiar con un progtwig que estoy creando en Python. Parece que cuando arrastro y suelto archivos de una ubicación a otra, los módulos no registran todos los archivos como eventos.

He estado trabajando con win32file y win32con para probar y obtener todos los eventos relacionados con mover archivos de una ubicación a otra para su procesamiento.

Aquí hay un fragmento de mi código de detección:

import win32file import win32con def main(): path_to_watch = 'D:\\' _file_list_dir = 1 # Create a watcher handle _h_dir = win32file.CreateFile( path_to_watch, _file_list_dir, win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE, None, win32con.OPEN_EXISTING, win32con.FILE_FLAG_BACKUP_SEMANTICS, None ) while 1: results = win32file.ReadDirectoryChangesW( _h_dir, 1024, True, win32con.FILE_NOTIFY_CHANGE_FILE_NAME | win32con.FILE_NOTIFY_CHANGE_DIR_NAME | win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES | win32con.FILE_NOTIFY_CHANGE_SIZE | win32con.FILE_NOTIFY_CHANGE_LAST_WRITE | win32con.FILE_NOTIFY_CHANGE_SECURITY, None, None ) for _action, _file in results: if _action == 1: print 'found!' if _action == 2: print 'deleted!' 

Arrastré y solté 7 archivos y solo encontré 4.

 # found! # found! # found! # found! 

¿Qué puedo hacer para detectar todos los archivos caídos?

[ActiveState.Docs]: win32file.ReadDirectoryChangesW (esta es la mejor documentación que pude encontrar para [GitHub]: mhammond / pywin32 – Python para Windows (pywin32) Extensions ) es una envoltura sobre [MS.Docs]: ReadDirectoryChangesW function . Esto es lo que dice (sobre el búfer):

  1. General

    Cuando llama por primera vez a ReadDirectoryChangesW , el sistema asigna un búfer para almacenar información de cambios. Este búfer se asocia con el identificador de directorio hasta que se cierra y su tamaño no cambia durante su vida útil. Los cambios de directorio que ocurren entre las llamadas a esta función se agregan al búfer y luego se devuelven con la siguiente llamada. Si el búfer se desborda, todo el contenido del búfer se descarta, el parámetro lpBytesReturned contiene cero y la función ReadDirectoryChangesW falla con el código de error ERROR_NOTIFY_ENUM_DIR .

    • Mi entendimiento es que este es un búfer diferente al que se pasa como argumento ( lpBuffer ):

      • El primer paso se pasa a cada llamada de ReadDirectoryChangesW (podrían ser diferentes buffers (con diferentes tamaños ) que se pasan para cada llamada)

      • Este último es asignado por el sistema, cuando el primero está claramente asignado (por el usuario) antes de la llamada a la función.

      y ese es el que almacena los datos (probablemente en algún formato sin formato) entre las llamadas a funciones, y cuando se llama a la función, el contenido del búfer se copia (y se formatea) a lpBuffer (si no se sobrepasa (y se descarta) mientras tanto)

  2. Sincrónico

    Una vez que se completa correctamente el proceso de sincronización, el parámetro lpBuffer es un búfer formateado y el número de bytes escritos en el búfer está disponible en lpBytesReturned . Si el número de bytes transferidos es cero, el búfer era demasiado grande para que el sistema lo asignara o demasiado pequeño para proporcionar información detallada sobre todos los cambios que ocurrieron en el directorio o subárbol. En este caso, debe calcular los cambios enumerando el directorio o subárbol.

    • Esto confirma un poco mi suposición anterior

      • el búfer era demasiado grande para que el sistema lo asignara “, tal vez cuando se asigna el búfer del punto anterior, ¿se toma en cuenta nBufferLength ?

De todos modos, tomé tu código y lo cambié “un poco”.

code.py :

 import sys import msvcrt import pywintypes import win32file import win32con import win32api import win32event FILE_LIST_DIRECTORY = 0x0001 FILE_ACTION_ADDED = 0x00000001 FILE_ACTION_REMOVED = 0x00000002 ASYNC_TIMEOUT = 5000 BUF_SIZE = 65536 def get_dir_handle(dir_name, async): flags_and_attributes = win32con.FILE_FLAG_BACKUP_SEMANTICS if async: flags_and_attributes |= win32con.FILE_FLAG_OVERLAPPED dir_handle = win32file.CreateFile( dir_name, FILE_LIST_DIRECTORY, (win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE), None, win32con.OPEN_EXISTING, flags_and_attributes, None ) return dir_handle def read_dir_changes(dir_handle, size_or_buf, overlapped): return win32file.ReadDirectoryChangesW( dir_handle, size_or_buf, True, (win32con.FILE_NOTIFY_CHANGE_FILE_NAME | win32con.FILE_NOTIFY_CHANGE_DIR_NAME | win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES | win32con.FILE_NOTIFY_CHANGE_SIZE | win32con.FILE_NOTIFY_CHANGE_LAST_WRITE | win32con.FILE_NOTIFY_CHANGE_SECURITY), overlapped, None ) def handle_results(results): for item in results: print(" {} {:d}".format(item, len(item[1]))) _action, _ = item if _action == FILE_ACTION_ADDED: print(" found!") if _action == FILE_ACTION_REMOVED: print(" deleted!") def esc_pressed(): return msvcrt.kbhit() and ord(msvcrt.getch()) == 27 def monitor_dir_sync(dir_handle): idx = 0 while True: print("Index: {:d}".format(idx)) idx += 1 results = read_dir_changes(dir_handle, BUF_SIZE, None) handle_results(results) if esc_pressed(): break def monitor_dir_async(dir_handle): idx = 0 buffer = win32file.AllocateReadBuffer(BUF_SIZE) overlapped = pywintypes.OVERLAPPED() overlapped.hEvent = win32event.CreateEvent(None, False, 0, None) while True: print("Index: {:d}".format(idx)) idx += 1 read_dir_changes(dir_handle, buffer, overlapped) rc = win32event.WaitForSingleObject(overlapped.hEvent, ASYNC_TIMEOUT) if rc == win32event.WAIT_OBJECT_0: bufer_size = win32file.GetOverlappedResult(dir_handle, overlapped, True) results = win32file.FILE_NOTIFY_INFORMATION(buffer, bufer_size) handle_results(results) elif rc == win32event.WAIT_TIMEOUT: #print(" timeout...") pass else: print("Received {:d}. Exiting".format(rc)) break if esc_pressed(): break win32api.CloseHandle(overlapped.hEvent) def monitor_dir(dir_name, async=False): dir_handle = get_dir_handle(dir_name, async) if async: monitor_dir_async(dir_handle) else: monitor_dir_sync(dir_handle) win32api.CloseHandle(dir_handle) def main(): print("Python {:s} on {:s}\n".format(sys.version, sys.platform)) async = True print("Attempting {}ynchronous mode using a buffer {:d} bytes long...".format("As" if async else "S", BUF_SIZE)) monitor_dir(".\\test", async=async) if __name__ == "__main__": main() 

Notas :

  • Constantes usadas siempre que sea posible
  • Divide tu código en funciones para que sea modular (y también para evitar duplicarlo)
  • Se agregaron declaraciones de impresión para boost la salida.
  • Se agregó la funcionalidad asíncrona (para que la secuencia de comandos no se cuelgue para siempre si no hay actividad en el directorio)
  • Se agregó una forma de salir cuando el usuario presiona ESC (por supuesto, en el modo síncrono también debe ocurrir un evento en el directorio)
  • Jugado con diferentes valores para diferentes resultados.

Salida :

 e:\Work\Dev\StackOverflow\q049799109>dir /b test 0123456789.txt 01234567890123456789.txt 012345678901234567890123456789.txt 0123456789012345678901234567890123456789.txt 01234567890123456789012345678901234567890123456789.txt 012345678901234567890123456789012345678901234567890123456789.txt 0123456789012345678901234567890123456789012345678901234567890123456789.txt 01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt e:\Work\Dev\StackOverflow\q049799109> e:\Work\Dev\StackOverflow\q049799109>"C:\Install\x64\HPE\OPSWpython\2.7.10__00\python.exe" code.py Python 2.7.10 (default, Mar 8 2016, 15:02:46) [MSC v.1600 64 bit (AMD64)] on win32 Attempting Synchronous mode using a buffer 512 bytes long... Index: 0 (2, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104 deleted! Index: 1 (2, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94 deleted! Index: 2 (2, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84 deleted! Index: 3 (2, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74 deleted! (2, u'012345678901234567890123456789012345678901234567890123456789.txt') 64 deleted! Index: 4 (2, u'01234567890123456789012345678901234567890123456789.txt') 54 deleted! Index: 5 (2, u'0123456789012345678901234567890123456789.txt') 44 deleted! (2, u'012345678901234567890123456789.txt') 34 deleted! Index: 6 (2, u'01234567890123456789.txt') 24 deleted! (2, u'0123456789.txt') 14 deleted! Index: 7 (1, u'0123456789.txt') 14 found! Index: 8 (3, u'0123456789.txt') 14 Index: 9 (1, u'01234567890123456789.txt') 24 found! Index: 10 (3, u'01234567890123456789.txt') 24 (1, u'012345678901234567890123456789.txt') 34 found! (3, u'012345678901234567890123456789.txt') 34 (1, u'0123456789012345678901234567890123456789.txt') 44 found! Index: 11 (3, u'0123456789012345678901234567890123456789.txt') 44 (1, u'01234567890123456789012345678901234567890123456789.txt') 54 found! (3, u'01234567890123456789012345678901234567890123456789.txt') 54 Index: 12 Index: 13 (1, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84 found! Index: 14 Index: 15 (1, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104 found! Index: 16 (3, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104 Index: 17 (1, u'a') 1 found! Index: 18 (3, u'a') 1 e:\Work\Dev\StackOverflow\q049799109> e:\Work\Dev\StackOverflow\q049799109>"C:\Install\x64\HPE\OPSWpython\2.7.10__00\python.exe" code.py Python 2.7.10 (default, Mar 8 2016, 15:02:46) [MSC v.1600 64 bit (AMD64)] on win32 Attempting Synchronous mode using a buffer 65536 bytes long... Index: 0 (2, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104 deleted! Index: 1 (2, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94 deleted! Index: 2 (2, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84 deleted! Index: 3 (2, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74 deleted! Index: 4 (2, u'012345678901234567890123456789012345678901234567890123456789.txt') 64 deleted! Index: 5 (2, u'01234567890123456789012345678901234567890123456789.txt') 54 deleted! Index: 6 (2, u'0123456789012345678901234567890123456789.txt') 44 deleted! Index: 7 (2, u'012345678901234567890123456789.txt') 34 deleted! (2, u'01234567890123456789.txt') 24 deleted! (2, u'0123456789.txt') 14 deleted! Index: 8 (1, u'0123456789.txt') 14 found! Index: 9 (3, u'0123456789.txt') 14 Index: 10 (1, u'01234567890123456789.txt') 24 found! Index: 11 (3, u'01234567890123456789.txt') 24 Index: 12 (1, u'012345678901234567890123456789.txt') 34 found! Index: 13 (3, u'012345678901234567890123456789.txt') 34 Index: 14 (1, u'0123456789012345678901234567890123456789.txt') 44 found! Index: 15 (3, u'0123456789012345678901234567890123456789.txt') 44 Index: 16 (1, u'01234567890123456789012345678901234567890123456789.txt') 54 found! (3, u'01234567890123456789012345678901234567890123456789.txt') 54 Index: 17 (1, u'012345678901234567890123456789012345678901234567890123456789.txt') 64 found! (3, u'012345678901234567890123456789012345678901234567890123456789.txt') 64 (1, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74 found! Index: 18 (3, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74 (1, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84 found! (3, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84 (1, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94 found! (3, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94 (1, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104 found! (3, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104 Index: 20 (2, u'a') 1 deleted! e:\Work\Dev\StackOverflow\q049799109> e:\Work\Dev\StackOverflow\q049799109>"C:\Install\x64\HPE\OPSWpython\2.7.10__00\python.exe" code.py Python 2.7.10 (default, Mar 8 2016, 15:02:46) [MSC v.1600 64 bit (AMD64)] on win32 Attempting Asynchronous mode using a buffer 512 bytes long... Index: 0 Index: 1 (2, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104 deleted! Index: 2 (2, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94 deleted! Index: 3 (2, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84 deleted! Index: 4 (2, u'012345678901234567890123456789012345678901234567890123456789.txt') 64 deleted! Index: 5 (2, u'01234567890123456789012345678901234567890123456789.txt') 54 deleted! Index: 6 (2, u'0123456789012345678901234567890123456789.txt') 44 deleted! Index: 7 (2, u'012345678901234567890123456789.txt') 34 deleted! Index: 8 (2, u'01234567890123456789.txt') 24 deleted! Index: 9 (2, u'0123456789.txt') 14 deleted! Index: 10 Index: 11 Index: 12 (1, u'0123456789.txt') 14 found! Index: 13 (1, u'01234567890123456789.txt') 24 found! Index: 14 (1, u'012345678901234567890123456789.txt') 34 found! Index: 15 (3, u'012345678901234567890123456789.txt') 34 Index: 16 (1, u'0123456789012345678901234567890123456789.txt') 44 found! (3, u'0123456789012345678901234567890123456789.txt') 44 Index: 17 Index: 18 (1, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74 found! Index: 19 Index: 20 (1, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94 found! Index: 21 Index: 22 Index: 23 Index: 24 e:\Work\Dev\StackOverflow\q049799109> e:\Work\Dev\StackOverflow\q049799109>"C:\Install\x64\HPE\OPSWpython\2.7.10__00\python.exe" code.py Python 2.7.10 (default, Mar 8 2016, 15:02:46) [MSC v.1600 64 bit (AMD64)] on win32 Attempting Asynchronous mode using a buffer 65536 bytes long... Index: 0 Index: 1 (2, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104 deleted! Index: 2 (2, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94 deleted! Index: 3 (2, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84 deleted! Index: 4 (2, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74 deleted! Index: 5 (2, u'012345678901234567890123456789012345678901234567890123456789.txt') 64 deleted! Index: 6 (2, u'01234567890123456789012345678901234567890123456789.txt') 54 deleted! Index: 7 (2, u'0123456789012345678901234567890123456789.txt') 44 deleted! Index: 8 (2, u'012345678901234567890123456789.txt') 34 deleted! (2, u'01234567890123456789.txt') 24 deleted! Index: 9 (2, u'0123456789.txt') 14 deleted! Index: 10 Index: 11 Index: 12 (1, u'0123456789.txt') 14 found! Index: 13 (1, u'01234567890123456789.txt') 24 found! Index: 14 (1, u'012345678901234567890123456789.txt') 34 found! Index: 15 (3, u'012345678901234567890123456789.txt') 34 (1, u'0123456789012345678901234567890123456789.txt') 44 found! (3, u'0123456789012345678901234567890123456789.txt') 44 Index: 16 (1, u'01234567890123456789012345678901234567890123456789.txt') 54 found! (3, u'01234567890123456789012345678901234567890123456789.txt') 54 (1, u'012345678901234567890123456789012345678901234567890123456789.txt') 64 found! (3, u'012345678901234567890123456789012345678901234567890123456789.txt') 64 (1, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74 found! Index: 17 (3, u'0123456789012345678901234567890123456789012345678901234567890123456789.txt') 74 (1, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84 found! (3, u'01234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 84 (1, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94 found! (3, u'012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 94 (1, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104 found! (3, u'0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789.txt') 104 Index: 18 Index: 19 

Observaciones :

  • Se utilizó una prueba de directorio que contiene 10 archivos con nombres diferentes (repeticiones de 0123456789 )
  • Hay 4 carreras:
    1. Sincrónico
      • 512B buffer
      • 64K buffer
    2. Asincrónico
      • 512B buffer
      • 64K buffer
  • Para cada ejecución (arriba), los archivos son (usando Windows Commander para operar):
    • Movido desde el directorio ( eliminación involucrada)
    • Movido (atrás) al directorio (envuelto)
  • Es solo una carrera para cada combinación, y eso no puede ser considerado como un punto de referencia , pero ejecuté el script varias veces y el patrón tiende a ser consistente
  • La eliminación de archivos no varía demasiado en las ejecuciones, lo que significa que los eventos se distribuyen de manera uniforme (la cantidad mínima)
  • Agregar archivos por otro lado depende del tamaño del búfer. Otra cosa notable es que por cada adición hay 2 eventos.
  • Desde la perspectiva del rendimiento, el modo asíncrono no trae ninguna mejora (como esperaba), por el contrario, tiende a ralentizar las cosas. Pero su mayor ventaja es la posibilidad de salir con gracia en el tiempo de espera ( una interrupción anormal puede mantener los recursos bloqueados hasta que el progtwig salga (y, a veces, incluso más allá)

La conclusión es que no hay una receta para evitar perder eventos . Cada medida tomada puede ser “superada” al boost el número de eventos generados.

Minimizando las pérdidas:

  • El tamaño del búfer. Este fue el problema (principal) en su caso. Desafortunadamente, la documentación no podría ser menos clara, no hay pautas sobre qué tan grande debe ser. Al buscar en los foros de C noté que 64K es un valor común. Sin embargo:

    • No es posible tener un búfer enorme y, en caso de fallas, disminuir su tamaño hasta que tenga éxito, porque eso significaría perder todos los eventos generados al calcular el tamaño del búfer

    • Incluso si 64k es suficiente para mantener (por varias veces) todos los eventos que generé en mis pruebas, algunos todavía se perdieron. Tal vez sea por el búfer “mágico” del que hablé al principio

  • Reducir el número de eventos tanto como sea posible. En su caso, noté que solo le interesa agregar y eliminar eventos ( FILE_ACTION_ADDED y FILE_ACTION_REMOVED ). Solo especifique los indicadores FILE_NOTIFY_CHANGE_ * apropiados para ReadDirectoryChangesW (por ejemplo, no le importa FILE_ACTION_MODIFIED , pero lo está recibiendo al agregar archivos)

  • Intente dividir el contenido del directorio en varios subdirectorios y monitoréelos al mismo tiempo. Por ejemplo, si solo te preocupan los cambios ocurridos en un directorio y un grupo de sus subdirectorios, no tiene sentido monitorear recursivamente todo el árbol, ya que lo más probable es que produzca muchos eventos inútiles. De todos modos, si haces cosas en paralelo, ¡no uses hilos debido a GIL ! ( [Python.Wiki]: GlobalInterpreterLock ). Utilice [Python 2]: multiprocesamiento: en su lugar, interfaz de “subprocesos” basada en procesos

  • Aumente la velocidad del código que se ejecuta en el bucle para que pase el menor tiempo posible fuera de ReadDirectoryChangesW (cuando los eventos generados pueden desbordar el búfer). Por supuesto, algunos de los artículos a continuación pueden tener una influencia insignificante y (también tienen algunos efectos secundarios negativos) pero los estoy enumerando de todos modos:

    • Haga el menor procesamiento posible e intente retrasarlo. Tal vez hacerlo en otro proceso (debido a GIL )

    • Deshazte de todas las impresiones como declaraciones

    • En lugar de, por ejemplo, win32con.FILE_NOTIFY_CHANGE_FILE_NAME use from win32con import FILE_NOTIFY_CHANGE_FILE_NAME al comienzo del script, y solo use FILE_NOTIFY_CHANGE_FILE_NAME en el bucle (para evitar la búsqueda variable en el módulo)

    • No use funciones (debido a las instrucciones de llamada / ret ), no estoy seguro de eso

    • Intente usar el método win32file.GetQueuedCompletionStatus para obtener los resultados ( solo async )

    • Ya que con el tiempo, las cosas tienden a mejorar (hay excepciones, por supuesto), intente cambiar a una versión más reciente de Python . Tal vez se ejecute más rápido

    • Use C : esto es probablemente indeseable, pero podría tener algunos beneficios:

      • No habrá las conversiones de ida y vuelta entre Python y C que realiza pywin32 , pero no usé un generador de perfiles para comprobar cuánto tiempo se gasta en ellos.

      • lpCompletionRoutine (que pywin32 no ofrece) también estaría disponible, quizás sea más rápido

      • Como alternativa, C podría invocarse usando ctypes , pero eso requeriría un poco de trabajo y creo que no vale la pena.