config_flow.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. """Adds config flow for HACS."""
  2. from aiogithubapi import GitHubDeviceAPI, GitHubException
  3. from aiogithubapi.common.const import OAUTH_USER_LOGIN
  4. from awesomeversion import AwesomeVersion
  5. from homeassistant import config_entries
  6. from homeassistant.const import __version__ as HAVERSION
  7. from homeassistant.core import callback
  8. from homeassistant.helpers import aiohttp_client
  9. from homeassistant.helpers.event import async_call_later
  10. from homeassistant.loader import async_get_integration
  11. import voluptuous as vol
  12. from .base import HacsBase
  13. from .const import CLIENT_ID, DOMAIN, MINIMUM_HA_VERSION
  14. from .enums import ConfigurationType
  15. from .utils.configuration_schema import RELEASE_LIMIT, hacs_config_option_schema
  16. from .utils.logger import LOGGER
  17. class HacsFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
  18. """Config flow for HACS."""
  19. VERSION = 1
  20. CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
  21. def __init__(self):
  22. """Initialize."""
  23. self._errors = {}
  24. self.device = None
  25. self.activation = None
  26. self.log = LOGGER
  27. self._progress_task = None
  28. self._login_device = None
  29. self._reauth = False
  30. async def async_step_user(self, user_input):
  31. """Handle a flow initialized by the user."""
  32. self._errors = {}
  33. if self._async_current_entries():
  34. return self.async_abort(reason="single_instance_allowed")
  35. if self.hass.data.get(DOMAIN):
  36. return self.async_abort(reason="single_instance_allowed")
  37. if user_input:
  38. if [x for x in user_input if not user_input[x]]:
  39. self._errors["base"] = "acc"
  40. return await self._show_config_form(user_input)
  41. return await self.async_step_device(user_input)
  42. ## Initial form
  43. return await self._show_config_form(user_input)
  44. async def async_step_device(self, _user_input):
  45. """Handle device steps"""
  46. async def _wait_for_activation(_=None):
  47. if self._login_device is None or self._login_device.expires_in is None:
  48. async_call_later(self.hass, 1, _wait_for_activation)
  49. return
  50. response = await self.device.activation(device_code=self._login_device.device_code)
  51. self.activation = response.data
  52. self.hass.async_create_task(
  53. self.hass.config_entries.flow.async_configure(flow_id=self.flow_id)
  54. )
  55. if not self.activation:
  56. integration = await async_get_integration(self.hass, DOMAIN)
  57. if not self.device:
  58. self.device = GitHubDeviceAPI(
  59. client_id=CLIENT_ID,
  60. session=aiohttp_client.async_get_clientsession(self.hass),
  61. **{"client_name": f"HACS/{integration.version}"},
  62. )
  63. async_call_later(self.hass, 1, _wait_for_activation)
  64. try:
  65. response = await self.device.register()
  66. self._login_device = response.data
  67. return self.async_show_progress(
  68. step_id="device",
  69. progress_action="wait_for_device",
  70. description_placeholders={
  71. "url": OAUTH_USER_LOGIN,
  72. "code": self._login_device.user_code,
  73. },
  74. )
  75. except GitHubException as exception:
  76. self.log.error(exception)
  77. return self.async_abort(reason="github")
  78. return self.async_show_progress_done(next_step_id="device_done")
  79. async def _show_config_form(self, user_input):
  80. """Show the configuration form to edit location data."""
  81. if not user_input:
  82. user_input = {}
  83. if AwesomeVersion(HAVERSION) < MINIMUM_HA_VERSION:
  84. return self.async_abort(
  85. reason="min_ha_version",
  86. description_placeholders={"version": MINIMUM_HA_VERSION},
  87. )
  88. return self.async_show_form(
  89. step_id="user",
  90. data_schema=vol.Schema(
  91. {
  92. vol.Required("acc_logs", default=user_input.get("acc_logs", False)): bool,
  93. vol.Required("acc_addons", default=user_input.get("acc_addons", False)): bool,
  94. vol.Required(
  95. "acc_untested", default=user_input.get("acc_untested", False)
  96. ): bool,
  97. vol.Required("acc_disable", default=user_input.get("acc_disable", False)): bool,
  98. }
  99. ),
  100. errors=self._errors,
  101. )
  102. async def async_step_device_done(self, _user_input):
  103. """Handle device steps"""
  104. if self._reauth:
  105. existing_entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
  106. self.hass.config_entries.async_update_entry(
  107. existing_entry, data={"token": self.activation.access_token}
  108. )
  109. await self.hass.config_entries.async_reload(existing_entry.entry_id)
  110. return self.async_abort(reason="reauth_successful")
  111. return self.async_create_entry(title="", data={"token": self.activation.access_token})
  112. async def async_step_reauth(self, user_input=None):
  113. """Perform reauth upon an API authentication error."""
  114. return await self.async_step_reauth_confirm()
  115. async def async_step_reauth_confirm(self, user_input=None):
  116. """Dialog that informs the user that reauth is required."""
  117. if user_input is None:
  118. return self.async_show_form(
  119. step_id="reauth_confirm",
  120. data_schema=vol.Schema({}),
  121. )
  122. self._reauth = True
  123. return await self.async_step_device(None)
  124. @staticmethod
  125. @callback
  126. def async_get_options_flow(config_entry):
  127. return HacsOptionsFlowHandler(config_entry)
  128. class HacsOptionsFlowHandler(config_entries.OptionsFlow):
  129. """HACS config flow options handler."""
  130. def __init__(self, config_entry):
  131. """Initialize HACS options flow."""
  132. self.config_entry = config_entry
  133. async def async_step_init(self, _user_input=None):
  134. """Manage the options."""
  135. return await self.async_step_user()
  136. async def async_step_user(self, user_input=None):
  137. """Handle a flow initialized by the user."""
  138. hacs: HacsBase = self.hass.data.get(DOMAIN)
  139. if user_input is not None:
  140. limit = int(user_input.get(RELEASE_LIMIT, 5))
  141. if limit <= 0 or limit > 100:
  142. return self.async_abort(reason="release_limit_value")
  143. return self.async_create_entry(title="", data=user_input)
  144. if hacs is None or hacs.configuration is None:
  145. return self.async_abort(reason="not_setup")
  146. if hacs.configuration.config_type == ConfigurationType.YAML:
  147. schema = {vol.Optional("not_in_use", default=""): str}
  148. else:
  149. schema = hacs_config_option_schema(self.config_entry.options)
  150. del schema["frontend_repo"]
  151. del schema["frontend_repo_url"]
  152. return self.async_show_form(step_id="user", data_schema=vol.Schema(schema))