Skip to content

Commit

Permalink
Tackling memory leaks (hopefully once and for all)
Browse files Browse the repository at this point in the history
Removed reliance on static json files and I think these could be the cause of the memory leaks. They also weren't a very good idea anyway.
  • Loading branch information
jampez77 committed Sep 10, 2024
1 parent ef29e8e commit bc0a8d4
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 164 deletions.
4 changes: 3 additions & 1 deletion custom_components/ryanair/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from .const import DOMAIN
import homeassistant.helpers.config_validation as cv
import asyncio
from homeassistant.config_entries import ConfigEntryState

PLATFORMS = [Platform.SENSOR, Platform.IMAGE]
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
Expand Down Expand Up @@ -37,7 +38,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

async def options_update_listener(hass: HomeAssistant, config_entry: ConfigEntry):
"""Handle options update."""
await hass.config_entries.async_reload(config_entry.entry_id)
if config_entry.state == ConfigEntryState.LOADED:
await hass.config_entries.async_reload(config_entry.entry_id)


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
Expand Down
147 changes: 100 additions & 47 deletions custom_components/ryanair/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@
from typing import Any
import uuid
import hashlib
from pathlib import Path
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.helpers.json import save_json
from homeassistant.util.json import load_json_object, JsonObjectType
from .const import (
DOMAIN,
CONF_DEVICE_FINGERPRINT,
Expand All @@ -22,13 +19,12 @@
MFA_TOKEN,
MFA_CODE,
CODE_MFA_CODE_WRONG,
PERSISTENCE
CUSTOMERS,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .coordinator import RyanairCoordinator, RyanairMfaCoordinator
from .errors import CannotConnect

CREDENTIALS = Path(__file__).parent / PERSISTENCE

STEP_USER_DATA_SCHEMA = vol.Schema(
{
Expand All @@ -43,14 +39,15 @@
)


async def async_load_json_object(hass: HomeAssistant, path: Path) -> JsonObjectType:
return await hass.async_add_executor_job(load_json_object, path)
def generate_device_fingerprint(email: str) -> str:
unique_id = hashlib.md5(email.encode("UTF-8")).hexdigest()
return str(uuid.UUID(hex=unique_id))


async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
async def validate_input(hass: HomeAssistant, data: dict[str, Any], fingerprint: str) -> dict[str, Any]:
"""Validate the user input allows us to connect."""
session = async_get_clientsession(hass)
coordinator = RyanairCoordinator(hass, session, data)
coordinator = RyanairCoordinator(hass, session, data, fingerprint)

await coordinator.async_refresh()

Expand Down Expand Up @@ -122,12 +119,10 @@ async def validate_mfa_input(
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Ryanair."""

VERSION = 1
VERSION = 2

def __init__(self) -> None:
self._mfa_token: str | None = None
self._fingerprint: str | None = None
self._email: str | None = None

async def async_step_mfa(
self, user_input: dict[str, Any] | None = None
Expand All @@ -136,9 +131,6 @@ async def async_step_mfa(

errors = {}
placeholder = ""
user_input[MFA_TOKEN] = self._mfa_token
user_input[CONF_EMAIL] = self._email
user_input[CONF_DEVICE_FINGERPRINT] = self._fingerprint

try:
info = await validate_mfa_input(self.hass, user_input)
Expand All @@ -151,18 +143,42 @@ async def async_step_mfa(
else:
# if data is not null and contains MFA TOKEN then initiate MFA capture
if CUSTOMER_ID in info["data"]:
users = await async_load_json_object(self.hass, CREDENTIALS)

users[user_input[CONF_DEVICE_FINGERPRINT]
][CONF_DEVICE_FINGERPRINT] = user_input[CONF_DEVICE_FINGERPRINT]
users[user_input[CONF_DEVICE_FINGERPRINT]
][CUSTOMER_ID] = info["data"][CUSTOMER_ID]
users[user_input[CONF_DEVICE_FINGERPRINT]
][TOKEN] = info["data"][TOKEN]
data = dict(user_input)
fingerprint = user_input[CONF_DEVICE_FINGERPRINT]

if CUSTOMERS not in data:
data[CUSTOMERS] = dict()

data[CUSTOMERS][self._fingerprint] = {
CONF_DEVICE_FINGERPRINT: fingerprint,
CUSTOMER_ID: info["data"][CUSTOMER_ID],
TOKEN: info["data"][TOKEN],
MFA_TOKEN: user_input[MFA_TOKEN],
CONF_EMAIL: user_input[CONF_EMAIL]
}

existing_entries = self.hass.config_entries.async_entries(
DOMAIN)

# Check if an entry already exists with the same username
existing_entry = next(
(entry for entry in existing_entries
if entry.data.get(CONF_EMAIL) == user_input[CONF_EMAIL]),
None
)

save_json(CREDENTIALS, users)
if existing_entry is not None:
# Update specific data in the entry
updated_data = existing_entry.data.copy()
# Merge the import_data into the entry_data
updated_data.update(data)
# Update the entry with the new data
self.hass.config_entries.async_update_entry(
existing_entry,
data=updated_data
)
return self.async_create_entry(
title=info["title"], data=users[user_input[CONF_DEVICE_FINGERPRINT]]
title=info["title"], data=data
)

return self.async_show_form(
Expand All @@ -186,23 +202,41 @@ async def async_step_user(
errors = {}
placeholder = ""

unique_id = hashlib.md5(
user_input[CONF_EMAIL].encode("UTF-8")).hexdigest()
self._fingerprint = str(uuid.UUID(hex=unique_id))
self._email = user_input[CONF_EMAIL]
self._fingerprint = generate_device_fingerprint(user_input[CONF_EMAIL])

data = dict(user_input)

user_input[CONF_DEVICE_FINGERPRINT] = self._fingerprint
if CUSTOMERS not in data:
data[CUSTOMERS] = dict()

users = await async_load_json_object(self.hass, CREDENTIALS)
ryanairData = {
data[CUSTOMERS][self._fingerprint] = {
CONF_DEVICE_FINGERPRINT: self._fingerprint,
CONF_EMAIL: user_input[CONF_EMAIL],
CONF_PASSWORD: user_input[CONF_PASSWORD],
CONF_DEVICE_FINGERPRINT: user_input[CONF_DEVICE_FINGERPRINT],
CONF_PASSWORD: user_input[CONF_PASSWORD]
}
users[user_input[CONF_DEVICE_FINGERPRINT]] = ryanairData
save_json(CREDENTIALS, users)

existing_entries = self.hass.config_entries.async_entries(
DOMAIN)

# Check if an entry already exists with the same username
existing_entry = next(
(entry for entry in existing_entries
if entry.data.get(CONF_EMAIL) == user_input[CONF_EMAIL]),
None
)

if existing_entry is not None:
# Update specific data in the entry
updated_data = existing_entry.data.copy()
# Merge the import_data into the entry_data
updated_data.update(data)
# Update the entry with the new data
self.hass.config_entries.async_update_entry(
existing_entry,
data=updated_data
)
try:
info = await validate_input(self.hass, user_input)
info = await validate_input(self.hass, data, self._fingerprint)
except CannotConnect:
errors["base"] = "cannot_connect"
else:
Expand All @@ -214,7 +248,6 @@ async def async_step_user(
if info["data"] is not None:
# MFA TOKEN initiates MFA code capture
if MFA_TOKEN in info["data"]:
self._mfa_token = info["data"][MFA_TOKEN]
return self.async_show_form(
step_id="mfa",
data_schema=STEP_MFA,
Expand All @@ -224,18 +257,38 @@ async def async_step_user(
},
)
if CUSTOMER_ID in info["data"]:
users = await async_load_json_object(self.hass, CREDENTIALS)

users[user_input[CONF_DEVICE_FINGERPRINT]
][CONF_DEVICE_FINGERPRINT] = user_input[CONF_DEVICE_FINGERPRINT]
users[user_input[CONF_DEVICE_FINGERPRINT]
][CUSTOMER_ID] = info["data"][CUSTOMER_ID]
users[user_input[CONF_DEVICE_FINGERPRINT]
][TOKEN] = info["data"][TOKEN]
if CUSTOMERS not in data:
data[CUSTOMERS] = dict()

data[CUSTOMERS][self._fingerprint] = {
CONF_DEVICE_FINGERPRINT: self._fingerprint,
CUSTOMER_ID: info["data"][CUSTOMER_ID],
TOKEN: info["data"][TOKEN]
}

existing_entries = self.hass.config_entries.async_entries(
DOMAIN)

# Check if an entry already exists with the same username
existing_entry = next(
(entry for entry in existing_entries
if entry.data.get(CONF_EMAIL) == user_input[CONF_EMAIL]),
None
)

save_json(CREDENTIALS, users)
if existing_entry is not None:
# Update specific data in the entry
updated_data = existing_entry.data.copy()
# Merge the import_data into the entry_data
updated_data.update(data)
# Update the entry with the new data
self.hass.config_entries.async_update_entry(
existing_entry,
data=updated_data
)
return self.async_create_entry(
title=info["title"], data=users[user_input[CONF_DEVICE_FINGERPRINT]]
title=info["title"], data=data
)

return self.async_show_form(
Expand Down
2 changes: 0 additions & 2 deletions custom_components/ryanair/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@
CODE_TRIES_REMAINING = "Account.Password.TryCount.Remaining"
CODE_UNKNOWN_DEVICE = "Account.UnknownDeviceFingerprint"
CODE_MFA_TOKEN = "Mfa.Token"
PERSISTENCE = ".ryanair.json"
BOARDING_PASS_HEADERS = ".boarding_pass.json"
BOARDING_PASSES_URI = "boardingpasses"
BOOKING_REFERENCES = "bookingreferences"
BOOKING_REFERENCE = "bookingreference"
Expand Down
Loading

0 comments on commit bc0a8d4

Please sign in to comment.