180 lines
6.2 KiB
Python
180 lines
6.2 KiB
Python
import hashlib
|
|
from dataclasses import dataclass, field, asdict
|
|
from typing import List, Optional, Dict, Any
|
|
from datetime import datetime
|
|
import os
|
|
import re
|
|
from logging_config import setup_logging
|
|
|
|
# Set up logging
|
|
logger = setup_logging()
|
|
|
|
@dataclass
|
|
class RepeatRule:
|
|
type: str # "daily" or "weekly" or "once"
|
|
days_of_week: Optional[List[str]] = field(default_factory=list)
|
|
at: Optional[str] = None
|
|
|
|
def validate(self) -> bool:
|
|
"""Validate repeat rule configuration"""
|
|
valid_types = {"daily", "weekly", "once"}
|
|
valid_days = {"mon", "tue", "wed", "thu", "fri", "sat", "sun"}
|
|
|
|
|
|
if self.type not in valid_types:
|
|
logger.error(f"Invalid RepeatRule type: '{self.type}'. Must be one of {valid_types}")
|
|
return False
|
|
|
|
if self.type == "weekly":
|
|
if self.days_of_week is None:
|
|
logger.error("days_of_week is None")
|
|
return False
|
|
|
|
if not self.days_of_week:
|
|
logger.error("days_of_week is empty")
|
|
return False
|
|
|
|
invalid_days = [day for day in self.days_of_week if day.lower() not in valid_days]
|
|
if invalid_days:
|
|
logger.error(f"Invalid RepeatRule weekday names: {invalid_days}. Must be one of {valid_days}")
|
|
return False
|
|
if not self.days_of_week:
|
|
logger.error("Weekly repeat rule must specify at least one day")
|
|
return False
|
|
|
|
if self.type == "once" and self.days_of_week:
|
|
if self.days_of_week:
|
|
logger.error("One-time alert does not support days_of_week")
|
|
return False
|
|
if not self.at:
|
|
logger.error("One-time repeat rule must specify a valid 'at' date")
|
|
return False
|
|
try:
|
|
datetime.strptime(self.at, "%d.%m.%Y")
|
|
except ValueError:
|
|
logger.error(f"Invalid 'at' date format: '{self.at}'. Expected format: 'dd.mm.yyyy'")
|
|
return False
|
|
|
|
logger.debug(f"RepeatRule validation passed: {self.__dict__}")
|
|
return True
|
|
|
|
|
|
@dataclass
|
|
class Snooze:
|
|
enabled: bool = True
|
|
duration: int = 10 # minutes
|
|
max_count: int = 3
|
|
|
|
def validate(self) -> bool:
|
|
"""Validate snooze configuration"""
|
|
if not isinstance(self.enabled, bool):
|
|
logger.error(f"Snooze enabled must be boolean, got {type(self.enabled)}")
|
|
return False
|
|
|
|
if not isinstance(self.duration, int):
|
|
logger.error(f"Snooze duration must be integer, got {type(self.duration)}")
|
|
return False
|
|
|
|
if self.duration <= 0:
|
|
logger.error(f"Snooze duration must be positive, got {self.duration}")
|
|
return False
|
|
|
|
if not isinstance(self.max_count, int):
|
|
logger.error(f"Snooze max_count must be integer, got {type(self.max_count)}")
|
|
return False
|
|
|
|
if self.max_count <= 0:
|
|
logger.error(f"Snooze max_count must be positive, got {self.max_count}")
|
|
return False
|
|
|
|
logger.debug(f"Snooze validation passed: {self.__dict__}")
|
|
return True
|
|
|
|
|
|
@dataclass
|
|
class Metadata:
|
|
volume: int = 100
|
|
notes: str = ""
|
|
md5sum: str = ""
|
|
|
|
def validate(self) -> bool:
|
|
"""Validate metadata configuration"""
|
|
if not isinstance(self.volume, int):
|
|
logger.error(f"Volume must be integer, got {type(self.volume)}")
|
|
return False
|
|
|
|
if not 0 <= self.volume <= 100:
|
|
logger.error(f"Volume must be between 0 and 100, got {self.volume}")
|
|
return False
|
|
|
|
logger.debug(f"Metadata validation passed: {self.__dict__}")
|
|
return True
|
|
|
|
|
|
@dataclass
|
|
class Alarm:
|
|
name: str
|
|
time: str # Format: "HH:MM:SS"
|
|
repeat_rule: RepeatRule
|
|
file_to_play: str = field(default=os.path.expanduser("~/.alarms/alarm-lofi.mp3"))
|
|
enabled: bool = field(default=True)
|
|
snooze: Snooze = field(default_factory=Snooze)
|
|
metadata: Metadata = field(default_factory=Metadata)
|
|
id: Optional[int] = field(default=None)
|
|
|
|
def validate(self) -> bool:
|
|
"""Validate complete alarm configuration"""
|
|
logger.debug(f"Starting validation for alarm: {self.name}")
|
|
|
|
# Validate name
|
|
if not isinstance(self.name, str) or len(self.name.strip()) == 0:
|
|
logger.error(f"Invalid alarm name: '{self.name}'. Must be non-empty string")
|
|
return False
|
|
|
|
# Validate time format
|
|
time_pattern = re.compile(r'^([0-1]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$')
|
|
if not time_pattern.match(self.time):
|
|
logger.error(f"Invalid time format: '{self.time}'. Must match HH:MM:SS")
|
|
return False
|
|
|
|
# Validate enabled flag
|
|
if not isinstance(self.enabled, bool):
|
|
logger.error(f"Enabled must be boolean, got {type(self.enabled)}")
|
|
return False
|
|
|
|
# Create default alarms directory if it doesn't exist
|
|
default_alarms_dir = os.path.expanduser("~/.alarms")
|
|
os.makedirs(default_alarms_dir, exist_ok=True)
|
|
|
|
# Validate audio file
|
|
if self.file_to_play != os.path.expanduser("~/.alarms/alarm-lofi.mp3") and not os.path.exists(self.file_to_play):
|
|
logger.error(f"Audio file not found: '{self.file_to_play}'")
|
|
return False
|
|
|
|
# Validate repeat rule
|
|
if not isinstance(self.repeat_rule, RepeatRule):
|
|
logger.error(f"Invalid repeat_rule type: {type(self.repeat_rule)}")
|
|
return False
|
|
if not self.repeat_rule.validate():
|
|
logger.error("Repeat rule validation failed")
|
|
return False
|
|
|
|
# Validate snooze
|
|
if not isinstance(self.snooze, Snooze):
|
|
logger.error(f"Invalid snooze type: {type(self.snooze)}")
|
|
return False
|
|
if not self.snooze.validate():
|
|
logger.error("Snooze validation failed")
|
|
return False
|
|
|
|
# Validate metadata
|
|
if not isinstance(self.metadata, Metadata):
|
|
logger.error(f"Invalid metadata type: {type(self.metadata)}")
|
|
return False
|
|
if not self.metadata.validate():
|
|
logger.error("Metadata validation failed")
|
|
return False
|
|
|
|
logger.debug(f"Alarm validation passed: {self.name}")
|
|
return True
|