config: add validator

This commit is contained in:
alfrix 2022-08-17 07:31:41 -03:00 committed by Alfredo Monclus
parent 2c8436500c
commit 3e5855d207
3 changed files with 120 additions and 37 deletions

View File

@ -34,6 +34,7 @@ class KlipperScreenConfig:
do_not_edit_prefix = "#~#" do_not_edit_prefix = "#~#"
def __init__(self, configfile, screen=None): def __init__(self, configfile, screen=None):
self.errors = []
self.default_config_path = os.path.join(klipperscreendir, "ks_includes", "defaults.conf") self.default_config_path = os.path.join(klipperscreendir, "ks_includes", "defaults.conf")
self.config = configparser.ConfigParser() self.config = configparser.ConfigParser()
self.config_path = self.get_config_file_location(configfile) self.config_path = self.get_config_file_location(configfile)
@ -58,29 +59,39 @@ class KlipperScreenConfig:
if saved_def is not None: if saved_def is not None:
self.config.read_string(saved_def) self.config.read_string(saved_def)
logging.info(f"====== Saved Def ======\n{saved_def}\n=======================") logging.info(f"====== Saved Def ======\n{saved_def}\n=======================")
# This is the final config # This is the final config
# self.log_config(self.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: 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: 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 ")]) printers = sorted([i for i in self.config.sections() if i.startswith("printer ")])
if len(printers) == 0:
printers.append("Printer Printer")
self.printers = [ self.printers = [
{printer[8:]: { {printer[8:]: {
"moonraker_host": self.config.get(printer, "moonraker_host", fallback="127.0.0.1"), "moonraker_host": self.config.get(printer, "moonraker_host", fallback="127.0.0.1"),
"moonraker_port": self.config.get(printer, "moonraker_port", fallback="7125"), "moonraker_port": self.config.get(printer, "moonraker_port", fallback="7125"),
"moonraker_api_key": self.config.get(printer, "moonraker_api_key", fallback=False) "moonraker_api_key": self.config.get(printer, "moonraker_api_key", fallback="")
}} for printer in printers] }} 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="")
}
})
conf_printers_debug = copy.deepcopy(self.printers) conf_printers_debug = copy.deepcopy(self.printers)
for printer in conf_printers_debug: for printer in conf_printers_debug:
@ -99,6 +110,72 @@ class KlipperScreenConfig:
self._create_configurable_options(screen) 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): def _create_configurable_options(self, screen):
self.configurable_options = [ self.configurable_options = [

View File

@ -126,7 +126,10 @@ class KlipperScreen(Gtk.Window):
self.base_panel.activate() self.base_panel.activate()
self.printer_initializing(_("Initializing")) 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')) self.set_screenblanking_timeout(self._config.get_main_config().get('screen_blanking'))
# Move mouse to 0,0 # Move mouse to 0,0
@ -406,31 +409,34 @@ class KlipperScreen(Gtk.Window):
logging.exception(f"Showing error modal: {err}") logging.exception(f"Showing error modal: {err}")
title = Gtk.Label() title = Gtk.Label()
title.set_markup(f"<b>{err}</b>\n\n") title.set_markup(f"<b>{err}</b>\n")
title.set_line_wrap(True) title.set_line_wrap(True)
title.set_halign(Gtk.Align.START) title.set_halign(Gtk.Align.START)
message = Gtk.Label() title.set_hexpand(True)
message.set_markup( version = Gtk.Label(label=f"{self.version}")
"Provide /tmp/KlipperScreen.log when asking for help.\n\n" version.set_halign(Gtk.Align.END)
+ f"KlipperScreen: {self.version}\n"
+ f"<i>{e}</i>\n" message = Gtk.Label(label=f"{e}")
)
message.set_line_wrap(True) 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 = self.gtk.ScrolledWindow()
scroll.set_vexpand(True)
scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) 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 = [ buttons = [
{"name": _("Go Back"), "response": Gtk.ResponseType.CANCEL} {"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 @staticmethod
def error_modal_response(widget, response_id): def error_modal_response(widget, response_id):
@ -459,11 +465,6 @@ class KlipperScreen(Gtk.Window):
def restart_ks(self, widget, response_id): def restart_ks(self, widget, response_id):
if response_id == Gtk.ResponseType.OK: if response_id == Gtk.ResponseType.OK:
logging.debug("Restarting") 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"}) self._ws.send_method("machine.services.restart", {"service": "KlipperScreen"})
widget.destroy() widget.destroy()

View File

@ -165,6 +165,11 @@ scrollbar slider {
background-color: #404E57; background-color: #404E57;
} }
separator {
margin: 1em 2em;
background-color: rgba(255, 255, 255, 0.5);
}
textview, textview text { textview, textview text {
background: transparent; background: transparent;
font-family: Free Mono; font-family: Free Mono;