feat: v.1.1.0 work: добавил режим ручного управления. Теперь можно отключать расписание!

POST api/schedule/pause
POST api/schedule/resume
This commit is contained in:
Fedorov Dmitriy
2026-09-19 19:05:55 +03:00
parent b02d18d283
commit a5c9d07db5
7 changed files with 784 additions and 20 deletions
+109 -1
View File
@@ -1,5 +1,6 @@
import asyncio
import json
from pathlib import Path
from app.tion.service import TionService
from datetime import (
@@ -29,6 +30,7 @@ WEEKDAYS = (
)
class ScheduleService:
def __init__(
@@ -36,6 +38,7 @@ class ScheduleService:
config,
tion: TionService | None = None,
check_interval: float = 5.0,
state_path: str | Path | None = None,
):
self._config = config
self._tion = tion
@@ -59,6 +62,21 @@ class ScheduleService:
# Ошибка именно ScheduleService.
self._last_error: str | None = None
self._paused = False
self._state_path = (
Path(state_path)
if state_path is not None
else None
)
self._load_runtime_state()
@property
def paused(self) -> bool:
return self._paused
@property
def running(self) -> bool:
@@ -87,6 +105,62 @@ class ScheduleService:
def config(self) -> ScheduleConfig:
return self._config
def _load_runtime_state(self) -> None:
if self._state_path is None:
return
if not self._state_path.exists():
return
try:
data = json.loads(
self._state_path.read_text(
encoding="utf-8"
)
)
except (
OSError,
json.JSONDecodeError,
):
return
paused = data.get("paused")
if isinstance(paused, bool):
self._paused = paused
def _save_runtime_state(self) -> None:
if self._state_path is None:
return
data = {
"paused": self._paused,
}
self._state_path.parent.mkdir(
parents=True,
exist_ok=True,
)
temp_path = (
self._state_path.with_suffix(
self._state_path.suffix + ".tmp"
)
)
temp_path.write_text(
json.dumps(
data,
indent=2,
),
encoding="utf-8",
)
temp_path.replace(
self._state_path
)
async def apply_override(self, settings: ScheduledSettings) -> None:
@@ -165,6 +239,36 @@ class ScheduleService:
# Сразу пересчитываем и применяем текущее состояние расписания.
await self._process()
async def pause(self) -> None:
if self._paused:
return
# Сначала запрещаем расписанию вмешиваться.
self._paused = True
# Temporary override больше не имеет смысла:
# мы переходим в полноценный ручной режим.
self._clear_override()
self._last_applied_when = None
self._save_runtime_state()
async def resume(self) -> None:
if not self._paused:
return
self._paused = False
self._save_runtime_state()
# Сразу применяем состояние расписания,
# актуальное именно сейчас.
await self._process()
def resolve(
self,
now: datetime | None = None,
@@ -378,6 +482,10 @@ class ScheduleService:
async def _process(self) -> None:
#Если ручное управление то сразу выходим
if self._paused:
return
now = datetime.now()
resolution = self.resolve(now)