dom/scheduler.py

129 lines
5.0 KiB
Python

import asyncio
import random
import logging
from datetime import datetime, time, timedelta
from typing import Optional
from config import Config
from ollama_client import OllamaClient
from template_manager import TemplateManager
from notification_client import NotificationClient
logger = logging.getLogger(__name__)
class NotificationScheduler:
"""Handles scheduling of notifications with random intervals and silent time."""
def __init__(self, config: Config):
self.config = config
self.template_manager = TemplateManager(config.templates_dir)
self.notification_client = NotificationClient(config)
self.running = False
def _is_silent_time(self) -> bool:
"""Check if current time is within silent hours."""
import pytz
# Get the configured timezone
timezone_name = getattr(self.config, 'timezone', 'UTC')
try:
tz = pytz.timezone(timezone_name)
current_datetime = datetime.now(tz)
time_type = f"timezone {timezone_name}"
except pytz.exceptions.UnknownTimeZoneError:
logger.warning(f"Unknown timezone '{timezone_name}', falling back to UTC")
tz = pytz.UTC
current_datetime = datetime.now(tz)
time_type = "UTC (fallback)"
current_time = current_datetime.time()
# Parse silent time configuration
silent_start = datetime.strptime(self.config.silent_start, "%H:%M").time()
silent_end = datetime.strptime(self.config.silent_end, "%H:%M").time()
# Log current time for debugging
logger.info(f"Current time: {current_datetime.strftime('%Y-%m-%d %H:%M:%S')} ({time_type})")
logger.info(f"Silent period: {self.config.silent_start} to {self.config.silent_end}")
# Handle overnight silent period (e.g., 20:00 to 12:00)
if silent_start >= silent_end:
is_silent = current_time >= silent_start or current_time <= silent_end
else:
is_silent = silent_start <= current_time <= silent_end
logger.info(f"Silent time check: {is_silent}")
return is_silent
def _get_next_interval(self) -> int:
"""Get random interval in seconds between min and max."""
minutes = random.randint(self.config.min_interval, self.config.max_interval)
return minutes * 60
async def _send_notification(self) -> bool:
"""Send a notification with Ollama response."""
try:
async with OllamaClient(self.config) as client:
# Check if Ollama is available
if not await client.check_health():
logger.error("Ollama service is not available")
return False
# Get random prompt
prompt = self.template_manager.get_random_prompt()
logger.info(f"Using prompt: {prompt}")
# Get response from Ollama
response = await client.generate_response(prompt)
if not response:
logger.error("Failed to get response from Ollama")
return False
# Send notification
success = await self.notification_client.send_notification(response)
if success:
logger.info("Notification sent successfully")
else:
logger.error("Failed to send notification")
return success
except Exception as e:
logger.error(f"Error sending notification: {e}")
return False
async def start(self):
"""Start the notification scheduler."""
self.running = True
logger.info("Starting notification scheduler...")
while self.running:
try:
# Check if it's silent time
if self._is_silent_time():
logger.info("Silent time - skipping notification")
# Wait 30 minutes before checking again
await asyncio.sleep(1800)
continue
# Send notification
await self._send_notification()
# Calculate next interval
interval = self._get_next_interval()
next_time = datetime.now() + timedelta(seconds=interval)
logger.info(f"Next notification scheduled for: {next_time.strftime('%Y-%m-%d %H:%M:%S')}")
# Wait for next interval
await asyncio.sleep(interval)
except asyncio.CancelledError:
logger.info("Scheduler cancelled")
break
except Exception as e:
logger.error(f"Error in scheduler: {e}")
# Wait 5 minutes on error
await asyncio.sleep(300)
def stop(self):
"""Stop the notification scheduler."""
self.running = False