plugin.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. """Class for plugins in HACS."""
  2. from __future__ import annotations
  3. from typing import TYPE_CHECKING
  4. from ..enums import HacsCategory, HacsDispatchEvent
  5. from ..exceptions import HacsException
  6. from ..utils.decorator import concurrent
  7. from ..utils.json import json_loads
  8. from .base import HacsRepository
  9. if TYPE_CHECKING:
  10. from ..base import HacsBase
  11. class HacsPluginRepository(HacsRepository):
  12. """Plugins in HACS."""
  13. def __init__(self, hacs: HacsBase, full_name: str):
  14. """Initialize."""
  15. super().__init__(hacs=hacs)
  16. self.data.full_name = full_name
  17. self.data.full_name_lower = full_name.lower()
  18. self.data.file_name = None
  19. self.data.category = HacsCategory.PLUGIN
  20. self.content.path.local = self.localpath
  21. @property
  22. def localpath(self):
  23. """Return localpath."""
  24. return f"{self.hacs.core.config_path}/www/community/{self.data.full_name.split('/')[-1]}"
  25. async def validate_repository(self):
  26. """Validate."""
  27. # Run common validation steps.
  28. await self.common_validate()
  29. # Custom step 1: Validate content.
  30. self.update_filenames()
  31. if self.content.path.remote is None:
  32. raise HacsException(
  33. f"{self.string} Repository structure for {self.ref.replace('tags/','')} is not compliant"
  34. )
  35. if self.content.path.remote == "release":
  36. self.content.single = True
  37. # Handle potential errors
  38. if self.validate.errors:
  39. for error in self.validate.errors:
  40. if not self.hacs.status.startup:
  41. self.logger.error("%s %s", self.string, error)
  42. return self.validate.success
  43. @concurrent(concurrenttasks=10, backoff_time=5)
  44. async def update_repository(self, ignore_issues=False, force=False):
  45. """Update."""
  46. if not await self.common_update(ignore_issues, force) and not force:
  47. return
  48. # Get plugin objects.
  49. self.update_filenames()
  50. if self.content.path.remote is None:
  51. self.validate.errors.append(
  52. f"{self.string} Repository structure for {self.ref.replace('tags/','')} is not compliant"
  53. )
  54. if self.content.path.remote == "release":
  55. self.content.single = True
  56. # Signal entities to refresh
  57. if self.data.installed:
  58. self.hacs.async_dispatch(
  59. HacsDispatchEvent.REPOSITORY,
  60. {
  61. "id": 1337,
  62. "action": "update",
  63. "repository": self.data.full_name,
  64. "repository_id": self.data.id,
  65. },
  66. )
  67. async def get_package_content(self):
  68. """Get package content."""
  69. try:
  70. package = await self.repository_object.get_contents("package.json", self.ref)
  71. package = json_loads(package.content)
  72. if package:
  73. self.data.authors = package["author"]
  74. except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
  75. pass
  76. def update_filenames(self) -> None:
  77. """Get the filename to target."""
  78. # Handler for plug requirement 3
  79. if self.repository_manifest.filename:
  80. valid_filenames = (self.repository_manifest.filename,)
  81. else:
  82. valid_filenames = (
  83. f"{self.data.name.replace('lovelace-', '')}.js",
  84. f"{self.data.name}.js",
  85. f"{self.data.name}.umd.js",
  86. f"{self.data.name}-bundle.js",
  87. )
  88. if not self.repository_manifest.content_in_root:
  89. if self.releases.objects:
  90. release = self.releases.objects[0]
  91. if release.assets:
  92. if assetnames := [
  93. filename
  94. for filename in valid_filenames
  95. for asset in release.assets
  96. if filename == asset.name
  97. ]:
  98. self.data.file_name = assetnames[0]
  99. self.content.path.remote = "release"
  100. return
  101. for location in ("",) if self.repository_manifest.content_in_root else ("dist", ""):
  102. for filename in valid_filenames:
  103. if f"{location+'/' if location else ''}{filename}" in [
  104. x.full_path for x in self.tree
  105. ]:
  106. self.data.file_name = filename.split("/")[-1]
  107. self.content.path.remote = location
  108. break