Simplify Jellyfin (#127353)

* Simplify Jellyfin

* Fix comment
This commit is contained in:
Joost Lekkerkerker 2024-10-04 15:59:11 +02:00 committed by GitHub
parent e30db943db
commit 79de27544c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 37 additions and 91 deletions

View File

@ -9,10 +9,9 @@ from homeassistant.helpers import device_registry as dr
from .client_wrapper import CannotConnect, InvalidAuth, create_client, validate_input from .client_wrapper import CannotConnect, InvalidAuth, create_client, validate_input
from .const import CONF_CLIENT_DEVICE_ID, DOMAIN, PLATFORMS from .const import CONF_CLIENT_DEVICE_ID, DOMAIN, PLATFORMS
from .coordinator import JellyfinDataUpdateCoordinator, SessionsDataUpdateCoordinator from .coordinator import JellyfinDataUpdateCoordinator
from .models import JellyfinData
type JellyfinConfigEntry = ConfigEntry[JellyfinData] type JellyfinConfigEntry = ConfigEntry[JellyfinDataUpdateCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: JellyfinConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: JellyfinConfigEntry) -> bool:
@ -36,20 +35,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: JellyfinConfigEntry) ->
server_info: dict[str, Any] = connect_result["Servers"][0] server_info: dict[str, Any] = connect_result["Servers"][0]
coordinators: dict[str, JellyfinDataUpdateCoordinator[Any]] = { coordinator = JellyfinDataUpdateCoordinator(hass, client, server_info, user_id)
"sessions": SessionsDataUpdateCoordinator(
hass, client, server_info, entry.data[CONF_CLIENT_DEVICE_ID], user_id
),
}
for coordinator in coordinators.values(): await coordinator.async_config_entry_first_refresh()
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = JellyfinData( entry.runtime_data = coordinator
client_device_id=entry.data[CONF_CLIENT_DEVICE_ID], entry.async_on_unload(client.stop)
jellyfin_client=client,
coordinators=coordinators,
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@ -58,19 +49,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: JellyfinConfigEntry) ->
async def async_unload_entry(hass: HomeAssistant, entry: JellyfinConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: JellyfinConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unloaded = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unloaded:
entry.runtime_data.jellyfin_client.stop()
return unloaded
async def async_remove_config_entry_device( async def async_remove_config_entry_device(
hass: HomeAssistant, config_entry: JellyfinConfigEntry, device_entry: dr.DeviceEntry hass: HomeAssistant, config_entry: JellyfinConfigEntry, device_entry: dr.DeviceEntry
) -> bool: ) -> bool:
"""Remove device from a config entry.""" """Remove device from a config entry."""
data = config_entry.runtime_data coordinator = config_entry.runtime_data
coordinator = data.coordinators["sessions"]
return not device_entry.identifiers.intersection( return not device_entry.identifiers.intersection(
( (

View File

@ -2,32 +2,28 @@
from __future__ import annotations from __future__ import annotations
from abc import ABC, abstractmethod
from datetime import timedelta from datetime import timedelta
from typing import Any, TypeVar from typing import Any
from jellyfin_apiclient_python import JellyfinClient from jellyfin_apiclient_python import JellyfinClient
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN, LOGGER, USER_APP_NAME from .const import CONF_CLIENT_DEVICE_ID, DOMAIN, LOGGER, USER_APP_NAME
JellyfinDataT = TypeVar(
"JellyfinDataT",
bound=dict[str, dict[str, Any]] | dict[str, Any],
)
class JellyfinDataUpdateCoordinator(DataUpdateCoordinator[JellyfinDataT], ABC): class JellyfinDataUpdateCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]):
"""Data update coordinator for the Jellyfin integration.""" """Data update coordinator for the Jellyfin integration."""
config_entry: ConfigEntry
def __init__( def __init__(
self, self,
hass: HomeAssistant, hass: HomeAssistant,
api_client: JellyfinClient, api_client: JellyfinClient,
system_info: dict[str, Any], system_info: dict[str, Any],
client_device_id: str,
user_id: str, user_id: str,
) -> None: ) -> None:
"""Initialize the coordinator.""" """Initialize the coordinator."""
@ -37,32 +33,18 @@ class JellyfinDataUpdateCoordinator(DataUpdateCoordinator[JellyfinDataT], ABC):
name=DOMAIN, name=DOMAIN,
update_interval=timedelta(seconds=10), update_interval=timedelta(seconds=10),
) )
self.api_client: JellyfinClient = api_client self.api_client = api_client
self.server_id: str = system_info["Id"] self.server_id: str = system_info["Id"]
self.server_name: str = system_info["Name"] self.server_name: str = system_info["Name"]
self.server_version: str | None = system_info.get("Version") self.server_version: str | None = system_info.get("Version")
self.client_device_id: str = client_device_id self.client_device_id: str = self.config_entry.data[CONF_CLIENT_DEVICE_ID]
self.user_id: str = user_id self.user_id: str = user_id
self.session_ids: set[str] = set() self.session_ids: set[str] = set()
self.device_ids: set[str] = set() self.device_ids: set[str] = set()
async def _async_update_data(self) -> JellyfinDataT: async def _async_update_data(self) -> dict[str, dict[str, Any]]:
"""Get the latest data from Jellyfin.""" """Get the latest data from Jellyfin."""
return await self._fetch_data()
@abstractmethod
async def _fetch_data(self) -> JellyfinDataT:
"""Fetch the actual data."""
class SessionsDataUpdateCoordinator(
JellyfinDataUpdateCoordinator[dict[str, dict[str, Any]]]
):
"""Sessions update coordinator for Jellyfin."""
async def _fetch_data(self) -> dict[str, dict[str, Any]]:
"""Fetch the data."""
sessions = await self.hass.async_add_executor_job( sessions = await self.hass.async_add_executor_job(
self.api_client.jellyfin.sessions self.api_client.jellyfin.sessions
) )

View File

@ -17,8 +17,7 @@ async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: JellyfinConfigEntry hass: HomeAssistant, entry: JellyfinConfigEntry
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Return diagnostics for a config entry.""" """Return diagnostics for a config entry."""
data = entry.runtime_data coordinator = entry.runtime_data
sessions = data.coordinators["sessions"]
return { return {
"entry": { "entry": {
@ -26,9 +25,9 @@ async def async_get_config_entry_diagnostics(
"data": async_redact_data(entry.data, TO_REDACT), "data": async_redact_data(entry.data, TO_REDACT),
}, },
"server": { "server": {
"id": sessions.server_id, "id": coordinator.server_id,
"name": sessions.server_name, "name": coordinator.server_name,
"version": sessions.server_version, "version": coordinator.server_version,
}, },
"sessions": [ "sessions": [
{ {
@ -42,6 +41,6 @@ async def async_get_config_entry_diagnostics(
"now_playing": session_data.get("NowPlayingItem"), "now_playing": session_data.get("NowPlayingItem"),
"play_state": session_data.get("PlayState"), "play_state": session_data.get("PlayState"),
} }
for session_id, session_data in sessions.data.items() for session_id, session_data in coordinator.data.items()
], ],
} }

View File

@ -7,17 +7,17 @@ from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DEFAULT_NAME, DOMAIN from .const import DEFAULT_NAME, DOMAIN
from .coordinator import JellyfinDataT, JellyfinDataUpdateCoordinator from .coordinator import JellyfinDataUpdateCoordinator
class JellyfinEntity(CoordinatorEntity[JellyfinDataUpdateCoordinator[JellyfinDataT]]): class JellyfinEntity(CoordinatorEntity[JellyfinDataUpdateCoordinator]):
"""Defines a base Jellyfin entity.""" """Defines a base Jellyfin entity."""
_attr_has_entity_name = True _attr_has_entity_name = True
def __init__( def __init__(
self, self,
coordinator: JellyfinDataUpdateCoordinator[JellyfinDataT], coordinator: JellyfinDataUpdateCoordinator,
description: EntityDescription, description: EntityDescription,
) -> None: ) -> None:
"""Initialize the Jellyfin entity.""" """Initialize the Jellyfin entity."""

View File

@ -31,8 +31,7 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Jellyfin media_player from a config entry.""" """Set up Jellyfin media_player from a config entry."""
jellyfin_data = entry.runtime_data coordinator = entry.runtime_data
coordinator = jellyfin_data.coordinators["sessions"]
@callback @callback
def handle_coordinator_update() -> None: def handle_coordinator_update() -> None:

View File

@ -56,9 +56,9 @@ async def async_get_media_source(hass: HomeAssistant) -> MediaSource:
"""Set up Jellyfin media source.""" """Set up Jellyfin media source."""
# Currently only a single Jellyfin server is supported # Currently only a single Jellyfin server is supported
entry: JellyfinConfigEntry = hass.config_entries.async_entries(DOMAIN)[0] entry: JellyfinConfigEntry = hass.config_entries.async_entries(DOMAIN)[0]
jellyfin_data = entry.runtime_data coordinator = entry.runtime_data
return JellyfinSource(hass, jellyfin_data.jellyfin_client, entry) return JellyfinSource(hass, coordinator.api_client, entry)
class JellyfinSource(MediaSource): class JellyfinSource(MediaSource):

View File

@ -1,18 +0,0 @@
"""Models for the Jellyfin integration."""
from __future__ import annotations
from dataclasses import dataclass
from jellyfin_apiclient_python import JellyfinClient
from .coordinator import JellyfinDataUpdateCoordinator
@dataclass
class JellyfinData:
"""Data for the Jellyfin integration."""
client_device_id: str
jellyfin_client: JellyfinClient
coordinators: dict[str, JellyfinDataUpdateCoordinator]

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -11,7 +12,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from . import JellyfinConfigEntry from . import JellyfinConfigEntry
from .coordinator import JellyfinDataT
from .entity import JellyfinEntity from .entity import JellyfinEntity
@ -19,10 +19,10 @@ from .entity import JellyfinEntity
class JellyfinSensorEntityDescription(SensorEntityDescription): class JellyfinSensorEntityDescription(SensorEntityDescription):
"""Describes Jellyfin sensor entity.""" """Describes Jellyfin sensor entity."""
value_fn: Callable[[JellyfinDataT], StateType] value_fn: Callable[[dict[str, dict[str, Any]]], StateType]
def _count_now_playing(data: JellyfinDataT) -> int: def _count_now_playing(data: dict[str, dict[str, Any]]) -> int:
"""Count the number of now playing.""" """Count the number of now playing."""
session_ids = [ session_ids = [
sid for (sid, session) in data.items() if "NowPlayingItem" in session sid for (sid, session) in data.items() if "NowPlayingItem" in session
@ -31,14 +31,14 @@ def _count_now_playing(data: JellyfinDataT) -> int:
return len(session_ids) return len(session_ids)
SENSOR_TYPES: dict[str, JellyfinSensorEntityDescription] = { SENSOR_TYPES: tuple[JellyfinSensorEntityDescription, ...] = (
"sessions": JellyfinSensorEntityDescription( JellyfinSensorEntityDescription(
key="watching", key="watching",
translation_key="watching", translation_key="watching",
value_fn=_count_now_playing, value_fn=_count_now_playing,
native_unit_of_measurement="clients", native_unit_of_measurement="clients",
) ),
} )
async def async_setup_entry( async def async_setup_entry(
@ -47,18 +47,16 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Jellyfin sensor based on a config entry.""" """Set up Jellyfin sensor based on a config entry."""
data = entry.runtime_data coordinator = entry.runtime_data
async_add_entities( async_add_entities(
JellyfinSensor(data.coordinators[coordinator_type], description) JellyfinSensor(coordinator, description) for description in SENSOR_TYPES
for coordinator_type, description in SENSOR_TYPES.items()
) )
class JellyfinSensor(JellyfinEntity, SensorEntity): class JellyfinSensor(JellyfinEntity, SensorEntity):
"""Defines a Jellyfin sensor entity.""" """Defines a Jellyfin sensor entity."""
_attr_has_entity_name = True
entity_description: JellyfinSensorEntityDescription entity_description: JellyfinSensorEntityDescription
@property @property