Skip to content

Commit

Permalink
[inotify] Add support for IN_OPEN events via FileOpenedEvent
Browse files Browse the repository at this point in the history
…events (#941)

* Add FileOpenedEvent

As per #901 , this adds
support to detect file open events on supported OSes.

* Add file_opened_event in test_file_system_event_handler_dispatch

* Get tests passing on OS X Ventura

* Update changelog.rst

* flake8: Fix long line in test_emitter.py

Co-authored-by: Douglas Staple <staple.douglas@gmail.com>
  • Loading branch information
dstaple and Douglas Staple authored Jan 24, 2023
1 parent d565049 commit 2b09f64
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 3 deletions.
4 changes: 2 additions & 2 deletions changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Changelog

2023-xx-xx • `full history <https://github.com/gorakhargosh/watchdog/compare/v2.2.1...HEAD>`__

-
- Thanks to our beloved contributors: @BoboTiG
- [inotify] Add support for ``IN_OPEN`` events: a ``FileOpenedEvent`` event will be fired. (`#941 <https://github.com/gorakhargosh/watchdog/pull/941>`__)
- Thanks to our beloved contributors: @BoboTiG, @dstaple

2.2.1
~~~~~
Expand Down
21 changes: 21 additions & 0 deletions src/watchdog/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
:members:
:show-inheritance:
.. autoclass:: FileOpenedEvent
:members:
:show-inheritance:
.. autoclass:: DirCreatedEvent
:members:
:show-inheritance:
Expand Down Expand Up @@ -100,6 +104,7 @@
EVENT_TYPE_CREATED = 'created'
EVENT_TYPE_MODIFIED = 'modified'
EVENT_TYPE_CLOSED = 'closed'
EVENT_TYPE_OPENED = 'opened'


class FileSystemEvent:
Expand Down Expand Up @@ -223,6 +228,12 @@ class FileClosedEvent(FileSystemEvent):
event_type = EVENT_TYPE_CLOSED


class FileOpenedEvent(FileSystemEvent):
"""File system event representing file close on the file system."""

event_type = EVENT_TYPE_OPENED


# Directory events.


Expand Down Expand Up @@ -275,6 +286,7 @@ def dispatch(self, event):
EVENT_TYPE_MODIFIED: self.on_modified,
EVENT_TYPE_MOVED: self.on_moved,
EVENT_TYPE_CLOSED: self.on_closed,
EVENT_TYPE_OPENED: self.on_opened,
}[event.event_type](event)

def on_any_event(self, event):
Expand Down Expand Up @@ -331,6 +343,15 @@ def on_closed(self, event):
:class:`FileClosedEvent`
"""

def on_opened(self, event):
"""Called when a file is opened.
:param event:
Event representing file opening.
:type event:
:class:`FileOpenedEvent`
"""


class PatternMatchingEventHandler(FileSystemEventHandler):
"""
Expand Down
4 changes: 4 additions & 0 deletions src/watchdog/observers/inotify.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
FileMovedEvent,
FileCreatedEvent,
FileClosedEvent,
FileOpenedEvent,
generate_sub_moved_events,
generate_sub_created_events,
)
Expand Down Expand Up @@ -176,6 +177,9 @@ def queue_events(self, timeout, full_events=False):
cls = FileClosedEvent
self.queue_event(cls(src_path))
self.queue_event(DirModifiedEvent(os.path.dirname(src_path)))
elif event.is_open and not event.is_directory:
cls = FileOpenedEvent
self.queue_event(cls(src_path))
# elif event.is_close_nowrite and not event.is_directory:
# cls = FileClosedEvent
# self.queue_event(cls(src_path))
Expand Down
5 changes: 5 additions & 0 deletions src/watchdog/observers/inotify_c.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ class InotifyConstants:
InotifyConstants.IN_DELETE_SELF,
InotifyConstants.IN_DONT_FOLLOW,
InotifyConstants.IN_CLOSE_WRITE,
InotifyConstants.IN_OPEN,
])


Expand Down Expand Up @@ -486,6 +487,10 @@ def is_close_write(self):
def is_close_nowrite(self):
return self._mask & InotifyConstants.IN_CLOSE_NOWRITE > 0

@property
def is_open(self):
return self._mask & InotifyConstants.IN_OPEN > 0

@property
def is_access(self):
return self._mask & InotifyConstants.IN_ACCESS > 0
Expand Down
25 changes: 24 additions & 1 deletion tests/test_emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
DirCreatedEvent,
DirMovedEvent,
FileClosedEvent,
FileOpenedEvent,
)
from watchdog.observers.api import ObservedWatch

Expand Down Expand Up @@ -122,6 +123,9 @@ def test_create():
expect_event(DirModifiedEvent(p()))

if platform.is_linux():
event = event_queue.get(timeout=5)[0]
assert event.src_path == p('a')
assert isinstance(event, FileOpenedEvent)
event = event_queue.get(timeout=5)[0]
assert event.src_path == p('a')
assert isinstance(event, FileClosedEvent)
Expand Down Expand Up @@ -188,6 +192,11 @@ def test_modify():

touch(p('a'))

if platform.is_linux():
event = event_queue.get(timeout=5)[0]
assert event.src_path == p('a')
assert isinstance(event, FileOpenedEvent)

expect_event(FileModifiedEvent(p('a')))

if platform.is_linux():
Expand Down Expand Up @@ -429,6 +438,11 @@ def test_recursive_on():
assert event.src_path == p('dir1', 'dir2', 'dir3')
assert isinstance(event, DirModifiedEvent)

if platform.is_linux():
event = event_queue.get(timeout=5)[0]
assert event.src_path == p('dir1', 'dir2', 'dir3', 'a')
assert isinstance(event, FileOpenedEvent)

if not platform.is_bsd():
event = event_queue.get(timeout=5)[0]
assert event.src_path == p('dir1', 'dir2', 'dir3', 'a')
Expand All @@ -450,6 +464,7 @@ def test_recursive_off():
expect_event(DirModifiedEvent(p()))

if platform.is_linux():
expect_event(FileOpenedEvent(p('b')))
expect_event(FileClosedEvent(p('b')))

# currently limiting these additional events to macOS only, see https://github.com/gorakhargosh/watchdog/pull/779
Expand Down Expand Up @@ -505,7 +520,8 @@ def test_renaming_top_level_directory():
if event_queue.empty():
break

assert all([isinstance(e, (FileCreatedEvent, FileMovedEvent, DirModifiedEvent, FileClosedEvent)) for e in events])
assert all([isinstance(e, (FileCreatedEvent, FileMovedEvent, FileOpenedEvent, DirModifiedEvent, FileClosedEvent))
for e in events])

for event in events:
if isinstance(event, FileCreatedEvent):
Expand Down Expand Up @@ -595,6 +611,11 @@ def test_move_nested_subdirectories():

touch(p('dir2/dir3', 'a'))

if platform.is_linux():
event = event_queue.get(timeout=5)[0]
assert event.src_path == p('dir2/dir3', 'a')
assert isinstance(event, FileOpenedEvent)

event = event_queue.get(timeout=5)[0]
assert event.src_path == p('dir2/dir3', 'a')
assert isinstance(event, FileModifiedEvent)
Expand Down Expand Up @@ -658,8 +679,10 @@ def test_file_lifecyle():
expect_event(DirModifiedEvent(p()))

if platform.is_linux():
expect_event(FileOpenedEvent(p('a')))
expect_event(FileClosedEvent(p('a')))
expect_event(DirModifiedEvent(p()))
expect_event(FileOpenedEvent(p('a')))

expect_event(FileModifiedEvent(p('a')))

Expand Down
15 changes: 15 additions & 0 deletions tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
FileModifiedEvent,
FileCreatedEvent,
FileClosedEvent,
FileOpenedEvent,
DirDeletedEvent,
DirModifiedEvent,
DirCreatedEvent,
Expand All @@ -31,6 +32,7 @@
EVENT_TYPE_DELETED,
EVENT_TYPE_MOVED,
EVENT_TYPE_CLOSED,
EVENT_TYPE_OPENED,
)

path_1 = '/path/xyz'
Expand Down Expand Up @@ -92,6 +94,14 @@ def test_file_closed_event():
assert not event.is_synthetic


def test_file_opened_event():
event = FileOpenedEvent(path_1)
assert path_1 == event.src_path
assert EVENT_TYPE_OPENED == event.event_type
assert not event.is_directory
assert not event.is_synthetic


def test_dir_deleted_event():
event = DirDeletedEvent(path_1)
assert path_1 == event.src_path
Expand Down Expand Up @@ -122,6 +132,7 @@ def test_file_system_event_handler_dispatch():
dir_cre_event = DirCreatedEvent('/path/blah.py')
file_cre_event = FileCreatedEvent('/path/blah.txt')
file_cls_event = FileClosedEvent('/path/blah.txt')
file_opened_event = FileOpenedEvent('/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')
Expand All @@ -137,6 +148,7 @@ def test_file_system_event_handler_dispatch():
file_cre_event,
file_mov_event,
file_cls_event,
file_opened_event,
]

class TestableEventHandler(FileSystemEventHandler):
Expand All @@ -159,6 +171,9 @@ def on_created(self, event):
def on_closed(self, event):
assert event.event_type == EVENT_TYPE_CLOSED

def on_opened(self, event):
assert event.event_type == EVENT_TYPE_OPENED

handler = TestableEventHandler()

for event in all_events:
Expand Down

0 comments on commit 2b09f64

Please sign in to comment.