Exemple #1
0
async def async_save_file(location, content):
    """Save files."""
    logger = Logger("hacs.download.save")
    logger.debug(f"Saving {location}")
    mode = "w"
    encoding = "utf-8"
    errors = "ignore"

    if not isinstance(content, str):
        mode = "wb"
        encoding = None
        errors = None

    try:
        async with aiofiles.open(location,
                                 mode=mode,
                                 encoding=encoding,
                                 errors=errors) as outfile:
            await outfile.write(content)
            outfile.close()

        # Create gz for .js files
        if os.path.isfile(location):
            if location.endswith(".js") or location.endswith(".css"):
                with open(location, "rb") as f_in:
                    with gzip.open(location + ".gz", "wb") as f_out:
                        shutil.copyfileobj(f_in, f_out)

    except Exception as error:  # pylint: disable=broad-except
        msg = "Could not write data to {} - {}".format(location, error)
        logger.debug(msg)

    return os.path.exists(location)
Exemple #2
0
async def async_download_file(url):
    """
    Download files, and return the content.
    """
    hacs = get_hacs()
    logger = Logger("hacs.download.downloader")
    if url is None:
        return

    # There is a bug somewhere... TODO: Find that bug....
    if "tags/" in url:
        url = url.replace("tags/", "")

    logger.debug(f"Downloading {url}")

    result = None

    with async_timeout.timeout(60, loop=hacs.hass.loop):
        request = await hacs.session.get(url)

        # Make sure that we got a valid result
        if request.status == 200:
            result = await request.read()
        else:
            raise HacsException(
                "Got status code {} when trying to download {}".format(
                    request.status, url))

    return result
Exemple #3
0
 def print(self):
     """Print the current configuration to the log."""
     logger = Logger("hacs.configuration")
     config = self.to_json()
     for key in config:
         if key in ["config", "config_entry", "options", "token"]:
             continue
         logger.debug(f"{key}: {config[key]}")
Exemple #4
0
async def async_save_file(location, content):
    """Save files."""
    logger = Logger("hacs.download.save")
    logger.debug(f"Saving {location}")
    mode = "w"
    encoding = "utf-8"
    errors = "ignore"

    if not isinstance(content, str):
        mode = "wb"
        encoding = None
        errors = None

    try:
        async with aiofiles.open(location,
                                 mode=mode,
                                 encoding=encoding,
                                 errors=errors) as outfile:
            await outfile.write(content)
            outfile.close()

        # Create gz for .js files
        if os.path.isfile(location):
            if location.endswith(".js") or location.endswith(".css"):
                with open(location, "rb") as f_in:
                    with gzip.open(location + ".gz", "wb") as f_out:
                        shutil.copyfileobj(f_in, f_out)

        # Remove with 2.0
        if "themes" in location and location.endswith(".yaml"):
            filename = location.split("/")[-1]
            base = location.split("/themes/")[0]
            combined = f"{base}/themes/{filename}"
            if os.path.exists(combined):
                logger.info(f"Removing old theme file {combined}")
                os.remove(combined)

    except Exception as error:  # pylint: disable=broad-except
        msg = "Could not write data to {} - {}".format(location, error)
        logger.error(msg)
        return False

    return os.path.exists(location)
Exemple #5
0
class BackupNetDaemon:
    """BackupNetDaemon."""
    def __init__(self, repository):
        """Initialize."""
        self.repository = repository
        self.logger = Logger("hacs.backup")
        self.backup_path = (tempfile.gettempdir() +
                            "/hacs_persistent_netdaemon/" +
                            repository.data.name)

    def create(self):
        """Create a backup in /tmp"""
        if os.path.exists(self.backup_path):
            shutil.rmtree(self.backup_path)
            while os.path.exists(self.backup_path):
                sleep(0.1)
        os.makedirs(self.backup_path, exist_ok=True)

        for filename in os.listdir(self.repository.content.path.local):
            if filename.endswith(".yaml"):
                source_file_name = f"{self.repository.content.path.local}/{filename}"
                target_file_name = f"{self.backup_path}/{filename}"
                shutil.copyfile(source_file_name, target_file_name)

    def restore(self):
        """Create a backup in /tmp"""
        if os.path.exists(self.backup_path):
            for filename in os.listdir(self.backup_path):
                if filename.endswith(".yaml"):
                    source_file_name = f"{self.backup_path}/{filename}"
                    target_file_name = (
                        f"{self.repository.content.path.local}/{filename}")
                    shutil.copyfile(source_file_name, target_file_name)

    def cleanup(self):
        """Create a backup in /tmp"""
        if os.path.exists(self.backup_path):
            shutil.rmtree(self.backup_path)
            while os.path.exists(self.backup_path):
                sleep(0.1)
            self.logger.debug(f"Backup dir {self.backup_path} cleared")
Exemple #6
0
class Backup:
    """Backup."""
    def __init__(self, local_path, backup_path=BACKUP_PATH):
        """initialize."""
        self.logger = Logger("hacs.backup")
        self.local_path = local_path
        self.backup_path = backup_path
        self.backup_path_full = f"{self.backup_path}{self.local_path.split('/')[-1]}"

    def create(self):
        """Create a backup in /tmp"""
        if not os.path.exists(self.local_path):
            return
        if os.path.exists(self.backup_path):
            shutil.rmtree(self.backup_path)
            while os.path.exists(self.backup_path):
                sleep(0.1)
        os.makedirs(self.backup_path, exist_ok=True)

        try:
            if os.path.isfile(self.local_path):
                shutil.copyfile(self.local_path, self.backup_path_full)
                os.remove(self.local_path)
            else:
                shutil.copytree(self.local_path, self.backup_path_full)
                shutil.rmtree(self.local_path)
                while os.path.exists(self.local_path):
                    sleep(0.1)
            self.logger.debug(
                f"Backup for {self.local_path}, created in {self.backup_path_full}"
            )
        except Exception:  # pylint: disable=broad-except
            pass

    def restore(self):
        """Restore from backup."""
        if not os.path.exists(self.backup_path_full):
            return

        if os.path.isfile(self.backup_path_full):
            if os.path.exists(self.local_path):
                os.remove(self.local_path)
            shutil.copyfile(self.backup_path_full, self.local_path)
        else:
            if os.path.exists(self.local_path):
                shutil.rmtree(self.local_path)
                while os.path.exists(self.local_path):
                    sleep(0.1)
            shutil.copytree(self.backup_path_full, self.local_path)
        self.logger.debug(
            f"Restored {self.local_path}, from backup {self.backup_path_full}")

    def cleanup(self):
        """Cleanup backup files."""
        if os.path.exists(self.backup_path):
            shutil.rmtree(self.backup_path)
            while os.path.exists(self.backup_path):
                sleep(0.1)
            self.logger.debug(f"Backup dir {self.backup_path} cleared")
class Backup:
    """Backup."""
    def __init__(self, local_path):
        """initialize."""
        self.logger = Logger("hacs.backup")
        self.local_path = local_path
        self.backup_path = "/tmp/hacs_backup"

    def create(self):
        """Create a backup in /tmp"""
        if os.path.exists(self.backup_path):
            rmtree(self.backup_path)
            while os.path.exists(self.backup_path):
                sleep(0.1)
        os.makedirs(self.backup_path, exist_ok=True)

        try:
            if os.path.isfile(self.local_path):
                copy2(self.local_path, self.backup_path)
                os.remove(self.local_path)
            else:
                copy_tree(self.local_path, self.backup_path)
                rmtree(self.local_path)
                while os.path.exists(self.local_path):
                    sleep(0.1)
            self.logger.debug(
                f"Backup for {self.local_path}, created in {self.backup_path}")
        except Exception:  # pylint: disable=broad-except
            pass

    def restore(self):
        """Restore from backup."""
        if os.path.isfile(self.local_path):
            os.remove(self.local_path)
        else:
            rmtree(self.local_path)
            while os.path.exists(self.local_path):
                sleep(0.1)
        copy2(self.backup_path, self.local_path)
        self.logger.debug(
            f"Restored {self.local_path}, from backup {self.backup_path}")

    def cleanup(self):
        """Cleanup backup files."""
        rmtree(self.backup_path)
        while os.path.exists(self.backup_path):
            sleep(0.1)
        self.logger.debug(f"Backup dir {self.backup_path} cleared")
Exemple #8
0
class HacsAPI(HacsWebResponse):
    """HacsAPI class."""

    name = "hacsapi"

    def __init__(self):
        """Initialize."""
        self.logger = Logger("hacs.api")
        self.url = self.hacsapi + "/{endpoint}"

    async def post(self, request, endpoint):  # pylint: disable=unused-argument
        """Handle HACS API requests."""
        if self.system.disabled:
            return web.Response(status=404)
        self.endpoint = endpoint
        self.postdata = await request.post()
        self.raw_headers = request.raw_headers
        self.request = request
        self.logger.debug(f"Endpoint ({endpoint}) called")
        if self.configuration.dev:
            self.logger.debug(f"Raw headers ({self.raw_headers})")
            self.logger.debug(f"Postdata ({self.postdata})")
        if self.endpoint in APIRESPONSE:
            try:
                response = APIRESPONSE[self.endpoint]
                response = await response.response(self)
            except Exception as exception:
                render = self.render(f"error", message=exception)
                return web.Response(
                    body=render, content_type="text/html", charset="utf-8"
                )
        else:
            # Return default response.
            response = await APIRESPONSE["generic"].response(self)

        # set headers
        response.headers["Cache-Control"] = "max-age=0, must-revalidate"

        # serve the response
        return response
Exemple #9
0
class HacsData(Hacs):
    """HacsData class."""
    def __init__(self):
        """Initialize."""
        self.logger = Logger("hacs.data")

    def check_corrupted_files(self):
        """Return True if one (or more) of the files are corrupted."""
        for store in STORES:
            path = f"{self.system.config_path}/.storage/{STORES[store]}"
            if os.path.exists(path):
                if os.stat(path).st_size == 0:
                    # File is empty (corrupted)
                    return True
        return False

    def read(self, store):
        """Return data from a store."""
        path = f"{self.system.config_path}/.storage/{STORES[store]}"
        content = None
        if os.path.exists(path):
            with open(path, "r", encoding="utf-8") as storefile:
                content = storefile.read()
                content = json.loads(content)
        return content

    async def async_write(self):
        """Write content to the store files."""
        if self.system.status.background_task:
            return

        self.logger.debug("Saving data")

        # Hacs
        await async_save_to_store(self.hass, "hacs",
                                  {"view": self.configuration.frontend_mode})

        # Repositories
        content = {}
        for repository in self.repositories:
            if repository.repository_manifest is not None:
                repository_manifest = repository.repository_manifest.manifest
            else:
                repository_manifest = None
            content[repository.information.uid] = {
                "authors": repository.information.authors,
                "topics": repository.information.topics,
                "category": repository.information.category,
                "description": repository.information.description,
                "full_name": repository.information.full_name,
                "hide": repository.status.hide,
                "installed_commit": repository.versions.installed_commit,
                "installed": repository.status.installed,
                "last_commit": repository.versions.available_commit,
                "last_release_tag": repository.versions.available,
                "repository_manifest": repository_manifest,
                "name": repository.information.name,
                "new": repository.status.new,
                "selected_tag": repository.status.selected_tag,
                "show_beta": repository.status.show_beta,
                "version_installed": repository.versions.installed,
            }

        await async_save_to_store(self.hass, "repositories", content)
        self.hass.bus.async_fire("hacs/repository", {})
        self.hass.bus.fire("hacs/config", {})

    async def restore(self):
        """Restore saved data."""
        hacs = {}
        repositories = {}

        try:
            hacs = await async_load_from_store(self.hass, "hacs")
        except KeyError:
            await async_save_to_store(self.hass, "hacs",
                                      self.data.read("hacs")["data"])
            hacs = await async_load_from_store(self.hass, "hacs")

        try:
            repositories = await async_load_from_store(self.hass,
                                                       "repositories")
        except KeyError:
            await async_save_to_store(self.hass, "repositories",
                                      self.data.read("repositories")["data"])
            repositories = await async_load_from_store(self.hass,
                                                       "repositories")

        try:
            if self.check_corrupted_files():
                # Coruptted installation
                self.logger.critical(
                    "Restore failed one or more files are corrupted!")
                return False
            if hacs is None and repositories is None:
                # Assume new install
                return True

            self.logger.info("Restore started")

            # Hacs
            self.configuration.frontend_mode = hacs.get("view", "Grid")

            # Repositories
            repositories = repositories
            for entry in repositories:
                repo = repositories[entry]
                if repo["full_name"] == "custom-components/hacs":
                    # Skip the old repo location
                    continue
                if not self.is_known(repo["full_name"]):
                    await self.register_repository(repo["full_name"],
                                                   repo["category"], False)
                repository = self.get_by_name(repo["full_name"])
                if repository is None:
                    self.logger.error(f"Did not find {repo['full_name']}")
                    continue

                # Restore repository attributes
                if repo.get("authors") is not None:
                    repository.information.authors = repo["authors"]

                if repo.get("topics", []):
                    repository.information.topics = repo["topics"]

                if repo.get("description") is not None:
                    repository.information.description = repo["description"]

                if repo.get("name") is not None:
                    repository.information.name = repo["name"]

                if repo.get("hide") is not None:
                    repository.status.hide = repo["hide"]

                if repo.get("installed") is not None:
                    repository.status.installed = repo["installed"]
                    if repository.status.installed:
                        repository.status.first_install = False

                if repo.get("selected_tag") is not None:
                    repository.status.selected_tag = repo["selected_tag"]

                if repo.get("repository_manifest") is not None:
                    repository.repository_manifest = HacsManifest.from_dict(
                        repo["repository_manifest"])

                if repo.get("show_beta") is not None:
                    repository.status.show_beta = repo["show_beta"]

                if repo.get("last_commit") is not None:
                    repository.versions.available_commit = repo["last_commit"]

                repository.information.uid = entry

                if repo.get("last_release_tag") is not None:
                    repository.releases.last_release = repo["last_release_tag"]
                    repository.versions.available = repo["last_release_tag"]

                if repo.get("new") is not None:
                    repository.status.new = repo["new"]

                if repo["full_name"] == "hacs/integration":
                    repository.versions.installed = VERSION
                    repository.status.installed = True
                    if "b" in VERSION:
                        repository.status.show_beta = True
                elif repo.get("version_installed") is not None:
                    repository.versions.installed = repo["version_installed"]

                if repo.get("installed_commit") is not None:
                    repository.versions.installed_commit = repo[
                        "installed_commit"]

            self.logger.info("Restore done")
        except Exception as exception:
            self.logger.critical(
                f"[{exception}] Restore Failed! see https://github.com/hacs/integration/issues/639 for more details."
            )
            return False
        return True
Exemple #10
0
class WebClient:
    """Web client."""
    def __init__(self, session=None, logger=None):
        """
        Initialize.

        Sample Usage:
        from integrationhelper.webclient import WebClient
        url = "https://sample.com/api"
        webclient = WebClient()
        print(await webclient.async_get_json(url))
        """
        self.session = session
        if logger is not None:
            self.logger = logger
        else:
            from integrationhelper import Logger

            self.logger = Logger(__name__)

    @backoff.on_exception(backoff.expo, Exception, max_tries=3)
    async def async_get_json(self, url: str, custom_headers: dict = None):
        """Get json response from server."""
        try:
            assert isinstance(url, str)
        except AssertionError:
            self.logger.error(f"({url}) is not a string.")
            return None

        try:
            assert custom_headers is None or isinstance(custom_headers, dict)
        except AssertionError:
            self.logger.error(f"({custom_headers}) is not a dict.")
            return None

        headers = {"Content-Type": "application/json"}
        if custom_headers is not None:
            for header in custom_headers:
                headers[header] = custom_headers[header]

        jsondata = None
        try:
            if self.session is not None:
                async with async_timeout.timeout(
                        10, loop=asyncio.get_event_loop()):
                    response = await self.session.get(url, headers=headers)
                    if response.status not in GOOD_HTTP_CODES:
                        self.logger.error(
                            f"Recieved HTTP code ({response.status}) from {url}"
                        )
                        return jsondata
                    jsondata = await response.json()
            else:
                async with aiohttp.ClientSession() as session:
                    async with async_timeout.timeout(
                            10, loop=asyncio.get_event_loop()):
                        response = await session.get(url, headers=headers)
                        if response.status not in GOOD_HTTP_CODES:
                            self.logger.error(
                                f"Recieved HTTP code ({response.status}) from {url}"
                            )
                            return jsondata
                        jsondata = await response.json()

            self.logger.debug(jsondata)

        except asyncio.TimeoutError as error:
            self.logger.error(
                f"Timeout error fetching information from {url} - ({error})")
        except (KeyError, TypeError) as error:
            self.logger.error(
                f"Error parsing information from {url} - ({error})")
        except (aiohttp.ClientError, socket.gaierror) as error:
            self.logger.error(
                f"Error fetching information from {url} - ({error})")
        except Exception as error:  # pylint: disable=broad-except
            self.logger.error(f"Something really wrong happend! - ({error})")
        return jsondata

    @backoff.on_exception(backoff.expo, Exception, max_tries=3)
    async def async_get_text(self, url: str, custom_headers: dict = None):
        """Get text response from server."""
        try:
            assert isinstance(url, str)
        except AssertionError:
            self.logger.error(f"({url}) is not a string.")
            return None

        try:
            assert url is None or isinstance(custom_headers, dict)
        except AssertionError:
            self.logger.error(f"({custom_headers}) is not a dict.")
            return None

        headers = {"Content-Type": "application/json"}
        if custom_headers is not None:
            for header in custom_headers:
                headers[header] = custom_headers[header]

        textdata = None
        try:
            if self.session is not None:
                async with async_timeout.timeout(
                        10, loop=asyncio.get_event_loop()):
                    response = await self.session.get(url, headers=headers)
                    if response.status not in GOOD_HTTP_CODES:
                        self.logger.error(
                            f"Recieved HTTP code ({response.status}) from {url}"
                        )
                        return textdata
                    textdata = await response.text()
            else:
                async with aiohttp.ClientSession() as session:
                    async with async_timeout.timeout(
                            10, loop=asyncio.get_event_loop()):
                        response = await session.get(url, headers=headers)
                        if response.status not in GOOD_HTTP_CODES:
                            self.logger.error(
                                f"Recieved HTTP code ({response.status}) from {url}"
                            )
                            return textdata
                        textdata = await response.text()

            self.logger.debug(textdata)

        except asyncio.TimeoutError as error:
            self.logger.error(
                f"Timeout error fetching information from {url} - ({error})")
        except (KeyError, TypeError) as error:
            self.logger.error(
                f"Error parsing information from {url} - ({error})")
        except (aiohttp.ClientError, socket.gaierror) as error:
            self.logger.error(
                f"Error fetching information from {url} - ({error})")
        except Exception as error:  # pylint: disable=broad-except
            self.logger.error(f"Something really wrong happend! - ({error})")
        return textdata
Exemple #11
0
class HacsRepository(Hacs):
    """HacsRepository."""
    def __init__(self):
        """Set up HacsRepository."""

        self.content = RepositoryContent()
        self.content.path = RepositoryPath()
        self.information = RepositoryInformation()
        self.repository_object = None
        self.status = RepositoryStatus()
        self.repository_manifest = None
        self.validate = Validate()
        self.releases = RepositoryReleases()
        self.versions = RepositoryVersions()
        self.pending_restart = False
        self.logger = None

    @property
    def pending_upgrade(self):
        """Return pending upgrade."""
        if self.status.installed:
            if self.display_installed_version != self.display_available_version:
                return True

        return False

    @property
    def ref(self):
        """Return the ref."""
        if self.status.selected_tag is not None:
            if self.status.selected_tag == self.information.default_branch:
                return self.information.default_branch
            return "tags/{}".format(self.status.selected_tag)

        if self.releases.releases:
            return "tags/{}".format(self.versions.available)

        return self.information.default_branch

    @property
    def custom(self):
        """Return flag if the repository is custom."""
        if self.information.full_name.split("/")[0] in [
                "custom-components",
                "custom-cards",
        ]:
            return False
        if self.information.full_name in self.common.default:
            return False
        return True

    @property
    def can_install(self):
        """Return bool if repository can be installed."""
        target = None
        if self.information.homeassistant_version is not None:
            target = self.information.homeassistant_version
        if self.repository_manifest is not None:
            if self.repository_manifest.homeassistant is not None:
                target = self.repository_manifest.homeassistant

        if target is not None:
            if self.releases.releases:
                if LooseVersion(self.system.ha_version) < LooseVersion(target):
                    return False
        return True

    @property
    def display_name(self):
        """Return display name."""
        name = None
        if self.information.category == "integration":
            if self.manifest is not None:
                name = self.manifest["name"]

        if self.repository_manifest is not None:
            name = self.repository_manifest.name

        if name is not None:
            return name

        if self.information.name:
            name = self.information.name.replace("-",
                                                 " ").replace("_",
                                                              " ").title()

        if name is not None:
            return name

        name = self.information.full_name

        return name

    @property
    def display_status(self):
        """Return display_status."""
        if self.status.new:
            status = "new"
        elif self.pending_restart:
            status = "pending-restart"
        elif self.pending_upgrade:
            status = "pending-upgrade"
        elif self.status.installed:
            status = "installed"
        else:
            status = "default"
        return status

    @property
    def display_status_description(self):
        """Return display_status_description."""
        description = {
            "default": "Not installed.",
            "pending-restart": "Restart pending.",
            "pending-upgrade": "Upgrade pending.",
            "installed": "No action required.",
            "new": "This is a newly added repository.",
        }
        return description[self.display_status]

    @property
    def display_installed_version(self):
        """Return display_authors"""
        if self.versions.installed is not None:
            installed = self.versions.installed
        else:
            if self.versions.installed_commit is not None:
                installed = self.versions.installed_commit
            else:
                installed = ""
        return installed

    @property
    def display_available_version(self):
        """Return display_authors"""
        if self.versions.available is not None:
            available = self.versions.available
        else:
            if self.versions.available_commit is not None:
                available = self.versions.available_commit
            else:
                available = ""
        return available

    @property
    def display_version_or_commit(self):
        """Does the repositoriy use releases or commits?"""
        if self.versions.installed is not None:
            version_or_commit = "version"
        else:
            version_or_commit = "commit"
        return version_or_commit

    @property
    def main_action(self):
        """Return the main action."""
        actions = {
            "new": "INSTALL",
            "default": "INSTALL",
            "installed": "REINSTALL",
            "pending-restart": "REINSTALL",
            "pending-upgrade": "UPGRADE",
        }
        return actions[self.display_status]

    async def common_validate(self):
        """Common validation steps of the repository."""
        # Attach helpers
        self.validate.errors = []
        self.logger = Logger(
            f"hacs.repository.{self.information.category}.{self.information.full_name}"
        )

        # Step 1: Make sure the repository exist.
        self.logger.debug("Checking repository.")
        try:
            self.repository_object = await self.github.get_repo(
                self.information.full_name)
        except Exception as exception:  # Gotta Catch 'Em All
            if not self.system.status.startup:
                self.logger.error(exception)
            self.validate.errors.append("Repository does not exist.")
            return

        # Step 2: Make sure the repository is not archived.
        if self.repository_object.archived:
            self.validate.errors.append("Repository is archived.")
            return

        # Step 3: Make sure the repository is not in the blacklist.
        if self.information.full_name in self.common.blacklist:
            self.validate.errors.append("Repository is in the blacklist.")
            return

        # Step 4: default branch
        self.information.default_branch = self.repository_object.default_branch

        # Step 5: Get releases.
        await self.get_releases()

        # Set repository name
        self.information.name = self.information.full_name.split("/")[1]

    async def common_registration(self):
        """Common registration steps of the repository."""
        # Attach logger
        if self.logger is None:
            self.logger = Logger(
                f"hacs.repository.{self.information.category}.{self.information.full_name}"
            )

        # Attach repository
        if self.repository_object is None:
            self.repository_object = await self.github.get_repo(
                self.information.full_name)

        # Set id
        self.information.uid = str(self.repository_object.id)

        # Set topics
        self.information.topics = self.repository_object.topics

        # Set description
        if self.repository_object.description:
            self.information.description = self.repository_object.description

    async def common_update(self):
        """Common information update steps of the repository."""
        # Attach logger
        if self.logger is None:
            self.logger = Logger(
                f"hacs.repository.{self.information.category}.{self.information.full_name}"
            )

        # Attach repository
        self.repository_object = await self.github.get_repo(
            self.information.full_name)

        # Update description
        if self.repository_object.description:
            self.information.description = self.repository_object.description

        # Update default branch
        if self.information.full_name != "custom-components/hacs":
            self.information.default_branch = self.repository_object.default_branch
        else:
            self.information.default_branch = "next"

        # Update last available commit
        await self.repository_object.set_last_commit()
        self.versions.available_commit = self.repository_object.last_commit

        # Update last updaeted
        self.information.last_updated = self.repository_object.pushed_at

        # Update topics
        self.information.topics = self.repository_object.topics

        # Get the content of hacs.json
        await self.get_repository_manifest_content()

        # Update "info.md"
        await self.get_info_md_content()

        # Update releases
        await self.get_releases()

    async def install(self):
        """Common installation steps of the repository."""
        self.validate.errors = []

        await self.update_repository()

        if self.status.installed and not self.content.single:
            backup = Backup(self.content.path.local)
            backup.create()

        validate = await self.download_content(self.validate,
                                               self.content.path.remote,
                                               self.content.path.local,
                                               self.ref)

        if validate.errors:
            for error in validate.errors:
                self.logger.error(error)
            if self.status.installed and not self.content.single:
                backup.restore()

        if self.status.installed and not self.content.single:
            backup.cleanup()

        if validate.success:
            if self.information.full_name not in self.common.installed:
                if self.information.full_name != "custom-components/hacs":
                    self.common.installed.append(self.information.full_name)
            self.status.installed = True
            self.versions.installed_commit = self.versions.available_commit

            if self.status.selected_tag is not None:
                self.versions.installed = self.status.selected_tag
            else:
                self.versions.installed = self.versions.available

            if self.information.category == "integration":
                if (self.config_flow and self.information.full_name !=
                        "custom-components/hacs"):
                    await self.reload_custom_components()
                else:
                    self.pending_restart = True

    async def download_content(self, validate, directory_path, local_directory,
                               ref):
        """Download the content of a directory."""
        try:
            # Get content
            if self.content.single:
                contents = self.content.objects
            else:
                contents = await self.repository_object.get_contents(
                    directory_path, self.ref)

            for content in contents:
                if content.type == "dir" and self.content.path.remote != "":
                    await self.download_content(validate, content.path,
                                                local_directory, ref)
                    continue
                if self.information.category == "plugin":
                    if not content.name.endswith(".js"):
                        if self.content.path.remote != "dist":
                            continue

                self.logger.debug(f"Downloading {content.name}")

                filecontent = await async_download_file(
                    self.hass, content.download_url)

                if filecontent is None:
                    validate.errors.append(
                        f"[{content.name}] was not downloaded.")
                    continue

                # Save the content of the file.
                if self.content.single:
                    local_directory = self.content.path.local
                else:
                    _content_path = content.path
                    _content_path = _content_path.replace(
                        f"{self.content.path.remote}/", "")

                    local_directory = f"{self.content.path.local}/{_content_path}"
                    local_directory = local_directory.split("/")
                    del local_directory[-1]
                    local_directory = "/".join(local_directory)

                # Check local directory
                pathlib.Path(local_directory).mkdir(parents=True,
                                                    exist_ok=True)

                local_file_path = f"{local_directory}/{content.name}"
                result = await async_save_file(local_file_path, filecontent)
                if result:
                    self.logger.info(f"download of {content.name} complete")
                    continue
                validate.errors.append(f"[{content.name}] was not downloaded.")

        except SystemError:
            pass
        return validate

    async def get_repository_manifest_content(self):
        """Get the content of the hacs.json file."""
        try:
            manifest = await self.repository_object.get_contents(
                "hacs.json", self.ref)
            self.repository_manifest = HacsManifest(
                json.loads(manifest.content))
        except AIOGitHubException:  # Gotta Catch 'Em All
            pass

    async def get_info_md_content(self):
        """Get the content of info.md"""
        from ..handler.template import render_template

        info = None
        info_files = ["info", "info.md"]

        if self.repository_manifest is not None:
            if self.repository_manifest.render_readme:
                info_files = ["readme", "readme.md"]
        try:
            root = await self.repository_object.get_contents("", self.ref)
            for file in root:
                if file.name.lower() in info_files:
                    info = await self.repository_object.get_contents(
                        file.name, self.ref)
                    break
            if info is None:
                self.information.additional_info = ""
            else:
                info = await self.github.render_markdown(info.content)
                info = info.replace("&lt;", "<")
                info = info.replace("<h3>", "<h6>").replace("</h3>", "</h6>")
                info = info.replace("<h2>", "<h5>").replace("</h2>", "</h5>")
                info = info.replace("<h1>", "<h4>").replace("</h1>", "</h4>")
                info = info.replace("<code>", "<code class='codeinfo'>")
                info = info.replace(
                    '<a href="http',
                    '<a rel="noreferrer" target="_blank" href="http')
                info = info.replace("<ul>", "")
                info = info.replace("</ul>", "")
                info += "</br>"
                self.information.additional_info = render_template(info, self)

        except Exception:  # Gotta Catch 'Em All
            self.information.additional_info = ""

    async def get_releases(self):
        """Get repository releases."""
        if self.status.show_beta:
            temp = await self.repository_object.get_releases(prerelease=True)
        else:
            temp = await self.repository_object.get_releases(prerelease=False)

        if not temp:
            return

        self.releases.releases = True

        self.releases.published_tags = []

        for release in temp:
            self.releases.published_tags.append(release.tag_name)

        self.releases.last_release_object = temp[0]
        if self.status.selected_tag is not None:
            if self.status.selected_tag != self.information.default_branch:
                for release in temp:
                    if release.tag_name == self.status.selected_tag:
                        self.releases.last_release_object = release
                        break
        self.versions.available = temp[0].tag_name

    def remove(self):
        """Run remove tasks."""
        # Attach logger
        if self.logger is None:
            self.logger = Logger(
                f"hacs.repository.{self.information.category}.{self.information.full_name}"
            )
        self.logger.info("Starting removal")

        if self.information.uid in self.common.installed:
            self.common.installed.remove(self.information.uid)
        for repository in self.repositories:
            if repository.information.uid == self.information.uid:
                self.repositories.remove(repository)

    async def uninstall(self):
        """Run uninstall tasks."""
        # Attach logger
        if self.logger is None:
            self.logger = Logger(
                f"hacs.repository.{self.information.category}.{self.information.full_name}"
            )
        self.logger.info("Uninstalling")
        await self.remove_local_directory()
        self.status.installed = False
        if self.information.category == "integration":
            if self.config_flow:
                await self.reload_custom_components()
            else:
                self.pending_restart = True
        if self.information.full_name in self.common.installed:
            self.common.installed.remove(self.information.full_name)
        self.versions.installed = None
        self.versions.installed_commit = None

    async def remove_local_directory(self):
        """Check the local directory."""
        import os
        import shutil
        from asyncio import sleep

        try:
            if self.information.category == "python_script":
                local_path = "{}/{}.py".format(self.content.path.local,
                                               self.information.name)
            elif self.information.category == "theme":
                local_path = "{}/{}.yaml".format(self.content.path.local,
                                                 self.information.name)
            else:
                local_path = self.content.path.local

            if os.path.exists(local_path):
                self.logger.debug(f"Removing {local_path}")

                if self.information.category in ["python_script", "theme"]:
                    os.remove(local_path)
                else:
                    shutil.rmtree(local_path)

                while os.path.exists(local_path):
                    await sleep(1)

        except Exception as exception:
            self.logger.debug(f"Removing {local_path} failed with {exception}")
            return
Exemple #12
0
class HacsData:
    """HacsData class."""
    def __init__(self):
        """Initialize."""
        self.logger = Logger("hacs.data")
        self.hacs = get_hacs()

    async def async_write(self):
        """Write content to the store files."""
        if self.hacs.system.status.background_task or self.hacs.system.disabled:
            return

        self.logger.debug("Saving data")

        # Hacs
        await async_save_to_store(
            self.hacs.hass,
            "hacs",
            {
                "view": self.hacs.configuration.frontend_mode,
                "compact": self.hacs.configuration.frontend_compact,
                "onboarding_done": self.hacs.configuration.onboarding_done,
            },
        )

        # Repositories
        content = {}
        for repository in self.hacs.repositories:
            if repository.repository_manifest is not None:
                repository_manifest = repository.repository_manifest.manifest
            else:
                repository_manifest = None
            data = {
                "authors": repository.data.authors,
                "category": repository.data.category,
                "description": repository.data.description,
                "domain": repository.data.domain,
                "downloads": repository.data.downloads,
                "full_name": repository.data.full_name,
                "first_install": repository.status.first_install,
                "installed_commit": repository.data.installed_commit,
                "installed": repository.data.installed,
                "last_commit": repository.data.last_commit,
                "last_release_tag": repository.data.last_version,
                "last_updated": repository.data.last_updated,
                "name": repository.data.name,
                "new": repository.data.new,
                "repository_manifest": repository_manifest,
                "selected_tag": repository.data.selected_tag,
                "show_beta": repository.data.show_beta,
                "stars": repository.data.stargazers_count,
                "topics": repository.data.topics,
                "version_installed": repository.data.installed_version,
            }
            if data:
                if repository.data.installed and (
                        repository.data.installed_commit
                        or repository.data.installed_version):
                    await async_save_to_store(
                        self.hacs.hass,
                        f"hacs/{repository.data.id}.hacs",
                        repository.data.to_json(),
                    )
                content[str(repository.data.id)] = data

        await async_save_to_store(self.hacs.hass, "repositories", content)
        self.hacs.hass.bus.async_fire("hacs/repository", {})
        self.hacs.hass.bus.fire("hacs/config", {})

    async def restore(self):
        """Restore saved data."""
        hacs = await async_load_from_store(self.hacs.hass, "hacs")
        repositories = await async_load_from_store(self.hacs.hass,
                                                   "repositories")
        try:
            if not hacs and not repositories:
                # Assume new install
                self.hacs.system.status.new = True
                return True
            self.logger.info("Restore started")
            self.hacs.system.status.new = False

            # Hacs
            self.hacs.configuration.frontend_mode = hacs.get("view", "Grid")
            self.hacs.configuration.frontend_compact = hacs.get(
                "compact", False)
            self.hacs.configuration.onboarding_done = hacs.get(
                "onboarding_done", False)

            # Repositories
            for entry in repositories:
                repo = repositories[entry]
                if not self.hacs.is_known(entry):
                    await register_repository(repo["full_name"],
                                              repo["category"], False)
                repository = [
                    x for x in self.hacs.repositories
                    if str(x.data.id) == str(entry)
                    or x.data.full_name == repo["full_name"]
                ]
                if not repository:
                    self.logger.error(
                        f"Did not find {repo['full_name']} ({entry})")
                    continue

                repository = repository[0]

                # Restore repository attributes
                repository.data.id = entry
                await self.hacs.hass.async_add_executor_job(
                    restore_repository_data, repository, repo)

                restored = await async_load_from_store(self.hacs.hass,
                                                       f"hacs/{entry}.hacs")

                if restored:
                    repository.data.update_data(restored)
                    if not repository.data.installed:
                        repository.logger.debug(
                            "Should be installed but is not... Fixing that!")
                        repository.data.installed = True

            self.logger.info("Restore done")
        except Exception as exception:  # pylint: disable=broad-except
            self.logger.critical(f"[{exception}] Restore Failed!")
            return False
        return True
Exemple #13
0
class HacsData(Hacs):
    """HacsData class."""
    def __init__(self):
        """Initialize."""
        self.logger = Logger("hacs.data")

    async def async_write(self):
        """Write content to the store files."""
        if self.system.status.background_task:
            return

        self.logger.debug("Saving data")

        # Hacs
        await async_save_to_store(self.hass, "hacs",
                                  {"view": self.configuration.frontend_mode})

        # Repositories
        content = {}
        for repository in self.repositories:
            if repository.repository_manifest is not None:
                repository_manifest = repository.repository_manifest.manifest
            else:
                repository_manifest = None
            content[repository.information.uid] = {
                "authors": repository.information.authors,
                "topics": repository.information.topics,
                "category": repository.information.category,
                "description": repository.information.description,
                "full_name": repository.information.full_name,
                "hide": repository.status.hide,
                "installed_commit": repository.versions.installed_commit,
                "installed": repository.status.installed,
                "last_commit": repository.versions.available_commit,
                "last_release_tag": repository.versions.available,
                "repository_manifest": repository_manifest,
                "name": repository.information.name,
                "new": repository.status.new,
                "selected_tag": repository.status.selected_tag,
                "show_beta": repository.status.show_beta,
                "version_installed": repository.versions.installed,
            }

        await async_save_to_store(self.hass, "repositories", content)
        self.hass.bus.async_fire("hacs/repository", {})
        self.hass.bus.fire("hacs/config", {})

    async def restore(self):
        """Restore saved data."""
        hacs = await async_load_from_store(self.hass, "hacs")
        repositories = await async_load_from_store(self.hass, "repositories")
        try:
            if not hacs and not repositories:
                # Assume new install
                return True
            self.logger.info("Restore started")

            # Hacs
            self.configuration.frontend_mode = hacs.get("view", "Grid")

            # Repositories
            repositories = repositories
            for entry in repositories:
                repo = repositories[entry]
                if repo["full_name"] == "custom-components/hacs":
                    # Skip the old repo location
                    continue
                if not self.is_known(repo["full_name"]):
                    await self.register_repository(repo["full_name"],
                                                   repo["category"], False)
                repository = self.get_by_name(repo["full_name"])
                if repository is None:
                    self.logger.error(f"Did not find {repo['full_name']}")
                    continue

                # Restore repository attributes
                repository.information.uid = entry
                await self.hass.async_add_executor_job(restore_repository_data,
                                                       repository, repo)

            self.logger.info("Restore done")
        except Exception as exception:  # pylint: disable=broad-except
            self.logger.critical(f"[{exception}] Restore Failed!")
            return False
        return True
Exemple #14
0
class HacsData:
    """HacsData class."""
    def __init__(self):
        """Initialize."""
        self.logger = Logger("hacs.data")
        self.hacs = get_hacs()

    async def async_write(self):
        """Write content to the store files."""
        if self.hacs.system.status.background_task or self.hacs.system.disabled:
            return

        self.logger.debug("Saving data")

        # Hacs
        await async_save_to_store(
            self.hacs.hass,
            "hacs",
            {
                "view": self.hacs.configuration.frontend_mode,
                "compact": self.hacs.configuration.frontend_compact,
                "onboarding_done": self.hacs.configuration.onboarding_done,
            },
        )

        await async_save_to_store(self.hacs.hass, "removed",
                                  [x.__dict__ for x in removed_repositories])

        # Repositories
        content = {}
        for repository in self.hacs.repositories:
            if repository.repository_manifest is not None:
                repository_manifest = repository.repository_manifest.manifest
            else:
                repository_manifest = None
            content[repository.information.uid] = {
                "authors": repository.data.authors,
                "category": repository.data.category,
                "description": repository.data.description,
                "downloads": repository.releases.downloads,
                "full_name": repository.data.full_name,
                "first_install": repository.status.first_install,
                "hide": repository.status.hide,
                "installed_commit": repository.versions.installed_commit,
                "installed": repository.status.installed,
                "last_commit": repository.versions.available_commit,
                "last_release_tag": repository.versions.available,
                "last_updated": repository.information.last_updated,
                "name": repository.data.name,
                "new": repository.status.new,
                "repository_manifest": repository_manifest,
                "selected_tag": repository.status.selected_tag,
                "show_beta": repository.status.show_beta,
                "stars": repository.data.stargazers_count,
                "topics": repository.data.topics,
                "version_installed": repository.versions.installed,
            }

        await async_save_to_store(self.hacs.hass, "repositories", content)
        self.hacs.hass.bus.async_fire("hacs/repository", {})
        self.hacs.hass.bus.fire("hacs/config", {})

    async def restore(self):
        """Restore saved data."""
        hacs = await async_load_from_store(self.hacs.hass, "hacs")
        repositories = await async_load_from_store(self.hacs.hass,
                                                   "repositories")
        removed = await async_load_from_store(self.hacs.hass, "removed")
        try:
            if not hacs and not repositories:
                # Assume new install
                self.hacs.system.status.new = True
                return True
            self.logger.info("Restore started")

            # Hacs
            self.hacs.configuration.frontend_mode = hacs.get("view", "Grid")
            self.hacs.configuration.frontend_compact = hacs.get(
                "compact", False)
            self.hacs.configuration.onboarding_done = hacs.get(
                "onboarding_done", False)

            for entry in removed:
                removed_repo = get_removed(entry["repository"])
                removed_repo.update_data(entry)

            # Repositories
            for entry in repositories:
                repo = repositories[entry]
                if not self.hacs.is_known(repo["full_name"]):
                    await register_repository(repo["full_name"],
                                              repo["category"], False)
                repository = self.hacs.get_by_name(repo["full_name"])
                if repository is None:
                    self.logger.error(f"Did not find {repo['full_name']}")
                    continue

                # Restore repository attributes
                repository.information.uid = entry
                await self.hacs.hass.async_add_executor_job(
                    restore_repository_data, repository, repo)

            self.logger.info("Restore done")
        except Exception as exception:  # pylint: disable=broad-except
            self.logger.critical(f"[{exception}] Restore Failed!")
            return False
        return True
Exemple #15
0
class HacsWebResponse(HomeAssistantView, Hacs):
    """Base View Class for HACS."""

    requires_auth = False
    name = "hacs"

    def __init__(self):
        """Initialize."""
        self.logger = Logger("hacs.http")
        self.url = self.hacsweb + "/{path:.+}"
        self.endpoint = None
        self.postdata = None
        self.raw_headers = None
        self.repository_id = None
        self.request = None
        self.requested_file = None

    async def get(self, request, path):  # pylint: disable=unused-argument
        """Handle HACS Web requests."""
        if self.system.disabled:
            return web.Response(status=404)
        self.endpoint = path.split("/")[0]
        self.raw_headers = request.raw_headers
        self.request = request
        self.requested_file = path.replace(self.endpoint + "/", "")
        self.repository_id = path.replace(self.endpoint + "/", "")
        if self.endpoint != "static":
            self.logger.debug(f"Endpoint ({self.endpoint}) called")
        if self.endpoint in WEBRESPONSE:
            try:
                response = WEBRESPONSE[self.endpoint]
                response = await response.response(self)
            except Exception as exception:
                render = self.render("error", message=exception)
                return web.Response(body=render,
                                    content_type="text/html",
                                    charset="utf-8")
        else:
            # Return default response.
            response = await WEBRESPONSE["generic"].response(self)

        # set headers
        response.headers[
            "Cache-Control"] = "no-cache, must-revalidate, s-max_age=0"
        response.headers["Pragma"] = "no-cache"

        # serve the response
        return response

    def render(self,
               templatefile,
               location=None,
               repository=None,
               message=None):
        """Render a template file."""
        loader = Environment(
            loader=PackageLoader("custom_components.hacs.frontend"))
        template = loader.get_template(templatefile + ".html")
        return template.render({
            "hacs": self,
            "location": location,
            "repository": repository,
            "message": message,
            "timestamp": time(),
        })
Exemple #16
0
class HacsRepository(Hacs):
    """HacsRepository."""
    def __init__(self):
        """Set up HacsRepository."""

        self.data = {}
        self.content = RepositoryContent()
        self.content.path = RepositoryPath()
        self.information = RepositoryInformation()
        self.repository_object = None
        self.status = RepositoryStatus()
        self.state = None
        self.manifest = {}
        self.repository_manifest = HacsManifest.from_dict({})
        self.validate = Validate()
        self.releases = RepositoryReleases()
        self.versions = RepositoryVersions()
        self.pending_restart = False
        self.logger = None
        self.tree = []
        self.treefiles = []
        self.ref = None

    @property
    def pending_upgrade(self):
        """Return pending upgrade."""
        if self.status.installed:
            if self.status.selected_tag is not None:
                if self.status.selected_tag == self.information.default_branch:
                    if self.versions.installed_commit != self.versions.available_commit:
                        return True
                    return False
            if self.display_installed_version != self.display_available_version:
                return True
        return False

    @property
    def config_flow(self):
        """Return bool if integration has config_flow."""
        if self.manifest:
            if self.information.full_name == "hacs/integration":
                return False
            return self.manifest.get("config_flow", False)
        return False

    @property
    def custom(self):
        """Return flag if the repository is custom."""
        if self.information.full_name.split("/")[0] in [
                "custom-components",
                "custom-cards",
        ]:
            return False
        if self.information.full_name in self.common.default:
            return False
        if self.information.full_name == "hacs/integration":
            return False
        return True

    @property
    def can_install(self):
        """Return bool if repository can be installed."""
        target = None
        if self.information.homeassistant_version is not None:
            target = self.information.homeassistant_version
        if self.repository_manifest is not None:
            if self.repository_manifest.homeassistant is not None:
                target = self.repository_manifest.homeassistant

        if target is not None:
            if self.releases.releases:
                if not version_left_higher_then_right(self.system.ha_version,
                                                      target):
                    return False
        return True

    @property
    def display_name(self):
        """Return display name."""
        return get_repository_name(
            self.repository_manifest,
            self.information.name,
            self.information.category,
            self.manifest,
        )

    @property
    def display_status(self):
        """Return display_status."""
        if self.status.new:
            status = "new"
        elif self.pending_restart:
            status = "pending-restart"
        elif self.pending_upgrade:
            status = "pending-upgrade"
        elif self.status.installed:
            status = "installed"
        else:
            status = "default"
        return status

    @property
    def display_status_description(self):
        """Return display_status_description."""
        description = {
            "default": "Not installed.",
            "pending-restart": "Restart pending.",
            "pending-upgrade": "Upgrade pending.",
            "installed": "No action required.",
            "new": "This is a newly added repository.",
        }
        return description[self.display_status]

    @property
    def display_installed_version(self):
        """Return display_authors"""
        if self.versions.installed is not None:
            installed = self.versions.installed
        else:
            if self.versions.installed_commit is not None:
                installed = self.versions.installed_commit
            else:
                installed = ""
        return installed

    @property
    def display_available_version(self):
        """Return display_authors"""
        if self.versions.available is not None:
            available = self.versions.available
        else:
            if self.versions.available_commit is not None:
                available = self.versions.available_commit
            else:
                available = ""
        return available

    @property
    def display_version_or_commit(self):
        """Does the repositoriy use releases or commits?"""
        if self.releases.releases:
            version_or_commit = "version"
        else:
            version_or_commit = "commit"
        return version_or_commit

    @property
    def main_action(self):
        """Return the main action."""
        actions = {
            "new": "INSTALL",
            "default": "INSTALL",
            "installed": "REINSTALL",
            "pending-restart": "REINSTALL",
            "pending-upgrade": "UPGRADE",
        }
        return actions[self.display_status]

    async def common_validate(self):
        """Common validation steps of the repository."""
        # Attach helpers
        self.validate.errors = []
        self.logger = Logger(
            f"hacs.repository.{self.information.category}.{self.information.full_name}"
        )
        if self.ref is None:
            self.ref = version_to_install(self)

        # Step 1: Make sure the repository exist.
        self.logger.debug("Checking repository.")
        try:
            self.repository_object = await self.github.get_repo(
                self.information.full_name)
            self.data = self.repository_object.attributes
        except Exception as exception:  # Gotta Catch 'Em All
            if not self.system.status.startup:
                self.logger.error(exception)
            self.validate.errors.append("Repository does not exist.")
            return

        if not self.tree:
            self.tree = await self.repository_object.get_tree(self.ref)
            self.treefiles = []
            for treefile in self.tree:
                self.treefiles.append(treefile.full_path)

        # Step 2: Make sure the repository is not archived.
        if self.repository_object.archived:
            self.validate.errors.append("Repository is archived.")
            return

        # Step 3: Make sure the repository is not in the blacklist.
        if self.information.full_name in self.common.blacklist:
            self.validate.errors.append("Repository is in the blacklist.")
            return

        # Step 4: default branch
        self.information.default_branch = self.repository_object.default_branch

        # Step 5: Get releases.
        await self.get_releases()

        # Step 6: Get the content of hacs.json
        await self.get_repository_manifest_content()

        # Set repository name
        self.information.name = self.information.full_name.split("/")[1]

    async def common_registration(self):
        """Common registration steps of the repository."""
        # Attach logger
        if self.logger is None:
            self.logger = Logger(
                f"hacs.repository.{self.information.category}.{self.information.full_name}"
            )

        # Attach repository
        if self.repository_object is None:
            self.repository_object = await self.github.get_repo(
                self.information.full_name)

        # Set id
        self.information.uid = str(self.repository_object.id)

        # Set topics
        self.information.topics = self.repository_object.topics

        # Set stargazers_count
        self.information.stars = self.repository_object.attributes.get(
            "stargazers_count", 0)

        # Set description
        if self.repository_object.description:
            self.information.description = self.repository_object.description

    async def common_update(self):
        """Common information update steps of the repository."""
        # Attach logger
        if self.logger is None:
            self.logger = Logger(
                f"hacs.repository.{self.information.category}.{self.information.full_name}"
            )

        self.logger.debug("Getting repository information")

        # Set ref
        self.ref = version_to_install(self)

        # Attach repository
        self.repository_object = await self.github.get_repo(
            self.information.full_name)

        # Update tree
        self.tree = await self.repository_object.get_tree(self.ref)
        self.treefiles = []
        for treefile in self.tree:
            self.treefiles.append(treefile.full_path)

        # Update description
        if self.repository_object.description:
            self.information.description = self.repository_object.description

        # Set stargazers_count
        self.information.stars = self.repository_object.attributes.get(
            "stargazers_count", 0)

        # Update default branch
        self.information.default_branch = self.repository_object.default_branch

        # Update last updaeted
        self.information.last_updated = self.repository_object.attributes.get(
            "pushed_at", 0)

        # Update topics
        self.information.topics = self.repository_object.topics

        # Update last available commit
        await self.repository_object.set_last_commit()
        self.versions.available_commit = self.repository_object.last_commit

        # Get the content of hacs.json
        await self.get_repository_manifest_content()

        # Update "info.md"
        self.information.additional_info = await get_info_md_content(self)

        # Update releases
        await self.get_releases()

    async def install(self):
        """Common installation steps of the repository."""
        await install_repository(self)

    async def download_zip(self, validate):
        """Download ZIP archive from repository release."""
        try:
            contents = False

            for release in self.releases.objects:
                self.logger.info(
                    f"ref: {self.ref}  ---  tag: {release.tag_name}")
                if release.tag_name == self.ref.split("/")[1]:
                    contents = release.assets

            if not contents:
                return validate

            for content in contents or []:
                filecontent = await async_download_file(
                    self.hass, content.download_url)

                if filecontent is None:
                    validate.errors.append(
                        f"[{content.name}] was not downloaded.")
                    continue

                result = await async_save_file(
                    f"{tempfile.gettempdir()}/{self.repository_manifest.filename}",
                    filecontent,
                )
                with zipfile.ZipFile(
                        f"{tempfile.gettempdir()}/{self.repository_manifest.filename}",
                        "r") as zip_file:
                    zip_file.extractall(self.content.path.local)

                if result:
                    self.logger.info(f"download of {content.name} complete")
                    continue
                validate.errors.append(f"[{content.name}] was not downloaded.")
        except Exception:
            validate.errors.append(f"Download was not complete.")

        return validate

    async def download_content(self, validate, directory_path, local_directory,
                               ref):
        """Download the content of a directory."""
        from custom_components.hacs.helpers.download import download_content

        validate = await download_content(self, validate, local_directory)
        return validate

    async def get_repository_manifest_content(self):
        """Get the content of the hacs.json file."""
        if self.ref is None:
            self.ref = version_to_install(self)
        try:
            manifest = await self.repository_object.get_contents(
                "hacs.json", self.ref)
            self.repository_manifest = HacsManifest.from_dict(
                json.loads(manifest.content))
        except (AIOGitHubException, Exception):  # Gotta Catch 'Em All
            pass

    async def get_releases(self):
        """Get repository releases."""
        if self.status.show_beta:
            self.releases.objects = await self.repository_object.get_releases(
                prerelease=True, returnlimit=self.configuration.release_limit)
        else:
            self.releases.objects = await self.repository_object.get_releases(
                prerelease=False, returnlimit=self.configuration.release_limit)

        if not self.releases.objects:
            return

        self.releases.releases = True

        self.releases.published_tags = []

        for release in self.releases.objects:
            self.releases.published_tags.append(release.tag_name)

        self.releases.last_release_object = self.releases.objects[0]
        if self.status.selected_tag is not None:
            if self.status.selected_tag != self.information.default_branch:
                for release in self.releases.objects:
                    if release.tag_name == self.status.selected_tag:
                        self.releases.last_release_object = release
                        break
        if self.releases.last_release_object.assets:
            self.releases.last_release_object_downloads = self.releases.last_release_object.assets[
                0].attributes.get("download_count")
        self.versions.available = self.releases.objects[0].tag_name

    def remove(self):
        """Run remove tasks."""
        # Attach logger
        if self.logger is None:
            self.logger = Logger(
                f"hacs.repository.{self.information.category}.{self.information.full_name}"
            )
        self.logger.info("Starting removal")

        if self.information.uid in self.common.installed:
            self.common.installed.remove(self.information.uid)
        for repository in self.repositories:
            if repository.information.uid == self.information.uid:
                self.repositories.remove(repository)

    async def uninstall(self):
        """Run uninstall tasks."""
        # Attach logger
        if self.logger is None:
            self.logger = Logger(
                f"hacs.repository.{self.information.category}.{self.information.full_name}"
            )
        self.logger.info("Uninstalling")
        await self.remove_local_directory()
        self.status.installed = False
        if self.information.category == "integration":
            if self.config_flow:
                await self.reload_custom_components()
            else:
                self.pending_restart = True
        elif self.information.category == "theme":
            try:
                await self.hass.services.async_call("frontend",
                                                    "reload_themes", {})
            except Exception:  # pylint: disable=broad-except
                pass
        if self.information.full_name in self.common.installed:
            self.common.installed.remove(self.information.full_name)
        self.versions.installed = None
        self.versions.installed_commit = None
        self.hass.bus.async_fire(
            "hacs/repository",
            {
                "id": 1337,
                "action": "uninstall",
                "repository": self.information.full_name,
            },
        )

    async def remove_local_directory(self):
        """Check the local directory."""
        import shutil
        from asyncio import sleep

        try:
            if self.information.category == "python_script":
                local_path = "{}/{}.py".format(self.content.path.local,
                                               self.information.name)
            elif self.information.category == "theme":
                local_path = "{}/{}.yaml".format(self.content.path.local,
                                                 self.information.name)
            else:
                local_path = self.content.path.local

            if os.path.exists(local_path):
                self.logger.debug(f"Removing {local_path}")

                if self.information.category in ["python_script", "theme"]:
                    os.remove(local_path)
                else:
                    shutil.rmtree(local_path)

                while os.path.exists(local_path):
                    await sleep(1)

        except Exception as exception:
            self.logger.debug(f"Removing {local_path} failed with {exception}")
            return
class HacsData(Hacs):
    """HacsData class."""
    def __init__(self):
        """Initialize."""
        self.logger = Logger("hacs.data")

    def check_corrupted_files(self):
        """Return True if one (or more) of the files are corrupted."""
        for store in STORES:
            path = f"{self.system.config_path}/.storage/{STORES[store]}"
            if os.path.exists(path):
                if os.stat(path).st_size == 0:
                    # File is empty (corrupted)
                    return True
        return False

    def read(self, store):
        """Return data from a store."""
        path = f"{self.system.config_path}/.storage/{STORES[store]}"
        content = None
        if os.path.exists(path):
            with open(path, "r", encoding="utf-8") as storefile:
                content = storefile.read()
                content = json.loads(content)
        return content

    def write(self):
        """Write content to the store files."""
        if self.system.status.background_task:
            return

        self.logger.debug("Saving data")

        # Hacs
        path = f"{self.system.config_path}/.storage/{STORES['hacs']}"
        hacs = {"view": self.configuration.frontend_mode}
        save(self.logger, path, hacs)

        # Installed
        path = f"{self.system.config_path}/.storage/{STORES['installed']}"
        installed = {}
        for repository_name in self.common.installed:
            repository = self.get_by_name(repository_name)
            if repository is None:
                self.logger.warning(
                    f"Did not save information about {repository_name}")
                continue
            installed[repository.information.full_name] = {
                "version_type": repository.display_version_or_commit,
                "version_installed": repository.display_installed_version,
                "version_available": repository.display_available_version,
            }
        save(self.logger, path, installed)

        # Repositories
        path = f"{self.system.config_path}/.storage/{STORES['repositories']}"
        content = {}
        for repository in self.repositories:
            if repository.repository_manifest is not None:
                repository_manifest = repository.repository_manifest.manifest
            else:
                repository_manifest = None
            content[repository.information.uid] = {
                "authors": repository.information.authors,
                "topics": repository.information.topics,
                "category": repository.information.category,
                "description": repository.information.description,
                "full_name": repository.information.full_name,
                "hide": repository.status.hide,
                "installed_commit": repository.versions.installed_commit,
                "installed": repository.status.installed,
                "last_commit": repository.versions.available_commit,
                "last_release_tag": repository.versions.available,
                "repository_manifest": repository_manifest,
                "name": repository.information.name,
                "new": repository.status.new,
                "selected_tag": repository.status.selected_tag,
                "show_beta": repository.status.show_beta,
                "version_installed": repository.versions.installed,
            }

        # Validate installed repositories
        count_installed = len(installed) + 1  # For HACS it self
        count_installed_restore = 0
        for repository in self.repositories:
            if repository.status.installed:
                count_installed_restore += 1

        if count_installed < count_installed_restore:
            self.logger.debug("Save failed!")
            self.logger.debug(
                f"Number of installed repositories does not match the number of stored repositories [{count_installed} vs {count_installed_restore}]"
            )
            return
        save(self.logger, path, content)

    async def restore(self):
        """Restore saved data."""
        try:
            hacs = self.read("hacs")
            installed = self.read("installed")
            repositrories = self.read("repositories")
            if self.check_corrupted_files():
                # Coruptted installation
                self.logger.critical(
                    "Restore failed one or more files are corrupted!")
                return False
            if hacs is None and installed is None and repositrories is None:
                # Assume new install
                return True

            self.logger.info("Restore started")

            # Hacs
            hacs = hacs["data"]
            self.configuration.frontend_mode = hacs["view"]

            # Installed
            installed = installed["data"]
            for repository in installed:
                self.common.installed.append(repository)

            # Repositories
            repositrories = repositrories["data"]
            for entry in repositrories:
                repo = repositrories[entry]
                if not self.is_known(repo["full_name"]):
                    await self.register_repository(repo["full_name"],
                                                   repo["category"], False)
                repository = self.get_by_name(repo["full_name"])
                if repository is None:
                    self.logger.error(f"Did not find {repo['full_name']}")
                    continue

                # Restore repository attributes
                if repo.get("authors") is not None:
                    repository.information.authors = repo["authors"]

                if repo.get("topics", []):
                    repository.information.topics = repo["topics"]

                if repo.get("description") is not None:
                    repository.information.description = repo["description"]

                if repo.get("name") is not None:
                    repository.information.name = repo["name"]

                if repo.get("hide") is not None:
                    repository.status.hide = repo["hide"]

                if repo.get("installed") is not None:
                    repository.status.installed = repo["installed"]
                    if repository.status.installed:
                        repository.status.first_install = False

                if repo.get("selected_tag") is not None:
                    repository.status.selected_tag = repo["selected_tag"]

                if repo.get("repository_manifest") is not None:
                    repository.repository_manifest = HacsManifest(
                        repo["repository_manifest"])

                if repo.get("show_beta") is not None:
                    repository.status.show_beta = repo["show_beta"]

                if repo.get("last_commit") is not None:
                    repository.versions.available_commit = repo["last_commit"]

                repository.information.uid = entry

                if repo.get("last_release_tag") is not None:
                    repository.releases.last_release = repo["last_release_tag"]
                    repository.versions.available = repo["last_release_tag"]

                if repo.get("new") is not None:
                    repository.status.new = repo["new"]

                if repo["full_name"] == "custom-components/hacs":
                    repository.versions.installed = VERSION
                    repository.status.installed = True
                    if "b" in VERSION:
                        repository.status.show_beta = True
                elif repo.get("version_installed") is not None:
                    repository.versions.installed = repo["version_installed"]

                if repo.get("installed_commit") is not None:
                    repository.versions.installed_commit = repo[
                        "installed_commit"]

                if repo["full_name"] in self.common.installed:
                    repository.status.installed = True
                    repository.status.new = False
                    frominstalled = installed[repo["full_name"]]
                    if frominstalled["version_type"] == "commit":
                        repository.versions.installed_commit = frominstalled[
                            "version_installed"]
                        repository.versions.available_commit = frominstalled[
                            "version_available"]
                    else:
                        repository.versions.installed = frominstalled[
                            "version_installed"]
                        repository.versions.available = frominstalled[
                            "version_available"]

            # Check the restore.
            count_installed = len(installed) + 1  # For HACS it self
            count_installed_restore = 0
            installed_restore = []
            for repository in self.repositories:
                if repository.status.installed:
                    installed_restore.append(repository.information.full_name)
                    if (repository.information.full_name
                            not in self.common.installed
                            and repository.information.full_name !=
                            "custom-components/hacs"):
                        self.logger.warning(
                            f"{repository.information.full_name} is not in common.installed"
                        )
                    count_installed_restore += 1

            if count_installed < count_installed_restore:
                for repo in installed:
                    installed_restore.remove(repo)
                self.logger.warning(f"Check {repo}")

                self.logger.critical("Restore failed!")
                self.logger.critical(
                    f"Number of installed repositories does not match the number of restored repositories [{count_installed} vs {count_installed_restore}]"
                )
                return False

            self.logger.info("Restore done")
        except Exception as exception:
            self.logger.critical(f"[{exception}] Restore Failed!")
            return False
        return True
Exemple #18
0
class HacsRepository(Hacs):
    """HacsRepository."""
    def __init__(self):
        """Set up HacsRepository."""

        self.content = RepositoryContent()
        self.content.path = RepositoryPath()
        self.information = RepositoryInformation()
        self.repository_object = None
        self.status = RepositoryStatus()
        self.state = None
        self.manifest = {}
        self.repository_manifest = HacsManifest.from_dict({})
        self.validate = Validate()
        self.releases = RepositoryReleases()
        self.versions = RepositoryVersions()
        self.pending_restart = False
        self.logger = None

    @property
    def pending_upgrade(self):
        """Return pending upgrade."""
        if self.status.installed:
            if self.display_installed_version != self.display_available_version:
                return True

        return False

    @property
    def ref(self):
        """Return the ref."""
        if self.status.selected_tag is not None:
            if self.status.selected_tag == self.information.default_branch:
                return self.information.default_branch
            return "tags/{}".format(self.status.selected_tag)

        if self.releases.releases:
            return "tags/{}".format(self.versions.available)

        return self.information.default_branch

    @property
    def custom(self):
        """Return flag if the repository is custom."""
        if self.information.full_name.split("/")[0] in [
                "custom-components",
                "custom-cards",
        ]:
            return False
        if self.information.full_name in self.common.default:
            return False
        if self.information.full_name == "hacs/integration":
            return False
        return True

    @property
    def can_install(self):
        """Return bool if repository can be installed."""
        target = None
        if self.information.homeassistant_version is not None:
            target = self.information.homeassistant_version
        if self.repository_manifest is not None:
            if self.repository_manifest.homeassistant is not None:
                target = self.repository_manifest.homeassistant

        if target is not None:
            if self.releases.releases:
                if not version_left_higher_then_right(self.system.ha_version,
                                                      target):
                    return False
        return True

    @property
    def display_name(self):
        """Return display name."""
        return get_repository_name(
            self.repository_manifest,
            self.information.name,
            self.information.category,
            self.manifest,
        )

    @property
    def display_status(self):
        """Return display_status."""
        if self.status.new:
            status = "new"
        elif self.pending_restart:
            status = "pending-restart"
        elif self.pending_upgrade:
            status = "pending-upgrade"
        elif self.status.installed:
            status = "installed"
        else:
            status = "default"
        return status

    @property
    def display_status_description(self):
        """Return display_status_description."""
        description = {
            "default": "Not installed.",
            "pending-restart": "Restart pending.",
            "pending-upgrade": "Upgrade pending.",
            "installed": "No action required.",
            "new": "This is a newly added repository.",
        }
        return description[self.display_status]

    @property
    def display_installed_version(self):
        """Return display_authors"""
        if self.versions.installed is not None:
            installed = self.versions.installed
        else:
            if self.versions.installed_commit is not None:
                installed = self.versions.installed_commit
            else:
                installed = ""
        return installed

    @property
    def display_available_version(self):
        """Return display_authors"""
        if self.versions.available is not None:
            available = self.versions.available
        else:
            if self.versions.available_commit is not None:
                available = self.versions.available_commit
            else:
                available = ""
        return available

    @property
    def display_version_or_commit(self):
        """Does the repositoriy use releases or commits?"""
        if self.releases.releases:
            version_or_commit = "version"
        else:
            version_or_commit = "commit"
        return version_or_commit

    @property
    def main_action(self):
        """Return the main action."""
        actions = {
            "new": "INSTALL",
            "default": "INSTALL",
            "installed": "REINSTALL",
            "pending-restart": "REINSTALL",
            "pending-upgrade": "UPGRADE",
        }
        return actions[self.display_status]

    async def common_validate(self):
        """Common validation steps of the repository."""
        # Attach helpers
        self.validate.errors = []
        self.logger = Logger(
            f"hacs.repository.{self.information.category}.{self.information.full_name}"
        )

        # Step 1: Make sure the repository exist.
        self.logger.debug("Checking repository.")
        try:
            self.repository_object = await self.github.get_repo(
                self.information.full_name)
        except Exception as exception:  # Gotta Catch 'Em All
            if not self.system.status.startup:
                self.logger.error(exception)
            self.validate.errors.append("Repository does not exist.")
            return

        # Step 2: Make sure the repository is not archived.
        if self.repository_object.archived:
            self.validate.errors.append("Repository is archived.")
            return

        # Step 3: Make sure the repository is not in the blacklist.
        if self.information.full_name in self.common.blacklist:
            self.validate.errors.append("Repository is in the blacklist.")
            return

        # Step 4: default branch
        self.information.default_branch = self.repository_object.default_branch

        # Step 5: Get releases.
        await self.get_releases()

        # Step 6: Get the content of hacs.json
        await self.get_repository_manifest_content()

        # Set repository name
        self.information.name = self.information.full_name.split("/")[1]

    async def common_registration(self):
        """Common registration steps of the repository."""
        # Attach logger
        if self.logger is None:
            self.logger = Logger(
                f"hacs.repository.{self.information.category}.{self.information.full_name}"
            )

        # Attach repository
        if self.repository_object is None:
            self.repository_object = await self.github.get_repo(
                self.information.full_name)

        # Set id
        self.information.uid = str(self.repository_object.id)

        # Set topics
        self.information.topics = self.repository_object.topics

        # Set stargazers_count
        self.information.stars = self.repository_object.attributes.get(
            "stargazers_count", 0)

        # Set description
        if self.repository_object.description:
            self.information.description = self.repository_object.description

    async def common_update(self):
        """Common information update steps of the repository."""
        # Attach logger
        if self.logger is None:
            self.logger = Logger(
                f"hacs.repository.{self.information.category}.{self.information.full_name}"
            )

        # Attach repository
        self.repository_object = await self.github.get_repo(
            self.information.full_name)

        # Update description
        if self.repository_object.description:
            self.information.description = self.repository_object.description

        # Set stargazers_count
        self.information.stars = self.repository_object.attributes.get(
            "stargazers_count", 0)

        # Update default branch
        self.information.default_branch = self.repository_object.default_branch

        # Update last available commit
        await self.repository_object.set_last_commit()
        self.versions.available_commit = self.repository_object.last_commit

        # Update last updaeted
        self.information.last_updated = self.repository_object.pushed_at

        # Update topics
        self.information.topics = self.repository_object.topics

        # Get the content of hacs.json
        await self.get_repository_manifest_content()

        # Update "info.md"
        await self.get_info_md_content()

        # Update releases
        await self.get_releases()

    async def install(self):
        """Common installation steps of the repository."""
        self.validate.errors = []
        persistent_directory = None

        await self.update_repository()

        if self.repository_manifest:
            if self.repository_manifest.persistent_directory:
                if os.path.exists(
                        f"{self.content.path.local}/{self.repository_manifest.persistent_directory}"
                ):
                    persistent_directory = Backup(
                        f"{self.content.path.local}/{self.repository_manifest.persistent_directory}",
                        tempfile.TemporaryFile() +
                        "/hacs_persistent_directory/",
                    )
                    persistent_directory.create()

        if self.status.installed and not self.content.single:
            backup = Backup(self.content.path.local)
            backup.create()

        if self.repository_manifest.zip_release:
            validate = await self.download_zip(self.validate)
        else:
            validate = await self.download_content(
                self.validate,
                self.content.path.remote,
                self.content.path.local,
                self.ref,
            )

        if validate.errors:
            for error in validate.errors:
                self.logger.error(error)
            if self.status.installed and not self.content.single:
                backup.restore()

        if self.status.installed and not self.content.single:
            backup.cleanup()

        if persistent_directory is not None:
            persistent_directory.restore()
            persistent_directory.cleanup()

        if validate.success:
            if self.information.full_name not in self.common.installed:
                if self.information.full_name == "hacs/integration":
                    self.common.installed.append(self.information.full_name)
            self.status.installed = True
            self.versions.installed_commit = self.versions.available_commit

            if self.status.selected_tag is not None:
                self.versions.installed = self.status.selected_tag
            else:
                self.versions.installed = self.versions.available

            if self.information.category == "integration":
                if (self.config_flow
                        and self.information.full_name != "hacs/integration"):
                    await self.reload_custom_components()
                self.pending_restart = True

            elif self.information.category == "theme":
                try:
                    await self.hass.services.async_call(
                        "frontend", "reload_themes", {})
                except Exception:  # pylint: disable=broad-except
                    pass
            self.hass.bus.async_fire(
                "hacs/repository",
                {
                    "id": 1337,
                    "action": "install",
                    "repository": self.information.full_name,
                },
            )

    async def download_zip(self, validate):
        """Download ZIP archive from repository release."""
        try:
            contents = False

            for release in self.releases.objects:
                self.logger.info(
                    f"ref: {self.ref}  ---  tag: {release.tag_name}")
                if release.tag_name == self.ref.split("/")[1]:
                    contents = release.assets

            if not contents:
                return validate

            for content in contents or []:
                filecontent = await async_download_file(
                    self.hass, content.download_url)

                if filecontent is None:
                    validate.errors.append(
                        f"[{content.name}] was not downloaded.")
                    continue

                result = await async_save_file(
                    f"{tempfile.gettempdir()}/{self.repository_manifest.filename}",
                    filecontent,
                )
                with zipfile.ZipFile(
                        f"{tempfile.gettempdir()}/{self.repository_manifest.filename}",
                        "r") as zip_file:
                    zip_file.extractall(self.content.path.local)

                if result:
                    self.logger.info(f"download of {content.name} complete")
                    continue
                validate.errors.append(f"[{content.name}] was not downloaded.")
        except Exception:
            validate.errors.append(f"Download was not complete.")

        return validate

    async def download_content(self, validate, directory_path, local_directory,
                               ref):
        """Download the content of a directory."""
        try:
            # Get content
            contents = []
            if self.releases.releases:
                for release in self.releases.objects:
                    if self.status.selected_tag == release.tag_name:
                        contents = release.assets
            if not contents:
                if self.content.single:
                    contents = self.content.objects
                else:
                    contents = await self.repository_object.get_contents(
                        directory_path, self.ref)

            for content in contents:
                if content.type == "dir" and (
                        self.repository_manifest.content_in_root
                        or self.content.path.remote != ""):
                    await self.download_content(validate, content.path,
                                                local_directory, ref)
                    continue
                if self.information.category == "plugin":
                    if not content.name.endswith(".js"):
                        if self.content.path.remote != "dist":
                            continue

                self.logger.debug(f"Downloading {content.name}")

                filecontent = await async_download_file(
                    self.hass, content.download_url)

                if filecontent is None:
                    validate.errors.append(
                        f"[{content.name}] was not downloaded.")
                    continue

                # Save the content of the file.
                if self.content.single:
                    local_directory = self.content.path.local

                else:
                    _content_path = content.path
                    if not self.repository_manifest.content_in_root:
                        _content_path = _content_path.replace(
                            f"{self.content.path.remote}/", "")

                    local_directory = f"{self.content.path.local}/{_content_path}"
                    local_directory = local_directory.split("/")
                    del local_directory[-1]
                    local_directory = "/".join(local_directory)

                # Check local directory
                pathlib.Path(local_directory).mkdir(parents=True,
                                                    exist_ok=True)

                local_file_path = f"{local_directory}/{content.name}"
                result = await async_save_file(local_file_path, filecontent)
                if result:
                    self.logger.info(f"download of {content.name} complete")
                    continue
                validate.errors.append(f"[{content.name}] was not downloaded.")

        except Exception:
            validate.errors.append(f"Download was not complete.")
        return validate

    async def get_repository_manifest_content(self):
        """Get the content of the hacs.json file."""
        try:
            manifest = await self.repository_object.get_contents(
                "hacs.json", self.ref)
            self.repository_manifest = HacsManifest.from_dict(
                json.loads(manifest.content))
        except (AIOGitHubException, Exception):  # Gotta Catch 'Em All
            pass

    async def get_info_md_content(self):
        """Get the content of info.md"""
        from ..handler.template import render_template

        info = None
        info_files = ["info", "info.md"]

        if self.repository_manifest is not None:
            if self.repository_manifest.render_readme:
                info_files = ["readme", "readme.md"]
        try:
            root = await self.repository_object.get_contents("", self.ref)
            for file in root:
                if file.name.lower() in info_files:

                    info = await self.repository_object.get_contents(
                        file.name, self.ref)
                    break
            if info is None:
                self.information.additional_info = ""
            else:
                info = info.content.replace("<svg", "<disabled").replace(
                    "</svg", "</disabled")
                info = info.replace(
                    '<a href="http',
                    '<a rel="noreferrer" target="_blank" href="http')

                self.information.additional_info = render_template(info, self)

        except (AIOGitHubException, Exception):
            self.information.additional_info = ""

    async def get_releases(self):
        """Get repository releases."""
        if self.status.show_beta:
            self.releases.objects = await self.repository_object.get_releases(
                prerelease=True, returnlimit=self.configuration.release_limit)
        else:
            self.releases.objects = await self.repository_object.get_releases(
                prerelease=False, returnlimit=self.configuration.release_limit)

        if not self.releases.objects:
            return

        self.releases.releases = True

        self.releases.published_tags = []

        for release in self.releases.objects:
            self.releases.published_tags.append(release.tag_name)

        self.releases.last_release_object = self.releases.objects[0]
        if self.status.selected_tag is not None:
            if self.status.selected_tag != self.information.default_branch:
                for release in self.releases.objects:
                    if release.tag_name == self.status.selected_tag:
                        self.releases.last_release_object = release
                        break
        self.versions.available = self.releases.objects[0].tag_name

    def remove(self):
        """Run remove tasks."""
        # Attach logger
        if self.logger is None:
            self.logger = Logger(
                f"hacs.repository.{self.information.category}.{self.information.full_name}"
            )
        self.logger.info("Starting removal")

        if self.information.uid in self.common.installed:
            self.common.installed.remove(self.information.uid)
        for repository in self.repositories:
            if repository.information.uid == self.information.uid:
                self.repositories.remove(repository)

    async def uninstall(self):
        """Run uninstall tasks."""
        # Attach logger
        if self.logger is None:
            self.logger = Logger(
                f"hacs.repository.{self.information.category}.{self.information.full_name}"
            )
        self.logger.info("Uninstalling")
        await self.remove_local_directory()
        self.status.installed = False
        if self.information.category == "integration":
            if self.config_flow:
                await self.reload_custom_components()
            else:
                self.pending_restart = True
        elif self.information.category == "theme":
            try:
                await self.hass.services.async_call("frontend",
                                                    "reload_themes", {})
            except Exception:  # pylint: disable=broad-except
                pass
        if self.information.full_name in self.common.installed:
            self.common.installed.remove(self.information.full_name)
        self.versions.installed = None
        self.versions.installed_commit = None
        self.hass.bus.async_fire(
            "hacs/repository",
            {
                "id": 1337,
                "action": "uninstall",
                "repository": self.information.full_name,
            },
        )

    async def remove_local_directory(self):
        """Check the local directory."""
        import shutil
        from asyncio import sleep

        try:
            if self.information.category == "python_script":
                local_path = "{}/{}.py".format(self.content.path.local,
                                               self.information.name)
            elif self.information.category == "theme":
                local_path = "{}/{}.yaml".format(self.content.path.local,
                                                 self.information.name)
            else:
                local_path = self.content.path.local

            if os.path.exists(local_path):
                self.logger.debug(f"Removing {local_path}")

                if self.information.category in ["python_script", "theme"]:
                    os.remove(local_path)
                else:
                    shutil.rmtree(local_path)

                while os.path.exists(local_path):
                    await sleep(1)

        except Exception as exception:
            self.logger.debug(f"Removing {local_path} failed with {exception}")
            return