From 27588153a7aee849fbe36a608ee378b09a16e476 Mon Sep 17 00:00:00 2001 From: SamSchott Date: Wed, 16 Dec 2020 21:13:34 +0000 Subject: [PATCH] Uniformize event for deletion of watched dir (#727) * monitor our own root * properly handle root change event * test on all platforms that root deleted event is emitted ... ... and that emitter is stopped * basic test for root moved * winapi: emit DirDeletedEvent of removed root * removed leftover commented-out code * expect KeyError when unscheduling removed watch * propagate root deleted event in inotify * remove `test_move_self` * catch OSError when stopping watch in fixture * adapt test_inotify_c to stopped emitter after delete_self * don't emit root deleted for child watches --- src/watchdog/observers/fsevents.py | 13 +++++++++++++ src/watchdog/observers/inotify.py | 3 +++ src/watchdog/observers/read_directory_changes.py | 2 ++ src/watchdog_fsevents.c | 2 +- tests/test_emitter.py | 16 +++++++++++----- tests/test_fsevents.py | 4 +++- tests/test_inotify_c.py | 6 +++++- 7 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/watchdog/observers/fsevents.py b/src/watchdog/observers/fsevents.py index 1226ca4a..ecfdcee4 100644 --- a/src/watchdog/observers/fsevents.py +++ b/src/watchdog/observers/fsevents.py @@ -123,6 +123,19 @@ def queue_events(self, timeout): cls = DirDeletedEvent if event.is_directory else FileDeletedEvent 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 + self.stop() + + elif event.is_root_changed: + # This will be set if root or any if its parents is renamed or + # deleted. + # TODO: find out new path and generate DirMovedEvent? + self.queue_event(DirDeletedEvent(self.watch.path)) + self.stop() + i += 1 def run(self): diff --git a/src/watchdog/observers/inotify.py b/src/watchdog/observers/inotify.py index f814fa96..5cb8c5dd 100644 --- a/src/watchdog/observers/inotify.py +++ b/src/watchdog/observers/inotify.py @@ -170,6 +170,9 @@ def queue_events(self, timeout, full_events=False): 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_delete_self and src_path == self.watch.path: + self.queue_event(DirDeletedEvent(src_path)) + self.stop() def _decode_path(self, path): """Decode path only if unicode string was passed to this emitter. """ diff --git a/src/watchdog/observers/read_directory_changes.py b/src/watchdog/observers/read_directory_changes.py index 5555630a..a0e088eb 100644 --- a/src/watchdog/observers/read_directory_changes.py +++ b/src/watchdog/observers/read_directory_changes.py @@ -22,6 +22,7 @@ from watchdog.events import ( DirCreatedEvent, + DirDeletedEvent, DirMovedEvent, DirModifiedEvent, FileCreatedEvent, @@ -121,6 +122,7 @@ def queue_events(self, timeout): 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() diff --git a/src/watchdog_fsevents.c b/src/watchdog_fsevents.c index 5314ff8c..bbda66a3 100644 --- a/src/watchdog_fsevents.c +++ b/src/watchdog_fsevents.c @@ -493,7 +493,7 @@ watchdog_FSEventStreamCreate(StreamCallbackInfo *stream_callback_info_ref, paths, kFSEventStreamEventIdSinceNow, stream_latency, - kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents); + kFSEventStreamCreateFlagNoDefer | kFSEventStreamCreateFlagFileEvents | kFSEventStreamCreateFlagWatchRoot); CFRelease(paths); return stream_ref; } diff --git a/tests/test_emitter.py b/tests/test_emitter.py index 082ec383..6df98ae4 100644 --- a/tests/test_emitter.py +++ b/tests/test_emitter.py @@ -64,7 +64,11 @@ def setup_teardown(tmpdir): yield - emitter.stop() + try: + emitter.stop() + except OSError: + # watch was already stopped, e.g., in `test_delete_self` + pass emitter.join(5) assert not emitter.is_alive() @@ -283,10 +287,12 @@ def test_delete_self(): start_watching(p('dir1')) rm(p('dir1'), True) - if platform.is_darwin(): - event = event_queue.get(timeout=5)[0] - assert event.src_path == p('dir1') - assert isinstance(event, FileDeletedEvent) + event = event_queue.get(timeout=5)[0] + assert event.src_path == p('dir1') + assert isinstance(event, DirDeletedEvent) + + emitter.join(timeout=1) + assert not emitter.is_alive() @pytest.mark.skipif(platform.is_windows() or platform.is_bsd(), diff --git a/tests/test_fsevents.py b/tests/test_fsevents.py index bdbf35ad..88a42e69 100644 --- a/tests/test_fsevents.py +++ b/tests/test_fsevents.py @@ -100,7 +100,9 @@ def on_thread_stop(self): w = observer.schedule(FileSystemEventHandler(), a, recursive=False) rmdir(a) time.sleep(0.1) - observer.unschedule(w) + with pytest.raises(KeyError): + # watch no longer exists! + observer.unschedule(w) def test_watchdog_recursive(): diff --git a/tests/test_inotify_c.py b/tests/test_inotify_c.py index 81f6586c..a18f140c 100644 --- a/tests/test_inotify_c.py +++ b/tests/test_inotify_c.py @@ -40,7 +40,11 @@ def watching(path=None, use_full_emitter=False): emitter = Emitter(event_queue, ObservedWatch(path, recursive=True)) emitter.start() yield - emitter.stop() + try: + emitter.stop() + except OSError: + # watch was already stopped, e.g., because root was deleted + pass emitter.join(5)