def test_dispatch(self): # Utilities. dir_del_event = DirDeletedEvent("/path/blah.py") file_del_event = FileDeletedEvent("/path/blah.txt") dir_cre_event = DirCreatedEvent("/path/blah.py") file_cre_event = FileCreatedEvent("/path/blah.txt") dir_mod_event = DirModifiedEvent("/path/blah.py") file_mod_event = FileModifiedEvent("/path/blah.txt") dir_mov_event = DirMovedEvent("/path/blah.py", "/path/blah") file_mov_event = FileMovedEvent("/path/blah.txt", "/path/blah") all_events = [ dir_mod_event, dir_del_event, dir_cre_event, dir_mov_event, file_mod_event, file_del_event, file_cre_event, file_mov_event, ] handler = _TestableEventHandler() for event in all_events: handler.dispatch(event)
def queue_events(self, timeout): with self._lock: if not self.watch.is_recursive\ and self.watch.path not in self.pathnames: return new_snapshot = DirectorySnapshot(self.watch.path, self.watch.is_recursive) events = new_snapshot - self.snapshot self.snapshot = new_snapshot # Files. for src_path in events.files_deleted: self.queue_event(FileDeletedEvent(src_path)) for src_path in events.files_modified: self.queue_event(FileModifiedEvent(src_path)) for src_path in events.files_created: self.queue_event(FileCreatedEvent(src_path)) for src_path, dest_path in events.files_moved: self.queue_event(FileMovedEvent(src_path, dest_path)) # Directories. for src_path in events.dirs_deleted: self.queue_event(DirDeletedEvent(src_path)) for src_path in events.dirs_modified: self.queue_event(DirModifiedEvent(src_path)) for src_path in events.dirs_created: self.queue_event(DirCreatedEvent(src_path)) for src_path, dest_path in events.dirs_moved: self.queue_event(DirMovedEvent(src_path, dest_path))
def test_file_lifecyle(): start_watching() mkfile(p('a')) touch(p('a')) mv(p('a'), p('b')) rm(p('b')) expect_event(FileCreatedEvent(p('a'))) if not platform.is_windows(): expect_event(DirModifiedEvent(p())) if platform.is_linux(): expect_event(FileClosedEvent(p('a'))) expect_event(DirModifiedEvent(p())) expect_event(FileModifiedEvent(p('a'))) if platform.is_linux(): expect_event(FileClosedEvent(p('a'))) expect_event(DirModifiedEvent(p())) expect_event(FileMovedEvent(p('a'), p('b'))) if not platform.is_windows(): expect_event(DirModifiedEvent(p())) expect_event(DirModifiedEvent(p())) expect_event(FileDeletedEvent(p('b'))) if not platform.is_windows(): expect_event(DirModifiedEvent(p()))
def queue_events(self, timeout): if self.stopped_event.wait(timeout): return with self._lock: if not self.should_keep_running(): return # Get event diff between fresh snapshot and previous snapshot. # Update snapshot. new_snapshot = self._take_snapshot() events = DirectorySnapshotDiff(self._snapshot, new_snapshot) self._snapshot = new_snapshot # Files. for src_path in events.files_deleted: self.queue_event(FileDeletedEvent(src_path)) for src_path in events.files_modified: self.queue_event(FileModifiedEvent(src_path)) for src_path in events.files_created: self.queue_event(FileCreatedEvent(src_path)) for src_path, dest_path in events.files_moved: self.queue_event(FileMovedEvent(src_path, dest_path)) # Directories. for src_path in events.dirs_deleted: self.queue_event(DirDeletedEvent(src_path)) for src_path in events.dirs_modified: self.queue_event(DirModifiedEvent(src_path)) for src_path in events.dirs_created: self.queue_event(DirCreatedEvent(src_path)) for src_path, dest_path in events.dirs_moved: self.queue_event(DirMovedEvent(src_path, dest_path))
def queue_events(self, timeout): with self._lock: # We don't want to hit the disk continuously. # timeout behaves like an interval for polling emitters. time.sleep(timeout) # Get event diff between fresh snapshot and previous snapshot. # Update snapshot. new_snapshot = DirectorySnapshot(self.watch.path, self.watch.is_recursive) events = DirectorySnapshotDiff(self._snapshot, new_snapshot) self._snapshot = new_snapshot # Files. for src_path in events.files_deleted: self.queue_event(FileDeletedEvent(src_path)) for src_path in events.files_modified: self.queue_event(FileModifiedEvent(src_path)) for src_path in events.files_created: self.queue_event(FileCreatedEvent(src_path)) for src_path, dest_path in events.files_moved: self.queue_event(FileMovedEvent(src_path, dest_path)) # Directories. for src_path in events.dirs_deleted: self.queue_event(DirDeletedEvent(src_path)) for src_path in events.dirs_modified: self.queue_event(DirModifiedEvent(src_path)) for src_path in events.dirs_created: self.queue_event(DirCreatedEvent(src_path)) for src_path, dest_path in events.dirs_moved: self.queue_event(DirMovedEvent(src_path, dest_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(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 _watch_file(self, path): self._watchlist.add(path) for exists in self._loop(path): if not exists: break self.queue_event(FileModifiedEvent(path)) self._watchlist.discard(path) self.queue_event(FileDeletedEvent(path))
def test_delete(): mkfile(p('a')) start_watching() rm(p('a')) expect_event(FileDeletedEvent(p('a'))) if not platform.is_windows(): expect_event(DirModifiedEvent(p()))
def test_single_file_events(sync): # only a single event for every path -> no consolidation file_events = [ FileModifiedEvent(ipath(1)), FileCreatedEvent(ipath(2)), FileDeletedEvent(ipath(3)), FileMovedEvent(ipath(4), ipath(5)), ] res = [ FileModifiedEvent(ipath(1)), FileCreatedEvent(ipath(2)), FileDeletedEvent(ipath(3)), FileMovedEvent(ipath(4), ipath(5)), ] cleaned_events = sync._clean_local_events(file_events) assert set(cleaned_events) == set(res)
def queue_events(self, timeout): # We don't want to hit the disk continuously. # timeout behaves like an interval for polling emitters. if self.stopped_event.wait(timeout): return with self._lock: if not self.should_keep_running(): return # Get event diff between fresh snapshot and previous snapshot. # Update snapshot. try: new_snapshot = self._take_snapshot() except OSError as e: if e.errno in DEFAULT_RESUMABLE_ERRNO: return elif e.errno == errno.ENOENT: # The monitor directory has been removed. if not SUPPORT_ENOENT_RECOVERY: self.queue_event(DirDeletedEvent(self.watch.path)) self.stop() return else: raise if self._snapshot.mount != new_snapshot.mount: # The mount state of monitor directory has changed, such as a local directory is # mounted or a NAS directory is unmounted. if not SUPPORT_UNMOUNT_RECOVERY: self.queue_event(DirDeletedEvent(self.watch.path)) self.stop() return events = DirectorySnapshotDiff(self._snapshot, new_snapshot) self._snapshot = new_snapshot # Files. for src_path in events.files_deleted: self.queue_event(FileDeletedEvent(src_path)) for src_path in events.files_modified: self.queue_event(FileModifiedEvent(src_path)) for src_path in events.files_created: self.queue_event(FileCreatedEvent(src_path)) for src_path, dest_path in events.files_moved: self.queue_event(FileMovedEvent(src_path, dest_path)) # Directories. for src_path in events.dirs_deleted: self.queue_event(DirDeletedEvent(src_path)) for src_path in events.dirs_modified: self.queue_event(DirModifiedEvent(src_path)) for src_path in events.dirs_created: self.queue_event(DirCreatedEvent(src_path)) for src_path, dest_path in events.dirs_moved: self.queue_event(DirMovedEvent(src_path, dest_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 test_move_from(): mkdir(p('dir1')) mkdir(p('dir2')) mkfile(p('dir1', 'a')) start_watching(p('dir1')) mv(p('dir1', 'a'), p('dir2', 'b')) expect_event(FileDeletedEvent(p('dir1', 'a'))) if not platform.is_windows(): expect_event(DirModifiedEvent(p('dir1')))
def test_type_changes_difficult(sync): file_events = [ # convert to FileDeleted -> DirCreated FileModifiedEvent(ipath(1)), FileDeletedEvent(ipath(1)), FileCreatedEvent(ipath(1)), FileDeletedEvent(ipath(1)), DirCreatedEvent(ipath(1)), # convert to FileDeleted(path1) -> DirCreated(path2) FileModifiedEvent(ipath(2)), FileDeletedEvent(ipath(2)), FileCreatedEvent(ipath(2)), FileDeletedEvent(ipath(2)), DirCreatedEvent(ipath(2)), DirMovedEvent(ipath(2), ipath(3)), ] res = [ FileDeletedEvent(ipath(1)), DirCreatedEvent(ipath(1)), FileDeletedEvent(ipath(2)), DirCreatedEvent(ipath(3)), ] cleaned_events = sync._clean_local_events(file_events) assert set(cleaned_events) == set(res)
def test_type_changes(sync): file_events = [ # keep as is FileDeletedEvent(ipath(1)), DirCreatedEvent(ipath(1)), # keep as is DirDeletedEvent(ipath(2)), FileCreatedEvent(ipath(2)), ] res = [ # keep as is FileDeletedEvent(ipath(1)), DirCreatedEvent(ipath(1)), # keep as is DirDeletedEvent(ipath(2)), FileCreatedEvent(ipath(2)), ] cleaned_events = sync._clean_local_events(file_events) assert set(cleaned_events) == set(res)
def test_on_deleted(self, _remote_rm, _os): with mock.patch.object(self.handler, "del_gitignore") as _del_gitignore: _os.path.abspath.return_value = "/a/.gitignore" _os.path.join.return_value = "/b/a/.gitignore" _remote_rm.return_value = True _del_gitignore.return_value = True _event = FileDeletedEvent(src_path="/a/.gitignore") self.handler.on_deleted(_event) _os.path.abspath.assert_called_once_with("/a/.gitignore") _os.path.join.assert_called_once_with("/b/a/", ".gitignore") _remote_rm.assert_called_once_with(dst_ssh=self.handler.dst_ssh, dst_path="/b/a/.gitignore") _del_gitignore.assert_called_once_with("/a/.gitignore")
def test_macos_safe_save(sync): file_events = [ FileMovedEvent(ipath(1), ipath(1) + ".sb-b78ef837-dLht38"), # move to backup FileCreatedEvent(ipath(1)), # create new version FileDeletedEvent(ipath(1) + ".sb-b78ef837-dLht38"), # delete backup ] res = [ FileModifiedEvent(ipath(1)), # modified file ] cleaned_events = sync._clean_local_events(file_events) assert set(cleaned_events) == set(res)
def on_moved(self, event): """ event.is_directory True | False event.src_path path/to/observed/file """ raw_name = event.src_path.replace("\\", "/") if event.src_path.endswith("Neues Textdokument.txt"): self.on_created(FileCreatedEvent(event.dest_path)) else: if not isinstance(event, DirMovedEvent): self.on_deleted(FileDeletedEvent(raw_name)) self.on_created(FileCreatedEvent(event.dest_path))
def test_performance(sync): # 10,000 nested deleted events (5,000 folders, 5,000 files) file_events = [DirDeletedEvent(n * ipath(1)) for n in range(1, 5001)] file_events += [ FileDeletedEvent(n * ipath(1) + ".txt") for n in range(1, 5001) ] # 10,000 nested moved events (5,000 folders, 5,000 files) file_events += [ DirMovedEvent(n * ipath(2), n * ipath(3)) for n in range(1, 5001) ] file_events += [ FileMovedEvent(n * ipath(2) + ".txt", n * ipath(3) + ".txt") for n in range(1, 5001) ] # 4,995 unrelated created events file_events += [FileCreatedEvent(ipath(n)) for n in range(5, 5001)] res = [ DirDeletedEvent(ipath(1)), DirMovedEvent(ipath(2), ipath(3)), FileDeletedEvent(ipath(1) + ".txt"), FileMovedEvent(ipath(2) + ".txt", ipath(3) + ".txt"), ] res += [FileCreatedEvent(ipath(n)) for n in range(5, 5001)] cleaned_events = sync._clean_local_events(file_events) assert set(cleaned_events) == set(res) n_loops = 4 duration = timeit.timeit(lambda: sync._clean_local_events(file_events), number=n_loops) assert duration < 10 * n_loops
def test_single_path_cases(sync): file_events = [ # created + deleted -> None FileCreatedEvent(ipath(1)), FileDeletedEvent(ipath(1)), # deleted + created -> modified FileDeletedEvent(ipath(2)), FileCreatedEvent(ipath(2)), # created + modified -> created FileCreatedEvent(ipath(3)), FileModifiedEvent(ipath(3)), ] res = [ # created + deleted -> None # deleted + created -> modified FileModifiedEvent(ipath(2)), # created + modified -> created FileCreatedEvent(ipath(3)), ] cleaned_events = sync._clean_local_events(file_events) assert set(cleaned_events) == set(res)
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: 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)) 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) 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 is_directory: self.queue_event(DirDeletedEvent(src_path)) else: self.queue_event(FileDeletedEvent(src_path))
def test_nested_events(sync): file_events = [ # convert to a single DirDeleted DirDeletedEvent(ipath(1)), FileDeletedEvent(ipath(1) + "/file1.txt"), FileDeletedEvent(ipath(1) + "/file2.txt"), DirDeletedEvent(ipath(1) + "/sub"), FileDeletedEvent(ipath(1) + "/sub/file3.txt"), # convert to a single DirMoved DirMovedEvent(ipath(2), ipath(3)), FileMovedEvent(ipath(2) + "/file1.txt", ipath(3) + "/file1.txt"), FileMovedEvent(ipath(2) + "/file2.txt", ipath(3) + "/file2.txt"), DirMovedEvent(ipath(2) + "/sub", ipath(3) + "/sub"), FileMovedEvent(ipath(2) + "/sub/file3.txt", ipath(3) + "/sub/file3.txt"), ] res = [ DirDeletedEvent(ipath(1)), DirMovedEvent(ipath(2), ipath(3)), ] cleaned_events = sync._clean_local_events(file_events) assert set(cleaned_events) == set(res)
def _queue_events_except_renames_and_dir_modifications( self, event_list): """ Queues events from the kevent list returned from the call to :meth:`select.kqueue.control`. .. NOTE:: Queues only the deletions, file modifications, attribute modifications. The other events, namely, file creation, directory modification, file rename, directory rename, directory creation, etc. are determined by comparing directory snapshots. """ files_renamed = set() dirs_renamed = set() dirs_modified = set() for kev in event_list: descriptor = self._descriptors.get_for_fd(kev.ident) src_path = descriptor.path if is_deleted(kev): if descriptor.is_directory: self.queue_event(DirDeletedEvent(src_path)) else: self.queue_event(FileDeletedEvent(src_path)) elif is_attrib_modified(kev): if descriptor.is_directory: self.queue_event(DirModifiedEvent(src_path)) else: self.queue_event(FileModifiedEvent(src_path)) elif is_modified(kev): if descriptor.is_directory: # When a directory is modified, it may be due to # sub-file/directory renames or new file/directory # creation. We determine all this by comparing # snapshots later. dirs_modified.add(src_path) else: self.queue_event(FileModifiedEvent(src_path)) elif is_renamed(kev): # Kqueue does not specify the destination names for renames # to, so we have to process these after taking a snapshot # of the directory. if descriptor.is_directory: dirs_renamed.add(src_path) else: files_renamed.add(src_path) return files_renamed, dirs_renamed, dirs_modified
def test_msoffice_created(sync): file_events = [ FileCreatedEvent(ipath(1)), FileDeletedEvent(ipath(1)), FileCreatedEvent(ipath(1)), FileCreatedEvent("~$" + ipath(1)), ] res = [ FileCreatedEvent(ipath(1)), # created file FileCreatedEvent("~$" + ipath(1)), # backup ] cleaned_events = sync._clean_local_events(file_events) assert set(cleaned_events) == set(res)
def on_moved(self, event): """Translate filesystem move events. DO NOT OVERRIDE.""" src_path = unicode_paths.decode(event.src_path) dest_path = unicode_paths.decode(event.dest_path) src_match = any(r.match(src_path) for r in self.regexes) dest_match = any(r.match(dest_path) for r in self.regexes) if src_match and dest_match: self.on_drf_moved(event) elif src_match: new_event = FileDeletedEvent(event.src_path) self.on_deleted(new_event) else: new_event = FileCreatedEvent(event.dest_path) self.on_created(new_event)
def test_logging_event_handler_dispatch(): class _TestableEventHandler(LoggingEventHandler): def on_any_event(self, event): assert True def on_modified(self, event): super(_TestableEventHandler, self).on_modified(event) assert event.event_type == EVENT_TYPE_MODIFIED def on_deleted(self, event): super(_TestableEventHandler, self).on_deleted(event) assert event.event_type == EVENT_TYPE_DELETED def on_moved(self, event): super(_TestableEventHandler, self).on_moved(event) assert event.event_type == EVENT_TYPE_MOVED def on_created(self, event): super(_TestableEventHandler, self).on_created(event) assert event.event_type == EVENT_TYPE_CREATED # Utilities. dir_del_event = DirDeletedEvent('/path/blah.py') file_del_event = FileDeletedEvent('/path/blah.txt') dir_cre_event = DirCreatedEvent('/path/blah.py') file_cre_event = FileCreatedEvent('/path/blah.txt') dir_mod_event = DirModifiedEvent('/path/blah.py') file_mod_event = FileModifiedEvent('/path/blah.txt') dir_mov_event = DirMovedEvent('/path/blah.py', '/path/blah') file_mov_event = FileMovedEvent('/path/blah.txt', '/path/blah') all_events = [ dir_mod_event, dir_del_event, dir_cre_event, dir_mov_event, file_mod_event, file_del_event, file_cre_event, file_mov_event, ] handler = _TestableEventHandler() for event in all_events: handler.dispatch(event)
def test_file_system_event_handler_dispatch(): dir_del_event = DirDeletedEvent('/path/blah.py') file_del_event = FileDeletedEvent('/path/blah.txt') dir_cre_event = DirCreatedEvent('/path/blah.py') file_cre_event = FileCreatedEvent('/path/blah.txt') file_cls_event = FileClosedEvent('/path/blah.txt') dir_mod_event = DirModifiedEvent('/path/blah.py') file_mod_event = FileModifiedEvent('/path/blah.txt') dir_mov_event = DirMovedEvent('/path/blah.py', '/path/blah') file_mov_event = FileMovedEvent('/path/blah.txt', '/path/blah') all_events = [ dir_mod_event, dir_del_event, dir_cre_event, dir_mov_event, file_mod_event, file_del_event, file_cre_event, file_mov_event, file_cls_event, ] class TestableEventHandler(FileSystemEventHandler): def on_any_event(self, event): assert True def on_modified(self, event): assert event.event_type == EVENT_TYPE_MODIFIED def on_deleted(self, event): assert event.event_type == EVENT_TYPE_DELETED def on_moved(self, event): assert event.event_type == EVENT_TYPE_MOVED def on_created(self, event): assert event.event_type == EVENT_TYPE_CREATED def on_closed(self, event): assert event.event_type == EVENT_TYPE_CLOSED handler = TestableEventHandler() for event in all_events: assert not event.is_synthetic handler.dispatch(event)
def _gen_kqueue_events(self, kev, ref_snapshot, new_snapshot): """ Generate events from the kevent list returned from the call to :meth:`select.kqueue.control`. .. NOTE:: kqueue only tells us about deletions, file modifications, attribute modifications. The other events, namely, file creation, directory modification, file rename, directory rename, directory creation, etc. are determined by comparing directory snapshots. """ descriptor = self._descriptors.get_for_fd(kev.ident) src_path = descriptor.path if is_renamed(kev): # Kqueue does not specify the destination names for renames # to, so we have to process these using the a snapshot # of the directory. for event in self._gen_renamed_events(src_path, descriptor.is_directory, ref_snapshot, new_snapshot): yield event elif is_attrib_modified(kev): if descriptor.is_directory: yield DirModifiedEvent(src_path) else: yield FileModifiedEvent(src_path) elif is_modified(kev): if descriptor.is_directory: if self.watch.is_recursive or self.watch.path == src_path: # When a directory is modified, it may be due to # sub-file/directory renames or new file/directory # creation. We determine all this by comparing # snapshots later. yield DirModifiedEvent(src_path) else: yield FileModifiedEvent(src_path) elif is_deleted(kev): if descriptor.is_directory: yield DirDeletedEvent(src_path) else: yield FileDeletedEvent(src_path)
def test_dispatch(self): dir_del_event = DirDeletedEvent('/path/blah.py') file_del_event = FileDeletedEvent('/path/blah.txt') dir_cre_event = DirCreatedEvent('/path/blah.py') file_cre_event = FileCreatedEvent('/path/blah.txt') dir_mod_event = DirModifiedEvent('/path/blah.py') file_mod_event = FileModifiedEvent('/path/blah.txt') dir_mov_event = DirMovedEvent('/path/blah.py', '/path/blah') file_mov_event = FileMovedEvent('/path/blah.txt', '/path/blah') all_events = [ dir_mod_event, dir_del_event, dir_cre_event, dir_mov_event, file_mod_event, file_del_event, file_cre_event, file_mov_event, ] def assert_equal(a, b): self.assertEqual(a, b) class TestableEventHandler(FileSystemEventHandler): def on_any_event(self, event): assert True def on_modified(self, event): assert_equal(event.event_type, EVENT_TYPE_MODIFIED) def on_deleted(self, event): assert_equal(event.event_type, EVENT_TYPE_DELETED) def on_moved(self, event): assert_equal(event.event_type, EVENT_TYPE_MOVED) def on_created(self, event): assert_equal(event.event_type, EVENT_TYPE_CREATED) handler = TestableEventHandler() for event in all_events: handler.dispatch(event)
def queue_events(self, timeout): # We don't want to hit the disk continuously. # timeout behaves like an interval for polling emitters. if self.stopped_event.wait(timeout): return with self._lock: if not self.should_keep_running(): return # Get event diff between fresh snapshot and previous snapshot. # Update snapshot. try: new_snapshot = self._take_snapshot() except Exception as e: # OSError: logging.debug('queue_events _take_snapshot: %s' % e) # stop commented to do not stop the thread if an # error happened (i.e. watch folder not accessible temporarily) self.queue_event(FileSystemErrorEvent(self.watch.path, e)) #self.stop() return events = DirectorySnapshotDiff(self._snapshot if self._snapshot else new_snapshot, new_snapshot) self._snapshot = new_snapshot # Files. for src_path in events.files_deleted: self.queue_event(FileDeletedEvent(src_path)) for src_path in events.files_modified: self.queue_event(FileModifiedEvent(src_path)) for src_path in events.files_created: self.queue_event(FileCreatedEvent(src_path)) for src_path, dest_path in events.files_moved: self.queue_event(FileMovedEvent(src_path, dest_path)) # Directories. for src_path in events.dirs_deleted: self.queue_event(DirDeletedEvent(src_path)) for src_path in events.dirs_modified: self.queue_event(DirModifiedEvent(src_path)) for src_path in events.dirs_created: self.queue_event(DirCreatedEvent(src_path)) for src_path, dest_path in events.dirs_moved: self.queue_event(DirMovedEvent(src_path, dest_path))
def queue_events(self, timeout): if self.stopped_event.wait(self.polling_interval): return if _paused(): return if self.is_offline(): return files_created, files_deleted = self.take_snapshot() if self.is_offline(): log("poller(%s). path went offline while taking snapshot. " "ignoring changes" % self.watch.path) return for path in files_created: self.queue_event(FileCreatedEvent(path)) for path in files_deleted: self.queue_event(FileDeletedEvent(path))