gpt4 book ai didi

python - ReadDirectoryChangesW无法检测和处理监视目录的删除

转载 作者:行者123 更新时间:2023-11-28 22:19:33 24 4
gpt4 key购买 nike

我尝试监视目录树的内容,其中包含大量文件(例如,每个目录有9000个文件的许多目录)。

Synchron mode:



我首先尝试在阻塞模式(同步)下使用ReadDirectoryChangesW,但是当我删除监视的目录时,我最终陷入了死锁,无法检测也无法退出。
#
# Monitors a directory for changes and pass the changes to the queue
#
def MonitorDirectory(self, out_queue):

print("Monitoring instance \'{0}\' is watching directory: {1}".format(self.name, self.path))

# File monitor
FILE_LIST_DIRECTORY = 0x0001

buffer = win32file.AllocateReadBuffer(1024 * 64)

hDir = win32file.CreateFile(self.path,
FILE_LIST_DIRECTORY,
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE,
None,
win32con.OPEN_EXISTING,
win32con.FILE_FLAG_BACKUP_SEMANTICS,
None)

# Monitor directory for changes
while not self._shutdown.is_set():

# Create handle to directory if missing
#if os.path.isdir(self.path):

self.fh.write("ReOpen Exists {0}\n".format(os.path.isdir(self.path)))
self.fh.flush()
try:
hDir = win32file.CreateFile(self.path,
FILE_LIST_DIRECTORY,
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE,
None,
win32con.OPEN_EXISTING,
win32con.FILE_FLAG_BACKUP_SEMANTICS,
None)
except:
self.fh.write("Handle is dead\n")
self.fh.flush()

try:
self.fh.write("{0}\n".format(newH))
self.fh.flush()
except:
self.fh.write("Write failed\n")
self.fh.flush()

self.fh.write("Check Changes\n")
self.fh.flush()

results = win32file.ReadDirectoryChangesW(hDir,
1024 * 64,
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)

# Add all changes to queue
for action, file in results:

self.fh.write("Action: {0} on {1}\n".format(action, file))

out_queue.put((action, time.time(), os.path.join(self.path, file)))

self.fh.flush()


#else:


# Done main loop
print("Monitoring instance \'{0}\' has finished watching directory: {1}".format(self.name, self.path))

似乎没有办法避免在删除受监视的目录时阻止该调用吗?

另外,由于该函数在线程中运行,因此当死锁时,我无法从“supervisor”线程中杀死它,该线程将监视父目录的监视目录上的DELETE操作,而且我真的不认为这是一个好的解决方案,因为它涉及很多更多代码。

ASynchron mode:



然后,我尝试了不会在死锁中阻塞的重叠模式(异步),但是我无法检测到删除分区后目录句柄何时变为无效。
WaitForSingleObject调用只是超时了,并且检查目录是否存在os.path.isdir并没有帮助,因为如果在此同时重新创建目录,它将不会返回False,但是旧目录句柄仍然无效并且会无法在新创建的目录中检测到具有相同名称的更改。

在尝试各种方法之后,我终于找到了这段代码,但是由于它仍然无法检测到受监视目录的删除,并且无法快速删除大量文件,因此它也无法正常工作。同步模式没有的东西。
#
# Monitors a directory for changes and pass the changes to the queue
#
def MonitorDirectory(self, out_queue):

print("Monitoring instance \'{0}\' is watching directory: {1}".format(self.name, self.path))

# File monitor
FILE_LIST_DIRECTORY = 0x0001

overlapped = pywintypes.OVERLAPPED()
overlapped.hEvent = win32event.CreateEvent(None, False, 0, None)

buffer = win32file.AllocateReadBuffer(1024 * 64)

# Main loop to keep watching active
while not self._shutdown.is_set():

# Open directory
try:
hDir = win32file.CreateFile(self.path,
FILE_LIST_DIRECTORY,
win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE,
None,
win32con.OPEN_EXISTING,
win32con.FILE_FLAG_BACKUP_SEMANTICS | win32con.FILE_FLAG_OVERLAPPED,
None)

except:

# Wait before retry
time.sleep(1)

else:

# Monitor directory for changes
while not self._shutdown.is_set():

win32file.ReadDirectoryChangesW(hDir,
buffer,
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)

# Wait for the changes
rc = win32event.WaitForSingleObject(overlapped.hEvent, 10000)

if rc == win32event.WAIT_OBJECT_0:

try:
bytes_returned = win32file.GetOverlappedResult(hDir, overlapped, True)

except:
raise Exception("Error: handle invalid?")

else:

# Get the changes
for action, file in win32file.FILE_NOTIFY_INFORMATION(buffer, bytes_returned):
out_queue.put((action, time.time(), os.path.join(self.path, file)))

elif rc == win32event.WAIT_TIMEOUT:
print("Monitoring instance \'{0}\': Timeout, no actions")

else:
raise Exception("Error?! RC = {0}".format(rc))

# Done main loop
print("Monitoring instance \'{0}\' has finished watching directory: {1}".format(self.name, self.path))

有没有一种方法可以处理检测到的监视目录的删除,而不仅仅是删除win32con.FILE_SHARE_DELETE标志?

最佳答案

注意事项

  • 首先,我邀请您阅读[SO]: win32file.ReadDirectoryChangesW doesn't find all moved files(@CristiFati's answer),以获取有关[ActiveState.Docs]: win32file.ReadDirectoryChangesW(包装[MS.Docs]: ReadDirectoryChangesW function)的一般注意事项。我将把其他答案(确实很重要)的某些方面放在这里
  • 始终针对异​​步,因为它允许在超时时正常退出(异​​常中断可能会使开放资源锁定,直到程序退出(有时甚至超过!)为止)。
  • 不要因为GIL([Python.Wiki]: GlobalInterpreterLock)
  • 而使用线程

    现在,在FILE_SHARE_DELETE上说几句话(可以在 [MS.Docs]: CreateFileW function上找到有关它的一些文档):
    黄金法则(或不变法,如果愿意的话)是 用户不能真正删除具有打开的句柄的文件(或目录)。
    尝试删除或重命名(这似乎与当前问题无关,但不是),具有打开句柄的目录可能会产生不同的结果(取决于创建句柄的方式以及用于重命名/删除目录的API) :
  • 错误(ERROR_ACCESS_DENIED)-当未指定FILE_SHARE_DELETE(在某些情况下)时发生
  • 没有错误,但目录仍然存在-通常意味着已计划将其删除,并且一旦其最后打开的句柄将被关闭,它将自动消失
  • 成功,并且目录被删除。实际上,这是不正确的,该目录仅被移动(重命名)到“RECYCLE.BIN”(尝试从该目录中删除它会导致#1。;因此,尝试在第一个位置真正将其删除(Shift +来自浏览器的Del))

  • 我测试了以上方案,尝试以各种方式删除/重命名目录:
  • cmd:rmdir /q /smove /y
  • 资源管理器:Del,Shift + Del
  • Windows Commander:F8,Shift + F8

  • 我开始研究解决问题的方法,但遇到了 [MS.Docs]: GetFinalPathNameByHandleW function(win32file.GetFinalPathNameByHandle)。玩过:

    >>> import sys
    >>> import os
    >>> import win32api
    >>> import win32file
    >>> import win32con
    >>>
    >>> print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    Python 3.5.4 (v3.5.4:3f56838, Aug 8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32

    >>> os.listdir()
    ['code00.py', 'test']
    >>> test_dir = ".\\test"
    >>> os.path.abspath(test_dir)
    'e:\\Work\\Dev\\StackOverflow\\q049652110\\test'
    >>> h = win32file.CreateFile(test_dir, win32con.GENERIC_READ, win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE, None, win32con.OPEN_EXISTING, win32con.FILE_FLAG_BACKUP_SEMANTICS, None)
    >>> h
    <PyHANDLE:620>
    >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED)
    '\\\\?\\E:\\Work\\Dev\\StackOverflow\\q049652110\\test'
    >>> test_dir1 = test_dir + "1"
    >>> os.rename(test_dir, test_dir1)
    >>> os.listdir()
    ['code00.py', 'test1']
    >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED)
    '\\\\?\\E:\\Work\\Dev\\StackOverflow\\q049652110\\test1'
    >>> os.rename(test_dir1, test_dir)
    >>> os.listdir()
    ['code00.py', 'test']
    >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED)
    '\\\\?\\E:\\Work\\Dev\\StackOverflow\\q049652110\\test'
    >>> os.unlink(test_dir)
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    PermissionError: [WinError 5] Access is denied: '.\\test'
    >>> # Delete the dir from elsewhere (don't use os.rmdir since that will only schedule the dir for deletion)
    ...
    >>> os.listdir()
    ['code00.py']
    >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED)
    '\\\\?\\E:\\$RECYCLE.BIN\\S-1-5-21-1906798797-2830956273-3148971768-1002\\$RY7SH8D'
    >>> os.mkdir(test_dir)
    >>> os.listdir()
    ['code00.py', 'test']
    >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED)
    '\\\\?\\E:\\$RECYCLE.BIN\\S-1-5-21-1906798797-2830956273-3148971768-1002\\$RY7SH8D'
    >>> os.rmdir(test_dir) # Since the new "test" dir wasn't open, operation successful
    >>> os.listdir()
    ['code00.py']
    >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED)
    '\\\\?\\E:\\$RECYCLE.BIN\\S-1-5-21-1906798797-2830956273-3148971768-1002\\$RY7SH8D'
    >>> # Restore the dir from RECYCLE.BIN
    ...
    >>> os.listdir()
    ['code00.py', 'test']
    >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED)
    '\\\\?\\E:\\Work\\Dev\\StackOverflow\\q049652110\\test'
    >>> os.rmdir(test_dir) # Still an open handle, scheduled to be deleted
    >>> os.listdir()
    ['code00.py', 'test']
    >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED)
    '\\\\?\\E:\\Work\\Dev\\StackOverflow\\q049652110\\test'
    >>> win32api.CloseHandle(h)
    >>> os.listdir()
    ['code00.py'] # After closing the handle the dir was deleted
    >>> h
    <PyHANDLE:0>
    >>> win32file.GetFinalPathNameByHandle(h, win32con.FILE_NAME_NORMALIZED)
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    pywintypes.error: (6, 'GetFinalPathNameByHandle', 'The handle is invalid.')

    注意:我也尝试了 [MS.Docs]: GetFileInformationByHandle function(win32file.GetFileInformationByHandle),但是即使是3个pywintypes.datetime字段之一(也应该是上次访问/修改时间),我也无法重现此行为。重命名/删除目录时,所有信息均未更改。我没有花时间去调查,我想到了两个可能的原因:
  • 数据以某种方式存储在“HANDLE”内部,并且该函数在调用时实际上并不查询FS(与GetFinalPathNameByHandle相反)
  • 重命名/删除目录后,这些日期字段将更改为其父目录

  • 因此,我们似乎有一个赢家。我只打算发布算法(代码应该相当简单):
  • CreateFile之后,在句柄上调用GetFinalPathNameByHandle并将其保存在变量
  • 在循环中(不断调用ReadDirectoryChangesW),在每次迭代(WAIT_TIMEOUT)时,再次在句柄上调用GetFinalPathNameByHandle并将结果与​​保存的值进行比较;如果它们不同(目录已重命名/“删除”),则采取适当的措施,例如:
  • 打破循环
  • 重新打开(重新创建)目录并从
  • 重新开始
  • 让用户知道


  • 其他可能的方法(尽管不理想):
  • 不要指定FILE_SHARE_DELETE(如您所述),这样dir将是不可重命名/不可删除的
  • 监视父目录(作为ReadDirectoryChangesW):

    Retrieves information that describes the changes within the specified directory. The function does not report changes to the specified directory itself.


  • 由于父目录可能包含其他您不关心的目录/文件,因此我想到了一个(lam回)解决方法(gainarie),该方法可能会起作用,也可能不会起作用:
  • 选择一个目录(与我们谈论的目录完全不同)
  • 创建目录的符号链接(symbolic link)以监视内部(您控制该目录,没有其他人会弄乱它)
  • 监视它(这里我既不知道事件是否通过符号链接(symbolic link)转发,也不知道断开链接时监视的 react )


  • 关于“事件丢失”,正如我在另一个答案中指定的那样,没有办法确保将所有这些事件都进行处理,只有办法可以使丢失的事件数量最小化。

    关于python - ReadDirectoryChangesW无法检测和处理监视目录的删除,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49652110/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com