New Addon
Google BK
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from aiohttp.client_exceptions import ClientResponseError
|
||||
from injector import inject, singleton
|
||||
|
||||
from ..model import Coordinator, Backup
|
||||
from ..config import Config, Setting
|
||||
from ..util import GlobalInfo, Backoff, Estimator
|
||||
from .harequests import HaRequests
|
||||
from ..time import Time
|
||||
from ..worker import Worker
|
||||
from ..const import SOURCE_HA, SOURCE_GOOGLE_DRIVE
|
||||
from ..logger import getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
NOTIFICATION_TITLE = "Home Assistant Google Drive Backup is Having Trouble"
|
||||
NOTIFICATION_DESC_LINK = "The add-on is having trouble making backups and needs attention. Please visit the add-on [status page]({0}) for details."
|
||||
NOTIFICATION_DESC_STATIC = "The add-on is having trouble making backups and needs attention. Please visit the add-on status page for details."
|
||||
|
||||
MAX_BACKOFF = 60 * 5 # 5 minutes
|
||||
FIRST_BACKOFF = 60 # 1 minute
|
||||
|
||||
# Wait 5 minutes before logging
|
||||
NOTIFY_DELAY = 60 * 5 # 5 minute
|
||||
|
||||
OLD_BACKUP_ENTITY_NAME = "sensor.snapshot_backup"
|
||||
BACKUP_ENTITY_NAME = "sensor.backup_state"
|
||||
|
||||
REASSURING_MESSAGE = "Unable to reach Home Assistant (HTTP {0}). This is normal if Home Assistant is restarting. You will probably see some errors in the supervisor logs until it comes back online."
|
||||
|
||||
|
||||
@singleton
|
||||
class HaUpdater(Worker):
|
||||
@inject
|
||||
def __init__(self, requests: HaRequests, coordinator: Coordinator, config: Config, time: Time, global_info: GlobalInfo):
|
||||
self._config = config
|
||||
super().__init__("Sensor Updater", self.update, time, self.getInterval)
|
||||
self._time = time
|
||||
self._coordinator = coordinator
|
||||
self._requests: HaRequests = requests
|
||||
self._info = global_info
|
||||
self._notified = False
|
||||
self._backoff = Backoff(max=MAX_BACKOFF, base=FIRST_BACKOFF)
|
||||
self._first_error = None
|
||||
self._trigger_once = False
|
||||
|
||||
self._last_backup_update = None
|
||||
self.last_backup_update_time = time.now() - timedelta(days=1)
|
||||
self._config.subscribe(self.config_updated)
|
||||
self._last_interval = self.getInterval()
|
||||
|
||||
def config_updated(self):
|
||||
if self._last_interval != self.getInterval():
|
||||
self._wait_event.set()
|
||||
self._last_interval = self.getInterval()
|
||||
|
||||
def getInterval(self):
|
||||
return self._config.get(Setting.HA_REPORTING_INTERVAL_SECONDS)
|
||||
|
||||
async def update(self):
|
||||
try:
|
||||
if self._config.get(Setting.ENABLE_BACKUP_STALE_SENSOR):
|
||||
await self._requests.updateBackupStaleSensor('on' if self._stale() else 'off')
|
||||
if self._config.get(Setting.ENABLE_BACKUP_STATE_SENSOR):
|
||||
await self._maybeSendBackupUpdate()
|
||||
if self._config.get(Setting.NOTIFY_FOR_STALE_BACKUPS):
|
||||
if self._stale() and not self._notified:
|
||||
if self._info.url is None or len(self._info.url) == 0:
|
||||
message = NOTIFICATION_DESC_STATIC
|
||||
else:
|
||||
message = NOTIFICATION_DESC_LINK.format(self._info.url)
|
||||
await self._requests.sendNotification(NOTIFICATION_TITLE, message)
|
||||
self._notified = True
|
||||
elif not self._stale() and self._notified:
|
||||
await self._requests.dismissNotification()
|
||||
self._notified = False
|
||||
self._backoff.reset()
|
||||
self._first_error = None
|
||||
self._trigger_once = False
|
||||
except ClientResponseError as e:
|
||||
if self._first_error is None:
|
||||
self._first_error = self._time.now()
|
||||
if int(e.status / 100) == 5:
|
||||
if self._time.now() > self._first_error + timedelta(seconds=NOTIFY_DELAY):
|
||||
logger.error(
|
||||
"Unable to reach Home Assistant (HTTP {0}). This is normal if Home Assistant is restarting. You will probably see some errors in the supervisor logs until it comes back online.".format(e.status))
|
||||
else:
|
||||
logger.error("Trouble updating Home Assistant sensors.")
|
||||
self._last_backup_update = None
|
||||
await self._time.sleepAsync(self._backoff.backoff(e))
|
||||
except Exception as e:
|
||||
self._last_backup_update = None
|
||||
logger.error("Trouble updating Home Assistant sensors.")
|
||||
logger.printException(e)
|
||||
await self._time.sleepAsync(self._backoff.backoff(e))
|
||||
|
||||
async def _maybeSendBackupUpdate(self):
|
||||
update = self._buildBackupUpdate()
|
||||
if self._trigger_once or update != self._last_backup_update or self._time.now() > self.last_backup_update_time + timedelta(hours=1):
|
||||
if self._config.get(Setting.CALL_BACKUP_SNAPSHOT):
|
||||
await self._requests.updateEntity(OLD_BACKUP_ENTITY_NAME, update)
|
||||
else:
|
||||
await self._requests.updateEntity(BACKUP_ENTITY_NAME, update)
|
||||
self._last_backup_update = update
|
||||
self.last_backup_update_time = self._time.now()
|
||||
|
||||
def _stale(self):
|
||||
if self._info._first_sync:
|
||||
return False
|
||||
if self._info._last_error:
|
||||
return self._time.now() > self._info._last_success + timedelta(seconds=self._config.get(Setting.BACKUP_STALE_SECONDS))
|
||||
else:
|
||||
next_backup = self._coordinator.nextBackupTime(include_pending=False)
|
||||
if not next_backup:
|
||||
# no backups are configured
|
||||
return False
|
||||
|
||||
# Determine if a lot of time has passed since the last backup "should" have been made.
|
||||
warn_after = next_backup + timedelta(seconds=self._config.get(Setting.LONG_TERM_STALE_BACKUP_SECONDS))
|
||||
return self._time.now() >= warn_after
|
||||
|
||||
def _state(self):
|
||||
if self._stale():
|
||||
return "error"
|
||||
else:
|
||||
return "waiting" if self._info._first_sync else "backed_up"
|
||||
|
||||
def triggerRefresh(self):
|
||||
self._trigger_once = True
|
||||
|
||||
def _buildBackupUpdate(self):
|
||||
backups = list(filter(lambda s: not s.ignore(), self._coordinator.backups()))
|
||||
last = "Never"
|
||||
if len(backups) > 0:
|
||||
last = max(backups, key=lambda s: s.date()).date().isoformat()
|
||||
|
||||
def makeBackupData(backup: Backup):
|
||||
return {
|
||||
"name": backup.name(),
|
||||
"date": str(backup.date().isoformat()),
|
||||
"state": backup.status(),
|
||||
"size": backup.sizeString(),
|
||||
"slug": backup.slug()
|
||||
}
|
||||
ha_backups = list(filter(lambda s: s.getSource(SOURCE_HA) is not None, backups))
|
||||
drive_backups = list(filter(lambda s: s.getSource(SOURCE_GOOGLE_DRIVE) is not None, backups))
|
||||
|
||||
last_uploaded = "Never"
|
||||
if len(drive_backups) > 0:
|
||||
last_uploaded = max(drive_backups, key=lambda s: s.date()).date().isoformat()
|
||||
if self._config.get(Setting.CALL_BACKUP_SNAPSHOT):
|
||||
return {
|
||||
"state": self._state(),
|
||||
"attributes": {
|
||||
"friendly_name": "Snapshot State",
|
||||
"last_snapshot": last, # type: ignore
|
||||
"snapshots_in_google_drive": len(drive_backups),
|
||||
"snapshots_in_hassio": len(ha_backups),
|
||||
"snapshots_in_home_assistant": len(ha_backups),
|
||||
"size_in_google_drive": Estimator.asSizeString(sum(map(lambda v: v.sizeInt(), drive_backups))),
|
||||
"size_in_home_assistant": Estimator.asSizeString(sum(map(lambda v: v.sizeInt(), ha_backups))),
|
||||
"snapshots": list(map(makeBackupData, backups))
|
||||
}
|
||||
}
|
||||
else:
|
||||
source_metrics = self._coordinator.buildBackupMetrics()
|
||||
next = self._coordinator.nextBackupTime()
|
||||
if next is not None:
|
||||
next = next.isoformat()
|
||||
attr = {
|
||||
"friendly_name": "Backup State",
|
||||
"last_backup": last, # type: ignore
|
||||
"next_backup": next,
|
||||
"last_uploaded": last_uploaded,
|
||||
"backups_in_google_drive": len(drive_backups),
|
||||
"backups_in_home_assistant": len(ha_backups),
|
||||
"size_in_google_drive": Estimator.asSizeString(sum(map(lambda v: v.sizeInt(), drive_backups))),
|
||||
"size_in_home_assistant": Estimator.asSizeString(sum(map(lambda v: v.sizeInt(), ha_backups))),
|
||||
"backups": list(map(makeBackupData, backups))
|
||||
}
|
||||
if SOURCE_GOOGLE_DRIVE in source_metrics and 'free_space' in source_metrics[SOURCE_GOOGLE_DRIVE]:
|
||||
attr["free_space_in_google_drive"] = source_metrics[SOURCE_GOOGLE_DRIVE]['free_space']
|
||||
else:
|
||||
attr["free_space_in_google_drive"] = ""
|
||||
return {
|
||||
"state": self._state(),
|
||||
"attributes": attr
|
||||
}
|
||||
Reference in New Issue
Block a user