Files
wecker/api/schema.py
T

134 lines
3.6 KiB
Python

import strawberry
from typing import List, Optional
import os
import sys
import subprocess
import signal
from pathlib import Path
from api.crontab_manager import CrontabManager
from common import PID_FILE
def get_manager():
tabfile = os.getenv("TABFILE")
return CrontabManager(tabfile=tabfile)
def is_wecker_ringing() -> bool:
"""Check if the wecker process is currently running via the PID file."""
if not os.path.exists(PID_FILE):
return False
try:
pid: int
with open(PID_FILE) as f:
pid = int(f.read().strip())
os.kill(pid, 0)
return True
except (ValueError, ProcessLookupError, PermissionError, OSError):
return False
def _default_command() -> str:
"""Return the default shell command to run wecker.py."""
project_root = Path(__file__).parent.parent.absolute()
python_exec = sys.executable
return f"cd {project_root} && {python_exec} wecker.py >> wecker.log 2>&1"
@strawberry.type
class Alarm:
id: str
cron_expression: str
command: str
is_enabled: bool
@strawberry.type
class Query:
@strawberry.field
def is_ringing(self) -> bool:
return is_wecker_ringing()
@strawberry.field
def get_alarms(self) -> List[Alarm]:
manager = get_manager()
return [Alarm(**a) for a in manager.get_alarms()]
@strawberry.field
def get_alarm(self, id: str) -> Optional[Alarm]:
manager = get_manager()
alarms = manager.get_alarms()
for a in alarms:
if a["id"] == id:
return Alarm(**a)
return None
@strawberry.type
class Mutation:
@strawberry.field
def set_alarm(
self,
cron_expression: str,
is_enabled: bool = True,
id: Optional[str] = None,
) -> Alarm:
command = _default_command()
manager = get_manager()
new_id = manager.set_alarm(
alarm_id=id,
cron_expression=cron_expression,
command=command,
is_enabled=is_enabled,
)
return Alarm(
id=new_id,
cron_expression=cron_expression,
command=command,
is_enabled=is_enabled,
)
@strawberry.field
def delete_alarm(self, id: str) -> bool:
manager = get_manager()
# Verify it exists
alarms = manager.get_alarms()
exists = any(a["id"] == id for a in alarms)
if exists:
manager.delete_alarm(id)
return True
return False
@strawberry.field
def start_ringing(self) -> bool:
"""Start the wecker alarm if it is not already ringing.
Returns True if started, False if already ringing."""
if is_wecker_ringing():
return False
cmd = _default_command()
subprocess.Popen(cmd, shell=True)
return True
@strawberry.field
def stop_ringing(self) -> bool:
"""Stop the wecker alarm if it is currently ringing.
Returns True if stopped, False if not ringing."""
if not is_wecker_ringing():
return False
try:
with open(PID_FILE) as f:
pid = int(f.read().strip())
os.kill(pid, signal.SIGTERM)
if os.path.exists(PID_FILE):
os.remove(PID_FILE)
return True
except (ValueError, ProcessLookupError, PermissionError, OSError):
# PID file is stale or process already dead
if os.path.exists(PID_FILE):
os.remove(PID_FILE)
return False
schema = strawberry.Schema(query=Query, mutation=Mutation)