screen-locker/screen_locker/_shutdown_base.py

103 lines
3.5 KiB
Python
Raw Normal View History

"""Daily shutdown-time base reset for the screen locker.
On each new calendar day the shutdown config is reset to base hours (21:00
by default) so that the day's workout bonuses always layer on top of a known
floor rather than accumulating indefinitely across days.
The sick-day state file is cleared on reset so the sick-restore path cannot
overwrite the fresh base when it runs later in the same startup.
"""
from __future__ import annotations
from datetime import datetime, timezone
import json
import logging
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from pathlib import Path
_logger = logging.getLogger(__name__)
_DEFAULT_BASE_HOUR = 21
def get_base_hours(state_file: Path) -> tuple[int, int]:
"""Return ``(base_mon_wed_hour, base_thu_sun_hour)`` from *state_file*.
Falls back to ``(21, 21)`` if the file is missing or corrupt.
"""
if not state_file.exists():
return (_DEFAULT_BASE_HOUR, _DEFAULT_BASE_HOUR)
try:
with state_file.open() as f:
state: dict[str, Any] = json.load(f)
return (
int(state.get("base_mon_wed_hour", _DEFAULT_BASE_HOUR)),
int(state.get("base_thu_sun_hour", _DEFAULT_BASE_HOUR)),
)
except (OSError, json.JSONDecodeError, ValueError):
return (_DEFAULT_BASE_HOUR, _DEFAULT_BASE_HOUR)
def reset_to_base_if_new_day(
state_file: Path,
mixin: object,
sick_day_state_file: Path | None = None,
) -> bool:
"""Reset the shutdown config to base hours if a new calendar day has begun.
Writes base hours via *mixin*._write_shutdown_config (with restore=True so
the script allows moving the time earlier), updates ``last_reset_date`` in
*state_file*, and removes *sick_day_state_file* if it exists so the
sick-restore path does not fight with the fresh base on the same startup.
Returns True if a reset was performed, False if today was already reset.
"""
today = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
if state_file.exists():
try:
with state_file.open() as f:
state: dict[str, Any] = json.load(f)
if state.get("last_reset_date") == today:
return False
except (OSError, json.JSONDecodeError):
pass
base_mw, base_ts = get_base_hours(state_file)
# Preserve the morning-end hour from the live config.
config = mixin._read_shutdown_config()
morning_end = config[2] if config else 5
ok: bool = mixin._write_shutdown_config(base_mw, base_ts, morning_end, restore=True)
if not ok:
_logger.warning("Daily base reset: failed to write shutdown config.")
return False
# Clear stale sick-day state so it does not override the base reset.
if sick_day_state_file is not None and sick_day_state_file.exists():
try:
sick_day_state_file.unlink()
_logger.info("Daily base reset: cleared stale sick-day state.")
except OSError as exc:
_logger.warning(
"Daily base reset: could not remove sick-day state: %s", exc
)
new_state: dict[str, Any] = {
"base_mon_wed_hour": base_mw,
"base_thu_sun_hour": base_ts,
"last_reset_date": today,
}
try:
with state_file.open("w") as f:
json.dump(new_state, f, indent=2)
except OSError as exc:
_logger.warning("Daily base reset: failed to write state file: %s", exc)
_logger.info("Daily base reset: Mon-Wed=%d, Thu-Sun=%d.", base_mw, base_ts)
return True