Skip to content

Commit

Permalink
refactor: switch to proper reconfig flow (#936)
Browse files Browse the repository at this point in the history
* refactor: switch to proper reconfig flow

* update tests, remove commented code

* linting
  • Loading branch information
firstof9 authored Jul 5, 2024
1 parent 4f5fdb9 commit 5bf4fbc
Show file tree
Hide file tree
Showing 28 changed files with 903 additions and 1,516 deletions.
71 changes: 7 additions & 64 deletions custom_components/mail_and_packages/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import (
CONF_ALLOW_EXTERNAL,
CONF_AMAZON_DAYS,
CONF_AMAZON_FWDS,
CONF_IMAGE_SECURITY,
Expand All @@ -23,13 +22,12 @@
COORDINATOR,
DEFAULT_AMAZON_DAYS,
DEFAULT_AMAZON_FWDS,
DEFAULT_IMAP_TIMEOUT,
DOMAIN,
ISSUE_URL,
PLATFORMS,
VERSION,
)
from .helpers import default_image_path, process_emails
from .helpers import process_emails

_LOGGER = logging.getLogger(__name__)

Expand All @@ -51,36 +49,14 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
hass.data.setdefault(DOMAIN, {})
updated_config = config_entry.data.copy()

# Set amazon fwd blank if missing
if CONF_AMAZON_FWDS not in updated_config.keys():
updated_config[CONF_AMAZON_FWDS] = []

# Set default timeout if missing
if CONF_IMAP_TIMEOUT not in updated_config.keys():
updated_config[CONF_IMAP_TIMEOUT] = DEFAULT_IMAP_TIMEOUT

# Set external path off by default
if CONF_ALLOW_EXTERNAL not in config_entry.data.keys():
updated_config[CONF_ALLOW_EXTERNAL] = False

updated_config[CONF_PATH] = default_image_path(hass, config_entry)

# Set image security always on
if CONF_IMAGE_SECURITY not in config_entry.data.keys():
updated_config[CONF_IMAGE_SECURITY] = True

# Sort the resources
updated_config[CONF_RESOURCES] = sorted(updated_config[CONF_RESOURCES])

if updated_config != config_entry.data:
hass.config_entries.async_update_entry(config_entry, data=updated_config)

config_entry.add_update_listener(update_listener)

hass.config_entries.async_update_entry(config_entry, options=config_entry.data)
config = config_entry.data

# Variables for data coordinator
config = config_entry.data
host = config.get(CONF_HOST)
the_timeout = config.get(CONF_IMAP_TIMEOUT)
interval = config.get(CONF_SCAN_INTERVAL)
Expand Down Expand Up @@ -124,34 +100,16 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
return unload_ok


async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Update listener."""
_LOGGER.debug("Attempting to reload sensors from the %s integration", DOMAIN)

if config_entry.data == config_entry.options:
_LOGGER.debug("No changes detected not reloading sensors.")
return

new_data = config_entry.options.copy()

hass.config_entries.async_update_entry(
entry=config_entry,
data=new_data,
)

await hass.config_entries.async_reload(config_entry.entry_id)


async def async_migrate_entry(hass, config_entry):
"""Migrate an old config entry."""
version = config_entry.version
new_version = 7

_LOGGER.debug("Migrating from version %s", version)
updated_config = config_entry.data.copy()

# 1 -> 4: Migrate format
if version == 1:
_LOGGER.debug("Migrating from version %s", version)
updated_config = config_entry.data.copy()

if CONF_AMAZON_FWDS in updated_config.keys():
if not isinstance(updated_config[CONF_AMAZON_FWDS], list):
updated_config[CONF_AMAZON_FWDS] = [
Expand All @@ -163,7 +121,7 @@ async def async_migrate_entry(hass, config_entry):
_LOGGER.warning("Missing configuration data: %s", CONF_AMAZON_FWDS)

# Force path change
updated_config[CONF_PATH] = "images/mail_and_packages/"
updated_config[CONF_PATH] = "custom_components/mail_and_packages/images/"

# Always on image security
if not config_entry.data[CONF_IMAGE_SECURITY]:
Expand All @@ -174,11 +132,8 @@ async def async_migrate_entry(hass, config_entry):

# 2 -> 4
if version <= 2:
_LOGGER.debug("Migrating from version %s", version)
updated_config = config_entry.data.copy()

# Force path change
updated_config[CONF_PATH] = "images/mail_and_packages/"
updated_config[CONF_PATH] = "custom_components/mail_and_packages/images/"

# Always on image security
if not config_entry.data[CONF_IMAGE_SECURITY]:
Expand All @@ -188,32 +143,20 @@ async def async_migrate_entry(hass, config_entry):
updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS

if version <= 3:
_LOGGER.debug("Migrating from version %s", version)
updated_config = config_entry.data.copy()

# Add default Amazon Days configuration
updated_config[CONF_AMAZON_DAYS] = DEFAULT_AMAZON_DAYS

if version <= 4:
_LOGGER.debug("Migrating from version %s", version)
updated_config = config_entry.data.copy()

if CONF_AMAZON_FWDS in updated_config and updated_config[CONF_AMAZON_FWDS] == [
'""'
]:
updated_config[CONF_AMAZON_FWDS] = DEFAULT_AMAZON_FWDS

if version <= 5:
_LOGGER.debug("Migrating from version %s", version)
updated_config = config_entry.data.copy()

if CONF_VERIFY_SSL not in updated_config:
updated_config[CONF_VERIFY_SSL] = True

if version <= 6:
_LOGGER.debug("Migrating from version %s", version)
updated_config = config_entry.data.copy()

if CONF_IMAP_SECURITY not in updated_config:
updated_config[CONF_IMAP_SECURITY] = "SSL"

Expand Down
104 changes: 45 additions & 59 deletions custom_components/mail_and_packages/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
CONF_RESOURCES,
CONF_USERNAME,
)
from homeassistant.core import callback

from .const import (
CONF_ALLOW_EXTERNAL,
Expand Down Expand Up @@ -199,7 +198,7 @@ def _get_default(key: str, fallback_default: Any = None) -> None:
CONF_RESOURCES, default=_get_default(CONF_RESOURCES)
): cv.multi_select(get_resources()),
vol.Optional(
CONF_AMAZON_FWDS, default=_get_default(CONF_AMAZON_FWDS)
CONF_AMAZON_FWDS, default=_get_default(CONF_AMAZON_FWDS, "(none)")
): cv.string,
vol.Optional(CONF_AMAZON_DAYS, default=_get_default(CONF_AMAZON_DAYS)): int,
vol.Optional(
Expand Down Expand Up @@ -252,6 +251,7 @@ class MailAndPackagesFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):

def __init__(self):
"""Initialize."""
self._entry = {}
self._data = {}
self._errors = {}

Expand Down Expand Up @@ -359,27 +359,15 @@ async def _show_config_3(self, user_input):
errors=self._errors,
)

@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Redirect to options flow."""
return MailAndPackagesOptionsFlow(config_entry)


class MailAndPackagesOptionsFlow(config_entries.OptionsFlow):
"""Options flow for Mail and Packages."""

def __init__(self, config_entry):
"""Initialize."""
self.config = config_entry
self._data = dict(config_entry.options)
async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None):
"""Add reconfigure step to allow to reconfigure a config entry."""
self._entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
assert self._entry
self._data = dict(self._entry.data)
self._errors = {}

async def async_step_init(self, user_input=None):
"""Manage Mail and Packages options."""
if user_input is not None:
self._data.update(user_input)

valid = await _test_login(
user_input[CONF_HOST],
user_input[CONF_PORT],
Expand All @@ -391,80 +379,78 @@ async def async_step_init(self, user_input=None):
if not valid:
self._errors["base"] = "communication"
else:
return await self.async_step_options_2()
return await self.async_step_reconfig_2()

return await self._show_options_form(user_input)
return await self._show_reconfig_form(user_input)

return await self._show_options_form(user_input)
return await self._show_reconfig_form(user_input)

async def _show_options_form(self, user_input):
"""Show the configuration form to edit location data."""
async def _show_reconfig_form(self, user_input):
"""Show the configuration form to edit configuration data."""
return self.async_show_form(
step_id="init",
step_id="reconfigure",
data_schema=_get_schema_step_1(user_input, self._data),
errors=self._errors,
)

async def async_step_options_2(self, user_input=None):
async def async_step_reconfig_2(self, user_input=None):
"""Configure form step 2."""
self._errors = {}
if user_input is not None:
self._errors, user_input = await _validate_user_input(user_input)
self._data.update(user_input)
if len(self._errors) == 0:
if self._data[CONF_CUSTOM_IMG]:
return await self.async_step_options_3()
return self.async_create_entry(title="", data=self._data)
return await self._show_step_options_2(user_input)
return await self._show_step_options_2(user_input)
return await self.async_step_reconfig_3()
self.hass.config_entries.async_update_entry(
self._entry, data=self._data
)
await self.hass.config_entries.async_reload(self._entry.entry_id)
_LOGGER.debug("%s reconfigured.", DOMAIN)
return self.async_abort(reason="reconfigure_successful")

async def _show_step_options_2(self, user_input):
"""Step 2 of options."""
# Defaults
defaults = {
CONF_FOLDER: self._data.get(CONF_FOLDER),
CONF_SCAN_INTERVAL: self._data.get(CONF_SCAN_INTERVAL),
CONF_PATH: self._data.get(CONF_PATH),
CONF_DURATION: self._data.get(CONF_DURATION),
CONF_IMAGE_SECURITY: self._data.get(CONF_IMAGE_SECURITY),
CONF_IMAP_TIMEOUT: self._data.get(CONF_IMAP_TIMEOUT)
or DEFAULT_IMAP_TIMEOUT,
CONF_AMAZON_FWDS: self._data.get(CONF_AMAZON_FWDS) or DEFAULT_AMAZON_FWDS,
CONF_AMAZON_DAYS: self._data.get(CONF_AMAZON_DAYS) or DEFAULT_AMAZON_DAYS,
CONF_GENERATE_MP4: self._data.get(CONF_GENERATE_MP4),
CONF_ALLOW_EXTERNAL: self._data.get(CONF_ALLOW_EXTERNAL),
CONF_RESOURCES: self._data.get(CONF_RESOURCES),
CONF_CUSTOM_IMG: self._data.get(CONF_CUSTOM_IMG) or DEFAULT_CUSTOM_IMG,
}
return await self._show_reconfig_2(user_input)

return await self._show_reconfig_2(user_input)

async def _show_reconfig_2(self, user_input):
"""Step 2 setup."""
if self._data[CONF_AMAZON_FWDS] == []:
self._data[CONF_AMAZON_FWDS] = "(none)"

return self.async_show_form(
step_id="options_2",
data_schema=_get_schema_step_2(self._data, user_input, defaults),
step_id="reconfig_2",
data_schema=_get_schema_step_2(self._data, user_input, self._data),
errors=self._errors,
)

async def async_step_options_3(self, user_input=None):
"""Configure form step 3."""
async def async_step_reconfig_3(self, user_input=None):
"""Configure form step 2."""
self._errors = {}
if user_input is not None:
self._data.update(user_input)
self._errors, user_input = await _validate_user_input(self._data)
if len(self._errors) == 0:
return self.async_create_entry(title="", data=self._data)
return await self._show_step_options_3(user_input)
self.hass.config_entries.async_update_entry(
self._entry, data=self._data
)
await self.hass.config_entries.async_reload(self._entry.entry_id)
_LOGGER.debug("%s reconfigured.", DOMAIN)
return self.async_abort(reason="reconfigure_successful")

return await self._show_step_options_3(user_input)
return await self._show_reconfig_3(user_input)

async def _show_step_options_3(self, user_input):
return await self._show_reconfig_3(user_input)

async def _show_reconfig_3(self, user_input):
"""Step 3 setup."""
# Defaults
defaults = {
CONF_CUSTOM_IMG_FILE: self._data.get(CONF_CUSTOM_IMG_FILE)
or DEFAULT_CUSTOM_IMG_FILE,
CONF_CUSTOM_IMG_FILE: DEFAULT_CUSTOM_IMG_FILE,
}

return self.async_show_form(
step_id="options_3",
step_id="reconfig_3",
data_schema=_get_schema_step_3(user_input, defaults),
errors=self._errors,
)
44 changes: 41 additions & 3 deletions custom_components/mail_and_packages/strings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"config": {
"abort": {
"single_instance_allowed": "Only a single configuration of Mail and Packages is allowed."
"single_instance_allowed": "Only a single configuration of Mail and Packages is allowed.",
"reconfigure_successful": "Reconfigure Successful"
},
"error": {
"communication": "Unable to connect or login to the mail server. Please check the log for details.",
Expand Down Expand Up @@ -43,13 +44,50 @@
"description": "Finish the configuration by customizing the following based on your email structure and Home Assistant installation.\n\nFor details on the [Mail and Packages integration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) options review the [configuration, templates, and automations section](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) on GitHub.\n\nIf using Amazon forwarded emails please seperate each address with a comma or enter (none) to clear this setting.",
"title": "Mail and Packages (Step 2 of 2)"
},
"options_3": {
"config_3": {
"data": {
"custom_img_file": "Path to custom image: (ie: images/my_custom_no_mail.jpg)"
},
"description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif",
"title": "Mail and Packages (Step 3 of 3)"
}
},
"reconfigure": {
"data": {
"host": "Host",
"password": "Password",
"port": "Port",
"username": "Username",
"imap_security": "IMAP Security",
"verify_ssl": "Verify SSL Cert"
},
"description": "Please enter the connection information of your mail server.",
"title": "Mail and Packages (Step 1 of 2)"
},
"reconfig_2": {
"data": {
"folder": "Mail Folder",
"scan_interval": "Scanning Interval (minutes)",
"image_path": "Image Path",
"gif_duration": "Image Duration (seconds)",
"image_security": "Random Image Filename",
"generate_mp4": "Create mp4 from images",
"resources": "Sensors List",
"imap_timeout": "Time in seconds before connection timeout (seconds, minimum 10)",
"amazon_fwds": "Amazon fowarded email addresses",
"allow_external": "Create image for notification apps",
"amazon_days": "Days back to check for Amazon emails",
"custom_img": "Use custom 'no image' image?"
},
"description": "Finish the configuration by customizing the following based on your email structure and Home Assistant installation.\n\nFor details on the [Mail and Packages integration](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) options review the [configuration, templates, and automations section](https://github.com/moralmunky/Home-Assistant-Mail-And-Packages/wiki/Configuration-and-Email-Settings#configuration) on GitHub.\n\nIf using Amazon forwarded emails please seperate each address with a comma or enter (none) to clear this setting.",
"title": "Mail and Packages (Step 2 of 2)"
},
"reconfig_3": {
"data": {
"custom_img_file": "Path to custom image: (i.e.: images/my_custom_no_mail.jpg)"
},
"description": "Enter the path and file name to your custom no mail image below.\n\nExample: images/custom_nomail.gif",
"title": "Mail and Packages (Step 3 of 3)"
}
}
},
"options": {
Expand Down
Loading

0 comments on commit 5bf4fbc

Please sign in to comment.