mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-10 19:07:16 +01:00
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (flutter, flutter-analyze, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (mypy, mypy, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (pylint, pylint, ) (push) Waiting to run
pre-commit / pre-commit (${{ matrix.group }}) (rest, , mypy,pylint,flutter-analyze) (push) Waiting to run
Tests / test (push) Waiting to run
Tests / lock-popup-safety (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (0) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (1) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (2) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (3) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (4) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (5) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (6) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (7) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (8) (push) Waiting to run
Tests / flutter test (shard ${{ matrix.shard }}) (9) (push) Waiting to run
Tests / flutter-coverage (push) Blocked by required conditions
Tests / web-coverage (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
135 lines
4.5 KiB
Python
135 lines
4.5 KiB
Python
"""Sick-day shutdown state: persistence and next-day restoration.
|
|
|
|
Split out of :mod:`screen_locker._shutdown` to keep every file under the
|
|
250-line cap. Composed back into ``ShutdownMixin`` there, so callers see no
|
|
change.
|
|
|
|
Tracks the shutdown times (minutes after midnight) that were in force before
|
|
a sick day moved them earlier, so the following day can put them back. A state
|
|
file written before the minutes migration holds whole hours under
|
|
``original_*_hour`` and is still read.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
|
|
from screen_locker._constants import SICK_DAY_STATE_FILE
|
|
from screen_locker._day import today_str
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
_MINUTES_PER_HOUR = 60
|
|
|
|
|
|
def _original_minutes(state: dict[str, object], band: str) -> int | None:
|
|
"""The saved time for *band* (``mon_wed``/``thu_sun``) in minutes, if any."""
|
|
minutes = state.get(f"original_{band}_minutes")
|
|
if minutes is not None:
|
|
return int(str(minutes))
|
|
hours = state.get(f"original_{band}_hour")
|
|
if hours is not None:
|
|
return int(str(hours)) * _MINUTES_PER_HOUR
|
|
return None
|
|
|
|
|
|
class SickDayStateMixin:
|
|
"""Persists and restores the pre-sick-day shutdown configuration."""
|
|
|
|
def _sick_mode_used_today(self) -> bool:
|
|
"""Check if sick mode was already used today."""
|
|
if not SICK_DAY_STATE_FILE.exists():
|
|
return False
|
|
|
|
try:
|
|
with SICK_DAY_STATE_FILE.open() as f:
|
|
state = json.load(f)
|
|
today = today_str()
|
|
return state.get("date") == today
|
|
except (OSError, json.JSONDecodeError) as exc:
|
|
_logger.warning(
|
|
"Could not read sick-day state from %s: %s — treating sick mode "
|
|
"as UNUSED today, which may allow a second sick day",
|
|
SICK_DAY_STATE_FILE,
|
|
exc,
|
|
)
|
|
return False
|
|
|
|
def _save_sick_day_state(
|
|
self,
|
|
date: str,
|
|
orig_mon_wed: int,
|
|
orig_thu_sun: int,
|
|
) -> bool:
|
|
"""Save sick day state with the original times, in minutes.
|
|
|
|
Returns True if saved successfully, False otherwise.
|
|
"""
|
|
state = {
|
|
"date": date,
|
|
"original_mon_wed_minutes": orig_mon_wed,
|
|
"original_thu_sun_minutes": orig_thu_sun,
|
|
}
|
|
try:
|
|
with SICK_DAY_STATE_FILE.open("w") as f:
|
|
json.dump(state, f, indent=2)
|
|
except OSError as e:
|
|
_logger.warning("Failed to save sick day state: %s", e)
|
|
return False
|
|
|
|
_logger.info("Saved sick day state for %s", date)
|
|
return True
|
|
|
|
def _load_sick_day_state(self) -> tuple[str, int, int] | None:
|
|
"""Load sick day state file.
|
|
|
|
Returns (date, orig_mon_wed, orig_thu_sun) in minutes, or None.
|
|
"""
|
|
with SICK_DAY_STATE_FILE.open() as f:
|
|
state = json.load(f)
|
|
date = state.get("date")
|
|
orig_mw = _original_minutes(state, "mon_wed")
|
|
orig_ts = _original_minutes(state, "thu_sun")
|
|
if date is None or orig_mw is None or orig_ts is None:
|
|
return None
|
|
return (str(date), orig_mw, orig_ts)
|
|
|
|
def _write_restored_config(
|
|
self,
|
|
orig_mw: int,
|
|
orig_ts: int,
|
|
state_date: str,
|
|
) -> None:
|
|
"""Write restored config values and clean up state file."""
|
|
config_values = self._read_shutdown_config()
|
|
if config_values:
|
|
_, _, morning_end = config_values
|
|
_logger.info(
|
|
"Restoring original shutdown config from %s",
|
|
state_date,
|
|
)
|
|
self._write_shutdown_config(
|
|
orig_mw,
|
|
orig_ts,
|
|
morning_end,
|
|
restore=True,
|
|
)
|
|
SICK_DAY_STATE_FILE.unlink()
|
|
_logger.info("Removed stale sick day state from %s", state_date)
|
|
|
|
def _restore_original_config_if_needed(self) -> None:
|
|
"""Restore original config if sick day state is from a previous day."""
|
|
if not SICK_DAY_STATE_FILE.exists():
|
|
return
|
|
try:
|
|
loaded = self._load_sick_day_state()
|
|
if loaded is None:
|
|
return
|
|
state_date, orig_mw, orig_ts = loaded
|
|
today = today_str()
|
|
if state_date != today:
|
|
self._write_restored_config(orig_mw, orig_ts, state_date)
|
|
except (OSError, json.JSONDecodeError) as e:
|
|
_logger.warning("Error checking sick day state: %s", e)
|