mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-11 18:53:55 +01:00
Six new modules, all composed or re-exported so no caller changes:
_shutdown.py (384) -> _shutdown_sick_state.py SickDayStateMixin
_wake_alarm.py WakeAlarmMixin
_phone_verification.py (380)-> _adb_transport.py AdbTransportMixin
_http_workout_fetch.py HttpWorkoutFetchMixin
_status_data.py (370) -> _status_types.py the 7 dataclasses
_status_projection.py shutdown projection
+ format_summary_line
Moving a name means moving where it is *read*, so 30-odd mock patch targets
move with it. Most importantly conftest.py's autouse _block_real_network
fixture: ruff correctly dropped the now-unused `socket` import from
_phone_verification, which silently removed the guard's target and would have
let subnet probes reach the real network. It now blocks socket in both modules
that actually own the probes, and both targets are asserted to exist.
976 tests pass at 100% branch coverage; pylint 8.70 -> 8.71; `screen_lock
--status` renders the real week unchanged. File-length gate: 41 -> 35.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EveB813BQ8abAP68VSqZ5Y
94 lines
2.9 KiB
Python
94 lines
2.9 KiB
Python
"""rtcwake wake-alarm scheduling, split out of the shutdown mixin.
|
|
|
|
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.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import calendar
|
|
from datetime import datetime, timedelta, timezone
|
|
import logging
|
|
import subprocess
|
|
|
|
from screen_locker._constants import ALARM_DAYS, RTCWAKE_BIN, WAKE_AFTER_HOURS
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class WakeAlarmMixin:
|
|
"""Schedules an RTC wake alarm for the next alarm day."""
|
|
|
|
# ------------------------------------------------------------------
|
|
# rtcwake integration for weekend wake alarm
|
|
# ------------------------------------------------------------------
|
|
|
|
@staticmethod
|
|
def _is_tomorrow_alarm_day() -> bool:
|
|
"""Check if tomorrow is an alarm day."""
|
|
tomorrow = datetime.now(tz=timezone.utc) + timedelta(days=1)
|
|
return tomorrow.weekday() in ALARM_DAYS
|
|
|
|
@staticmethod
|
|
def _compute_wake_timestamp() -> int:
|
|
"""Compute the UTC epoch timestamp for the next wake alarm.
|
|
|
|
Returns:
|
|
Epoch seconds WAKE_AFTER_HOURS from now.
|
|
"""
|
|
wake_time = datetime.now(tz=timezone.utc) + timedelta(
|
|
hours=WAKE_AFTER_HOURS,
|
|
)
|
|
return calendar.timegm(wake_time.utctimetuple())
|
|
|
|
@staticmethod
|
|
def _schedule_rtcwake() -> bool:
|
|
"""Set rtcwake to power on the PC after WAKE_AFTER_HOURS.
|
|
|
|
Uses ``rtcwake -m disk`` to hibernate immediately while programming
|
|
the RTC to restore power at wake_epoch. Hibernate is completely
|
|
silent and dark (state written to swap file), making it suitable
|
|
when the PC is in a bedroom.
|
|
|
|
Returns:
|
|
True if rtcwake was set successfully, False otherwise.
|
|
"""
|
|
wake_epoch = WakeAlarmMixin._compute_wake_timestamp()
|
|
cmd = [
|
|
"/usr/bin/sudo",
|
|
RTCWAKE_BIN,
|
|
"-m",
|
|
"disk",
|
|
"-t",
|
|
str(wake_epoch),
|
|
]
|
|
try:
|
|
subprocess.run(
|
|
cmd,
|
|
check=True,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
except subprocess.SubprocessError as exc:
|
|
_logger.warning("Failed to set rtcwake: %s", exc)
|
|
return False
|
|
_logger.info(
|
|
"rtcwake set: PC will wake at epoch %d",
|
|
wake_epoch,
|
|
)
|
|
return True
|
|
|
|
def schedule_wake_if_needed(self) -> bool:
|
|
"""Schedule rtcwake if tomorrow is an alarm day.
|
|
|
|
Call this at shutdown time.
|
|
|
|
Returns:
|
|
True if wake was scheduled, False if not needed or failed.
|
|
"""
|
|
if not self._is_tomorrow_alarm_day():
|
|
_logger.info("Tomorrow is not an alarm day — skipping rtcwake")
|
|
return False
|
|
return self._schedule_rtcwake()
|