mirror of https://github.com/home-assistant/core
220 lines
7.2 KiB
Python
220 lines
7.2 KiB
Python
"""The Minecraft Server integration."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
from homeassistant.config_entries import ConfigEntry
|
|
from homeassistant.const import (
|
|
CONF_ADDRESS,
|
|
CONF_HOST,
|
|
CONF_NAME,
|
|
CONF_PORT,
|
|
CONF_TYPE,
|
|
Platform,
|
|
)
|
|
from homeassistant.core import HomeAssistant, callback
|
|
from homeassistant.exceptions import ConfigEntryError
|
|
import homeassistant.helpers.device_registry as dr
|
|
import homeassistant.helpers.entity_registry as er
|
|
|
|
from .api import MinecraftServer, MinecraftServerAddressError, MinecraftServerType
|
|
from .const import DOMAIN, KEY_LATENCY, KEY_MOTD
|
|
from .coordinator import MinecraftServerCoordinator
|
|
|
|
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
"""Set up Minecraft Server from a config entry."""
|
|
|
|
# Create API instance.
|
|
api = MinecraftServer(
|
|
hass,
|
|
entry.data.get(CONF_TYPE, MinecraftServerType.JAVA_EDITION),
|
|
entry.data[CONF_ADDRESS],
|
|
)
|
|
|
|
# Initialize API instance.
|
|
try:
|
|
await api.async_initialize()
|
|
except MinecraftServerAddressError as error:
|
|
raise ConfigEntryError(
|
|
f"Server address in configuration entry is invalid: {error}"
|
|
) from error
|
|
|
|
# Create coordinator instance.
|
|
coordinator = MinecraftServerCoordinator(hass, entry.data[CONF_NAME], api)
|
|
await coordinator.async_config_entry_first_refresh()
|
|
|
|
# Store coordinator instance.
|
|
domain_data = hass.data.setdefault(DOMAIN, {})
|
|
domain_data[entry.entry_id] = coordinator
|
|
|
|
# Set up platforms.
|
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
|
|
|
return True
|
|
|
|
|
|
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
|
"""Unload Minecraft Server config entry."""
|
|
|
|
# Unload platforms.
|
|
unload_ok = await hass.config_entries.async_unload_platforms(
|
|
config_entry, PLATFORMS
|
|
)
|
|
|
|
# Clean up.
|
|
hass.data[DOMAIN].pop(config_entry.entry_id)
|
|
|
|
return unload_ok
|
|
|
|
|
|
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
|
"""Migrate old config entry to a new format."""
|
|
|
|
# 1 --> 2: Use config entry ID as base for unique IDs.
|
|
if config_entry.version == 1:
|
|
_LOGGER.debug("Migrating from version 1")
|
|
|
|
old_unique_id = config_entry.unique_id
|
|
assert old_unique_id
|
|
config_entry_id = config_entry.entry_id
|
|
|
|
# Migrate config entry.
|
|
_LOGGER.debug("Migrating config entry. Resetting unique ID: %s", old_unique_id)
|
|
config_entry.unique_id = None
|
|
config_entry.version = 2
|
|
hass.config_entries.async_update_entry(config_entry)
|
|
|
|
# Migrate device.
|
|
await _async_migrate_device_identifiers(hass, config_entry, old_unique_id)
|
|
|
|
# Migrate entities.
|
|
await er.async_migrate_entries(hass, config_entry_id, _migrate_entity_unique_id)
|
|
|
|
_LOGGER.debug("Migration to version 2 successful")
|
|
|
|
# 2 --> 3: Use address instead of host and port in config entry.
|
|
if config_entry.version == 2:
|
|
_LOGGER.debug("Migrating from version 2")
|
|
|
|
config_data = config_entry.data
|
|
|
|
# Migrate config entry.
|
|
address = config_data[CONF_HOST]
|
|
api = MinecraftServer(hass, MinecraftServerType.JAVA_EDITION, address)
|
|
|
|
try:
|
|
await api.async_initialize()
|
|
host_only_lookup_success = True
|
|
except MinecraftServerAddressError as error:
|
|
host_only_lookup_success = False
|
|
_LOGGER.debug(
|
|
"Hostname (without port) cannot be parsed, trying again with port: %s",
|
|
error,
|
|
)
|
|
|
|
if not host_only_lookup_success:
|
|
address = f"{config_data[CONF_HOST]}:{config_data[CONF_PORT]}"
|
|
api = MinecraftServer(hass, MinecraftServerType.JAVA_EDITION, address)
|
|
|
|
try:
|
|
await api.async_initialize()
|
|
except MinecraftServerAddressError as error:
|
|
_LOGGER.exception(
|
|
"Can't migrate configuration entry due to error while parsing server address, try again later: %s",
|
|
error,
|
|
)
|
|
return False
|
|
|
|
_LOGGER.debug(
|
|
"Migrating config entry, replacing host '%s' and port '%s' with address '%s'",
|
|
config_data[CONF_HOST],
|
|
config_data[CONF_PORT],
|
|
address,
|
|
)
|
|
|
|
new_data = config_data.copy()
|
|
new_data[CONF_ADDRESS] = address
|
|
del new_data[CONF_HOST]
|
|
del new_data[CONF_PORT]
|
|
config_entry.version = 3
|
|
hass.config_entries.async_update_entry(config_entry, data=new_data)
|
|
|
|
_LOGGER.debug("Migration to version 3 successful")
|
|
|
|
return True
|
|
|
|
|
|
async def _async_migrate_device_identifiers(
|
|
hass: HomeAssistant, config_entry: ConfigEntry, old_unique_id: str | None
|
|
) -> None:
|
|
"""Migrate the device identifiers to the new format."""
|
|
device_registry = dr.async_get(hass)
|
|
device_entry_found = False
|
|
for device_entry in dr.async_entries_for_config_entry(
|
|
device_registry, config_entry.entry_id
|
|
):
|
|
for identifier in device_entry.identifiers:
|
|
if identifier[1] == old_unique_id:
|
|
# Device found in registry. Update identifiers.
|
|
new_identifiers = {
|
|
(
|
|
DOMAIN,
|
|
config_entry.entry_id,
|
|
)
|
|
}
|
|
_LOGGER.debug(
|
|
"Migrating device identifiers from %s to %s",
|
|
device_entry.identifiers,
|
|
new_identifiers,
|
|
)
|
|
device_registry.async_update_device(
|
|
device_id=device_entry.id, new_identifiers=new_identifiers
|
|
)
|
|
# Device entry found. Leave inner for loop.
|
|
device_entry_found = True
|
|
break
|
|
|
|
# Leave outer for loop if device entry is already found.
|
|
if device_entry_found:
|
|
break
|
|
|
|
|
|
@callback
|
|
def _migrate_entity_unique_id(entity_entry: er.RegistryEntry) -> dict[str, Any]:
|
|
"""Migrate the unique ID of an entity to the new format."""
|
|
|
|
# Different variants of unique IDs are available in version 1:
|
|
# 1) SRV record: '<host>-srv-<entity_type>'
|
|
# 2) Host & port: '<host>-<port>-<entity_type>'
|
|
# 3) IP address & port: '<mac_address>-<port>-<entity_type>'
|
|
unique_id_pieces = entity_entry.unique_id.split("-")
|
|
entity_type = unique_id_pieces[2]
|
|
|
|
# Handle bug in version 1: Entity type names were used instead of
|
|
# keys (e.g. "Protocol Version" instead of "protocol_version").
|
|
new_entity_type = entity_type.lower()
|
|
new_entity_type = new_entity_type.replace(" ", "_")
|
|
|
|
# Special case 'MOTD': Name and key differs.
|
|
if new_entity_type == "world_message":
|
|
new_entity_type = KEY_MOTD
|
|
|
|
# Special case 'latency_time': Renamed to 'latency'.
|
|
if new_entity_type == "latency_time":
|
|
new_entity_type = KEY_LATENCY
|
|
|
|
new_unique_id = f"{entity_entry.config_entry_id}-{new_entity_type}"
|
|
_LOGGER.debug(
|
|
"Migrating entity unique ID from %s to %s",
|
|
entity_entry.unique_id,
|
|
new_unique_id,
|
|
)
|
|
|
|
return {"new_unique_id": new_unique_id}
|