mirror of https://github.com/Nezreka/SoulSync.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
135 lines
4.0 KiB
135 lines
4.0 KiB
"""Wishlist processing state helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Callable, Optional
|
|
|
|
|
|
WISHLIST_STUCK_TIMEOUT_SECONDS = 900
|
|
|
|
|
|
def flag_age_seconds(started_at: Optional[float], now: Optional[float] = None) -> float:
|
|
"""Return the age of a flag in seconds."""
|
|
if not started_at:
|
|
return 0.0
|
|
if now is None:
|
|
import time
|
|
|
|
now = time.time()
|
|
return max(0.0, now - started_at)
|
|
|
|
|
|
def is_flag_recent(
|
|
active: bool,
|
|
started_at: Optional[float],
|
|
timeout_seconds: int = WISHLIST_STUCK_TIMEOUT_SECONDS,
|
|
now: Optional[float] = None,
|
|
) -> bool:
|
|
"""Return True when an active flag is still within the allowed window."""
|
|
if not active or not started_at:
|
|
return False
|
|
return flag_age_seconds(started_at, now=now) <= timeout_seconds
|
|
|
|
|
|
def is_flag_stuck(
|
|
active: bool,
|
|
started_at: Optional[float],
|
|
timeout_seconds: int = WISHLIST_STUCK_TIMEOUT_SECONDS,
|
|
now: Optional[float] = None,
|
|
) -> bool:
|
|
"""Return True when an active flag has exceeded the timeout."""
|
|
if not active or not started_at:
|
|
return False
|
|
return flag_age_seconds(started_at, now=now) > timeout_seconds
|
|
|
|
|
|
def is_wishlist_actually_processing(
|
|
active: bool,
|
|
started_at: Optional[float],
|
|
timeout_seconds: int = WISHLIST_STUCK_TIMEOUT_SECONDS,
|
|
now: Optional[float] = None,
|
|
on_stuck: Callable[[], None] | None = None,
|
|
logger: Any | None = None,
|
|
) -> bool:
|
|
"""Return True only when wishlist processing is active and still recent."""
|
|
if not is_flag_recent(active, started_at, timeout_seconds=timeout_seconds, now=now):
|
|
if active:
|
|
stuck_minutes = flag_age_seconds(started_at, now=now) / 60
|
|
if logger is not None:
|
|
logger.warning(f"[Stuck Detection] Wishlist flag stuck for {stuck_minutes:.1f} minutes - auto-recovering")
|
|
if on_stuck is not None:
|
|
on_stuck()
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def reset_flag_if_stuck(
|
|
active: bool,
|
|
started_at: Optional[float],
|
|
*,
|
|
timeout_seconds: int = WISHLIST_STUCK_TIMEOUT_SECONDS,
|
|
now: Optional[float] = None,
|
|
label: str = "Wishlist auto-processing",
|
|
logger: Any | None = None,
|
|
reset_callback: Callable[[], None],
|
|
) -> bool:
|
|
"""Reset a processing flag if it has exceeded the timeout."""
|
|
if not is_flag_stuck(active, started_at, timeout_seconds=timeout_seconds, now=now):
|
|
return False
|
|
|
|
stuck_minutes = flag_age_seconds(started_at, now=now) / 60
|
|
if logger is not None:
|
|
logger.info(f"[Stuck Detection] {label} flag has been stuck for {stuck_minutes:.1f} minutes - RESETTING")
|
|
reset_callback()
|
|
return True
|
|
|
|
|
|
def get_wishlist_cycle(db_factory: Callable[[], Any], default_cycle: str = "albums") -> str:
|
|
"""Return the stored wishlist cycle, creating the default entry if needed."""
|
|
db = db_factory()
|
|
with db._get_connection() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute("SELECT value FROM metadata WHERE key = 'wishlist_cycle'")
|
|
row = cursor.fetchone()
|
|
|
|
if row:
|
|
return row["value"]
|
|
|
|
cursor.execute(
|
|
"""
|
|
INSERT OR REPLACE INTO metadata (key, value, updated_at)
|
|
VALUES ('wishlist_cycle', ?, CURRENT_TIMESTAMP)
|
|
""",
|
|
(default_cycle,),
|
|
)
|
|
conn.commit()
|
|
return default_cycle
|
|
|
|
|
|
def set_wishlist_cycle(db_factory: Callable[[], Any], cycle: str) -> None:
|
|
"""Persist the wishlist cycle value."""
|
|
db = db_factory()
|
|
with db._get_connection() as conn:
|
|
cursor = conn.cursor()
|
|
cursor.execute(
|
|
"""
|
|
INSERT OR REPLACE INTO metadata (key, value, updated_at)
|
|
VALUES ('wishlist_cycle', ?, CURRENT_TIMESTAMP)
|
|
""",
|
|
(cycle,),
|
|
)
|
|
conn.commit()
|
|
|
|
|
|
__all__ = [
|
|
"WISHLIST_STUCK_TIMEOUT_SECONDS",
|
|
"flag_age_seconds",
|
|
"is_flag_recent",
|
|
"is_flag_stuck",
|
|
"is_wishlist_actually_processing",
|
|
"reset_flag_if_stuck",
|
|
"get_wishlist_cycle",
|
|
"set_wishlist_cycle",
|
|
]
|