136 lines
5.1 KiB
Python
136 lines
5.1 KiB
Python
import time, json, dataclasses
|
|
from enum import Enum
|
|
from playsound import playsound
|
|
from sound import play
|
|
|
|
class SoundType(Enum):
|
|
FIRST_BELL = 1
|
|
SECOND_BELL = 2
|
|
BREAK = 3
|
|
CUSTOM = 4
|
|
|
|
BELL_NAMES = {
|
|
SoundType.FIRST_BELL: "1-й дзвоник",
|
|
SoundType.SECOND_BELL: "2-й дзвоник",
|
|
SoundType.BREAK: "Перерва"
|
|
}
|
|
|
|
@dataclasses.dataclass
|
|
class Bell:
|
|
sound: SoundType
|
|
hour: int
|
|
minute: int
|
|
played: bool = False
|
|
# only used when sound = SoundType.CUSTOM
|
|
custom_file: str = ""
|
|
custom_name: str = ""
|
|
|
|
@dataclasses.dataclass
|
|
class CustomSound:
|
|
name: str
|
|
times: list[list[int]]
|
|
sound_file: str
|
|
|
|
@dataclasses.dataclass
|
|
class Config:
|
|
# yes, this is permanently "borrowed" from the internet
|
|
# thx to https://stackoverflow.com/questions/53632152/why-cant-dataclasses-have-mutable-defaults-in-their-class-attributes-declaratio
|
|
lessons_start: list[int] = dataclasses.field(default_factory=lambda: [8, 0])
|
|
lesson_length: int = dataclasses.field(default_factory=lambda: 40)
|
|
break_length: int = dataclasses.field(default_factory=lambda: 5)
|
|
first_bell: int = dataclasses.field(default_factory=lambda: 1)
|
|
num_lessons: int = dataclasses.field(default_factory=lambda: 12)
|
|
workdays: list[bool] = dataclasses.field(default_factory=lambda: [True, True, True, True, True, True, False])
|
|
sound_files: list[str] = dataclasses.field(default_factory=lambda: ["", "", ""])
|
|
first_bell_before_first_lesson: bool = dataclasses.field(default_factory=lambda: True)
|
|
custom_sounds: list[CustomSound] = dataclasses.field(default_factory=lambda: [])
|
|
|
|
class Scheduler:
|
|
def __init__(self) -> None:
|
|
self.bells: list[Bell] = []
|
|
self.bells_enabled: bool = True
|
|
self.load_config()
|
|
|
|
def set_ui_class(self, ui_class) -> None:
|
|
self.ui = ui_class
|
|
self.generate_bells()
|
|
self.ui.set_settings(self.config)
|
|
|
|
def apply_config(self) -> None:
|
|
self.ui.get_settings(self.config, CustomSound)
|
|
self.generate_bells()
|
|
|
|
def generate_bells(self) -> None:
|
|
self.bells = []
|
|
if not self.config.workdays[time.localtime().tm_wday]:
|
|
self.ui.set_schedule(self.get_bells_list())
|
|
return
|
|
|
|
day_minute: int = self.config.lessons_start[0] * 60 + self.config.lessons_start[1] - self.config.first_bell
|
|
for lesson_number in range(self.config.num_lessons):
|
|
if lesson_number != 0 or self.config.first_bell_before_first_lesson:
|
|
self.bells.append(Bell(SoundType.FIRST_BELL, day_minute // 60, day_minute % 60))
|
|
day_minute += self.config.first_bell
|
|
self.bells.append(Bell(SoundType.SECOND_BELL, day_minute // 60, day_minute % 60))
|
|
day_minute += self.config.lesson_length
|
|
self.bells.append(Bell(SoundType.BREAK, day_minute // 60, day_minute % 60))
|
|
day_minute += self.config.break_length - self.config.first_bell
|
|
|
|
for sound in self.config.custom_sounds:
|
|
for sound_time in sound.times:
|
|
self.bells.append(Bell(SoundType.CUSTOM, *sound_time, custom_file=sound.sound_file, custom_name=sound.name))
|
|
|
|
# so that the first bell before the first lesson can't roll time into the negatives
|
|
self.bells = [bell for bell in self.bells if bell.hour <= 23]
|
|
self.bells.sort(key=lambda sound: sound.hour * 60 + sound.minute)
|
|
|
|
self.ui.set_schedule(self.get_bells_list())
|
|
|
|
def get_bells_list(self) -> list[str]:
|
|
bells_list: list[str] = []
|
|
for bell in self.bells:
|
|
if bell.sound != SoundType.CUSTOM: bell_name: str = BELL_NAMES[bell.sound]
|
|
else: bell_name: str = '"' + bell.custom_name + '"'
|
|
bells_list.append(f"{bell.hour:02}:{bell.minute:02} - {bell_name}")
|
|
return bells_list
|
|
|
|
def menu_event(self, button) -> None:
|
|
match button:
|
|
case 0: self.apply_config()
|
|
case 1: self.save_config()
|
|
case 2: self.bells_enabled = True
|
|
case 3: self.bells_enabled = False
|
|
|
|
def update(self) -> None:
|
|
if not self.bells_enabled:
|
|
return
|
|
t = time.localtime()
|
|
for bell_n, bell in enumerate(self.bells):
|
|
if (not bell.played) and (bell.hour == t.tm_hour) and (bell.minute == t.tm_min):
|
|
bell.played = True
|
|
self.ui.select_bell(bell_n)
|
|
match bell.sound:
|
|
case SoundType.FIRST_BELL: play(self.config.sound_files[0])
|
|
case SoundType.SECOND_BELL: play(self.config.sound_files[1])
|
|
case SoundType.BREAK: play(self.config.sound_files[2])
|
|
case SoundType.CUSTOM: play(bell.custom_file)
|
|
break
|
|
|
|
def load_config(self) -> None:
|
|
try:
|
|
with open("config.json", "r") as fp:
|
|
self.config = Config(**json.load(fp))
|
|
for i in range(len(self.config.custom_sounds)):
|
|
self.config.custom_sounds[i] = CustomSound(**self.config.custom_sounds[i])
|
|
except:
|
|
# if something goes wrong, load the default values
|
|
# nothing should go wrong with the config file, if the user doesn't edit
|
|
# it manually. if that's the case - it's their fault, not mine :P
|
|
self.config = Config()
|
|
|
|
def save_config(self) -> None:
|
|
temp_config = Config()
|
|
self.ui.get_settings(temp_config, CustomSound)
|
|
with open("config.json", "w") as fp:
|
|
json.dump(dataclasses.asdict(temp_config), fp)
|