From 3e5855d2074f9ca40ef3ffb8a5119fadaa460a9d Mon Sep 17 00:00:00 2001 From: alfrix Date: Wed, 17 Aug 2022 07:31:41 -0300 Subject: [PATCH] config: add validator --- ks_includes/config.py | 107 ++++++++++++++++++++++++++++++++++++------ screen.py | 45 +++++++++--------- styles/base.css | 5 ++ 3 files changed, 120 insertions(+), 37 deletions(-) diff --git a/ks_includes/config.py b/ks_includes/config.py index 4afbef1d..837b39a0 100644 --- a/ks_includes/config.py +++ b/ks_includes/config.py @@ -34,6 +34,7 @@ class KlipperScreenConfig: do_not_edit_prefix = "#~#" def __init__(self, configfile, screen=None): + self.errors = [] self.default_config_path = os.path.join(klipperscreendir, "ks_includes", "defaults.conf") self.config = configparser.ConfigParser() self.config_path = self.get_config_file_location(configfile) @@ -58,29 +59,39 @@ class KlipperScreenConfig: if saved_def is not None: self.config.read_string(saved_def) logging.info(f"====== Saved Def ======\n{saved_def}\n=======================") - # This is the final config - # self.log_config(self.config) + # This is the final config + # self.log_config(self.config) + if self.validate_config(): + logging.info('Configuration validated succesfuly') + else: + logging.error('Invalid configuration detected !!!') + logging.info('Loading default config') + self.config = configparser.ConfigParser() + self.config.read(self.default_config_path) except KeyError as Kerror: - raise ConfigError(f"Error reading config: {self.config_path}\n{Kerror}") from Kerror + msg = f"Error reading config: {self.config_path}\n{Kerror}" + logging.exception(msg) + self.errors.append(msg) + raise ConfigError(msg) from Kerror + except ValueError as Verror: + msg = f"Invalid Value in the config:\n{Verror}" + logging.exception(msg) + self.errors.append(msg) except Exception as e: - logging.exception(f"Unknown error with config:\n{e}") + msg = f"Unknown error with the config:\n{e}" + logging.exception(msg) + self.errors.append(msg) printers = sorted([i for i in self.config.sections() if i.startswith("printer ")]) + if len(printers) == 0: + printers.append("Printer Printer") self.printers = [ {printer[8:]: { "moonraker_host": self.config.get(printer, "moonraker_host", fallback="127.0.0.1"), "moonraker_port": self.config.get(printer, "moonraker_port", fallback="7125"), - "moonraker_api_key": self.config.get(printer, "moonraker_api_key", fallback=False) - }} for printer in printers] - - if len(printers) <= 0: - self.printers.append({ - "Printer": { - "moonraker_host": self.config.get("main", "moonraker_host", fallback="127.0.0.1"), - "moonraker_port": self.config.get("main", "moonraker_port", fallback="7125"), - "moonraker_api_key": self.config.get("main", "moonraker_api_key", fallback="") - } - }) + "moonraker_api_key": self.config.get(printer, "moonraker_api_key", fallback="") + }} for printer in printers + ] conf_printers_debug = copy.deepcopy(self.printers) for printer in conf_printers_debug: @@ -99,6 +110,72 @@ class KlipperScreenConfig: self._create_configurable_options(screen) + def validate_config(self): + valid = True + bools = strs = numbers = () + for section in self.config: + if section == 'main': + bools = ( + 'invert_x', 'invert_y', 'invert_z', '24htime', 'only_heaters', 'show_cursor', 'confirm_estop', + 'autoclose_popups', 'use_dpms', 'use_default_menu', 'side_macro_shortcut', 'use-matchbox-keyboard' + ) + strs = ( + 'default_printer', 'language', 'print_sort_dir', 'theme', 'screen_blanking', 'font_size', + 'print_estimate_method', 'screen_blanking' + ) + numbers = ( + 'job_complete_timeout', 'job_error_timeout', 'move_speed_xy', 'move_speed_z', + 'print_estimate_compensation' + ) + elif section.startswith('printer '): + bools = ( + 'invert_x', 'invert_y', 'invert_z', + ) + strs = ( + 'moonraker_api_key', 'moonraker_host', 'language', 'titlebar_name_type', + 'screw_positions', 'power_devices', 'titlebar_items' + ) + numbers = ( + 'moonraker_port', 'move_speed_xy', 'move_speed_z', 'z_babystep_values' + 'calibrate_x_position', 'calibrate_y_position', + ) + elif section.startswith('preheat '): + strs = ('gcode', '') + numbers = [f'{option}' for option in self.config[section] if option != 'gcode'] + elif section.startswith('menu '): + strs = ('name', 'icon', 'panel', 'method', 'params', 'enable', 'confirm') + elif section == 'bed_screws': + # This section may be deprecated in favor of moving this options under the printer section + numbers = ('rotation', '') + strs = ('screw_positions', '') + elif section.startswith('graph') or section.startswith('displayed_macros'): + bools = [f'{option}' for option in self.config[section]] + elif section.startswith('z_calibrate_position'): + # This section may be deprecated in favor of moving this options under the printer section + numbers = ('calibrate_x_position', 'calibrate_y_position') + elif section == 'DEFAULT': + continue + else: + self.errors.append(f'Section [{section}] not recognized') + + for key in self.config[section]: + if key not in bools and key not in strs and key not in numbers: + msg = f'Option "{key}" not recognized for section "[{section}]"' + self.errors.append(msg) + # This most probably is not a big issue, continue to load the config + elif key in numbers and not self.config[section][key].isnumeric() \ + or key in bools and self.config[section][key] not in ["False", "false", "True", "true"]: + msg = ( + f'Unable to parse "{key}" from [{section}]\n' + f'Expected a {"number" if key in numbers else "boolean"} but got: {self.config[section][key]}' + ) + self.errors.append(msg) + valid = False + return valid + + def get_errors(self): + return "".join(f'{error}\n\n' for error in self.errors) + def _create_configurable_options(self, screen): self.configurable_options = [ diff --git a/screen.py b/screen.py index dd24997d..d995ecd9 100644 --- a/screen.py +++ b/screen.py @@ -126,7 +126,10 @@ class KlipperScreen(Gtk.Window): self.base_panel.activate() self.printer_initializing(_("Initializing")) - + if self._config.errors: + self.show_error_modal("Invalid config file", self._config.get_errors()) + # Prevent this dialog from being destroyed + self.dialogs = [] self.set_screenblanking_timeout(self._config.get_main_config().get('screen_blanking')) # Move mouse to 0,0 @@ -406,31 +409,34 @@ class KlipperScreen(Gtk.Window): logging.exception(f"Showing error modal: {err}") title = Gtk.Label() - title.set_markup(f"{err}\n\n") + title.set_markup(f"{err}\n") title.set_line_wrap(True) title.set_halign(Gtk.Align.START) - message = Gtk.Label() - message.set_markup( - "Provide /tmp/KlipperScreen.log when asking for help.\n\n" - + f"KlipperScreen: {self.version}\n" - + f"{e}\n" - ) + title.set_hexpand(True) + version = Gtk.Label(label=f"{self.version}") + version.set_halign(Gtk.Align.END) + + message = Gtk.Label(label=f"{e}") message.set_line_wrap(True) - - vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) - vbox.set_halign(Gtk.Align.CENTER) - vbox.set_valign(Gtk.Align.CENTER) - vbox.add(title) - vbox.add(message) - scroll = self.gtk.ScrolledWindow() + scroll.set_vexpand(True) scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) - scroll.add(vbox) + scroll.add(message) + + help_notice = Gtk.Label(label="Provide /tmp/KlipperScreen.log when asking for help.\n") + help_notice.set_line_wrap(True) + + grid = Gtk.Grid() + grid.attach(title, 0, 0, 1, 1) + grid.attach(version, 1, 0, 1, 1) + grid.attach(Gtk.Separator(), 0, 1, 2, 1) + grid.attach(scroll, 0, 2, 2, 1) + grid.attach(help_notice, 0, 3, 2, 1) buttons = [ {"name": _("Go Back"), "response": Gtk.ResponseType.CANCEL} ] - self.gtk.Dialog(self, buttons, scroll, self.error_modal_response) + self.gtk.Dialog(self, buttons, grid, self.error_modal_response) @staticmethod def error_modal_response(widget, response_id): @@ -459,11 +465,6 @@ class KlipperScreen(Gtk.Window): def restart_ks(self, widget, response_id): if response_id == Gtk.ResponseType.OK: logging.debug("Restarting") - # This can be removed after a grace period - service = self._config.get_main_config().get('service') - if service is not None and service != "KlipperScreen": - self.show_popup_message("Error: option \"service\" is not supported anymore") - # ^^^ self._ws.send_method("machine.services.restart", {"service": "KlipperScreen"}) widget.destroy() diff --git a/styles/base.css b/styles/base.css index 2362d058..10c24882 100644 --- a/styles/base.css +++ b/styles/base.css @@ -165,6 +165,11 @@ scrollbar slider { background-color: #404E57; } +separator { + margin: 1em 2em; + background-color: rgba(255, 255, 255, 0.5); +} + textview, textview text { background: transparent; font-family: Free Mono;