mirror of https://github.com/home-assistant/core
Adds folder_watcher component (#12918)
* Create watchdog_file_watcher.py * Rename watchdog_file_watcher.py to folder_watcher.py * Address a number of issues * Adds filter * Adds pattern matching * Adds create_event_handler() * Update folder_watcher.py * Adds run_setup() * Remove stop_watching() * Adds shutdown() * Update config to allow patterns on each folder * Update to patterns from filters * Adds watchdog * Fix indents on schema * Update folder_watcher.py * Create test_file_watcher.py * Fix lints * Add test_invalid_path() * Adds folder_watcher * Update test_file_watcher.py * Update folder_watcher.py * Simplify config * Adapt for new config * Run observer.schedule() on EVENT_HOMEASSISTANT_START * Amend Watcher removing entity and tidying startup * Tidy config * Rename process to on_any_event for consistency * Rename on_any_event back to process Using `on_any_event` resulted in 2 events being fired * Update folder_watcher.py * Fix return False on setup * Update test_file_watcher.py * Update folder_watcher.py * Adds watchdog * Undo adding watchdog * Update test_file_watcher.py * Update test_file_watcher.py * Update test_file_watcher.py * Update test_file_watcher.py * Update test_file_watcher.py * Add event * Update test_file_watcher.py * Update .coveragerc * Update test_file_watcher.py * Update test_file_watcher.py * debug + join * test event * lint * lint * Rename test_file_watcher.py to test_folder_watcher.py * hound * Tidy test * Further refine test * Adds to test_all * Fix test for py35 * Change test again * Update test_folder_watcher.py * Fix test * Add watchdog to test * Update folder_watcher.py * add watchdog * Update folder_watcher.py
This commit is contained in:
parent
5908b55bba
commit
df78eecc1b
|
@ -402,6 +402,7 @@ omit =
|
|||
homeassistant/components/fan/mqtt.py
|
||||
homeassistant/components/fan/xiaomi_miio.py
|
||||
homeassistant/components/feedreader.py
|
||||
homeassistant/components/folder_watcher.py
|
||||
homeassistant/components/foursquare.py
|
||||
homeassistant/components/goalfeed.py
|
||||
homeassistant/components/ifttt.py
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
"""
|
||||
Component for monitoring activity on a folder.
|
||||
|
||||
For more details about this platform, refer to the documentation at
|
||||
https://home-assistant.io/components/folder_watcher/
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
import voluptuous as vol
|
||||
from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['watchdog==0.8.3']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_FOLDER = 'folder'
|
||||
CONF_PATTERNS = 'patterns'
|
||||
CONF_WATCHERS = 'watchers'
|
||||
DEFAULT_PATTERN = '*'
|
||||
DOMAIN = "folder_watcher"
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.All(cv.ensure_list, [vol.Schema({
|
||||
vol.Required(CONF_FOLDER): cv.isdir,
|
||||
vol.Optional(CONF_PATTERNS, default=[DEFAULT_PATTERN]):
|
||||
vol.All(cv.ensure_list, [cv.string]),
|
||||
})])
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""Set up the folder watcher."""
|
||||
conf = config[DOMAIN]
|
||||
for watcher in conf:
|
||||
path = watcher[CONF_FOLDER]
|
||||
patterns = watcher[CONF_PATTERNS]
|
||||
if not hass.config.is_allowed_path(path):
|
||||
_LOGGER.error("folder %s is not valid or allowed", path)
|
||||
return False
|
||||
Watcher(path, patterns, hass)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def create_event_handler(patterns, hass):
|
||||
""""Return the Watchdog EventHandler object."""
|
||||
from watchdog.events import PatternMatchingEventHandler
|
||||
|
||||
class EventHandler(PatternMatchingEventHandler):
|
||||
"""Class for handling Watcher events."""
|
||||
|
||||
def __init__(self, patterns, hass):
|
||||
"""Initialise the EventHandler."""
|
||||
super().__init__(patterns)
|
||||
self.hass = hass
|
||||
|
||||
def process(self, event):
|
||||
"""On Watcher event, fire HA event."""
|
||||
_LOGGER.debug("process(%s)", event)
|
||||
if not event.is_directory:
|
||||
folder, file_name = os.path.split(event.src_path)
|
||||
self.hass.bus.fire(
|
||||
DOMAIN, {
|
||||
"event_type": event.event_type,
|
||||
'path': event.src_path,
|
||||
'file': file_name,
|
||||
'folder': folder,
|
||||
})
|
||||
|
||||
def on_modified(self, event):
|
||||
"""File modified."""
|
||||
self.process(event)
|
||||
|
||||
def on_moved(self, event):
|
||||
"""File moved."""
|
||||
self.process(event)
|
||||
|
||||
def on_created(self, event):
|
||||
"""File created."""
|
||||
self.process(event)
|
||||
|
||||
def on_deleted(self, event):
|
||||
"""File deleted."""
|
||||
self.process(event)
|
||||
|
||||
return EventHandler(patterns, hass)
|
||||
|
||||
|
||||
class Watcher():
|
||||
"""Class for starting Watchdog."""
|
||||
|
||||
def __init__(self, path, patterns, hass):
|
||||
"""Initialise the watchdog observer."""
|
||||
from watchdog.observers import Observer
|
||||
self._observer = Observer()
|
||||
self._observer.schedule(
|
||||
create_event_handler(patterns, hass),
|
||||
path,
|
||||
recursive=True)
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, self.startup)
|
||||
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.shutdown)
|
||||
|
||||
def startup(self, event):
|
||||
"""Start the watcher."""
|
||||
self._observer.start()
|
||||
|
||||
def shutdown(self, event):
|
||||
"""Shutdown the watcher."""
|
||||
self._observer.stop()
|
||||
self._observer.join()
|
|
@ -1279,6 +1279,9 @@ waqiasync==1.0.0
|
|||
# homeassistant.components.cloud
|
||||
warrant==0.6.1
|
||||
|
||||
# homeassistant.components.folder_watcher
|
||||
watchdog==0.8.3
|
||||
|
||||
# homeassistant.components.waterfurnace
|
||||
waterfurnace==0.4.0
|
||||
|
||||
|
|
|
@ -199,5 +199,8 @@ wakeonlan==1.0.0
|
|||
# homeassistant.components.cloud
|
||||
warrant==0.6.1
|
||||
|
||||
# homeassistant.components.folder_watcher
|
||||
watchdog==0.8.3
|
||||
|
||||
# homeassistant.components.sensor.yahoo_finance
|
||||
yahoo-finance==1.4.0
|
||||
|
|
|
@ -90,6 +90,7 @@ TEST_REQUIREMENTS = (
|
|||
'yahoo-finance',
|
||||
'pythonwhois',
|
||||
'wakeonlan',
|
||||
'watchdog',
|
||||
'vultr'
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
"""The tests for the folder_watcher component."""
|
||||
import unittest
|
||||
from unittest.mock import MagicMock
|
||||
import os
|
||||
|
||||
from homeassistant.components import folder_watcher
|
||||
from homeassistant.setup import setup_component
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
CWD = os.path.join(os.path.dirname(__file__))
|
||||
FILE = 'file.txt'
|
||||
|
||||
|
||||
class TestFolderWatcher(unittest.TestCase):
|
||||
"""Test the file_watcher component."""
|
||||
|
||||
def setup_method(self, method):
|
||||
"""Set up things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.hass.config.whitelist_external_dirs = set((CWD))
|
||||
|
||||
def teardown_method(self, method):
|
||||
"""Stop everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_invalid_path_setup(self):
|
||||
"""Test that a invalid path is not setup."""
|
||||
config = {
|
||||
folder_watcher.DOMAIN: [{
|
||||
folder_watcher.CONF_FOLDER: 'invalid_path'
|
||||
}]
|
||||
}
|
||||
self.assertFalse(
|
||||
setup_component(self.hass, folder_watcher.DOMAIN, config))
|
||||
|
||||
def test_valid_path_setup(self):
|
||||
"""Test that a valid path is setup."""
|
||||
config = {
|
||||
folder_watcher.DOMAIN: [{folder_watcher.CONF_FOLDER: CWD}]
|
||||
}
|
||||
|
||||
self.assertTrue(setup_component(
|
||||
self.hass, folder_watcher.DOMAIN, config))
|
||||
|
||||
def test_event(self):
|
||||
"""Check that HASS events are fired correctly on watchdog event."""
|
||||
from watchdog.events import FileModifiedEvent
|
||||
|
||||
# Cant use setup_component as need to retrieve Watcher object.
|
||||
w = folder_watcher.Watcher(CWD,
|
||||
folder_watcher.DEFAULT_PATTERN,
|
||||
self.hass)
|
||||
w.startup(None)
|
||||
|
||||
self.hass.bus.fire = MagicMock()
|
||||
|
||||
# Trigger a fake filesystem event through the Watcher Observer emitter.
|
||||
(emitter,) = w._observer.emitters
|
||||
emitter.queue_event(FileModifiedEvent(FILE))
|
||||
|
||||
# Wait for the event to propagate.
|
||||
self.hass.block_till_done()
|
||||
|
||||
assert self.hass.bus.fire.called
|
Loading…
Reference in New Issue