Python script to monitor a directory triggers twice when a file is updated. Why?

838 views Asked by At

Short version: A Python script I've written/adapted to monitor a directory for changes triggers twice when a file is modified. Why?

Long version:

I'm writing some Python code to monitor a directory and its subdirectories for changes.

I'm starting with the example found in the "Use the ReadDirectoryChanges API" section of http://timgolden.me.uk/python/win32_how_do_i/watch_directory_for_changes.html

(For corporate IT reasons, using the Python Watchdog package isn't an option for me.)

Cutting-and-pasting from the example there:

import os

import win32file
import win32con

ACTIONS = {
  1 : "Created",
  2 : "Deleted",
  3 : "Updated",
  4 : "Renamed from something",
  5 : "Renamed to something"
}
# Thanks to Claudio Grondi for the correct set of numbers
FILE_LIST_DIRECTORY = 0x0001

path_to_watch = "."
hDir = win32file.CreateFile (
  path_to_watch,
  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
)
while 1:
  #
  # ReadDirectoryChangesW takes a previously-created
  # handle to a directory, a buffer size for results,
  # a flag to indicate whether to watch subtrees and
  # a filter of what changes to notify.
  #
  # NB Tim Juchcinski reports that he needed to up
  # the buffer size to be sure of picking up all
  # events when a large number of files were
  # deleted at once.
  #
  results = win32file.ReadDirectoryChangesW (
    hDir,
    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:
    full_filename = os.path.join (path_to_watch, file)
    print full_filename, ACTIONS.get (action, "Unknown")

In general, this example is working fine and doing what I want. In particular, it works fine when a file is created. However, when a file is edited / modified / updated, the print statement at the end (which is standing in for the action I really want to do) triggers twice.

Why does this happen? And how can prevent it, or at least work around it? The best idea I've had is a flag that is True the first time and False the second time. However, that feels like a kludge.

On a possibly related issue, where can I find documentation on the win32con package and the Microsoft ReadDirectoryChanges API? I've done some Googling but haven't found anything I consider useful.

Oh, yes - I'm running Python 3.5.1 on Windows 7 Enterprise.

EDIT: OK, it looks like what I'm seeing may be inherent in ReadDirectoryChangesW(). I discovered this StackOverflow thread which appears to be basically the same problem, except the original poster is using C++, not Python. C++ WinApi: ReadDirectoryChangesW() Receiving Double Notifications

1

There are 1 answers

10
vencaslac On

i don't have python 2.7 installed so i cannot test this myself by looking at this part

 win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
 win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
 win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
 win32con.FILE_NOTIFY_CHANGE_SIZE | #this will change when someone writes or deletes to the file
 win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |#this will change when someone modifies the file
 win32con.FILE_NOTIFY_CHANGE_SECURITY,

see how both conditions could be triggered by the same action? it's possible this is what is triggering your behaviour, try removing one of the two

Edit to clarify comments below:

in order to get an idea of the behaviour for each type of change try this:

results = {}
results['FName_Change'] = win32file.ReadDirectoryChangesW (
                        hDir,
                        1024,
                        True,
                        win32con.FILE_NOTIFY_CHANGE_FILE_NAME,
                        None,
                        None
                      )
results['DName_Change'] = win32file.ReadDirectoryChangesW (
                        hDir,
                        1024,
                        True,
                         win32con.FILE_NOTIFY_CHANGE_DIR_NAME, 
                        None,
                        None
                        )
  results['Attributes_Change'] = win32file.ReadDirectoryChangesW (
                        hDir,
                        1024,
                        True,
                         win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES, 
                        None,
                        None

by manipulating this data structure you should be able to extract behaviour type information from the changes