Add syncthing integration (#38331)
* Scaffold the integration
* Add config flow data schema
* Handle configuration errors
* Get folder states
* Support https
* Fix translations
* Listen to syncthing events in a separate thread
* Bump syncthing
* Automatically reconnect to the syncthing server
* Renames
* Improve loading and unloading
* Update folder states from events
* Refactoring, handle FolderPaused event
* Dynamic folder icons
* Refactoring
* Mark folders as unavailable when senrver is unavailable
* Update folder satus when server is available
* Raise PlatformNotReady
* Implement additional polling
* Stop polling when the server is not available
* Minor fixes
* Remove logging
* Check name uniqueness
* Refactoring
* Minor refactorings
* Bump python-syncthing
* Migrate to aiosyncthing
* Minor fixes
* Update .coveragerc
* Set quality scale
* Bump aiosyncthing, properly handle invalid token
* Fix logging
* Fix logging
* Use CONF_VERIFY_SSL from homeassistant.const
* Bump aiosyncthing. Add Syncthing device
* Fix device name
* Bump aiosyncthing
* Bump aiosyncthing
* Extract SyncthingClient
* Add folder to device_state_attributes
* Do not pass the loop
* Cover config_flow.py
* Move self.async_create_entry outside of the try block
* Raise ConfigEntryNotReady if syncthing server is not reachable
* Fix already configured error message
* Change default name to Syncthing
* Bump aiosyncthing
* Fix formatting
* Fix formatting
* Fix tests
* Fix typo, use lis comprehension
* Fix typo, remove unused CONFIG_SCHEMA
* Bump aiosyncthing
* Remove periods from log messages W0001
* Fix tests
* Black, isort
* Remove empty items from manifest.json
* Fix variable naming
* Remove async_setup
* Use SensorEntity
* Use asyncio.create_task instead of self._hass.loop.create_task
* Do not pass hass to FolderSensor initializer
* Rename device_state_attributes to extra_state_attributes
* Use callbacks
* Simplify tests
* Refactor _listen()
* Use url for the title
* Use the url instead of the name to identify the config entry
* Explicitly set sensor attributes, extract _filter_state
* Use server url instead of name in device_info
* Use server url instead of name in logs
* User server id as a device identifier
* Use URL instead of name to identify config entry
* Use shortened server id instead of name to build entity name and unique id
* Do not use CONF_NAME
* Cleanup unused strings
* Cleanup unused strings
* Add IOT class
* Scaffold the integration
* Add config flow data schema
* Handle configuration errors
* Get folder states
* Support https
* Fix translations
* Listen to syncthing events in a separate thread
* Bump syncthing
* Automatically reconnect to the syncthing server
* Renames
* Improve loading and unloading
* Update folder states from events
* Refactoring, handle FolderPaused event
* Dynamic folder icons
* Refactoring
* Mark folders as unavailable when senrver is unavailable
* Update folder satus when server is available
* Raise PlatformNotReady
* Implement additional polling
* Stop polling when the server is not available
* Minor fixes
* Remove logging
* Check name uniqueness
* Refactoring
* Minor refactorings
* Bump python-syncthing
* Migrate to aiosyncthing
* Minor fixes
* Update .coveragerc
* Set quality scale
* Bump aiosyncthing, properly handle invalid token
* Fix logging
* Fix logging
* Use CONF_VERIFY_SSL from homeassistant.const
* Bump aiosyncthing. Add Syncthing device
* Fix device name
* Bump aiosyncthing
* Bump aiosyncthing
* Extract SyncthingClient
* Add folder to device_state_attributes
* Do not pass the loop
* Cover config_flow.py
* Move self.async_create_entry outside of the try block
* Raise ConfigEntryNotReady if syncthing server is not reachable
* Fix already configured error message
* Change default name to Syncthing
* Bump aiosyncthing
* Fix formatting
* Fix formatting
* Fix tests
* Fix typo, use lis comprehension
* Fix typo, remove unused CONFIG_SCHEMA
* Bump aiosyncthing
* Remove periods from log messages W0001
* Fix tests
* Black, isort
* Remove empty items from manifest.json
* Fix variable naming
* Remove async_setup
* Use SensorEntity
* Use asyncio.create_task instead of self._hass.loop.create_task
* Do not pass hass to FolderSensor initializer
* Rename device_state_attributes to extra_state_attributes
* Use callbacks
* Simplify tests
* Refactor _listen()
* Use url for the title
* Use the url instead of the name to identify the config entry
* Explicitly set sensor attributes, extract _filter_state
* Use server url instead of name in device_info
* Use server url instead of name in logs
* User server id as a device identifier
* Use URL instead of name to identify config entry
* Use shortened server id instead of name to build entity name and unique id
* Do not use CONF_NAME
* Cleanup unused strings
* Cleanup unused strings
* Add IOT class
* Apply suggestions from code review
* Clean up
* Fix dict comprehension
* Clean sensor
* Use the server ID as a config entry unique ID
* Remove the AlreadyConfigured exception
* Clean up old error string
* Format json
* Convert sensor attributes to snake case
* Force CI
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-05-08 17:12:14 +00:00
|
|
|
"""The syncthing integration."""
|
|
|
|
import asyncio
|
|
|
|
import logging
|
|
|
|
|
|
|
|
import aiosyncthing
|
|
|
|
|
|
|
|
from homeassistant.config_entries import ConfigEntry
|
|
|
|
from homeassistant.const import (
|
|
|
|
CONF_TOKEN,
|
|
|
|
CONF_URL,
|
|
|
|
CONF_VERIFY_SSL,
|
|
|
|
EVENT_HOMEASSISTANT_STOP,
|
2021-12-04 12:43:48 +00:00
|
|
|
Platform,
|
Add syncthing integration (#38331)
* Scaffold the integration
* Add config flow data schema
* Handle configuration errors
* Get folder states
* Support https
* Fix translations
* Listen to syncthing events in a separate thread
* Bump syncthing
* Automatically reconnect to the syncthing server
* Renames
* Improve loading and unloading
* Update folder states from events
* Refactoring, handle FolderPaused event
* Dynamic folder icons
* Refactoring
* Mark folders as unavailable when senrver is unavailable
* Update folder satus when server is available
* Raise PlatformNotReady
* Implement additional polling
* Stop polling when the server is not available
* Minor fixes
* Remove logging
* Check name uniqueness
* Refactoring
* Minor refactorings
* Bump python-syncthing
* Migrate to aiosyncthing
* Minor fixes
* Update .coveragerc
* Set quality scale
* Bump aiosyncthing, properly handle invalid token
* Fix logging
* Fix logging
* Use CONF_VERIFY_SSL from homeassistant.const
* Bump aiosyncthing. Add Syncthing device
* Fix device name
* Bump aiosyncthing
* Bump aiosyncthing
* Extract SyncthingClient
* Add folder to device_state_attributes
* Do not pass the loop
* Cover config_flow.py
* Move self.async_create_entry outside of the try block
* Raise ConfigEntryNotReady if syncthing server is not reachable
* Fix already configured error message
* Change default name to Syncthing
* Bump aiosyncthing
* Fix formatting
* Fix formatting
* Fix tests
* Fix typo, use lis comprehension
* Fix typo, remove unused CONFIG_SCHEMA
* Bump aiosyncthing
* Remove periods from log messages W0001
* Fix tests
* Black, isort
* Remove empty items from manifest.json
* Fix variable naming
* Remove async_setup
* Use SensorEntity
* Use asyncio.create_task instead of self._hass.loop.create_task
* Do not pass hass to FolderSensor initializer
* Rename device_state_attributes to extra_state_attributes
* Use callbacks
* Simplify tests
* Refactor _listen()
* Use url for the title
* Use the url instead of the name to identify the config entry
* Explicitly set sensor attributes, extract _filter_state
* Use server url instead of name in device_info
* Use server url instead of name in logs
* User server id as a device identifier
* Use URL instead of name to identify config entry
* Use shortened server id instead of name to build entity name and unique id
* Do not use CONF_NAME
* Cleanup unused strings
* Cleanup unused strings
* Add IOT class
* Scaffold the integration
* Add config flow data schema
* Handle configuration errors
* Get folder states
* Support https
* Fix translations
* Listen to syncthing events in a separate thread
* Bump syncthing
* Automatically reconnect to the syncthing server
* Renames
* Improve loading and unloading
* Update folder states from events
* Refactoring, handle FolderPaused event
* Dynamic folder icons
* Refactoring
* Mark folders as unavailable when senrver is unavailable
* Update folder satus when server is available
* Raise PlatformNotReady
* Implement additional polling
* Stop polling when the server is not available
* Minor fixes
* Remove logging
* Check name uniqueness
* Refactoring
* Minor refactorings
* Bump python-syncthing
* Migrate to aiosyncthing
* Minor fixes
* Update .coveragerc
* Set quality scale
* Bump aiosyncthing, properly handle invalid token
* Fix logging
* Fix logging
* Use CONF_VERIFY_SSL from homeassistant.const
* Bump aiosyncthing. Add Syncthing device
* Fix device name
* Bump aiosyncthing
* Bump aiosyncthing
* Extract SyncthingClient
* Add folder to device_state_attributes
* Do not pass the loop
* Cover config_flow.py
* Move self.async_create_entry outside of the try block
* Raise ConfigEntryNotReady if syncthing server is not reachable
* Fix already configured error message
* Change default name to Syncthing
* Bump aiosyncthing
* Fix formatting
* Fix formatting
* Fix tests
* Fix typo, use lis comprehension
* Fix typo, remove unused CONFIG_SCHEMA
* Bump aiosyncthing
* Remove periods from log messages W0001
* Fix tests
* Black, isort
* Remove empty items from manifest.json
* Fix variable naming
* Remove async_setup
* Use SensorEntity
* Use asyncio.create_task instead of self._hass.loop.create_task
* Do not pass hass to FolderSensor initializer
* Rename device_state_attributes to extra_state_attributes
* Use callbacks
* Simplify tests
* Refactor _listen()
* Use url for the title
* Use the url instead of the name to identify the config entry
* Explicitly set sensor attributes, extract _filter_state
* Use server url instead of name in device_info
* Use server url instead of name in logs
* User server id as a device identifier
* Use URL instead of name to identify config entry
* Use shortened server id instead of name to build entity name and unique id
* Do not use CONF_NAME
* Cleanup unused strings
* Cleanup unused strings
* Add IOT class
* Apply suggestions from code review
* Clean up
* Fix dict comprehension
* Clean sensor
* Use the server ID as a config entry unique ID
* Remove the AlreadyConfigured exception
* Clean up old error string
* Format json
* Convert sensor attributes to snake case
* Force CI
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-05-08 17:12:14 +00:00
|
|
|
)
|
|
|
|
from homeassistant.core import HomeAssistant
|
|
|
|
from homeassistant.exceptions import ConfigEntryNotReady
|
|
|
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
|
|
|
|
|
|
|
from .const import (
|
|
|
|
DOMAIN,
|
|
|
|
EVENTS,
|
|
|
|
RECONNECT_INTERVAL,
|
|
|
|
SERVER_AVAILABLE,
|
|
|
|
SERVER_UNAVAILABLE,
|
|
|
|
)
|
|
|
|
|
2021-12-04 12:43:48 +00:00
|
|
|
PLATFORMS = [Platform.SENSOR]
|
Add syncthing integration (#38331)
* Scaffold the integration
* Add config flow data schema
* Handle configuration errors
* Get folder states
* Support https
* Fix translations
* Listen to syncthing events in a separate thread
* Bump syncthing
* Automatically reconnect to the syncthing server
* Renames
* Improve loading and unloading
* Update folder states from events
* Refactoring, handle FolderPaused event
* Dynamic folder icons
* Refactoring
* Mark folders as unavailable when senrver is unavailable
* Update folder satus when server is available
* Raise PlatformNotReady
* Implement additional polling
* Stop polling when the server is not available
* Minor fixes
* Remove logging
* Check name uniqueness
* Refactoring
* Minor refactorings
* Bump python-syncthing
* Migrate to aiosyncthing
* Minor fixes
* Update .coveragerc
* Set quality scale
* Bump aiosyncthing, properly handle invalid token
* Fix logging
* Fix logging
* Use CONF_VERIFY_SSL from homeassistant.const
* Bump aiosyncthing. Add Syncthing device
* Fix device name
* Bump aiosyncthing
* Bump aiosyncthing
* Extract SyncthingClient
* Add folder to device_state_attributes
* Do not pass the loop
* Cover config_flow.py
* Move self.async_create_entry outside of the try block
* Raise ConfigEntryNotReady if syncthing server is not reachable
* Fix already configured error message
* Change default name to Syncthing
* Bump aiosyncthing
* Fix formatting
* Fix formatting
* Fix tests
* Fix typo, use lis comprehension
* Fix typo, remove unused CONFIG_SCHEMA
* Bump aiosyncthing
* Remove periods from log messages W0001
* Fix tests
* Black, isort
* Remove empty items from manifest.json
* Fix variable naming
* Remove async_setup
* Use SensorEntity
* Use asyncio.create_task instead of self._hass.loop.create_task
* Do not pass hass to FolderSensor initializer
* Rename device_state_attributes to extra_state_attributes
* Use callbacks
* Simplify tests
* Refactor _listen()
* Use url for the title
* Use the url instead of the name to identify the config entry
* Explicitly set sensor attributes, extract _filter_state
* Use server url instead of name in device_info
* Use server url instead of name in logs
* User server id as a device identifier
* Use URL instead of name to identify config entry
* Use shortened server id instead of name to build entity name and unique id
* Do not use CONF_NAME
* Cleanup unused strings
* Cleanup unused strings
* Add IOT class
* Scaffold the integration
* Add config flow data schema
* Handle configuration errors
* Get folder states
* Support https
* Fix translations
* Listen to syncthing events in a separate thread
* Bump syncthing
* Automatically reconnect to the syncthing server
* Renames
* Improve loading and unloading
* Update folder states from events
* Refactoring, handle FolderPaused event
* Dynamic folder icons
* Refactoring
* Mark folders as unavailable when senrver is unavailable
* Update folder satus when server is available
* Raise PlatformNotReady
* Implement additional polling
* Stop polling when the server is not available
* Minor fixes
* Remove logging
* Check name uniqueness
* Refactoring
* Minor refactorings
* Bump python-syncthing
* Migrate to aiosyncthing
* Minor fixes
* Update .coveragerc
* Set quality scale
* Bump aiosyncthing, properly handle invalid token
* Fix logging
* Fix logging
* Use CONF_VERIFY_SSL from homeassistant.const
* Bump aiosyncthing. Add Syncthing device
* Fix device name
* Bump aiosyncthing
* Bump aiosyncthing
* Extract SyncthingClient
* Add folder to device_state_attributes
* Do not pass the loop
* Cover config_flow.py
* Move self.async_create_entry outside of the try block
* Raise ConfigEntryNotReady if syncthing server is not reachable
* Fix already configured error message
* Change default name to Syncthing
* Bump aiosyncthing
* Fix formatting
* Fix formatting
* Fix tests
* Fix typo, use lis comprehension
* Fix typo, remove unused CONFIG_SCHEMA
* Bump aiosyncthing
* Remove periods from log messages W0001
* Fix tests
* Black, isort
* Remove empty items from manifest.json
* Fix variable naming
* Remove async_setup
* Use SensorEntity
* Use asyncio.create_task instead of self._hass.loop.create_task
* Do not pass hass to FolderSensor initializer
* Rename device_state_attributes to extra_state_attributes
* Use callbacks
* Simplify tests
* Refactor _listen()
* Use url for the title
* Use the url instead of the name to identify the config entry
* Explicitly set sensor attributes, extract _filter_state
* Use server url instead of name in device_info
* Use server url instead of name in logs
* User server id as a device identifier
* Use URL instead of name to identify config entry
* Use shortened server id instead of name to build entity name and unique id
* Do not use CONF_NAME
* Cleanup unused strings
* Cleanup unused strings
* Add IOT class
* Apply suggestions from code review
* Clean up
* Fix dict comprehension
* Clean sensor
* Use the server ID as a config entry unique ID
* Remove the AlreadyConfigured exception
* Clean up old error string
* Format json
* Convert sensor attributes to snake case
* Force CI
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-05-08 17:12:14 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
|
|
"""Set up syncthing from a config entry."""
|
|
|
|
data = entry.data
|
|
|
|
|
|
|
|
if DOMAIN not in hass.data:
|
|
|
|
hass.data[DOMAIN] = {}
|
|
|
|
|
|
|
|
client = aiosyncthing.Syncthing(
|
|
|
|
data[CONF_TOKEN],
|
|
|
|
url=data[CONF_URL],
|
|
|
|
verify_ssl=data[CONF_VERIFY_SSL],
|
|
|
|
)
|
|
|
|
|
|
|
|
try:
|
|
|
|
status = await client.system.status()
|
|
|
|
except aiosyncthing.exceptions.SyncthingError as exception:
|
|
|
|
await client.close()
|
|
|
|
raise ConfigEntryNotReady from exception
|
|
|
|
|
|
|
|
server_id = status["myID"]
|
|
|
|
|
|
|
|
syncthing = SyncthingClient(hass, client, server_id)
|
|
|
|
syncthing.subscribe()
|
|
|
|
hass.data[DOMAIN][entry.entry_id] = syncthing
|
|
|
|
|
|
|
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
|
|
|
|
|
|
|
async def cancel_listen_task(_):
|
|
|
|
await syncthing.unsubscribe()
|
|
|
|
|
|
|
|
entry.async_on_unload(
|
|
|
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, cancel_listen_task)
|
|
|
|
)
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
|
|
"""Unload a config entry."""
|
|
|
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
|
|
|
if unload_ok:
|
|
|
|
syncthing = hass.data[DOMAIN].pop(entry.entry_id)
|
|
|
|
await syncthing.unsubscribe()
|
|
|
|
|
|
|
|
return unload_ok
|
|
|
|
|
|
|
|
|
|
|
|
class SyncthingClient:
|
|
|
|
"""A Syncthing client."""
|
|
|
|
|
|
|
|
def __init__(self, hass, client, server_id):
|
|
|
|
"""Initialize the client."""
|
|
|
|
self._hass = hass
|
|
|
|
self._client = client
|
|
|
|
self._server_id = server_id
|
|
|
|
self._listen_task = None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def server_id(self):
|
|
|
|
"""Get server id."""
|
|
|
|
return self._server_id
|
|
|
|
|
|
|
|
@property
|
|
|
|
def url(self):
|
|
|
|
"""Get server URL."""
|
|
|
|
return self._client.url
|
|
|
|
|
|
|
|
@property
|
|
|
|
def database(self):
|
|
|
|
"""Get database namespace client."""
|
|
|
|
return self._client.database
|
|
|
|
|
|
|
|
@property
|
|
|
|
def system(self):
|
|
|
|
"""Get system namespace client."""
|
|
|
|
return self._client.system
|
|
|
|
|
|
|
|
def subscribe(self):
|
|
|
|
"""Start event listener coroutine."""
|
|
|
|
self._listen_task = asyncio.create_task(self._listen())
|
|
|
|
|
|
|
|
async def unsubscribe(self):
|
|
|
|
"""Stop event listener coroutine."""
|
|
|
|
if self._listen_task:
|
|
|
|
self._listen_task.cancel()
|
|
|
|
await self._client.close()
|
|
|
|
|
|
|
|
async def _listen(self):
|
|
|
|
"""Listen to Syncthing events."""
|
|
|
|
events = self._client.events
|
|
|
|
server_was_unavailable = False
|
|
|
|
while True:
|
|
|
|
if await self._server_available():
|
|
|
|
if server_was_unavailable:
|
|
|
|
_LOGGER.info(
|
|
|
|
"The syncthing server '%s' is back online", self._client.url
|
|
|
|
)
|
|
|
|
async_dispatcher_send(
|
|
|
|
self._hass, f"{SERVER_AVAILABLE}-{self._server_id}"
|
|
|
|
)
|
|
|
|
server_was_unavailable = False
|
|
|
|
else:
|
|
|
|
await asyncio.sleep(RECONNECT_INTERVAL.total_seconds())
|
|
|
|
continue
|
|
|
|
try:
|
|
|
|
async for event in events.listen():
|
|
|
|
if events.last_seen_id == 0:
|
|
|
|
continue # skipping historical events from the first batch
|
|
|
|
if event["type"] not in EVENTS:
|
|
|
|
continue
|
|
|
|
|
|
|
|
signal_name = EVENTS[event["type"]]
|
|
|
|
folder = None
|
|
|
|
if "folder" in event["data"]:
|
|
|
|
folder = event["data"]["folder"]
|
|
|
|
else: # A workaround, some events store folder id under `id` key
|
|
|
|
folder = event["data"]["id"]
|
|
|
|
async_dispatcher_send(
|
|
|
|
self._hass,
|
|
|
|
f"{signal_name}-{self._server_id}-{folder}",
|
|
|
|
event,
|
|
|
|
)
|
|
|
|
except aiosyncthing.exceptions.SyncthingError:
|
|
|
|
_LOGGER.info(
|
|
|
|
"The syncthing server '%s' is not available. Sleeping %i seconds and retrying",
|
|
|
|
self._client.url,
|
|
|
|
RECONNECT_INTERVAL.total_seconds(),
|
|
|
|
)
|
|
|
|
async_dispatcher_send(
|
|
|
|
self._hass, f"{SERVER_UNAVAILABLE}-{self._server_id}"
|
|
|
|
)
|
|
|
|
await asyncio.sleep(RECONNECT_INTERVAL.total_seconds())
|
|
|
|
server_was_unavailable = True
|
|
|
|
continue
|
|
|
|
|
|
|
|
async def _server_available(self):
|
|
|
|
try:
|
|
|
|
await self._client.system.ping()
|
|
|
|
except aiosyncthing.exceptions.SyncthingError:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return True
|