Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The syncthing integration."""
2 
3 import asyncio
4 import logging
5 
6 import aiosyncthing
7 
8 from homeassistant.config_entries import ConfigEntry
9 from homeassistant.const import (
10  CONF_TOKEN,
11  CONF_URL,
12  CONF_VERIFY_SSL,
13  EVENT_HOMEASSISTANT_STOP,
14  Platform,
15 )
16 from homeassistant.core import HomeAssistant
17 from homeassistant.exceptions import ConfigEntryNotReady
18 from homeassistant.helpers.dispatcher import async_dispatcher_send
19 
20 from .const import (
21  DOMAIN,
22  EVENTS,
23  RECONNECT_INTERVAL,
24  SERVER_AVAILABLE,
25  SERVER_UNAVAILABLE,
26 )
27 
28 PLATFORMS = [Platform.SENSOR]
29 
30 _LOGGER = logging.getLogger(__name__)
31 
32 
33 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
34  """Set up syncthing from a config entry."""
35  data = entry.data
36 
37  if DOMAIN not in hass.data:
38  hass.data[DOMAIN] = {}
39 
40  client = aiosyncthing.Syncthing(
41  data[CONF_TOKEN],
42  url=data[CONF_URL],
43  verify_ssl=data[CONF_VERIFY_SSL],
44  )
45 
46  try:
47  status = await client.system.status()
48  except aiosyncthing.exceptions.SyncthingError as exception:
49  await client.close()
50  raise ConfigEntryNotReady from exception
51 
52  server_id = status["myID"]
53 
54  syncthing = SyncthingClient(hass, client, server_id)
55  syncthing.subscribe()
56  hass.data[DOMAIN][entry.entry_id] = syncthing
57 
58  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
59 
60  async def cancel_listen_task(_):
61  await syncthing.unsubscribe()
62 
63  entry.async_on_unload(
64  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, cancel_listen_task)
65  )
66 
67  return True
68 
69 
70 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
71  """Unload a config entry."""
72  unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
73  if unload_ok:
74  syncthing = hass.data[DOMAIN].pop(entry.entry_id)
75  await syncthing.unsubscribe()
76 
77  return unload_ok
78 
79 
81  """A Syncthing client."""
82 
83  def __init__(self, hass, client, server_id):
84  """Initialize the client."""
85  self._hass_hass = hass
86  self._client_client = client
87  self._server_id_server_id = server_id
88  self._listen_task_listen_task = None
89 
90  @property
91  def server_id(self):
92  """Get server id."""
93  return self._server_id_server_id
94 
95  @property
96  def url(self):
97  """Get server URL."""
98  return self._client_client.url
99 
100  @property
101  def database(self):
102  """Get database namespace client."""
103  return self._client_client.database
104 
105  @property
106  def system(self):
107  """Get system namespace client."""
108  return self._client_client.system
109 
110  def subscribe(self):
111  """Start event listener coroutine."""
112  self._listen_task_listen_task = asyncio.create_task(self._listen_listen())
113 
114  async def unsubscribe(self):
115  """Stop event listener coroutine."""
116  if self._listen_task_listen_task:
117  self._listen_task_listen_task.cancel()
118  await self._client_client.close()
119 
120  async def _listen(self):
121  """Listen to Syncthing events."""
122  events = self._client_client.events
123  server_was_unavailable = False
124  while True:
125  if await self._server_available_server_available():
126  if server_was_unavailable:
127  _LOGGER.warning(
128  "The syncthing server '%s' is back online", self._client_client.url
129  )
131  self._hass_hass, f"{SERVER_AVAILABLE}-{self._server_id}"
132  )
133  server_was_unavailable = False
134  else:
135  await asyncio.sleep(RECONNECT_INTERVAL.total_seconds())
136  continue
137  try:
138  async for event in events.listen():
139  if events.last_seen_id == 0:
140  continue # skipping historical events from the first batch
141  if event["type"] not in EVENTS:
142  continue
143 
144  signal_name = EVENTS[event["type"]]
145  folder = None
146  if "folder" in event["data"]:
147  folder = event["data"]["folder"]
148  else: # A workaround, some events store folder id under `id` key
149  folder = event["data"]["id"]
151  self._hass_hass,
152  f"{signal_name}-{self._server_id}-{folder}",
153  event,
154  )
155  except aiosyncthing.exceptions.SyncthingError:
156  _LOGGER.warning(
157  (
158  "The syncthing server '%s' is not available. Sleeping %i"
159  " seconds and retrying"
160  ),
161  self._client_client.url,
162  RECONNECT_INTERVAL.total_seconds(),
163  )
165  self._hass_hass, f"{SERVER_UNAVAILABLE}-{self._server_id}"
166  )
167  await asyncio.sleep(RECONNECT_INTERVAL.total_seconds())
168  server_was_unavailable = True
169  continue
170 
171  async def _server_available(self):
172  try:
173  await self._client_client.system.ping()
174  except aiosyncthing.exceptions.SyncthingError:
175  return False
176 
177  return True
def __init__(self, hass, client, server_id)
Definition: __init__.py:83
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:70
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:33
None async_dispatcher_send(HomeAssistant hass, str signal, *Any args)
Definition: dispatcher.py:193