Skip to content

Commit

Permalink
Merge pull request #351 from DonyorM/master
Browse files Browse the repository at this point in the history
Add feature to allow move events to be created even without both paths being watched.
  • Loading branch information
DonyorM committed Apr 12, 2016
2 parents d7ceb7d + 6547c6f commit f1d32fd
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 10 deletions.
47 changes: 41 additions & 6 deletions src/watchdog/observers/inotify.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ def on_thread_stop(self):
if self._inotify:
self._inotify.close()

def queue_events(self, timeout):
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:
Expand All @@ -144,8 +146,12 @@ def queue_events(self, timeout):

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))
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):
Expand All @@ -159,10 +165,14 @@ def queue_events(self, timeout):
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:
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))
Expand All @@ -175,12 +185,37 @@ def _decode_path(self, path):
return unicode_paths.decode(path)


class InotifyFullEmitter(InotifyEmitter):
"""
inotify(7)-based event emitter. By default this class produces move events even if they are not matched
Such move events will have a ``None`` value for the unmatched part.
:param event_queue:
The event queue to fill with events.
:param watch:
A watch object representing the directory to monitor.
:type watch:
:class:`watchdog.observers.api.ObservedWatch`
:param timeout:
Read events blocking timeout (in seconds).
:type timeout:
``float``
"""
def __init__(self, event_queue, watch, timeout=DEFAULT_EMITTER_TIMEOUT):
InotifyEmitter.__init__(self, event_queue, watch, timeout)

def queue_events(self, timeout, events=True):
InotifyEmitter.queue_events(self, timeout, full_events=events)

class InotifyObserver(BaseObserver):
"""
Observer thread that schedules watching directories and dispatches
calls to event handlers.
"""

def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT):
BaseObserver.__init__(self, emitter_class=InotifyEmitter,
def __init__(self, timeout=DEFAULT_OBSERVER_TIMEOUT, generate_full_events=False):
if (generate_full_events):
BaseObserver.__init__(self, emitter_class=InotifyFullEmitter, timeout=timeout)
else:
BaseObserver.__init__(self, emitter_class=InotifyEmitter,
timeout=timeout)
33 changes: 29 additions & 4 deletions tests/test_emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from watchdog.observers.inotify import InotifyEmitter as Emitter
elif platform.is_darwin():
from watchdog.observers.fsevents2 import FSEventsEmitter as Emitter
from watchdog.observers.inotify import InotifyFullEmitter

logging.basicConfig(level=logging.DEBUG)

Expand All @@ -43,10 +44,14 @@ def setup_function(function):
event_queue = Queue()


def start_watching(path=None):
def start_watching(path=None, use_full_emitter=False):
path = p('') if path is None else path
global emitter
emitter = Emitter(event_queue, ObservedWatch(path, recursive=True))
if platform.is_linux() and use_full_emitter:
emitter = InotifyFullEmitter(event_queue, ObservedWatch(path, recursive=True))
else:
emitter = Emitter(event_queue, ObservedWatch(path, recursive=True))

if platform.is_darwin():
# FSEvents will report old evens (like create for mkdtemp in test
# setup. Waiting for a considerable time seems to 'flush' the events.
Expand Down Expand Up @@ -131,6 +136,16 @@ def test_move_to():
assert isinstance(event, FileCreatedEvent)
assert event.src_path == p('dir2', 'b')

def test_move_to_full():
mkdir(p('dir1'))
mkdir(p('dir2'))
touch(p('dir1', 'a'))
start_watching(p('dir2'), use_full_emitter=True)
mv(p('dir1', 'a'), p('dir2', 'b'))
event = event_queue.get(timeout=5)[0]
assert isinstance(event, FileMovedEvent)
assert event.dest_path == p('dir2', 'b')
assert event.src_path == None #Should equal none since the path was not watched

def test_move_from():
mkdir(p('dir1'))
Expand All @@ -142,7 +157,17 @@ def test_move_from():
event = event_queue.get(timeout=5)[0]
assert isinstance(event, FileDeletedEvent)
assert event.src_path == p('dir1', 'a')


def test_move_from_full():
mkdir(p('dir1'))
mkdir(p('dir2'))
touch(p('dir1', 'a'))
start_watching(p('dir1'), use_full_emitter=True)
mv(p('dir1', 'a'), p('dir2', 'b'))
event = event_queue.get(timeout=5)[0]
assert isinstance(event, FileMovedEvent)
assert event.src_path == p('dir1', 'a')
assert event.dest_path == None #Should equal None since path not watched

def test_separate_consecutive_moves():
mkdir(p('dir1'))
Expand Down Expand Up @@ -186,4 +211,4 @@ def test_passing_bytes_should_give_bytes():
start_watching(p('').encode())
touch(p('a'))
event = event_queue.get(timeout=5)[0]
assert isinstance(event.src_path, bytes)
assert isinstance(event.src_path, bytes)

0 comments on commit f1d32fd

Please sign in to comment.