mirror of
https://github.com/kuhyx/steam-backlog-enforcer.git
synced 2026-11-10 19:27:04 +01:00
The 250-line split left function-sized copies behind: the confidence-only batch fetch was the search half of _fetch_batch verbatim (now _search_batch, and _SearchCtx carries one _HLTBExtras instead of two of its dicts); the workout and LeetCode bonuses each had the same loopback GET and answer cache (now _status_api); the poll-count refetch lived in both _cmd_done and _polls_reporting (now refetch_poll_counts); the candidate-selection prelude in both scanning and _scanning_assign; the /etc/hosts rewrite loop in both block and unblock (now rewrite_hosts_lines with a per-line rule); the timed HLTB fetch and its rate line; and the whitelist constants each module only half used. _hltb_cached's dead facade constants go too. Tests patch where the code now lives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HHCbkJWXHjtTUaqvnHRAqA
90 lines
2.9 KiB
Python
90 lines
2.9 KiB
Python
"""Whether today has a counted workout, asked over loopback HTTP.
|
|
|
|
The screen locker owns the question "did I work out today"; this module asks it
|
|
and caches the answer. Turning it into seconds is
|
|
:mod:`steam_backlog_enforcer._budget_resolve`'s job -- this file publishes only
|
|
the fact, mirroring the split screen-locker itself documents, so the two repos
|
|
cannot disagree about what a workout day is.
|
|
|
|
**Fail closed.** Any failure to get an answer -- status server down, timeout,
|
|
malformed body, non-200 -- yields ``None``, logged at warning with the concrete
|
|
reason, and the caller grants no workout bonus. Failing open would let the
|
|
whole coupling be defeated by stopping one user service, and a silent bonus
|
|
would be indistinguishable from an earned one.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
from steam_backlog_enforcer._status_api import AnswerCache, get_status
|
|
|
|
if TYPE_CHECKING:
|
|
from steam_backlog_enforcer.config import Config
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_cache = AnswerCache()
|
|
|
|
|
|
def reset_cache() -> None:
|
|
"""Drop the cached answer, forcing the next call to re-ask."""
|
|
_cache.clear()
|
|
|
|
|
|
def _fetch_workout_today(url: str) -> bool:
|
|
"""Read ``gaming.workout_today`` from the screen-locker status API.
|
|
|
|
Args:
|
|
url: The status endpoint, e.g. ``http://127.0.0.1:8770/api/status``.
|
|
|
|
Returns:
|
|
Whether a counted workout is logged for today.
|
|
|
|
Raises:
|
|
OSError: The endpoint could not be reached.
|
|
ValueError: The response was not usable JSON, or not a 200.
|
|
KeyError: The payload lacked the expected fields.
|
|
"""
|
|
return bool(get_status(url)["gaming"]["workout_today"])
|
|
|
|
|
|
def workout_logged_today(config: Config) -> bool | None:
|
|
"""Ask the screen locker whether today has a counted workout.
|
|
|
|
Args:
|
|
config: Loaded user configuration, for the status URL.
|
|
|
|
Returns:
|
|
True or False when the locker answered, ``None`` when it could not be
|
|
reached or its answer could not be understood. ``None`` is deliberately
|
|
distinct from ``False`` so the caller can log which one happened.
|
|
"""
|
|
url = config.workout_status_url
|
|
cached = _cache.fresh(url)
|
|
if cached is not None:
|
|
return cached
|
|
|
|
try:
|
|
answer = _fetch_workout_today(url)
|
|
except OSError as exc:
|
|
logger.warning(
|
|
"Could not reach the screen-locker status API at %s (%s) — "
|
|
"granting no workout bonus. Check "
|
|
"`systemctl --user status screen-locker-web`.",
|
|
url,
|
|
exc,
|
|
)
|
|
return None
|
|
except (ValueError, KeyError, TypeError) as exc:
|
|
logger.warning(
|
|
"Screen-locker status API at %s returned something unusable (%s) — "
|
|
"granting no workout bonus.",
|
|
url,
|
|
exc,
|
|
)
|
|
return None
|
|
|
|
return _cache.store(url, answer=answer)
|