Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions app/api/endpoints/catalogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,7 @@ async def get_catalog(type: str, id: str, response: Response, token: str):
logger.info(f"Found {len(recommendations)} recommendations for item {item_id}")

elif id.startswith("watchly.theme."):
recommendations = await recommendation_service.get_recommendations_for_theme(
theme_id=id, content_type=type
)
recommendations = await recommendation_service.get_recommendations_for_theme(theme_id=id, content_type=type)
logger.info(f"Found {len(recommendations)} recommendations for theme {id}")

else:
Expand Down
2 changes: 1 addition & 1 deletion app/api/endpoints/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def get_config_id(catalog) -> str | None:


async def _manifest_handler(response: Response, token: str):
response.headers["Cache-Control"] = "public, max-age=7200"
response.headers["Cache-Control"] = "no-cache"

if not token:
raise HTTPException(status_code=401, detail="Missing token. Please reconfigure the addon.")
Expand Down
2 changes: 1 addition & 1 deletion app/core/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.1.2"
__version__ = "1.1.4"
4 changes: 1 addition & 3 deletions app/services/scoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ def process_item(self, raw_item: dict) -> ScoredItem:
source_type="loved" if item.is_loved else ("liked" if item.is_liked else "watched"),
)

def calculate_score(
self, item: dict | StremioLibraryItem, is_loved: bool = False, is_liked: bool = False
) -> float:
def calculate_score(self, item: dict | StremioLibraryItem, is_loved: bool = False, is_liked: bool = False) -> float:
"""
Backwards compatible method to just get the float score.
Accepts either a raw dict or a StremioLibraryItem.
Expand Down
4 changes: 1 addition & 3 deletions app/services/stremio_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,7 @@ async def _post_with_retries(self, client: httpx.AsyncClient, url: str, json: di
except httpx.RequestError as e:
attempts += 1
backoff = (2 ** (attempts - 1)) + random.uniform(0, 0.25)
logger.warning(
f"Stremio POST {url} request error: {e}; retry {attempts}/{max_tries} in {backoff:.2f}s"
)
logger.warning(f"Stremio POST {url} request error: {e}; retry {attempts}/{max_tries} in {backoff:.2f}s")
await asyncio.sleep(backoff)
last_exc = e
continue
Expand Down
4 changes: 1 addition & 3 deletions app/services/tmdb_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ async def _make_request(self, endpoint: str, params: dict | None = None) -> dict
try:
return response.json()
except ValueError as e:
logger.error(
f"TMDB API returned invalid JSON for {endpoint}: {e}. Response: {response.text[:200]}"
)
logger.error(f"TMDB API returned invalid JSON for {endpoint}: {e}. Response: {response.text[:200]}")
return {}
except httpx.HTTPStatusError as e:
status = e.response.status_code
Expand Down
29 changes: 28 additions & 1 deletion app/services/token_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def _ensure_secure_salt(self) -> None:
if not settings.TOKEN_SALT or settings.TOKEN_SALT == "change-me":
logger.error("Refusing to store credentials because TOKEN_SALT is unset or using the insecure default.")
raise RuntimeError(
"Server misconfiguration: TOKEN_SALT must be set to a non-default value before storing credentials."
"Server misconfiguration: TOKEN_SALT must be set to a non-default value before storing" " credentials."
)

def _get_cipher(self) -> Fernet:
Expand Down Expand Up @@ -145,6 +145,20 @@ async def store_user_data(self, user_id: str, payload: dict[str, Any]) -> str:
else:
await client.set(key, json_str)

# Invalidate async LRU cache for fresh reads on subsequent requests
try:
# bound method supports targeted invalidation by argument(s)
self.get_user_data.cache_invalidate(token)
except KeyError:
# The token was not in the cache, no action needed.
pass
except Exception as e:
logger.warning(f"Targeted cache invalidation failed: {e}. Falling back to clearing cache.")
try:
self.get_user_data.cache_clear()
except Exception as e_clear:
logger.error(f"Error while clearing cache: {e_clear}")

# Ensure we remove from negative cache so new value is read next time
try:
if token in self._missing_tokens:
Expand Down Expand Up @@ -194,6 +208,19 @@ async def delete_token(self, token: str = None, key: str = None) -> None:
client = await self._get_client()
await client.delete(key)

# Invalidate async LRU cache so future reads reflect deletion
try:
if token:
self.get_user_data.cache_invalidate(token)
else:
# If only key is provided, clear cache entirely to be safe
self.get_user_data.cache_clear()
except KeyError:
# The token was not in the cache, no action needed.
pass
except Exception as e:
logger.warning(f"Failed to invalidate user data cache during token deletion: {e}")

# Remove from negative cache as token is deleted
try:
if token and token in self._missing_tokens:
Expand Down
Loading