diff --git a/API.md b/API.md index 90b3244c9..84f93fbb4 100644 --- a/API.md +++ b/API.md @@ -37,8 +37,9 @@ The addons from `addons` are only installed one. { "name": "xy bla", "slug": "xy", - "version": "INSTALL_VERSION", - "last_version": "VERSION_FOR_UPDATE", + "repository": "12345678|null", + "version": "LAST_VERSION", + "installed": "INSTALL_VERSION", "detached": "bool", "description": "description" } @@ -59,7 +60,7 @@ Get all available addons { "name": "xy bla", "slug": "xy", - "repository": "12345678|null", + "repository": "core|local|REP_ID", "version": "LAST_VERSION", "installed": "none|INSTALL_VERSION", "detached": "bool", @@ -70,8 +71,9 @@ Get all available addons { "slug": "12345678", "name": "Repitory Name", - "url": "WEBSITE", - "maintainer": "BLA BLU " + "source": "URL_OF_REPOSITORY", + "url": "null|WEBSITE", + "maintainer": "null|BLA BLU " } ] } diff --git a/hassio/addons/data.py b/hassio/addons/data.py index 5248778a6..28a7dc08f 100644 --- a/hassio/addons/data.py +++ b/hassio/addons/data.py @@ -12,9 +12,8 @@ from .validate import ( from ..const import ( FILE_HASSIO_ADDONS, ATTR_NAME, ATTR_VERSION, ATTR_SLUG, ATTR_DESCRIPTON, ATTR_STARTUP, ATTR_BOOT, ATTR_MAP, ATTR_OPTIONS, ATTR_PORTS, BOOT_AUTO, - DOCKER_REPO, ATTR_INSTALLED, ATTR_SCHEMA, ATTR_IMAGE, ATTR_DETACHED, - MAP_CONFIG, MAP_SSL, MAP_ADDONS, MAP_BACKUP, ATTR_REPOSITORY, ATTR_URL, - ATTR_MAINTAINER, ATTR_LAST_VERSION) + DOCKER_REPO, ATTR_SCHEMA, ATTR_IMAGE, MAP_CONFIG, MAP_SSL, MAP_ADDONS, + MAP_BACKUP, ATTR_REPOSITORY) from ..config import Config from ..tools import read_json_file, write_json_file @@ -142,48 +141,12 @@ class AddonsData(Config): return set(self._system_data.keys()) @property - def list_all_api(self): - """Return a list of available addons for api.""" - data = [] - all_addons = {**self._system_data, **self._addons_cache} - detached = self.list_detached - - for addon, values in all_addons.items(): - i_version = self._user_data.get(addon, {}).get(ATTR_VERSION) - - data.append({ - ATTR_NAME: values[ATTR_NAME], - ATTR_SLUG: addon, - ATTR_DESCRIPTON: values[ATTR_DESCRIPTON], - ATTR_VERSION: values[ATTR_VERSION], - ATTR_INSTALLED: i_version, - ATTR_DETACHED: addon in detached, - ATTR_REPOSITORY: values[ATTR_REPOSITORY], - }) - - return data - - @property - def list_installed_api(self): - """Return a list of available addons for api.""" - data = [] - all_addons = {**self._system_data, **self._addons_cache} - detached = self.list_detached - - for addon, values in all_addons.items(): - i_version = self._user_data.get(addon, {}).get(ATTR_VERSION) - - data.append({ - ATTR_NAME: values[ATTR_NAME], - ATTR_SLUG: addon, - ATTR_DESCRIPTON: values[ATTR_DESCRIPTON], - ATTR_VERSION: values[ATTR_VERSION], - ATTR_LAST_VERSION: values[ATTR_VERSION], - ATTR_INSTALLED: i_version, - ATTR_DETACHED: addon in detached - }) - - return data + def list_all(self): + """Return a list of all addons.""" + return { + **self._system_data, + **self._addons_cache + } def list_startup(self, start_type): """Get list of installed addon with need start by type.""" @@ -212,19 +175,9 @@ class AddonsData(Config): return addon_list @property - def list_repositories_api(self): + def list_repositories(self): """Return list of addon repositories.""" - repositories = [] - - for slug, data in self._repositories_data.items(): - repositories.append({ - ATTR_SLUG: slug, - ATTR_NAME: data[ATTR_NAME], - ATTR_URL: data.get(ATTR_URL), - ATTR_MAINTAINER: data.get(ATTR_MAINTAINER), - }) - - return repositories + return list(self._repositories_data.values()) def exists_addon(self, addon): """Return True if a addon exists.""" @@ -236,7 +189,7 @@ class AddonsData(Config): def version_installed(self, addon): """Return installed version.""" - return self._user_data[addon][ATTR_VERSION] + return self._user_data.get(addon, {}).get(ATTR_VERSION) def set_addon_install(self, addon, version): """Set addon as installed.""" diff --git a/hassio/addons/util.py b/hassio/addons/util.py index 152c28866..c0d097734 100644 --- a/hassio/addons/util.py +++ b/hassio/addons/util.py @@ -19,3 +19,8 @@ def extract_hash_from_path(path): if not RE_SHA1.match(repo_dir): return get_hash_from_repository(repo_dir) return repo_dir + + +def create_hash_index_list(name_list): + """Create a dict with hash from repositories list.""" + return {get_hash_from_repository(repo): repo for repo in name_list} diff --git a/hassio/api/host.py b/hassio/api/host.py index 822985cce..ab2021198 100644 --- a/hassio/api/host.py +++ b/hassio/api/host.py @@ -1,4 +1,5 @@ """Init file for HassIO host rest api.""" +import asyncio import logging import voluptuous as vol @@ -30,7 +31,7 @@ class APIHost(object): return { ATTR_TYPE: self.host_control.type, ATTR_VERSION: self.host_control.version, - ATTR_LAST_VERSION: self.host_control.last, + ATTR_LAST_VERSION: self.host_control.last_version, ATTR_FEATURES: self.host_control.features, ATTR_HOSTNAME: self.host_control.hostname, ATTR_OS: self.host_control.os_info, @@ -50,9 +51,10 @@ class APIHost(object): async def update(self, request): """Update host OS.""" body = await api_validate(SCHEMA_VERSION, request) - version = body.get(ATTR_VERSION) + version = body.get(ATTR_VERSION, self.host_control.last_version) if version == self.host_control.version: raise RuntimeError("Version is already in use") - return await self.host_control.update(version=version) + return await asyncio.shield( + self.host_control.update(version=version), loop=self.loop) diff --git a/hassio/api/supervisor.py b/hassio/api/supervisor.py index 8fc841af3..c3049c5df 100644 --- a/hassio/api/supervisor.py +++ b/hassio/api/supervisor.py @@ -5,9 +5,12 @@ import logging import voluptuous as vol from .util import api_process, api_process_raw, api_validate +from ..addons.util import create_hash_index_list from ..const import ( ATTR_ADDONS, ATTR_VERSION, ATTR_LAST_VERSION, ATTR_BETA_CHANNEL, - HASSIO_VERSION, ATTR_ADDONS_REPOSITORIES, ATTR_REPOSITORIES) + HASSIO_VERSION, ATTR_ADDONS_REPOSITORIES, ATTR_REPOSITORIES, + ATTR_REPOSITORY, ATTR_DESCRIPTON, ATTR_NAME, ATTR_SLUG, ATTR_INSTALLED, + ATTR_DETACHED, ATTR_SOURCE, ATTR_MAINTAINER, ATTR_URL) _LOGGER = logging.getLogger(__name__) @@ -33,6 +36,42 @@ class APISupervisor(object): self.addons = addons self.host_control = host_control + def _addons_list(self, only_installed): + """Return a list of addons.""" + data = [] + detached = self.addons.list_detached + + for addon, values in self.addons.list_all.items(): + i_version = self.addons.version_installed(addon) + + data.append({ + ATTR_NAME: values[ATTR_NAME], + ATTR_SLUG: addon, + ATTR_DESCRIPTON: values[ATTR_DESCRIPTON], + ATTR_VERSION: values[ATTR_VERSION], + ATTR_INSTALLED: i_version, + ATTR_DETACHED: addon in detached, + ATTR_REPOSITORY: values[ATTR_REPOSITORY], + }) + + return data + + def _repositories_list(self): + """Return a list of addons repositories.""" + data = [] + list_id = create_hash_index_list(self.config.addons_repositories) + + for repository in self.addons.list_repositories: + data.append({ + ATTR_SLUG: repository[ATTR_SLUG], + ATTR_NAME: repository[ATTR_NAME], + ATTR_SOURCE: list_id.get(repository[ATTR_SLUG]), + ATTR_URL: repository.get(ATTR_URL), + ATTR_MAINTAINER: repository.get(ATTR_MAINTAINER), + }) + + return data + @api_process async def ping(self, request): """Return ok for signal that the api is ready.""" @@ -45,7 +84,7 @@ class APISupervisor(object): ATTR_VERSION: HASSIO_VERSION, ATTR_LAST_VERSION: self.config.last_hassio, ATTR_BETA_CHANNEL: self.config.upstream_beta, - ATTR_ADDONS: self.addons.list_installed_api, + ATTR_ADDONS: self._addons_list(only_installed=True), ATTR_ADDONS_REPOSITORIES: self.config.addons_repositories, } @@ -53,8 +92,8 @@ class APISupervisor(object): async def available_addons(self, request): """Return information for all available addons.""" return { - ATTR_ADDONS: self.addons.list_all_api, - ATTR_REPOSITORIES: self.addons.list_repositories_api, + ATTR_ADDONS: self._addons_list(only_installed=False), + ATTR_REPOSITORIES: self._repositories_list(), } @api_process @@ -80,6 +119,9 @@ class APISupervisor(object): for url in set(old - new): self.addons.drop_git_repository(url) + # read repository + self.addons.read_data_from_repositories() + return True @api_process diff --git a/hassio/config.py b/hassio/config.py index 2e7b8f8a3..8965ced69 100644 --- a/hassio/config.py +++ b/hassio/config.py @@ -9,7 +9,7 @@ from voluptuous.humanize import humanize_error from .const import FILE_HASSIO_CONFIG, HASSIO_SHARE from .tools import ( - fetch_current_versions, write_json_file, read_json_file) + fetch_last_versions, write_json_file, read_json_file) _LOGGER = logging.getLogger(__name__) @@ -87,7 +87,7 @@ class CoreConfig(Config): async def fetch_update_infos(self): """Read current versions from web.""" - last = await fetch_current_versions( + last = await fetch_last_versions( self.websession, beta=self.upstream_beta) if last: diff --git a/hassio/const.py b/hassio/const.py index f48178328..8acc3e84c 100644 --- a/hassio/const.py +++ b/hassio/const.py @@ -1,7 +1,7 @@ """Const file for HassIO.""" from pathlib import Path -HASSIO_VERSION = '0.19' +HASSIO_VERSION = '0.20' URL_HASSIO_VERSION = ('https://raw.githubusercontent.com/home-assistant/' 'hassio/master/version.json') @@ -36,6 +36,7 @@ RESULT_OK = 'ok' ATTR_HOSTNAME = 'hostname' ATTR_OS = 'os' ATTR_TYPE = 'type' +ATTR_SOURCE = 'source' ATTR_FEATURES = 'features' ATTR_ADDONS = 'addons' ATTR_VERSION = 'version' diff --git a/hassio/core.py b/hassio/core.py index b4fae16ae..a0e74fa0f 100644 --- a/hassio/core.py +++ b/hassio/core.py @@ -57,11 +57,10 @@ class HassIO(object): # hostcontrol await self.host_control.load() - _LOGGER.info( - "Connected to HostControl. Type: %s Version: %s Hostname: %s " - "Features: %s", self.host_control.type, - self.host_control.version, self.host_control.hostname, - self.host_control.features) + + # schedule update info tasks + self.scheduler.register_task( + self.host_control.load, RUN_UPDATE_INFO_TASKS) # rest api views self.api.register_host(self.host_control) diff --git a/hassio/host_control.py b/hassio/host_control.py index 11a3db13c..845742f27 100644 --- a/hassio/host_control.py +++ b/hassio/host_control.py @@ -29,7 +29,7 @@ class HostControl(object): self.loop = loop self.active = False self.version = UNKNOWN - self.last = UNKNOWN + self.last_version = UNKNOWN self.type = UNKNOWN self.features = [] self.hostname = UNKNOWN @@ -57,8 +57,8 @@ class HostControl(object): writer.write("{}\n".format(command).encode()) data = await reader.readline() - response = data.decode() - _LOGGER.debug("Receive from HostControl: %s.", response) + response = data.decode().rstrip() + _LOGGER.info("Receive from HostControl: %s.", response) if response == "OK": return True @@ -70,7 +70,8 @@ class HostControl(object): try: return json.loads(response) except json.JSONDecodeError: - _LOGGER.warning("Json parse error from HostControl.") + _LOGGER.warning("Json parse error from HostControl '%s'.", + response) except asyncio.TimeoutError: _LOGGER.error("Timeout from HostControl!") @@ -88,7 +89,7 @@ class HostControl(object): return self.version = info.get(ATTR_VERSION, UNKNOWN) - self.last = info.get(ATTR_LAST_VERSION, UNKNOWN) + self.last_version = info.get(ATTR_LAST_VERSION, UNKNOWN) self.type = info.get(ATTR_TYPE, UNKNOWN) self.features = info.get(ATTR_FEATURES, []) self.hostname = info.get(ATTR_HOSTNAME, UNKNOWN) diff --git a/hassio/tools.py b/hassio/tools.py index f44e2a9ad..7fc3562bb 100644 --- a/hassio/tools.py +++ b/hassio/tools.py @@ -16,7 +16,7 @@ _RE_VERSION = re.compile(r"VERSION=(.*)") _IMAGE_ARCH = re.compile(r".*/([a-z0-9]*)-hassio-supervisor") -async def fetch_current_versions(websession, beta=False): +async def fetch_last_versions(websession, beta=False): """Fetch current versions from github. Is a coroutine. diff --git a/version.json b/version.json index 882bc7a7c..b2068bad3 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "hassio": "0.19", + "hassio": "0.20", "homeassistant": "0.43.2", "resinos": "0.6", "resinhup": "0.1",