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 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 = 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 = dataclasses.field(default_factory=lambda: [True, True, True, True, True, True, False]) sound_files: list = dataclasses.field(default_factory=lambda: ["", "", ""]) first_bell_before_first_lesson: bool = dataclasses.field(default_factory=lambda: True) custom_sounds: list = 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: bells_list: list = [] 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: if button == 0: self.apply_config() elif button == 1: self.save_config() elif button == 2: self.bells_enabled = True elif button == 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) if bell.sound == SoundType.FIRST_BELL: play(self.config.sound_files[0]) elif bell.sound == SoundType.SECOND_BELL: play(self.config.sound_files[1]) elif bell.sound == SoundType.BREAK: play(self.config.sound_files[2]) elif bell.sound == 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)