Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix/filewatcher #148

Merged
merged 2 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions ovos_utils/file_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import re
import tempfile
from threading import RLock
from typing import Optional, List

import time
Expand Down Expand Up @@ -319,7 +320,10 @@ def __init__(self, files, callback, recursive=False, ignore_creation=False):
self.observer = Observer()
self.handlers = []
for file_path in files:
watch_dir = dirname(file_path)
if os.path.isfile(file_path):
watch_dir = dirname(file_path)
else:
watch_dir = file_path
self.observer.schedule(FileEventHandler(file_path, callback, ignore_creation),
watch_dir, recursive=recursive)
self.observer.start()
Expand All @@ -330,22 +334,38 @@ def shutdown(self):


class FileEventHandler(FileSystemEventHandler):
def __init__(self, file_path, callback, ignore_creation=False):
def __init__(self, file_path: str, callback: callable,
ignore_creation: bool = False):
"""
Create a handler for file change events
@param file_path: file_path being watched Unused(?)
@param callback: function or method to call on file change
@param ignore_creation: if True, only track file modification events
"""
super().__init__()
self._callback = callback
self._file_path = file_path
self._debounce = 1
self._last_update = 0
if ignore_creation:
self._events = ('modified')
else:
self._events = ('created', 'modified')
self._changed_files = []
self._lock = RLock()

def on_any_event(self, event):
if event.is_directory:
return
elif event.event_type in self._events:
if event.src_path == self._file_path:
if time.time() - self._last_update >= self._debounce:
self._callback(event.src_path)
self._last_update = time.time()
with self._lock:
if event.event_type == "closed":
if event.src_path in self._changed_files:
self._changed_files.remove(event.src_path)
# fire event, it is now safe
try:
self._callback(event.src_path)
except:
LOG.exception("An error occurred handling file "
"change event callback")

elif event.event_type in self._events:
if event.src_path not in self._changed_files:
self._changed_files.append(event.src_path)
45 changes: 43 additions & 2 deletions test/unittests/test_file_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import unittest
from os.path import isdir, isfile
from os.path import isdir, join, dirname
from unittest.mock import Mock


class TestFileUtils(unittest.TestCase):
Expand Down Expand Up @@ -50,8 +51,48 @@ def test_read_translated_file(self):

def test_filewatcher(self):
from ovos_utils.file_utils import FileWatcher
test_file = join(dirname(__file__), "test.watch")
# TODO

def test_file_event_handler(self):
from ovos_utils.file_utils import FileEventHandler
# TODO
from watchdog.events import FileCreatedEvent, FileModifiedEvent, FileClosedEvent
test_file = join(dirname(__file__), "test.watch")
callback = Mock()

# Test ignore creation callbacks
handler = FileEventHandler(test_file, callback, True)
handler.on_any_event(FileCreatedEvent(test_file))
callback.assert_not_called()

# Closed before modification (i.e. listener started while file open)
handler.on_any_event(FileClosedEvent(test_file))
callback.assert_not_called()

# Modified
handler.on_any_event(FileModifiedEvent(test_file))
handler.on_any_event(FileModifiedEvent(test_file))
callback.assert_not_called()
# Closed triggers callback
handler.on_any_event(FileClosedEvent(test_file))
callback.assert_called_once()
# Second close won't trigger callback
handler.on_any_event(FileClosedEvent(test_file))
callback.assert_called_once()

# Test include creation callbacks
callback.reset_mock()
handler = FileEventHandler(test_file, callback, False)
handler.on_any_event(FileCreatedEvent(test_file))
callback.assert_not_called()

# Modified
handler.on_any_event(FileModifiedEvent(test_file))
handler.on_any_event(FileModifiedEvent(test_file))
callback.assert_not_called()
# Closed triggers callback
handler.on_any_event(FileClosedEvent(test_file))
callback.assert_called_once()
# Second close won't trigger callback
handler.on_any_event(FileClosedEvent(test_file))
callback.assert_called_once()