New Addon

Google BK
This commit is contained in:
2023-04-23 18:17:03 +07:00
parent 1c7fd2b476
commit 702d080a1e
248 changed files with 72745 additions and 0 deletions
@@ -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
}