Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Component for monitoring activity on a folder."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import os
7 from typing import cast
8 
9 from watchdog.events import (
10  FileClosedEvent,
11  FileCreatedEvent,
12  FileDeletedEvent,
13  FileModifiedEvent,
14  FileMovedEvent,
15  FileSystemEvent,
16  FileSystemMovedEvent,
17  PatternMatchingEventHandler,
18 )
19 from watchdog.observers import Observer
20 
21 from homeassistant.config_entries import ConfigEntry
22 from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP
23 from homeassistant.core import Event, HomeAssistant
24 from homeassistant.helpers.dispatcher import dispatcher_send
25 from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
26 
27 from .const import CONF_FOLDER, CONF_PATTERNS, DOMAIN, PLATFORMS
28 
29 _LOGGER = logging.getLogger(__name__)
30 
31 
32 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
33  """Set up Folder watcher from a config entry."""
34 
35  path: str = entry.options[CONF_FOLDER]
36  patterns: list[str] = entry.options[CONF_PATTERNS]
37  if not hass.config.is_allowed_path(path):
38  _LOGGER.error("Folder %s is not valid or allowed", path)
40  hass,
41  DOMAIN,
42  f"setup_not_allowed_path_{path}",
43  is_fixable=False,
44  is_persistent=False,
45  severity=IssueSeverity.ERROR,
46  translation_key="setup_not_allowed_path",
47  translation_placeholders={
48  "path": path,
49  "config_variable": "allowlist_external_dirs",
50  },
51  learn_more_url="https://www.home-assistant.io/docs/configuration/basic/#allowlist_external_dirs",
52  )
53  return False
54  await hass.async_add_executor_job(Watcher, path, patterns, hass, entry.entry_id)
55  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
56  return True
57 
58 
60  patterns: list[str], hass: HomeAssistant, entry_id: str
61 ) -> EventHandler:
62  """Return the Watchdog EventHandler object."""
63  return EventHandler(patterns, hass, entry_id)
64 
65 
66 class EventHandler(PatternMatchingEventHandler):
67  """Class for handling Watcher events."""
68 
69  def __init__(self, patterns: list[str], hass: HomeAssistant, entry_id: str) -> None:
70  """Initialise the EventHandler."""
71  super().__init__(patterns)
72  self.hasshass = hass
73  self.entry_identry_id = entry_id
74 
75  def process(self, event: FileSystemEvent, moved: bool = False) -> None:
76  """On Watcher event, fire HA event."""
77  _LOGGER.debug("process(%s)", event)
78  if not event.is_directory:
79  folder, file_name = os.path.split(event.src_path)
80  fireable = {
81  "event_type": event.event_type,
82  "path": event.src_path,
83  "file": file_name,
84  "folder": folder,
85  }
86 
87  _extra = {}
88  if moved:
89  event = cast(FileSystemMovedEvent, event)
90  dest_folder, dest_file_name = os.path.split(event.dest_path)
91  _extra = {
92  "dest_path": event.dest_path,
93  "dest_file": dest_file_name,
94  "dest_folder": dest_folder,
95  }
96  fireable.update(_extra)
97  self.hasshass.bus.fire(
98  DOMAIN,
99  fireable,
100  )
101  signal = f"folder_watcher-{self.entry_id}"
102  dispatcher_send(self.hasshass, signal, event.event_type, fireable)
103 
104  def on_modified(self, event: FileModifiedEvent) -> None:
105  """File modified."""
106  self.processprocess(event)
107 
108  def on_moved(self, event: FileMovedEvent) -> None:
109  """File moved."""
110  self.processprocess(event, moved=True)
111 
112  def on_created(self, event: FileCreatedEvent) -> None:
113  """File created."""
114  self.processprocess(event)
115 
116  def on_deleted(self, event: FileDeletedEvent) -> None:
117  """File deleted."""
118  self.processprocess(event)
119 
120  def on_closed(self, event: FileClosedEvent) -> None:
121  """File closed."""
122  self.processprocess(event)
123 
124 
125 class Watcher:
126  """Class for starting Watchdog."""
127 
128  def __init__(
129  self, path: str, patterns: list[str], hass: HomeAssistant, entry_id: str
130  ) -> None:
131  """Initialise the watchdog observer."""
132  self._observer_observer = Observer()
133  self._observer_observer.schedule(
134  create_event_handler(patterns, hass, entry_id), path, recursive=True
135  )
136  if not hass.is_running:
137  hass.bus.listen_once(EVENT_HOMEASSISTANT_START, self.startupstartup)
138  else:
139  self.startupstartup(None)
140  hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.shutdownshutdown)
141 
142  def startup(self, event: Event | None) -> None:
143  """Start the watcher."""
144  self._observer_observer.start()
145 
146  def shutdown(self, event: Event | None) -> None:
147  """Shutdown the watcher."""
148  self._observer_observer.stop()
149  self._observer_observer.join()
None __init__(self, list[str] patterns, HomeAssistant hass, str entry_id)
Definition: __init__.py:69
None on_moved(self, FileMovedEvent event)
Definition: __init__.py:108
None process(self, FileSystemEvent event, bool moved=False)
Definition: __init__.py:75
None on_deleted(self, FileDeletedEvent event)
Definition: __init__.py:116
None on_modified(self, FileModifiedEvent event)
Definition: __init__.py:104
None on_created(self, FileCreatedEvent event)
Definition: __init__.py:112
None on_closed(self, FileClosedEvent event)
Definition: __init__.py:120
None shutdown(self, Event|None event)
Definition: __init__.py:146
None __init__(self, str path, list[str] patterns, HomeAssistant hass, str entry_id)
Definition: __init__.py:130
None startup(self, Event|None event)
Definition: __init__.py:142
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:32
EventHandler create_event_handler(list[str] patterns, HomeAssistant hass, str entry_id)
Definition: __init__.py:61
None async_create_issue(HomeAssistant hass, str entry_id)
Definition: repairs.py:69
None dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:137