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):
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)
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
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 :
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 :
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.