Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions homeassistant/components/backup/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import IO, cast

from aiohttp import BodyPartReader
from aiohttp.hdrs import CONTENT_DISPOSITION
from aiohttp.hdrs import CONTENT_DISPOSITION, CONTENT_TYPE
from aiohttp.web import FileResponse, Request, Response, StreamResponse
from multidict import istr

Expand Down Expand Up @@ -76,7 +76,8 @@ async def get(
return Response(status=HTTPStatus.NOT_FOUND)

headers = {
CONTENT_DISPOSITION: f"attachment; filename={slugify(backup.name)}.tar"
CONTENT_DISPOSITION: f"attachment; filename={slugify(backup.name)}.tar",
CONTENT_TYPE: "application/x-tar",
}

try:
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/default_config/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"ssdp",
"stream",
"sun",
"usage_prediction",
"usb",
"webhook",
"zeroconf"
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/deluge/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ class DelugeSensorType(enum.StrEnum):
UPLOAD_SPEED_SENSOR = "upload_speed"
PROTOCOL_TRAFFIC_UPLOAD_SPEED_SENSOR = "protocol_traffic_upload_speed"
PROTOCOL_TRAFFIC_DOWNLOAD_SPEED_SENSOR = "protocol_traffic_download_speed"
DOWNLOADING_COUNT_SENSOR = "downloading_count"
SEEDING_COUNT_SENSOR = "seeding_count"
45 changes: 37 additions & 8 deletions homeassistant/components/deluge/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

from collections import Counter
from datetime import timedelta
from ssl import SSLError
from typing import Any
Expand All @@ -14,11 +15,22 @@
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import LOGGER, DelugeGetSessionStatusKeys
from .const import LOGGER, DelugeGetSessionStatusKeys, DelugeSensorType

type DelugeConfigEntry = ConfigEntry[DelugeDataUpdateCoordinator]


def count_states(data: dict[str, Any]) -> dict[str, int]:
"""Count the states of the provided torrents."""

counts = Counter(torrent[b"state"].decode() for torrent in data.values())

return {
DelugeSensorType.DOWNLOADING_COUNT_SENSOR.value: counts.get("Downloading", 0),
DelugeSensorType.SEEDING_COUNT_SENSOR.value: counts.get("Seeding", 0),
}


class DelugeDataUpdateCoordinator(
DataUpdateCoordinator[dict[Platform, dict[str, Any]]]
):
Expand All @@ -39,19 +51,22 @@ def __init__(
)
self.api = api

async def _async_update_data(self) -> dict[Platform, dict[str, Any]]:
"""Get the latest data from Deluge and updates the state."""
def _get_deluge_data(self):
"""Get the latest data from Deluge."""

data = {}
try:
_data = await self.hass.async_add_executor_job(
self.api.call,
data["session_status"] = self.api.call(
"core.get_session_status",
[iter_member.value for iter_member in list(DelugeGetSessionStatusKeys)],
)
data[Platform.SENSOR] = {k.decode(): v for k, v in _data.items()}
data[Platform.SWITCH] = await self.hass.async_add_executor_job(
self.api.call, "core.get_torrents_status", {}, ["paused"]
data["torrents_status_state"] = self.api.call(
"core.get_torrents_status", {}, ["state"]
)
data["torrents_status_paused"] = self.api.call(
"core.get_torrents_status", {}, ["paused"]
)

except (
ConnectionRefusedError,
TimeoutError,
Expand All @@ -66,4 +81,18 @@ async def _async_update_data(self) -> dict[Platform, dict[str, Any]]:
) from ex
LOGGER.error("Unknown error connecting to Deluge: %s", ex)
raise

return data

async def _async_update_data(self) -> dict[Platform, dict[str, Any]]:
"""Get the latest data from Deluge and updates the state."""

deluge_data = await self.hass.async_add_executor_job(self._get_deluge_data)

data = {}
data[Platform.SENSOR] = {
k.decode(): v for k, v in deluge_data["session_status"].items()
}
data[Platform.SENSOR].update(count_states(deluge_data["torrents_status_state"]))
data[Platform.SWITCH] = deluge_data["torrents_status_paused"]
return data
12 changes: 12 additions & 0 deletions homeassistant/components/deluge/icons.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"entity": {
"sensor": {
"downloading_count": {
"default": "mdi:download"
},
"seeding_count": {
"default": "mdi:upload"
}
}
}
}
12 changes: 12 additions & 0 deletions homeassistant/components/deluge/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ class DelugeSensorEntityDescription(SensorEntityDescription):
data, DelugeSensorType.PROTOCOL_TRAFFIC_DOWNLOAD_SPEED_SENSOR.value
),
),
DelugeSensorEntityDescription(
key=DelugeSensorType.DOWNLOADING_COUNT_SENSOR.value,
translation_key=DelugeSensorType.DOWNLOADING_COUNT_SENSOR.value,
state_class=SensorStateClass.TOTAL,
value=lambda data: data[DelugeSensorType.DOWNLOADING_COUNT_SENSOR.value],
),
DelugeSensorEntityDescription(
key=DelugeSensorType.SEEDING_COUNT_SENSOR.value,
translation_key=DelugeSensorType.SEEDING_COUNT_SENSOR.value,
state_class=SensorStateClass.TOTAL,
value=lambda data: data[DelugeSensorType.SEEDING_COUNT_SENSOR.value],
),
)


Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/deluge/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
"idle": "[%key:common::state::idle%]"
}
},
"downloading_count": {
"name": "Downloading count",
"unit_of_measurement": "torrents"
},
"download_speed": {
"name": "Download speed"
},
Expand All @@ -45,6 +49,10 @@
"protocol_traffic_upload_speed": {
"name": "Protocol traffic upload speed"
},
"seeding_count": {
"name": "Seeding count",
"unit_of_measurement": "[%key:component::deluge::entity::sensor::downloading_count::unit_of_measurement%]"
},
"upload_speed": {
"name": "Upload speed"
}
Expand Down
39 changes: 39 additions & 0 deletions homeassistant/components/geocaching/entity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Sensor entities for Geocaching."""

from typing import cast

from geocachingapi.models import GeocachingCache

from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN
from .coordinator import GeocachingDataUpdateCoordinator


# Base class for all platforms
class GeocachingBaseEntity(CoordinatorEntity[GeocachingDataUpdateCoordinator]):
"""Base class for Geocaching sensors."""

_attr_has_entity_name = True


# Base class for cache entities
class GeocachingCacheEntity(GeocachingBaseEntity):
"""Base class for Geocaching cache entities."""

def __init__(
self, coordinator: GeocachingDataUpdateCoordinator, cache: GeocachingCache
) -> None:
"""Initialize the Geocaching cache entity."""
super().__init__(coordinator)
self.cache = cache

# A device can have multiple entities, and for a cache which requires multiple entities we want to group them together.
# Therefore, we create a device for each cache, which holds all related entities.
self._attr_device_info = DeviceInfo(
name=f"Geocache {cache.name}",
identifiers={(DOMAIN, cast(str, cache.reference_code))},
entry_type=DeviceEntryType.SERVICE,
manufacturer=cache.owner.username,
)
18 changes: 18 additions & 0 deletions homeassistant/components/geocaching/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@
},
"awarded_favorite_points": {
"default": "mdi:heart"
},
"cache_name": {
"default": "mdi:label"
},
"cache_owner": {
"default": "mdi:account"
},
"cache_found_date": {
"default": "mdi:calendar-search"
},
"cache_found": {
"default": "mdi:package-variant-closed-check"
},
"cache_favorite_points": {
"default": "mdi:star-check"
},
"cache_hidden_date": {
"default": "mdi:calendar-badge"
}
}
}
Expand Down
Loading
Loading