from backup.config import Config, Setting from backup.file import JsonFileSaver from backup.worker import Worker from backup.exceptions import SupervisorFileSystemError from .harequests import HaRequests from injector import inject, singleton from backup.time import Time from backup.logger import getLogger from datetime import timedelta from asyncio import Lock LOGGER = getLogger(__name__) CHECK_DURATION = timedelta(seconds=60) ATTR_STATE = "state" ATTR_WATCHDOG = "watchdog" ATTR_NAME = "name" STATE_STOPPED = "stopped" STATE_STARTED = "started" STATES_STOPPED = ["stopped", "unknown", "error"] @singleton class AddonStopper(Worker): @inject def __init__(self, config: Config, requests: HaRequests, time: Time): super().__init__("StartandStopTimer", self.check, time, 10) self.requests = requests self.config = config self.time = time self.must_start = set() self.must_enable_watchdog = set() self.stop_start_check_time = time.now() self._backing_up = False self.allow_run = False self.lock = Lock() async def start(self, schedule=True): if schedule: await super().start() path = self.config.get(Setting.STOP_ADDON_STATE_PATH) if JsonFileSaver.exists(path): data = JsonFileSaver.read(path) self.must_enable_watchdog = set(data.get("watchdog", [])) self.must_start = set(data.get("start", [])) def allowRun(self): if not self.allow_run: for slug in self.config.get(Setting.STOP_ADDONS).split(','): if len(slug) == 0: continue self.must_start.add(slug) self.allow_run = True def isBackingUp(self, backingUp): self._backing_up = backingUp async def stopAddons(self, self_slug): async with self.lock: self._backing_up = True for slug in self.config.get(Setting.STOP_ADDONS).split(','): if slug == self_slug or len(slug) == 0: # Don't ask the supervisor to stop yourself. That would be BAD. continue try: info = await self.requests.getAddonInfo(slug) if info.get(ATTR_STATE, None) == STATE_STARTED: if info.get(ATTR_WATCHDOG, False): try: LOGGER.info("Temporarily disabling watchdog for addon '%s'", info.get(ATTR_NAME, slug)) await self.requests.updateAddonOptions(slug, {ATTR_WATCHDOG: False}) self.must_enable_watchdog.add(slug) except Exception as e: LOGGER.error("Unable to disable watchdog for addon {0}".format(info.get(ATTR_NAME, slug))) LOGGER.printException(e) try: LOGGER.info("Stopping addon '%s'", info.get(ATTR_NAME, slug)) await self.requests.stopAddon(slug) self.must_start.add(slug) except Exception as e: LOGGER.error("Unable to stop addon '{0}'".format(info.get(ATTR_NAME, slug))) LOGGER.printException(e) except Exception as e: LOGGER.error("Unable to lookup info for addon '{0}', please check your configuration".format(slug)) LOGGER.printException(e) self._save() async def startAddons(self): self._backing_up = False self.stop_start_check_time = self.time.now() + CHECK_DURATION await self.check() async def check(self): async with self.lock: if self._backing_up: return if not self.allow_run: return changes = False if len(self.must_start) > 0: for slug in list(self.must_start): try: info = await self.requests.getAddonInfo(slug) state = info.get(ATTR_STATE, None) if info.get(ATTR_STATE, None) in STATES_STOPPED: LOGGER.info("Starting addon '%s'", info.get(ATTR_NAME, slug)) await self.requests.startAddon(slug) self.must_start.remove(slug) changes = True elif info.get(ATTR_STATE, None) == STATE_STARTED and self.time.now() > self.stop_start_check_time: # Give up on restarting it, looks like it was never stopped self.must_start.remove(slug) changes = True else: LOGGER.error(f"Addon '{info.get(ATTR_NAME, slug)} had unrecognized state {state}'. The addon will most likely be unable to automatically restart this addon.", ) except Exception as e: LOGGER.error("Unable to start addon '%s'", slug) LOGGER.printException(e) self.must_start.remove(slug) changes = True if len(self.must_enable_watchdog) > 0: for slug in list(self.must_enable_watchdog): if slug in self.must_start: # Wait until we're done trying to start the addon before re-enabling the watchdog, otherwise the supervisor complains continue try: info = await self.requests.getAddonInfo(slug) if not info.get(ATTR_WATCHDOG, True): LOGGER.info("Re-enabling watchdog for addon '%s'", info.get(ATTR_NAME, slug)) await self.requests.updateAddonOptions(slug, {ATTR_WATCHDOG: True}) except Exception as e: LOGGER.error("Unable to re-enable watchdog for addon '%s'", slug) LOGGER.printException(e) self.must_enable_watchdog.remove(slug) changes = True if changes: self._save() def _save(self): try: path = self.config.get(Setting.STOP_ADDON_STATE_PATH) data = {"start": list(self.must_start), "watchdog": list(self.must_enable_watchdog)} JsonFileSaver.write(path, data) except OSError: raise SupervisorFileSystemError()