def _generate_sub_moved_events_for(src_dir_path, dest_dir_path, _walker=os.walk): """Generates an event list of :class:`DirMovedEvent` and :class:`FileMovedEvent` objects for all the files and directories within the given moved directory that were moved along with the directory. :param src_dir_path: The source path of the moved directory. :param dest_dir_path: The destination path of the moved directory. :param _walker: Walker used to walk directory trees :func:`os.walk` style. Sanity tests use this parameter to inject a mock walker that behaves like :func:`os.walk`. :returns: An iterable of file system events of type :class:`DirMovedEvent` and :class:`FileMovedEvent`. """ src_dir_path = absolute_path(src_dir_path) dest_dir_path = absolute_path(dest_dir_path) for root, directories, filenames in _walker(dest_dir_path): for directory in directories: full_path = os.path.join(root, directory) renamed_path = full_path.replace(dest_dir_path, src_dir_path) yield DirMovedEvent(renamed_path, full_path) for filename in filenames: full_path = os.path.join(root, filename) renamed_path = full_path.replace(dest_dir_path, src_dir_path) yield FileMovedEvent(renamed_path, full_path)
def __init__(self, path, is_directory): self._path = absolute_path(path) self._is_directory = is_directory self._fd = os.open(path, WATCHDOG_OS_OPEN_FLAGS) self._kev = select.kevent( self._fd, filter=WATCHDOG_KQ_FILTER, flags=WATCHDOG_KQ_EV_FLAGS, fflags=WATCHDOG_KQ_FFLAGS )
def __init__(self, path, recursive=False, event_mask=WATCHDOG_ALL_EVENTS, non_blocking=False): # The file descriptor associated with the inotify instance. if non_blocking: inotify_fd = inotify_init1(InotifyConstants.IN_NONBLOCK) else: inotify_fd = inotify_init() if inotify_fd == -1: Inotify._raise_error() self._inotify_fd = inotify_fd self._lock = threading.Lock() # Stores the watch descriptor for a given path. self._wd_for_path = dict() self._path_for_wd = dict() path = absolute_path(path) self._path = path self._event_mask = event_mask self._is_recursive = recursive self._is_non_blocking = non_blocking self._add_dir_watch(path, recursive, event_mask)
def get(self, path): """ Obtains a :class:`KeventDescriptor` object for the specified path. :param path: Path for which the descriptor will be obtained. """ with self._lock: path = absolute_path(path) return self._get(path)
def remove_watch(self, path): """ Removes a watch for the given path. :param path: Path string for which the watch will be removed. """ with self._lock: path = absolute_path(path) self._remove_watch(path)
def add_watch(self, path): """ Adds a watch for the given path. :param path: Path to begin monitoring. """ with self._lock: path = absolute_path(path) self._add_watch(path, self._event_mask)
def __contains__(self, path): """ Determines whether a :class:`KeventDescriptor has been registered for the specified path. :param path: Path for which the descriptor will be obtained. """ with self._lock: path = absolute_path(path) return self._has_path(path)
def remove(self, path): """ Removes the :class:`KeventDescriptor` object for the given path if it already exists. :param path: Path for which the :class:`KeventDescriptor` object will be removed. """ with self._lock: path = absolute_path(path) if self._has_path(path): self._remove_descriptor(self._get(path))
def _queue_renamed(self, src_path, is_directory, ref_snapshot, new_snapshot): """ Compares information from two directory snapshots (one taken before the rename operation and another taken right after) to determine the destination path of the file system object renamed, and adds appropriate events to the event queue. """ try: ref_stat_info = ref_snapshot.stat_info(src_path) except KeyError: # Probably caught a temporary file/directory that was renamed # and deleted. Fires a sequence of created and deleted events # for the path. if is_directory: self.queue_event(DirCreatedEvent(src_path)) self.queue_event(DirDeletedEvent(src_path)) else: self.queue_event(FileCreatedEvent(src_path)) self.queue_event(FileDeletedEvent(src_path)) # We don't process any further and bail out assuming # the event represents deletion/creation instead of movement. return try: dest_path = absolute_path(new_snapshot.path_for_inode(ref_stat_info.st_ino)) if is_directory: event = DirMovedEvent(src_path, dest_path) # TODO: Do we need to fire moved events for the items # inside the directory tree? Does kqueue does this # all by itself? Check this and then enable this code # only if it doesn't already. # A: It doesn't. So I've enabled this block. if self.watch.is_recursive: for sub_event in event.sub_moved_events(): self.queue_event(sub_event) self.queue_event(event) else: self.queue_event(FileMovedEvent(src_path, dest_path)) except KeyError: # If the new snapshot does not have an inode for the # old path, we haven't found the new name. Therefore, # we mark it as deleted and remove unregister the path. if is_directory: self.queue_event(DirDeletedEvent(src_path)) else: self.queue_event(FileDeletedEvent(src_path))
def add(self, path, is_directory): """ Adds a :class:`KeventDescriptor` to the collection for the given path. :param path: The path for which a :class:`KeventDescriptor` object will be added. :param is_directory: ``True`` if the path refers to a directory; ``False`` otherwise. :type is_directory: ``bool`` """ with self._lock: path = absolute_path(path) if not self._has_path(path): self._add_descriptor(KeventDescriptor(path, is_directory))
def queue_events(self, timeout): with self._lock: dir_changes, nbytes = read_directory_changes(self._directory_handle, self._buffer, self.watch.is_recursive) last_renamed_src_path = "" for action, src_path in get_FILE_NOTIFY_INFORMATION(dir_changes, nbytes): src_path = absolute_path(os.path.join(self.watch.path, src_path)) if action == FILE_ACTION_RENAMED_OLD_NAME: last_renamed_src_path = src_path elif action == FILE_ACTION_RENAMED_NEW_NAME: dest_path = src_path src_path = last_renamed_src_path if os.path.isdir(src_path): event = DirMovedEvent(src_path, dest_path) if self.watch.is_recursive: # HACK: We introduce a forced delay before # traversing the moved directory. This will read # only file movement that finishes within this # delay time. time.sleep(WATCHDOG_TRAVERSE_MOVED_DIR_DELAY) # The following block of code may not # obtain moved events for the entire tree if # the I/O is not completed within the above # delay time. So, it's not guaranteed to work. # TODO: Come up with a better solution, possibly # a way to wait for I/O to complete before # queuing events. for sub_moved_event in event.sub_moved_events(): self.queue_event(sub_moved_event) self.queue_event(event) else: self.queue_event(FileMovedEvent(src_path, dest_path)) else: if os.path.isdir(src_path): action_event_map = DIR_ACTION_EVENT_MAP else: action_event_map = FILE_ACTION_EVENT_MAP self.queue_event(action_event_map[action](src_path))
def _add_dir_watch(self, path, recursive, mask): """ Adds a watch (optionally recursively) for the given directory path to monitor events specified by the mask. :param path: Path to monitor :param recursive: ``True`` to monitor recursively. :param mask: Event bit mask. """ if not os.path.isdir(path): raise OSError('Path is not a directory') self._add_watch(path, mask) if recursive: for root, dirnames, _ in os.walk(path): for dirname in dirnames: full_path = absolute_path(os.path.join(root, dirname)) self._add_watch(full_path, mask)
def __init__(self, path, recursive=True, walker_callback=(lambda p, s: None), _copying=False): self._path = absolute_path(path) self._stat_snapshot = {} self._inode_to_path = {} self.is_recursive = recursive if not _copying: walk = get_walker(recursive) stat_info = os.stat(self._path) self._stat_snapshot[self._path] = stat_info self._inode_to_path[stat_info.st_ino] = self._path walker_callback(self._path, stat_info) for root, directories, files in walk(self._path): for directory_name in directories: try: directory_path = os.path.join(root, directory_name) stat_info = os.stat(directory_path) self._stat_snapshot[directory_path] = stat_info self._inode_to_path[stat_info.st_ino] = directory_path walker_callback(directory_path, stat_info) except OSError: continue for file_name in files: try: file_path = os.path.join(root, file_name) stat_info = os.stat(file_path) self._stat_snapshot[file_path] = stat_info self._inode_to_path[stat_info.st_ino] = file_path walker_callback(file_path, stat_info) except OSError: continue
except ImportError: from StringIO import StringIO from argh import arg, alias, ArghParser from watchdog.version import VERSION_STRING from watchdog.utils import\ read_text_file,\ load_class,\ absolute_path,\ get_parent_dir_path logging.basicConfig(level=logging.DEBUG) CURRENT_DIR = absolute_path(os.getcwd()) DEFAULT_TRICKS_FILE_NAME = 'tricks.yaml' DEFAULT_TRICKS_FILE_PATH = os.path.join(CURRENT_DIR, DEFAULT_TRICKS_FILE_NAME) CONFIG_KEY_TRICKS = 'tricks' CONFIG_KEY_PYTHON_PATH = 'python-path' def path_split(pathname_spec, separator=os.path.sep): """ Splits a pathname specification separated by an OS-dependent separator. :param pathname_spec: The pathname specification. :param separator: (OS Dependent) `:` on Unix and `;` on Windows or user-specified. """
def read_events(self, event_buffer_size=DEFAULT_EVENT_BUFFER_SIZE): """ Reads events from inotify and yields them. """ with self._lock: event_buffer = os.read(self._inotify_fd, event_buffer_size) event_list = [] #moved_from_events = dict() for wd, mask, cookie, name in Inotify._parse_event_buffer( event_buffer): wd_path = self._path_for_wd[wd] src_path = absolute_path(os.path.join(wd_path, name)) inotify_event = InotifyEvent(wd, mask, cookie, name, src_path) if inotify_event.is_ignored: # Clean up book-keeping for deleted watches. self._remove_watch_bookkeeping(src_path) continue event_list.append(inotify_event) if inotify_event.is_directory: if inotify_event.is_create: # HACK: We need to traverse the directory path # recursively and simulate events for newly # created subdirectories/files. This will handle # mkdir -p foobar/blah/bar; touch foobar/afile # TODO: When a directory from another part of the # filesystem is moved into a watched directory, this # will not generate events for the directory tree. # We need to coalesce IN_MOVED_TO events and those # IN_MOVED_TO events which don't pair up with # IN_MOVED_FROM events should be marked IN_CREATE # instead relative to this directory. self._add_watch(src_path, self._event_mask) for root, dirnames, filenames in os.walk(src_path): for dirname in dirnames: full_path = absolute_path( os.path.join(root, dirname)) wd_dir = self._add_watch(full_path, self._event_mask) event_list.append(InotifyEvent(wd_dir, InotifyConstants.IN_CREATE | InotifyConstants.IN_ISDIR , 0, dirname, full_path)) for filename in filenames: full_path = absolute_path( os.path.join(root, filename)) wd_parent_dir = self._wd_for_path[ absolute_path( os.path.dirname( full_path))] event_list.append( InotifyEvent(wd_parent_dir, InotifyConstants.IN_CREATE , 0, filename, full_path)) return event_list
def __init__(self, path, recursive): self._path = absolute_path(path) self._is_recursive = recursive