blob: e8eeefe49442b89c65d051ff9870fd3976d2194d [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Monitors a directory tree for changes using win32 APIs."""
import ctypes
import os
# FindNextChangeNotification constants (defined in FileAPI.h):
FILE_NOTIFY_CHANGE_FILE_NAME = 0x00000001
FILE_NOTIFY_CHANGE_DIR_NAME = 0x00000002
FILE_NOTIFY_CHANGE_ATTRIBUTES = 0x00000004
FILE_NOTIFY_CHANGE_SIZE = 0x00000008
FILE_NOTIFY_CHANGE_LAST_WRITE = 0x00000010
FILE_NOTIFY_CHANGE_CREATION = 0x00000040
FILE_NOTIFY_CHANGE_SECURITY = 0x00000100
# WaitForSingleObject return values (defined in WinBase.h):
WAIT_OBJECT_0 = 0x00000000L
WAIT_TIMEOUT = 0x00000102L
WAIT_FAILED = 0xffffffff
INVALID_HANDLE_VALUE = ctypes.c_void_p(-1).value
_INTERESTING_NOTIFICATIONS = (FILE_NOTIFY_CHANGE_FILE_NAME |
FILE_NOTIFY_CHANGE_DIR_NAME |
FILE_NOTIFY_CHANGE_ATTRIBUTES |
FILE_NOTIFY_CHANGE_SIZE |
FILE_NOTIFY_CHANGE_LAST_WRITE |
FILE_NOTIFY_CHANGE_CREATION |
FILE_NOTIFY_CHANGE_SECURITY)
class Win32FileWatcher(object):
"""Monitors a directory tree for changes using inotify."""
# TODO: evaluate whether we can directly support multiple directories.
SUPPORTS_MULTIPLE_DIRECTORIES = False
def __init__(self, directory):
"""Initializer for InotifyFileWatcher.
Args:
directory: A string representing the path to a directory that should
be monitored for changes i.e. files and directories added, renamed,
deleted or changed.
"""
self._directory = os.path.abspath(directory)
self._find_change_handle = None
def start(self):
"""Start watching the directory for changes."""
self._find_change_handle = (
ctypes.windll.kernel32.FindFirstChangeNotificationA(
self._directory,
True, # Recursive.
_INTERESTING_NOTIFICATIONS))
if self._find_change_handle == INVALID_HANDLE_VALUE:
raise ctypes.WinError()
if not ctypes.windll.kernel32.FindNextChangeNotification(
self._find_change_handle):
raise ctypes.WinError()
def quit(self):
"""Stop watching the directory for changes."""
ctypes.windll.kernel32.FindCloseChangeNotification(self._find_change_handle)
def changes(self):
"""Returns the paths changed in the watched directory since the last call.
start() must be called before this method.
Returns:
Returns an iterable of changed directories/files.
"""
found_change = False
# Loop until no new changes are found. This prevents future accesses to
# changes from returning the list of changes that happened before this
# call was made.
while True:
wait_result = ctypes.windll.kernel32.WaitForSingleObject(
self._find_change_handle, 0)
if wait_result == WAIT_OBJECT_0:
if not ctypes.windll.kernel32.FindNextChangeNotification(
self._find_change_handle):
raise ctypes.WinError()
found_change = True
continue
elif wait_result == WAIT_TIMEOUT:
return {'-'} if found_change else set() # TODO: implement
elif wait_result == WAIT_FAILED:
raise ctypes.WinError()
else:
assert 'Unexpected result for WaitForSingleObject: %r' % wait_result