def queue_events(self, timeout, full_events=False): # If "full_events" is true, then the method will report unmatched move events as separate events # This behavior is by default only called by a InotifyFullEmitter with self._lock: event = self._inotify.read_event() if event is None: return if isinstance(event, tuple): move_from, move_to = event src_path = self._decode_path(move_from.src_path) dest_path = self._decode_path(move_to.src_path) cls = DirMovedEvent if move_from.is_directory else FileMovedEvent self.queue_event(cls(src_path, dest_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) self.queue_event(DirModifiedEvent(os.path.dirname(dest_path))) if move_from.is_directory and self.watch.is_recursive: for sub_event in generate_sub_moved_events(src_path, dest_path): self.queue_event(sub_event) return src_path = self._decode_path(event.src_path) if event.is_moved_to: if full_events: cls = DirMovedEvent if event.is_directory else FileMovedEvent self.queue_event(cls(None, src_path)) else: cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) if event.is_directory and self.watch.is_recursive: for sub_event in generate_sub_created_events(src_path): self.queue_event(sub_event) elif event.is_attrib: cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(src_path)) elif event.is_modify: cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(src_path)) elif event.is_delete or (event.is_moved_from and not full_events): cls = DirDeletedEvent if event.is_directory else FileDeletedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) elif event.is_moved_from and full_events: cls = DirMovedEvent if event.is_directory else FileMovedEvent self.queue_event(cls(src_path, None)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) elif event.is_create: cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) elif event.is_close_write and not event.is_directory: cls = FileClosedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) # elif event.is_close_nowrite and not event.is_directory: # cls = FileClosedEvent # self.queue_event(cls(src_path)) elif event.is_delete_self and src_path == self.watch.path: self.queue_event(DirDeletedEvent(src_path)) self.stop()
def _gen_renamed_events(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 yields the appropriate events to be queued. """ try: f_inode = ref_snapshot.inode(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: yield DirCreatedEvent(src_path) yield DirDeletedEvent(src_path) else: yield FileCreatedEvent(src_path) yield FileDeletedEvent(src_path) # We don't process any further and bail out assuming # the event represents deletion/creation instead of movement. return dest_path = new_snapshot.path(f_inode) if dest_path is not None: dest_path = absolute_path(dest_path) if is_directory: event = DirMovedEvent(src_path, dest_path) yield event else: yield FileMovedEvent(src_path, dest_path) yield self._parent_dir_modified(src_path) yield self._parent_dir_modified(dest_path) if is_directory: # 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 generate_sub_moved_events(src_path, dest_path): yield sub_event else: # 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: yield DirDeletedEvent(src_path) else: yield FileDeletedEvent(src_path) yield self._parent_dir_modified(src_path)
def queue_events(self, timeout): winapi_events = self._read_events() with self._lock: last_renamed_src_path = "" for winapi_event in winapi_events: src_path = os.path.join(self.watch.path, winapi_event.src_path) if winapi_event.is_renamed_old: last_renamed_src_path = src_path elif winapi_event.is_renamed_new: dest_path = src_path src_path = last_renamed_src_path if os.path.isdir(dest_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 generate_sub_moved_events( src_path, dest_path): self.queue_event(sub_moved_event) self.queue_event(event) else: self.queue_event(FileMovedEvent(src_path, dest_path)) elif winapi_event.is_modified: cls = DirModifiedEvent if os.path.isdir( src_path) else FileModifiedEvent self.queue_event(cls(src_path)) elif winapi_event.is_added: isdir = os.path.isdir(src_path) cls = DirCreatedEvent if isdir else FileCreatedEvent self.queue_event(cls(src_path)) if isdir and self.watch.is_recursive: # If a directory is moved from outside the watched folder to inside it # we only get a created directory event out of it, not any events for its children # so use the same hack as for file moves to get the child events time.sleep(WATCHDOG_TRAVERSE_MOVED_DIR_DELAY) sub_events = generate_sub_created_events(src_path) for sub_created_event in sub_events: self.queue_event(sub_created_event) elif winapi_event.is_removed: self.queue_event(FileDeletedEvent(src_path)) elif winapi_event.is_removed_self: self.queue_event(DirDeletedEvent(self.watch.path)) self.stop()
def queue_events(self, timeout, full_events=False): #If "full_events" is true, then the method will report unmatched move events as seperate events #This behavior is by default only called by a InotifyFullEmitter with self._lock: event = self._inotify.read_event() if event is None: return if isinstance(event, tuple): move_from, move_to = event src_path = self._decode_path(move_from.src_path) dest_path = self._decode_path(move_to.src_path) cls = DirMovedEvent if move_from.is_directory else FileMovedEvent self.queue_event(cls(src_path, dest_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) self.queue_event(DirModifiedEvent(os.path.dirname(dest_path))) if move_from.is_directory and self.watch.is_recursive: for sub_event in generate_sub_moved_events(src_path, dest_path): self.queue_event(sub_event) return src_path = self._decode_path(event.src_path) if event.is_moved_to: if (full_events): cls = DirMovedEvent if event.is_directory else FileMovedEvent self.queue_event(cls(None, src_path)) else: cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) if event.is_directory and self.watch.is_recursive: for sub_event in generate_sub_created_events(src_path): self.queue_event(sub_event) elif event.is_attrib: cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(src_path)) elif event.is_modify: cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(src_path)) elif event.is_delete_self: cls = DirDeletedEvent if event.is_directory else FileDeletedEvent self.queue_event(cls(src_path)) elif event.is_delete or (event.is_moved_from and not full_events): cls = DirDeletedEvent if event.is_directory else FileDeletedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) elif event.is_moved_from and full_events: cls = DireMovedEvent if event.is_directory else FileMovedEvent self.queue_event(cls(src_path, None)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) elif event.is_create: cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))
def queue_events(self, timeout): winapi_events = read_events(self._handle, self.watch.is_recursive) with self._lock: last_renamed_src_path = "" for winapi_event in winapi_events: src_path = os.path.join(self.watch.path, winapi_event.src_path) if winapi_event.is_renamed_old: last_renamed_src_path = src_path elif winapi_event.is_renamed_new: dest_path = src_path src_path = last_renamed_src_path if os.path.isdir(dest_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 generate_sub_moved_events(src_path, dest_path): self.queue_event(sub_moved_event) self.queue_event(event) else: self.queue_event(FileMovedEvent(src_path, dest_path)) elif winapi_event.is_modified: cls = DirModifiedEvent if os.path.isdir(src_path) else FileModifiedEvent self.queue_event(cls(src_path)) elif winapi_event.is_added: isdir = os.path.isdir(src_path) cls = DirCreatedEvent if isdir else FileCreatedEvent self.queue_event(cls(src_path)) if isdir: # If a directory is moved from outside the watched folder to inside it # we only get a created directory event out of it, not any events for its children # so use the same hack as for file moves to get the child events time.sleep(WATCHDOG_TRAVERSE_MOVED_DIR_DELAY) sub_events = generate_sub_created_events(src_path) for sub_created_event in sub_events: self.queue_event(sub_created_event) elif winapi_event.is_removed: self.queue_event(FileDeletedEvent(src_path))
def queue_events(self, timeout): with self._lock: event = self._inotify.read_event() if event is STOP_EVENT: return if isinstance(event, tuple): move_from, move_to = event src_path = self._decode_path(move_from.src_path) dest_path = self._decode_path(move_to.src_path) cls = DirMovedEvent if move_from.is_directory else FileMovedEvent self.queue_event(cls(src_path, dest_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) self.queue_event(DirModifiedEvent(os.path.dirname(dest_path))) if move_from.is_directory and self.watch.is_recursive: for sub_event in generate_sub_moved_events(src_path, dest_path): self.queue_event(sub_event) return src_path = self._decode_path(event.src_path) if event.is_moved_to: cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) if event.is_directory and self.watch.is_recursive: for sub_event in generate_sub_created_events(src_path): self.queue_event(sub_event) elif event.is_attrib: cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(src_path)) elif event.is_modify: cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(src_path)) elif event.is_delete_self: cls = DirDeletedEvent if event.is_directory else FileDeletedEvent self.queue_event(cls(src_path)) elif event.is_delete or event.is_moved_from: cls = DirDeletedEvent if event.is_directory else FileDeletedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) elif event.is_create: cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))
def queue_events(self, timeout): with self._lock: event = self._inotify.read_event() if event is None: return if isinstance(event, tuple): move_from, move_to = event src_path = self._decode_path(move_from.src_path) dest_path = self._decode_path(move_to.src_path) cls = DirMovedEvent if move_from.is_directory else FileMovedEvent self.queue_event(cls(src_path, dest_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) self.queue_event(DirModifiedEvent(os.path.dirname(dest_path))) if move_from.is_directory and self.watch.is_recursive: for sub_event in generate_sub_moved_events( src_path, dest_path): self.queue_event(sub_event) return src_path = self._decode_path(event.src_path) if event.is_moved_to: cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) if event.is_directory and self.watch.is_recursive: for sub_event in generate_sub_created_events(src_path): self.queue_event(sub_event) elif event.is_attrib: cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(src_path)) elif event.is_modify: cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(src_path)) elif event.is_delete_self: cls = DirDeletedEvent if event.is_directory else FileDeletedEvent self.queue_event(cls(src_path)) elif event.is_delete or event.is_moved_from: cls = DirDeletedEvent if event.is_directory else FileDeletedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) elif event.is_create: cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))
def queue_events(self, timeout): winapi_events = read_events(self._handle, self.watch.is_recursive) with self._lock: last_renamed_src_path = '' for winapi_event in winapi_events: src_path = os.path.join(self.watch.path, winapi_event.src_path) if winapi_event.is_renamed_old: last_renamed_src_path = src_path elif winapi_event.is_renamed_new: dest_path = src_path src_path = last_renamed_src_path if os.path.isdir(dest_path): event = DirMovedEvent(src_path, dest_path) if self.watch.is_recursive: time.sleep(WATCHDOG_TRAVERSE_MOVED_DIR_DELAY) for sub_moved_event in generate_sub_moved_events( src_path, dest_path): self.queue_event(sub_moved_event) self.queue_event(event) else: self.queue_event(FileMovedEvent(src_path, dest_path)) elif winapi_event.is_modified: cls = DirModifiedEvent if os.path.isdir( src_path) else FileModifiedEvent self.queue_event(cls(src_path)) elif winapi_event.is_added: isdir = os.path.isdir(src_path) cls = DirCreatedEvent if isdir else FileCreatedEvent self.queue_event(cls(src_path)) if isdir: time.sleep(WATCHDOG_TRAVERSE_MOVED_DIR_DELAY) sub_events = generate_sub_created_events(src_path) for sub_created_event in sub_events: self.queue_event(sub_created_event) elif winapi_event.is_removed: self.queue_event(FileDeletedEvent(src_path))
def queue_events(self, timeout): winapi_events = read_events(self._handle, self.watch.is_recursive) with self._lock: last_renamed_src_path = '' for winapi_event in winapi_events: src_path = os.path.join(self.watch.path, winapi_event.src_path) if winapi_event.is_renamed_old: last_renamed_src_path = src_path elif winapi_event.is_renamed_new: dest_path = src_path src_path = last_renamed_src_path if os.path.isdir(dest_path): event = DirMovedEvent(src_path, dest_path) if self.watch.is_recursive: time.sleep(WATCHDOG_TRAVERSE_MOVED_DIR_DELAY) for sub_moved_event in generate_sub_moved_events(src_path, dest_path): self.queue_event(sub_moved_event) self.queue_event(event) else: self.queue_event(FileMovedEvent(src_path, dest_path)) elif winapi_event.is_modified: cls = DirModifiedEvent if os.path.isdir(src_path) else FileModifiedEvent self.queue_event(cls(src_path)) elif winapi_event.is_added: isdir = os.path.isdir(src_path) cls = DirCreatedEvent if isdir else FileCreatedEvent self.queue_event(cls(src_path)) if isdir: time.sleep(WATCHDOG_TRAVERSE_MOVED_DIR_DELAY) sub_events = generate_sub_created_events(src_path) for sub_created_event in sub_events: self.queue_event(sub_created_event) elif winapi_event.is_removed: self.queue_event(FileDeletedEvent(src_path))
def queue_events(self, timeout, events): if logger.getEffectiveLevel() <= logging.DEBUG: for event in events: flags = ", ".join(attr for attr in dir(event) if getattr(event, attr) is True) logger.debug(f"{event}: {flags}") while events: event = events.pop(0) src_path = self._encode_path(event.path) if event.is_renamed: dest_event = next(iter(e for e in events if e.is_renamed and e.inode == event.inode), None) if dest_event: # item was moved within the watched folder events.remove(dest_event) logger.debug("Destination event for rename is %s", dest_event) cls = DirMovedEvent if event.is_directory else FileMovedEvent dst_path = self._encode_path(dest_event.path) self.queue_event(cls(src_path, dst_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) self.queue_event(DirModifiedEvent(os.path.dirname(dst_path))) for sub_event in generate_sub_moved_events(src_path, dst_path): logger.debug("Generated sub event: %s", sub_event) self.queue_event(sub_event) elif os.path.exists(event.path): # item was moved into the watched folder cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) for sub_event in generate_sub_created_events(src_path): self.queue_event(sub_event) else: # item was moved out of the watched folder cls = DirDeletedEvent if event.is_directory else FileDeletedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) if event.is_created: cls = DirCreatedEvent if event.is_directory else FileCreatedEvent if not event.is_coalesced or ( event.is_coalesced and not event.is_renamed and not event.is_modified and not event.is_inode_meta_mod and not event.is_xattr_mod ): self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) if event.is_modified and not event.is_coalesced and os.path.exists(src_path): cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(src_path)) if event.is_inode_meta_mod or event.is_xattr_mod: if os.path.exists(src_path) and not event.is_coalesced: # NB: in the scenario of touch(file) -> rm(file) we can trigger this twice cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(src_path)) if event.is_removed: cls = DirDeletedEvent if event.is_directory else FileDeletedEvent if not event.is_coalesced or (event.is_coalesced and not os.path.exists(event.path)): self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) if src_path == self.watch.path: # this should not really occur, instead we expect # is_root_changed to be set logger.debug("Stopping because root path was removed") self.stop() if event.is_root_changed: # This will be set if root or any of its parents is renamed or # deleted. # TODO: find out new path and generate DirMovedEvent? self.queue_event(DirDeletedEvent(self.watch.path)) logger.debug("Stopping because root path was changed") self.stop()
def queue_events(self, timeout, events): if logger.getEffectiveLevel() <= logging.DEBUG: for event in events: flags = ", ".join(attr for attr in dir(event) if getattr(event, attr) is True) logger.debug(f"{event}: {flags}") if time.monotonic() - self._start_time > 60: # Event history is no longer needed, let's free some memory. self._starting_state = None while events: event = events.pop(0) src_path = self._encode_path(event.path) src_dirname = os.path.dirname(src_path) try: stat = os.stat(src_path) except OSError: stat = None exists = stat and stat.st_ino == event.inode # FSevents may coalesce multiple events for the same item + path into a # single event. However, events are never coalesced for different items at # the same path or for the same item at different paths. Therefore, the # event chains "removed -> created" and "created -> renamed -> removed" will # never emit a single native event and a deleted event *always* means that # the item no longer existed at the end of the event chain. # Some events will have a spurious `is_created` flag set, coalesced from an # already emitted and processed CreatedEvent. To filter those, we keep track # of all inodes which we know to be already created. This is safer than # keeping track of paths since paths are more likely to be reused than # inodes. # Likewise, some events will have a spurious `is_modified`, # `is_inode_meta_mod` or `is_xattr_mod` flag set. We currently do not # suppress those but could do so if the item still exists by caching the # stat result and verifying that it did change. if event.is_created and event.is_removed: # Events will only be coalesced for the same item / inode. # The sequence deleted -> created therefore cannot occur. # Any combination with renamed cannot occur either. if not self._is_historic_created_event(event): self._queue_created_event(event, src_path, src_dirname) self._fs_view.add(event.inode) if event.is_modified or self._is_meta_mod(event): self._queue_modified_event(event, src_path, src_dirname) self._queue_deleted_event(event, src_path, src_dirname) self._fs_view.discard(event.inode) else: if event.is_created and not self._is_historic_created_event( event): self._queue_created_event(event, src_path, src_dirname) self._fs_view.add(event.inode) if event.is_modified or self._is_meta_mod(event): self._queue_modified_event(event, src_path, src_dirname) if event.is_renamed: # Check if we have a corresponding destination event in the watched path. dst_event = next( iter(e for e in events if e.is_renamed and e.inode == event.inode), None) if dst_event: # Item was moved within the watched folder. logger.debug("Destination event for rename is %s", dst_event) dst_path = self._encode_path(dst_event.path) dst_dirname = os.path.dirname(dst_path) self._queue_renamed_event(event, src_path, dst_path, src_dirname, dst_dirname) self._fs_view.add(event.inode) for sub_event in generate_sub_moved_events( src_path, dst_path): self.queue_event(sub_event) # Process any coalesced flags for the dst_event. events.remove(dst_event) if dst_event.is_modified or self._is_meta_mod( dst_event): self._queue_modified_event(dst_event, dst_path, dst_dirname) if dst_event.is_removed: self._queue_deleted_event(dst_event, dst_path, dst_dirname) self._fs_view.discard(dst_event.inode) elif exists: # This is the destination event, item was moved into the watched # folder. self._queue_created_event(event, src_path, src_dirname) self._fs_view.add(event.inode) for sub_event in generate_sub_created_events(src_path): self.queue_event(sub_event) else: # This is the source event, item was moved out of the watched # folder. self._queue_deleted_event(event, src_path, src_dirname) self._fs_view.discard(event.inode) # Skip further coalesced processing. continue if event.is_removed: # Won't occur together with renamed. self._queue_deleted_event(event, src_path, src_dirname) self._fs_view.discard(event.inode) if event.is_root_changed: # This will be set if root or any of its parents is renamed or deleted. # TODO: find out new path and generate DirMovedEvent? self.queue_event(DirDeletedEvent(self.watch.path)) logger.debug("Stopping because root path was changed") self.stop() self._fs_view.clear()
def queue_events(self, timeout, events): i = 0 while i < len(events): event = events[i] logger.info(event) src_path = self._encode_path(event.path) if event.is_renamed: # Internal moves appears to always be consecutive in the same # buffer and have IDs differ by exactly one (while others # don't) making it possible to pair up the two events coming # from a singe move operation. (None of this is documented!) # Otherwise, guess whether file was moved in or out. # TODO: handle id wrapping if (i + 1 < len(events) and events[i + 1].is_renamed and events[i + 1].event_id == event.event_id + 1): logger.info("Next event for rename is %s", events[i + 1]) cls = DirMovedEvent if event.is_directory else FileMovedEvent dst_path = self._encode_path(events[i + 1].path) self.queue_event(cls(src_path, dst_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) self.queue_event(DirModifiedEvent(os.path.dirname(dst_path))) for sub_event in generate_sub_moved_events(src_path, dst_path): logger.info("Generated sub event: %s", sub_event) self.queue_event(sub_event) i += 1 elif os.path.exists(event.path): cls = DirCreatedEvent if event.is_directory else FileCreatedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) for sub_event in generate_sub_created_events(src_path): self.queue_event(sub_event) else: cls = DirDeletedEvent if event.is_directory else FileDeletedEvent self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) if event.is_created: cls = DirCreatedEvent if event.is_directory else FileCreatedEvent if not event.is_coalesced or ( event.is_coalesced and not event.is_renamed and not event.is_modified and not event.is_inode_meta_mod and not event.is_xattr_mod ): self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) if event.is_modified and not event.is_coalesced and os.path.exists(src_path): cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(src_path)) if event.is_inode_meta_mod or event.is_xattr_mod: if os.path.exists(src_path) and not event.is_coalesced: # NB: in the scenario of touch(file) -> rm(file) we can trigger this twice cls = DirModifiedEvent if event.is_directory else FileModifiedEvent self.queue_event(cls(src_path)) if event.is_removed: cls = DirDeletedEvent if event.is_directory else FileDeletedEvent if not event.is_coalesced or (event.is_coalesced and not os.path.exists(event.path)): self.queue_event(cls(src_path)) self.queue_event(DirModifiedEvent(os.path.dirname(src_path))) if src_path == self.watch.path: # this should not really occur, instead we expect # is_root_changed to be set logger.info("Stopping because root path was removed") self.stop() if event.is_root_changed: # This will be set if root or any of its parents is renamed or # deleted. # TODO: find out new path and generate DirMovedEvent? self.queue_event(DirDeletedEvent(self.watch.path)) logger.info("Stopping because root path was changed") self.stop() i += 1
def queue_events(self, timeout): try: logger.debug('Reading events') winapi_events = read_events(self._handle, self.watch.is_recursive) except WatchedDirDeleted: logger.warning("Watched dir was deleted") self.queue_event(FileDeletedEvent(self.watch.path)) self.stop() except OSError as err: # The specified network name is no longer available. if err.winerror == 64 or err.winerror == 59: self.queue_event(FileSystemDisconnected(self.watch.path)) time.sleep(0.2) return raise with self._lock: last_renamed_src_path = "" logger.debug('Processing events') for winapi_event in winapi_events: logger.debug('Raw event from winapi: %s', winapi_event) src_path = os.path.join(self.watch.path, winapi_event.src_path) if winapi_event.is_renamed_old: last_renamed_src_path = src_path elif winapi_event.is_renamed_new: dest_path = src_path src_path = last_renamed_src_path if os.path.isdir(dest_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 generate_sub_moved_events(src_path, dest_path): self.queue_event(sub_moved_event) self.queue_event(event) else: self.queue_event(FileMovedEvent(src_path, dest_path)) elif winapi_event.is_modified: cls = DirModifiedEvent if os.path.isdir(src_path) else FileModifiedEvent self.queue_event(cls(src_path)) elif winapi_event.is_added: isdir = os.path.isdir(src_path) cls = DirCreatedEvent if isdir else FileCreatedEvent self.queue_event(cls(src_path)) if isdir: # If a directory is moved from outside the watched folder to inside it # we only get a created directory event out of it, not any events for its children # so use the same hack as for file moves to get the child events time.sleep(WATCHDOG_TRAVERSE_MOVED_DIR_DELAY) sub_events = generate_sub_created_events(src_path) for sub_created_event in sub_events: self.queue_event(sub_created_event) elif winapi_event.is_removed: self.queue_event(FileDeletedEvent(src_path))