Skip to content

Commit dcb2087

Browse files
ElCruncharinohugo-vrijswijkemontnemery
authored
Add backblaze b2 backup integration (#149627)
Co-authored-by: Hugo van Rijswijk <git@hugovr.nl> Co-authored-by: ElCruncharino <ElCruncharino@users.noreply.github.com> Co-authored-by: Erik Montnemery <erik@montnemery.com>
1 parent 7de94f3 commit dcb2087

24 files changed

+3341
-0
lines changed

.strict-typing

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ homeassistant.components.automation.*
107107
homeassistant.components.awair.*
108108
homeassistant.components.axis.*
109109
homeassistant.components.azure_storage.*
110+
homeassistant.components.backblaze_b2.*
110111
homeassistant.components.backup.*
111112
homeassistant.components.baf.*
112113
homeassistant.components.bang_olufsen.*

CODEOWNERS

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""The Backblaze B2 integration."""
2+
3+
from __future__ import annotations
4+
5+
from datetime import timedelta
6+
import logging
7+
from typing import Any
8+
9+
from b2sdk.v2 import B2Api, Bucket, InMemoryAccountInfo, exception
10+
11+
from homeassistant.config_entries import ConfigEntry
12+
from homeassistant.core import HomeAssistant
13+
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
14+
from homeassistant.helpers.event import async_track_time_interval
15+
16+
from .const import (
17+
BACKBLAZE_REALM,
18+
CONF_APPLICATION_KEY,
19+
CONF_BUCKET,
20+
CONF_KEY_ID,
21+
DATA_BACKUP_AGENT_LISTENERS,
22+
DOMAIN,
23+
)
24+
from .repairs import (
25+
async_check_for_repair_issues,
26+
create_bucket_access_restricted_issue,
27+
create_bucket_not_found_issue,
28+
)
29+
30+
_LOGGER = logging.getLogger(__name__)
31+
32+
type BackblazeConfigEntry = ConfigEntry[Bucket]
33+
34+
35+
async def async_setup_entry(hass: HomeAssistant, entry: BackblazeConfigEntry) -> bool:
36+
"""Set up Backblaze B2 from a config entry."""
37+
38+
info = InMemoryAccountInfo()
39+
b2_api = B2Api(info)
40+
41+
def _authorize_and_get_bucket_sync() -> Bucket:
42+
"""Synchronously authorize the Backblaze B2 account and retrieve the bucket.
43+
44+
This function runs in the event loop's executor as b2sdk operations are blocking.
45+
"""
46+
b2_api.authorize_account(
47+
BACKBLAZE_REALM,
48+
entry.data[CONF_KEY_ID],
49+
entry.data[CONF_APPLICATION_KEY],
50+
)
51+
return b2_api.get_bucket_by_name(entry.data[CONF_BUCKET])
52+
53+
try:
54+
bucket = await hass.async_add_executor_job(_authorize_and_get_bucket_sync)
55+
except exception.Unauthorized as err:
56+
raise ConfigEntryAuthFailed(
57+
translation_domain=DOMAIN,
58+
translation_key="invalid_credentials",
59+
) from err
60+
except exception.RestrictedBucket as err:
61+
create_bucket_access_restricted_issue(hass, entry, err.bucket_name)
62+
raise ConfigEntryNotReady(
63+
translation_domain=DOMAIN,
64+
translation_key="restricted_bucket",
65+
translation_placeholders={
66+
"restricted_bucket_name": err.bucket_name,
67+
},
68+
) from err
69+
except exception.NonExistentBucket as err:
70+
create_bucket_not_found_issue(hass, entry, entry.data[CONF_BUCKET])
71+
raise ConfigEntryNotReady(
72+
translation_domain=DOMAIN,
73+
translation_key="invalid_bucket_name",
74+
) from err
75+
except exception.ConnectionReset as err:
76+
raise ConfigEntryNotReady(
77+
translation_domain=DOMAIN,
78+
translation_key="cannot_connect",
79+
) from err
80+
except exception.MissingAccountData as err:
81+
raise ConfigEntryAuthFailed(
82+
translation_domain=DOMAIN,
83+
translation_key="invalid_auth",
84+
) from err
85+
86+
entry.runtime_data = bucket
87+
88+
def _async_notify_backup_listeners() -> None:
89+
"""Notify any registered backup agent listeners."""
90+
_LOGGER.debug("Notifying backup listeners for entry %s", entry.entry_id)
91+
for listener in hass.data.get(DATA_BACKUP_AGENT_LISTENERS, []):
92+
listener()
93+
94+
entry.async_on_unload(entry.async_on_state_change(_async_notify_backup_listeners))
95+
96+
async def _periodic_issue_check(_now: Any) -> None:
97+
"""Periodically check for repair issues."""
98+
await async_check_for_repair_issues(hass, entry)
99+
100+
entry.async_on_unload(
101+
async_track_time_interval(hass, _periodic_issue_check, timedelta(minutes=30))
102+
)
103+
104+
hass.async_create_task(async_check_for_repair_issues(hass, entry))
105+
106+
return True
107+
108+
109+
async def async_unload_entry(hass: HomeAssistant, entry: BackblazeConfigEntry) -> bool:
110+
"""Unload a Backblaze B2 config entry.
111+
112+
Any resources directly managed by this entry that need explicit shutdown
113+
would be handled here. In this case, the `async_on_state_change` listener
114+
handles the notification logic on unload.
115+
"""
116+
return True

0 commit comments

Comments
 (0)