mirror of
https://github.com/CCOSTAN/Home-AssistantConfig.git
synced 2025-08-20 12:10:28 +00:00
Finally got around to installing This amazing component by @ludeeus
This commit is contained in:
185
config/custom_components/hacs/helpers/download.py
Executable file
185
config/custom_components/hacs/helpers/download.py
Executable file
@@ -0,0 +1,185 @@
|
||||
"""Helpers to download repository content."""
|
||||
import pathlib
|
||||
import tempfile
|
||||
import zipfile
|
||||
from custom_components.hacs.hacsbase.exceptions import HacsException
|
||||
from custom_components.hacs.handler.download import async_download_file, async_save_file
|
||||
from custom_components.hacs.helpers.filters import filter_content_return_one_of_type
|
||||
|
||||
|
||||
class FileInformation:
|
||||
def __init__(self, url, path, name):
|
||||
self.download_url = url
|
||||
self.path = path
|
||||
self.name = name
|
||||
|
||||
|
||||
def should_try_releases(repository):
|
||||
"""Return a boolean indicating whether to download releases or not."""
|
||||
if repository.ref == repository.information.default_branch:
|
||||
return False
|
||||
if repository.information.category not in ["plugin", "theme"]:
|
||||
return False
|
||||
if not repository.releases.releases:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def gather_files_to_download(repository):
|
||||
"""Return a list of file objects to be downloaded."""
|
||||
files = []
|
||||
tree = repository.tree
|
||||
ref = f"{repository.ref}".replace("tags/", "")
|
||||
releaseobjects = repository.releases.objects
|
||||
category = repository.information.category
|
||||
remotelocation = repository.content.path.remote
|
||||
|
||||
if should_try_releases(repository):
|
||||
for release in releaseobjects or []:
|
||||
if ref == release.tag_name:
|
||||
for asset in release.assets or []:
|
||||
files.append(asset)
|
||||
if files:
|
||||
return files
|
||||
|
||||
if repository.content.single:
|
||||
for treefile in tree:
|
||||
if treefile.filename == repository.information.file_name:
|
||||
files.append(
|
||||
FileInformation(
|
||||
treefile.download_url, treefile.full_path, treefile.filename
|
||||
)
|
||||
)
|
||||
return files
|
||||
|
||||
if category == "plugin":
|
||||
for treefile in tree:
|
||||
if treefile.path in ["", "dist"]:
|
||||
if not remotelocation:
|
||||
if treefile.filename != repository.information.file_name:
|
||||
continue
|
||||
if remotelocation == "dist" and not treefile.filename.startswith(
|
||||
"dist"
|
||||
):
|
||||
continue
|
||||
if treefile.is_directory:
|
||||
continue
|
||||
files.append(
|
||||
FileInformation(
|
||||
treefile.download_url, treefile.full_path, treefile.filename
|
||||
)
|
||||
)
|
||||
if files:
|
||||
return files
|
||||
|
||||
if repository.repository_manifest.content_in_root:
|
||||
if repository.repository_manifest.filename is None:
|
||||
if category == "theme":
|
||||
tree = filter_content_return_one_of_type(
|
||||
repository.tree, "themes", "yaml", "full_path"
|
||||
)
|
||||
|
||||
for path in tree:
|
||||
if path.is_directory:
|
||||
continue
|
||||
if path.full_path.startswith(repository.content.path.remote):
|
||||
files.append(
|
||||
FileInformation(path.download_url, path.full_path, path.filename)
|
||||
)
|
||||
return files
|
||||
|
||||
|
||||
async def download_zip(repository, validate):
|
||||
"""Download ZIP archive from repository release."""
|
||||
contents = []
|
||||
try:
|
||||
for release in repository.releases.objects:
|
||||
repository.logger.info(
|
||||
f"ref: {repository.ref} --- tag: {release.tag_name}"
|
||||
)
|
||||
if release.tag_name == repository.ref.split("/")[1]:
|
||||
contents = release.assets
|
||||
|
||||
if not contents:
|
||||
return validate
|
||||
|
||||
for content in contents:
|
||||
filecontent = await async_download_file(
|
||||
repository.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()}/{repository.repository_manifest.filename}",
|
||||
filecontent,
|
||||
)
|
||||
with zipfile.ZipFile(
|
||||
f"{tempfile.gettempdir()}/{repository.repository_manifest.filename}",
|
||||
"r",
|
||||
) as zip_file:
|
||||
zip_file.extractall(repository.content.path.local)
|
||||
|
||||
if result:
|
||||
repository.logger.info(f"download of {content.name} complete")
|
||||
continue
|
||||
validate.errors.append(f"[{content.name}] was not downloaded.")
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
validate.errors.append(f"Download was not complete [{exception}]")
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
async def download_content(repository, validate, local_directory):
|
||||
"""Download the content of a directory."""
|
||||
contents = gather_files_to_download(repository)
|
||||
try:
|
||||
if not contents:
|
||||
raise HacsException("No content to download")
|
||||
|
||||
for content in contents:
|
||||
if repository.repository_manifest.content_in_root:
|
||||
if repository.repository_manifest.filename is not None:
|
||||
if content.name != repository.repository_manifest.filename:
|
||||
continue
|
||||
repository.logger.debug(f"Downloading {content.name}")
|
||||
|
||||
filecontent = await async_download_file(
|
||||
repository.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 repository.content.single or content.path is None:
|
||||
local_directory = repository.content.path.local
|
||||
|
||||
else:
|
||||
_content_path = content.path
|
||||
if not repository.repository_manifest.content_in_root:
|
||||
_content_path = _content_path.replace(
|
||||
f"{repository.content.path.remote}", ""
|
||||
)
|
||||
|
||||
local_directory = f"{repository.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:
|
||||
repository.logger.info(f"download of {content.name} complete")
|
||||
continue
|
||||
validate.errors.append(f"[{content.name}] was not downloaded.")
|
||||
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
validate.errors.append(f"Download was not complete [{exception}]")
|
||||
return validate
|
44
config/custom_components/hacs/helpers/filters.py
Executable file
44
config/custom_components/hacs/helpers/filters.py
Executable file
@@ -0,0 +1,44 @@
|
||||
"""Filter functions."""
|
||||
|
||||
|
||||
def filter_content_return_one_of_type(
|
||||
content, namestartswith, filterfiltype, attr="name"
|
||||
):
|
||||
"""Only match 1 of the filter."""
|
||||
contents = []
|
||||
filetypefound = False
|
||||
for filename in content:
|
||||
if isinstance(filename, str):
|
||||
if filename.startswith(namestartswith):
|
||||
if filename.endswith(f".{filterfiltype}"):
|
||||
if not filetypefound:
|
||||
contents.append(filename)
|
||||
filetypefound = True
|
||||
continue
|
||||
else:
|
||||
contents.append(filename)
|
||||
else:
|
||||
if getattr(filename, attr).startswith(namestartswith):
|
||||
if getattr(filename, attr).endswith(f".{filterfiltype}"):
|
||||
if not filetypefound:
|
||||
contents.append(filename)
|
||||
filetypefound = True
|
||||
continue
|
||||
else:
|
||||
contents.append(filename)
|
||||
return contents
|
||||
|
||||
|
||||
def find_first_of_filetype(content, filterfiltype, attr="name"):
|
||||
"""Find the first of the file type."""
|
||||
filename = ""
|
||||
for _filename in content:
|
||||
if isinstance(_filename, str):
|
||||
if _filename.endswith(f".{filterfiltype}"):
|
||||
filename = _filename
|
||||
break
|
||||
else:
|
||||
if getattr(_filename, attr).endswith(f".{filterfiltype}"):
|
||||
filename = getattr(_filename, attr)
|
||||
break
|
||||
return filename
|
43
config/custom_components/hacs/helpers/get_defaults.py
Executable file
43
config/custom_components/hacs/helpers/get_defaults.py
Executable file
@@ -0,0 +1,43 @@
|
||||
"""Helpers to get default repositories."""
|
||||
import json
|
||||
from aiogithubapi import AIOGitHub, AIOGitHubException
|
||||
from integrationhelper import Logger
|
||||
|
||||
|
||||
async def get_default_repos_orgs(github: type(AIOGitHub), category: str) -> dict:
|
||||
"""Gets default org repositories."""
|
||||
repositories = []
|
||||
logger = Logger("hacs")
|
||||
orgs = {
|
||||
"plugin": "custom-cards",
|
||||
"integration": "custom-components",
|
||||
"theme": "home-assistant-community-themes",
|
||||
}
|
||||
if category not in orgs:
|
||||
return repositories
|
||||
|
||||
try:
|
||||
repos = await github.get_org_repos(orgs[category])
|
||||
for repo in repos:
|
||||
repositories.append(repo.full_name)
|
||||
|
||||
except AIOGitHubException as exception:
|
||||
logger.error(exception)
|
||||
|
||||
return repositories
|
||||
|
||||
|
||||
async def get_default_repos_lists(github: type(AIOGitHub), default: str) -> dict:
|
||||
"""Gets repositories from default list."""
|
||||
repositories = []
|
||||
logger = Logger("hacs")
|
||||
|
||||
try:
|
||||
repo = await github.get_repo("hacs/default")
|
||||
content = await repo.get_contents(default)
|
||||
repositories = json.loads(content.content)
|
||||
|
||||
except AIOGitHubException as exception:
|
||||
logger.error(exception)
|
||||
|
||||
return repositories
|
124
config/custom_components/hacs/helpers/install.py
Executable file
124
config/custom_components/hacs/helpers/install.py
Executable file
@@ -0,0 +1,124 @@
|
||||
"""Install helper for repositories."""
|
||||
import os
|
||||
import tempfile
|
||||
from custom_components.hacs.hacsbase.exceptions import HacsException
|
||||
from custom_components.hacs.hacsbase.backup import Backup
|
||||
|
||||
|
||||
async def install_repository(repository):
|
||||
"""Common installation steps of the repository."""
|
||||
persistent_directory = None
|
||||
await repository.update_repository()
|
||||
|
||||
if not repository.can_install:
|
||||
raise HacsException(
|
||||
"The version of Home Assistant is not compatible with this version"
|
||||
)
|
||||
|
||||
version = version_to_install(repository)
|
||||
if version == repository.information.default_branch:
|
||||
repository.ref = version
|
||||
else:
|
||||
repository.ref = f"tags/{version}"
|
||||
|
||||
if repository.repository_manifest:
|
||||
if repository.repository_manifest.persistent_directory:
|
||||
if os.path.exists(
|
||||
f"{repository.content.path.local}/{repository.repository_manifest.persistent_directory}"
|
||||
):
|
||||
persistent_directory = Backup(
|
||||
f"{repository.content.path.local}/{repository.repository_manifest.persistent_directory}",
|
||||
tempfile.gettempdir() + "/hacs_persistent_directory/",
|
||||
)
|
||||
persistent_directory.create()
|
||||
|
||||
if repository.status.installed and not repository.content.single:
|
||||
backup = Backup(repository.content.path.local)
|
||||
backup.create()
|
||||
|
||||
if (
|
||||
repository.repository_manifest.zip_release
|
||||
and version != repository.information.default_branch
|
||||
):
|
||||
validate = await repository.download_zip(repository.validate)
|
||||
else:
|
||||
validate = await repository.download_content(
|
||||
repository.validate,
|
||||
repository.content.path.remote,
|
||||
repository.content.path.local,
|
||||
repository.ref,
|
||||
)
|
||||
|
||||
if validate.errors:
|
||||
for error in validate.errors:
|
||||
repository.logger.error(error)
|
||||
if repository.status.installed and not repository.content.single:
|
||||
backup.restore()
|
||||
|
||||
if repository.status.installed and not repository.content.single:
|
||||
backup.cleanup()
|
||||
|
||||
if persistent_directory is not None:
|
||||
persistent_directory.restore()
|
||||
persistent_directory.cleanup()
|
||||
|
||||
if validate.success:
|
||||
if repository.information.full_name not in repository.common.installed:
|
||||
if repository.information.full_name == "hacs/integration":
|
||||
repository.common.installed.append(repository.information.full_name)
|
||||
repository.status.installed = True
|
||||
repository.versions.installed_commit = repository.versions.available_commit
|
||||
|
||||
if version == repository.information.default_branch:
|
||||
repository.versions.installed = None
|
||||
else:
|
||||
repository.versions.installed = version
|
||||
|
||||
await reload_after_install(repository)
|
||||
installation_complete(repository)
|
||||
|
||||
|
||||
async def reload_after_install(repository):
|
||||
"""Reload action after installation success."""
|
||||
if repository.information.category == "integration":
|
||||
if repository.config_flow:
|
||||
if repository.information.full_name != "hacs/integration":
|
||||
await repository.reload_custom_components()
|
||||
repository.pending_restart = True
|
||||
|
||||
elif repository.information.category == "theme":
|
||||
try:
|
||||
await repository.hass.services.async_call("frontend", "reload_themes", {})
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
|
||||
|
||||
def installation_complete(repository):
|
||||
"""Action to run when the installation is complete."""
|
||||
repository.hass.bus.async_fire(
|
||||
"hacs/repository",
|
||||
{
|
||||
"id": 1337,
|
||||
"action": "install",
|
||||
"repository": repository.information.full_name,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def version_to_install(repository):
|
||||
"""Determine which version to isntall."""
|
||||
if repository.versions.available is not None:
|
||||
if repository.status.selected_tag is not None:
|
||||
if repository.status.selected_tag == repository.versions.available:
|
||||
repository.status.selected_tag = None
|
||||
return repository.versions.available
|
||||
return repository.status.selected_tag
|
||||
return repository.versions.available
|
||||
if repository.status.selected_tag is not None:
|
||||
if repository.status.selected_tag == repository.information.default_branch:
|
||||
return repository.information.default_branch
|
||||
if repository.status.selected_tag in repository.releases.published_tags:
|
||||
return repository.status.selected_tag
|
||||
if repository.information.default_branch is None:
|
||||
return "master"
|
||||
return repository.information.default_branch
|
27
config/custom_components/hacs/helpers/misc.py
Executable file
27
config/custom_components/hacs/helpers/misc.py
Executable file
@@ -0,0 +1,27 @@
|
||||
"""Helper functions: misc"""
|
||||
import semantic_version
|
||||
|
||||
|
||||
def get_repository_name(
|
||||
hacs_manifest, repository_name: str, category: str = None, manifest: dict = None
|
||||
) -> str:
|
||||
"""Return the name of the repository for use in the frontend."""
|
||||
|
||||
if hacs_manifest.name is not None:
|
||||
return hacs_manifest.name
|
||||
|
||||
if category == "integration":
|
||||
if manifest:
|
||||
if "name" in manifest:
|
||||
return manifest["name"]
|
||||
|
||||
return repository_name.replace("-", " ").replace("_", " ").title()
|
||||
|
||||
|
||||
def version_left_higher_then_right(new: str, old: str) -> bool:
|
||||
"""Return a bool if source is newer than target, will also be true if identical."""
|
||||
if not isinstance(new, str) or not isinstance(old, str):
|
||||
return False
|
||||
if new == old:
|
||||
return True
|
||||
return semantic_version.Version.coerce(new) > semantic_version.Version.coerce(old)
|
Reference in New Issue
Block a user