mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
parent
e30db943db
commit
79de27544c
@ -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(
|
||||||
(
|
(
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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()
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -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."""
|
||||||
|
@ -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:
|
||||||
|
@ -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):
|
||||||
|
@ -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]
|
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user