Alfredo Monclus bfe6321c2f
Improve pathing (#368)
* Improve pathing

* Improve Pathing: config.py
2021-12-17 12:39:18 -05:00

425 lines
18 KiB
Python

import configparser
import gettext
import os
import logging
import json
import re
import copy
import pathlib
from io import StringIO
from os import path
SCREEN_BLANKING_OPTIONS = [
"300", # 5 Minutes
"900", # 15 Minutes
"1800", # 30 Minutes
"3600", # 1 Hour
"7200", # 2 Hours
"14400", # 4 Hours
]
klipperscreendir = pathlib.Path(__file__).parent.resolve().parent
class ConfigError(Exception):
pass
class KlipperScreenConfig:
config = None
configfile_name = "KlipperScreen.conf"
do_not_edit_line = "#~# --- Do not edit below this line. This section is auto generated --- #~#"
do_not_edit_prefix = "#~#"
def __init__(self, configfile, screen=None):
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)
logging.debug("Config path location: %s" % self.config_path)
self.defined_config = None
try:
self.config.read(self.default_config_path)
if self.config_path != self.default_config_path:
user_def, saved_def = self.separate_saved_config(self.config_path)
self.defined_config = configparser.ConfigParser()
self.defined_config.read_string(user_def)
includes = [i[8:] for i in self.defined_config.sections() if i.startswith("include ")]
for include in includes:
self._include_config("/".join(self.config_path.split("/")[:-1]), include)
for i in ['menu __main', 'menu __print', 'menu __splashscreen', 'preheat']:
for j in self.defined_config.sections():
if j.startswith(i):
for k in list(self.config.sections()):
if k.startswith(i):
del self.config[k]
break
self.log_config(self.defined_config)
self.config.read_string(user_def)
if saved_def is not None:
self.config.read_string(saved_def)
logging.info("====== Saved Def ======\n%s\n=======================" % saved_def)
except KeyError:
raise ConfigError(f"Error reading config: {self.config_path}")
except Exception:
logging.exception("Unknown error with config")
printers = sorted([i for i in self.config.sections() if i.startswith("printer ")])
self.printers = []
for printer in printers:
self.printers.append({
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)
}
})
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)
for printer in conf_printers_debug:
name = list(printer)[0]
item = conf_printers_debug[conf_printers_debug.index(printer)]
if item[list(printer)[0]]['moonraker_api_key'] != "":
item[list(printer)[0]]['moonraker_api_key'] = "redacted"
logging.debug("Configured printers: %s" % json.dumps(conf_printers_debug, indent=2))
lang = self.get_main_config_option("language", None)
lang = [lang] if lang is not None and lang != "default" else None
logging.info("Detected language: %s" % lang)
self.lang = gettext.translation('KlipperScreen', localedir='ks_includes/locales', languages=lang,
fallback=True)
self._create_configurable_options(screen)
def _create_configurable_options(self, screen):
_ = self.lang.gettext
_n = self.lang.ngettext
self.configurable_options = [
{"invert_x": {"section": "main", "name": _("Invert X"), "type": "binary", "value": "False"}},
{"invert_y": {"section": "main", "name": _("Invert Y"), "type": "binary", "value": "False"}},
{"invert_z": {"section": "main", "name": _("Invert Z"), "type": "binary", "value": "False"}},
{"language": {"section": "main", "name": _("Language"), "type": "dropdown", "value": "system_lang",
"callback": screen.restart_warning, "options": [
{"name": _("System") + " " + _("(default)"), "value": "system_lang"}
]}},
{"move_speed": {
"section": "main", "name": _("Move Speed (mm/s)"), "type": "scale", "value": "20",
"range": [5, 100], "step": 1}},
{"print_sort_dir": {"section": "main", "type": None, "value": "name_asc"}},
{"print_estimate_method": {
"section": "main", "name": _("Estimated Time Method"), "type": "dropdown",
"value": "file", "options": [
{"name": _("File") + " " + _("(default)"), "value": "file"},
{"name": _("Duration Only"), "value": "duration"},
{"name": _("Filament Used"), "value": "filament"},
{"name": _("Slicer"), "value": "slicer"}]}},
{"screen_blanking": {
"section": "main", "name": _("Screen Power Off Time"), "type": "dropdown",
"value": "3600", "callback": screen.set_screenblanking_timeout, "options": [
{"name": _("Off"), "value": "off"}]
}},
{"theme": {
"section": "main", "name": _("Icon Theme"), "type": "dropdown",
"value": "z-bolt", "callback": screen.restart_warning, "options": [
{"name": "Z-bolt" + " " + _("(default)"), "value": "z-bolt"}]}},
{"24htime": {"section": "main", "name": _("24 Hour Time"), "type": "binary", "value": "True"}},
{"side_macro_shortcut": {
"section": "main", "name": _("Macro shortcut on sidebar"), "type": "binary",
"value": "True", "callback": screen.toggle_macro_shortcut}},
{"font_size": {
"section": "main", "name": _("Font Size"), "type": "dropdown",
"value": "medium", "callback": screen.restart_warning, "options": [
{"name": _("Small"), "value": "small"},
{"name": _("Medium") + " " + _("(default)"), "value": "medium"},
{"name": _("Large"), "value": "large"}]}},
{"confirm_estop": {"section": "main", "name": "Confirm Emergency Stop", "type": "binary",
"value": "False"}},
# {"": {"section": "main", "name": _(""), "type": ""}}
]
lang_path = os.path.join(klipperscreendir, "ks_includes", "locales")
langs = [d for d in os.listdir(lang_path) if not os.path.isfile(os.path.join(lang_path, d))]
langs.sort()
lang_opt = self.configurable_options[3]['language']['options']
for lang in langs:
lang_opt.append({"name": lang, "value": lang})
t_path = os.path.join(klipperscreendir, 'styles')
themes = [d for d in os.listdir(t_path) if (not os.path.isfile(os.path.join(t_path, d)) and d != "z-bolt")]
themes.sort()
theme_opt = self.configurable_options[8]['theme']['options']
for theme in themes:
theme_opt.append({"name": theme, "value": theme})
index = self.configurable_options.index(
[i for i in self.configurable_options if list(i)[0] == "screen_blanking"][0])
for num in SCREEN_BLANKING_OPTIONS:
hour = int(int(num)/3600)
if hour > 0:
name = str(hour) + " " + _n("hour", "hours", hour)
else:
name = str(int(int(num)/60)) + " " + _("minutes")
self.configurable_options[index]['screen_blanking']['options'].append({
"name": name,
"value": num
})
for item in self.configurable_options:
name = list(item)[0]
vals = item[name]
if vals['section'] not in self.config.sections():
self.config.add_section(vals['section'])
if name not in list(self.config[vals['section']]):
self.config.set(vals['section'], name, vals['value'])
def _include_config(self, dir, path):
full_path = path if path[0] == "/" else "%s/%s" % (dir, path)
parse_files = []
if "*" in full_path:
parent_dir = "/".join(full_path.split("/")[:-1])
file = full_path.split("/")[-1]
if not os.path.exists(parent_dir):
logging.info("Config Error: Directory %s does not exist" % parent_dir)
return
files = os.listdir(parent_dir)
regex = "^%s$" % file.replace('*', '.*')
for file in files:
if re.match(regex, file):
parse_files.append(os.path.join(parent_dir, file))
else:
if not os.path.exists(os.path.join(full_path)):
logging.info("Config Error: %s does not exist" % full_path)
return
parse_files.append(full_path)
logging.info("Parsing files: %s" % parse_files)
for file in parse_files:
config = configparser.ConfigParser()
config.read(file)
includes = [i[8:] for i in config.sections() if i.startswith("include ")]
for include in includes:
self._include_config("/".join(full_path.split("/")[:-1]), include)
self.config.read(file)
self.defined_config.read(file)
def separate_saved_config(self, config_path):
user_def = []
saved_def = None
found_saved = False
if not path.exists(config_path):
return [None, None]
with open(config_path) as file:
for line in file:
line = line.replace('\n', '')
if line == self.do_not_edit_line:
found_saved = True
saved_def = []
continue
if found_saved is False:
user_def.append(line.replace('\n', ''))
else:
if line.startswith(self.do_not_edit_prefix):
saved_def.append(line[(len(self.do_not_edit_prefix)+1):])
return ["\n".join(user_def), None if saved_def is None else "\n".join(saved_def)]
def get_config_file_location(self, file):
logging.info("Passed config file: %s" % file)
if not path.exists(file):
file = os.path.join(klipperscreendir, self.configfile_name)
if not path.exists(file):
file = os.path.expanduser("~/") + "klipper_config/%s" % (self.configfile_name)
if not path.exists(file):
file = self.default_config_path
logging.info("Found configuration file at: %s" % file)
return file
def get_config(self):
return self.config
def get_configurable_options(self):
return self.configurable_options
def get_lang(self):
return self.lang
def get_main_config(self):
return self.config['main']
def get_main_config_option(self, option, default=None):
return self.config['main'].get(option, default)
def get_menu_items(self, menu="__main", subsection=""):
if subsection != "":
subsection = subsection + " "
index = "menu %s %s" % (menu, subsection)
items = [i[len(index):] for i in self.config.sections() if i.startswith(index)]
menu_items = []
for item in items:
split = item.split()
if len(split) == 1:
menu_items.append(self._build_menu_item(menu, index + item))
return menu_items
def get_menu_name(self, menu="__main", subsection=""):
name = ("menu %s %s" % (menu, subsection)) if subsection != "" else ("menu %s" % menu)
if name not in self.config:
return False
return self.config[name].get('name')
def get_preheat_options(self):
index = "preheat "
items = [i[len(index):] for i in self.config.sections() if i.startswith(index)]
preheat_options = {}
for item in items:
preheat_options[item] = self._build_preheat_item(index + item)
return preheat_options
def get_printer_config(self, name):
if not name.startswith("printer "):
name = "printer %s" % name
if name not in self.config:
return None
return self.config[name]
def get_printer_power_name(self):
return self.config['settings'].get("printer_power_name", "printer")
def get_printers(self):
return self.printers
def get_user_saved_config(self):
if self.config_path != self.default_config_path:
print("Get")
def save_user_config_options(self):
save_config = configparser.ConfigParser()
for item in self.configurable_options:
name = list(item)[0]
opt = item[name]
curval = self.config[opt['section']].get(name)
if curval != opt["value"] or (
self.defined_config is not None and opt['section'] in self.defined_config.sections() and
self.defined_config[opt['section']].get(name, None) not in (None, curval)):
if opt['section'] not in save_config.sections():
save_config.add_section(opt['section'])
save_config.set(opt['section'], name, str(curval))
macro_sections = [i for i in self.config.sections() if i.startswith("displayed_macros")]
for macro_sec in macro_sections:
for item in self.config.options(macro_sec):
value = self.config[macro_sec].getboolean(item, fallback=True)
if value is False or (self.defined_config is not None and
macro_sec in self.defined_config.sections() and
self.defined_config[macro_sec].getboolean(item, fallback=True) is False and
self.defined_config[macro_sec].getboolean(item, fallback=True) != value):
if macro_sec not in save_config.sections():
save_config.add_section(macro_sec)
save_config.set(macro_sec, item, str(value))
save_output = self._build_config_string(save_config).split("\n")
for i in range(len(save_output)):
save_output[i] = "%s %s" % (self.do_not_edit_prefix, save_output[i])
if self.config_path == self.default_config_path:
user_def = ""
saved_def = None
else:
user_def, saved_def = self.separate_saved_config(self.config_path)
extra_lb = "\n" if saved_def is not None else ""
contents = "%s\n%s%s\n%s\n%s\n%s\n" % (
user_def, self.do_not_edit_line, extra_lb, self.do_not_edit_prefix, "\n".join(save_output),
self.do_not_edit_prefix)
if self.config_path != self.default_config_path:
path = self.config_path
else:
path = os.path.expanduser("~/")
if os.path.exists(path+"klipper_config/"):
path = path + "klipper_config/KlipperScreen.conf"
else:
path = path + "KlipperScreen.conf"
try:
file = open(path, 'w')
file.write(contents)
file.close()
except Exception:
logging.error("Error writing configuration file in %s" % path)
def set(self, section, name, value):
self.config.set(section, name, value)
def log_config(self, config):
lines = [
" "
"===== Config File =====",
re.sub(
r'(moonraker_api_key\s*=\s*\S+)',
'moonraker_api_key = [redacted]',
self._build_config_string(config)
),
"======================="
]
logging.info("\n".join(lines))
def _build_config_string(self, config):
sfile = StringIO()
config.write(sfile)
sfile.seek(0)
return sfile.read().strip()
def _build_menu_item(self, menu, name):
if name not in self.config:
return False
cfg = self.config[name]
item = {
"name": cfg.get("name"),
"icon": cfg.get("icon"),
"panel": cfg.get("panel", False),
"method": cfg.get("method", False),
"confirm": cfg.get("confirm", False),
"enable": cfg.get("enable", True)
}
try:
item["params"] = json.loads(cfg.get("params", "{}"))
except Exception:
logging.debug("Unable to parse parameters for [%s]" % name)
item["params"] = {}
return {name[(len(menu) + 6):]: item}
def _build_preheat_item(self, name):
if name not in self.config:
return False
cfg = self.config[name]
item = {
"extruder": cfg.getint("extruder", 0),
"bed": cfg.getint("bed", 0),
"heater_generic": cfg.getint("heater_generic", 0),
"gcode": cfg.get("gcode", None)
}
return item