| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  | # Code for reading and writing the Klipper config file | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  | # | 
					
						
							| 
									
										
										
										
											2021-01-14 22:22:33 -05:00
										 |  |  | # Copyright (C) 2016-2021  Kevin O'Connor <kevin@koconnor.net> | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  | # | 
					
						
							|  |  |  | # This file may be distributed under the terms of the GNU GPLv3 license. | 
					
						
							| 
									
										
										
										
											2020-06-12 10:03:28 -04:00
										 |  |  | import os, glob, re, time, logging, ConfigParser as configparser, StringIO | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-12 10:03:28 -04:00
										 |  |  | error = configparser.Error | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | class sentinel: | 
					
						
							|  |  |  |     pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class ConfigWrapper: | 
					
						
							| 
									
										
										
										
											2020-06-12 10:03:28 -04:00
										 |  |  |     error = configparser.Error | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |     def __init__(self, printer, fileconfig, access_tracking, section): | 
					
						
							|  |  |  |         self.printer = printer | 
					
						
							|  |  |  |         self.fileconfig = fileconfig | 
					
						
							|  |  |  |         self.access_tracking = access_tracking | 
					
						
							|  |  |  |         self.section = section | 
					
						
							|  |  |  |     def get_printer(self): | 
					
						
							|  |  |  |         return self.printer | 
					
						
							|  |  |  |     def get_name(self): | 
					
						
							|  |  |  |         return self.section | 
					
						
							| 
									
										
										
										
											2020-05-21 17:50:51 -04:00
										 |  |  |     def _get_wrapper(self, parser, option, default, minval=None, maxval=None, | 
					
						
							|  |  |  |                      above=None, below=None, note_valid=True): | 
					
						
							| 
									
										
										
										
											2020-12-22 20:07:39 -05:00
										 |  |  |         if not self.fileconfig.has_option(self.section, option): | 
					
						
							|  |  |  |             if default is not sentinel: | 
					
						
							| 
									
										
										
										
											2021-01-14 22:22:33 -05:00
										 |  |  |                 if note_valid and default is not None: | 
					
						
							|  |  |  |                     acc_id = (self.section.lower(), option.lower()) | 
					
						
							|  |  |  |                     self.access_tracking[acc_id] = default | 
					
						
							| 
									
										
										
										
											2020-12-22 20:07:39 -05:00
										 |  |  |                 return default | 
					
						
							|  |  |  |             raise error("Option '%s' in section '%s' must be specified" | 
					
						
							|  |  |  |                         % (option, self.section)) | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |         try: | 
					
						
							|  |  |  |             v = parser(self.section, option) | 
					
						
							|  |  |  |         except self.error as e: | 
					
						
							|  |  |  |             raise | 
					
						
							|  |  |  |         except: | 
					
						
							| 
									
										
										
										
											2020-12-22 20:06:15 -05:00
										 |  |  |             raise error("Unable to parse option '%s' in section '%s'" | 
					
						
							|  |  |  |                         % (option, self.section)) | 
					
						
							| 
									
										
										
										
											2021-01-14 22:22:33 -05:00
										 |  |  |         if note_valid: | 
					
						
							|  |  |  |             self.access_tracking[(self.section.lower(), option.lower())] = v | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |         if minval is not None and v < minval: | 
					
						
							| 
									
										
										
										
											2020-12-22 20:06:15 -05:00
										 |  |  |             raise error("Option '%s' in section '%s' must have minimum of %s" | 
					
						
							|  |  |  |                         % (option, self.section, minval)) | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |         if maxval is not None and v > maxval: | 
					
						
							| 
									
										
										
										
											2020-12-22 20:06:15 -05:00
										 |  |  |             raise error("Option '%s' in section '%s' must have maximum of %s" | 
					
						
							|  |  |  |                         % (option, self.section, maxval)) | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |         if above is not None and v <= above: | 
					
						
							| 
									
										
										
										
											2020-12-22 20:06:15 -05:00
										 |  |  |             raise error("Option '%s' in section '%s' must be above %s" | 
					
						
							|  |  |  |                         % (option, self.section, above)) | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |         if below is not None and v >= below: | 
					
						
							| 
									
										
										
										
											2020-12-22 20:06:15 -05:00
										 |  |  |             raise self.error("Option '%s' in section '%s' must be below %s" | 
					
						
							|  |  |  |                              % (option, self.section, below)) | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |         return v | 
					
						
							| 
									
										
										
										
											2020-05-21 17:50:51 -04:00
										 |  |  |     def get(self, option, default=sentinel, note_valid=True): | 
					
						
							|  |  |  |         return self._get_wrapper(self.fileconfig.get, option, default, | 
					
						
							|  |  |  |                                  note_valid=note_valid) | 
					
						
							|  |  |  |     def getint(self, option, default=sentinel, minval=None, maxval=None, | 
					
						
							|  |  |  |                note_valid=True): | 
					
						
							|  |  |  |         return self._get_wrapper(self.fileconfig.getint, option, default, | 
					
						
							|  |  |  |                                  minval, maxval, note_valid=note_valid) | 
					
						
							|  |  |  |     def getfloat(self, option, default=sentinel, minval=None, maxval=None, | 
					
						
							|  |  |  |                  above=None, below=None, note_valid=True): | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |         return self._get_wrapper(self.fileconfig.getfloat, option, default, | 
					
						
							| 
									
										
										
										
											2020-05-21 17:50:51 -04:00
										 |  |  |                                  minval, maxval, above, below, | 
					
						
							|  |  |  |                                  note_valid=note_valid) | 
					
						
							|  |  |  |     def getboolean(self, option, default=sentinel, note_valid=True): | 
					
						
							|  |  |  |         return self._get_wrapper(self.fileconfig.getboolean, option, default, | 
					
						
							|  |  |  |                                  note_valid=note_valid) | 
					
						
							|  |  |  |     def getchoice(self, option, choices, default=sentinel, note_valid=True): | 
					
						
							| 
									
										
										
										
											2021-08-25 10:36:45 -04:00
										 |  |  |         if choices and type(list(choices.keys())[0]) == int: | 
					
						
							|  |  |  |             c = self.getint(option, default, note_valid=note_valid) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             c = self.get(option, default, note_valid=note_valid) | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |         if c not in choices: | 
					
						
							|  |  |  |             raise error("Choice '%s' for option '%s' in section '%s'" | 
					
						
							|  |  |  |                         " is not a valid choice" % (c, option, self.section)) | 
					
						
							|  |  |  |         return choices[c] | 
					
						
							| 
									
										
										
										
											2021-08-19 13:03:35 -04:00
										 |  |  |     def getlists(self, option, default=sentinel, seps=(',',), count=None, | 
					
						
							|  |  |  |                  parser=str, note_valid=True): | 
					
						
							|  |  |  |         def lparser(value, pos): | 
					
						
							|  |  |  |             if pos: | 
					
						
							|  |  |  |                 # Nested list | 
					
						
							|  |  |  |                 parts = [p.strip() for p in value.split(seps[pos])] | 
					
						
							|  |  |  |                 return tuple([lparser(p, pos - 1) for p in parts if p]) | 
					
						
							|  |  |  |             res = [parser(p.strip()) for p in value.split(seps[pos])] | 
					
						
							|  |  |  |             if count is not None and len(res) != count: | 
					
						
							|  |  |  |                 raise error("Option '%s' in section '%s' must have %d elements" | 
					
						
							|  |  |  |                             % (option, self.section, count)) | 
					
						
							|  |  |  |             return tuple(res) | 
					
						
							|  |  |  |         def fcparser(section, option): | 
					
						
							|  |  |  |             return lparser(self.fileconfig.get(section, option), len(seps) - 1) | 
					
						
							|  |  |  |         return self._get_wrapper(fcparser, option, default, | 
					
						
							|  |  |  |                                  note_valid=note_valid) | 
					
						
							|  |  |  |     def getlist(self, option, default=sentinel, sep=',', count=None, | 
					
						
							|  |  |  |                 note_valid=True): | 
					
						
							|  |  |  |         return self.getlists(option, default, seps=(sep,), count=count, | 
					
						
							|  |  |  |                              parser=str, note_valid=note_valid) | 
					
						
							|  |  |  |     def getintlist(self, option, default=sentinel, sep=',', count=None, | 
					
						
							|  |  |  |                    note_valid=True): | 
					
						
							|  |  |  |         return self.getlists(option, default, seps=(sep,), count=count, | 
					
						
							|  |  |  |                              parser=int, note_valid=note_valid) | 
					
						
							|  |  |  |     def getfloatlist(self, option, default=sentinel, sep=',', count=None, | 
					
						
							|  |  |  |                      note_valid=True): | 
					
						
							|  |  |  |         return self.getlists(option, default, seps=(sep,), count=count, | 
					
						
							|  |  |  |                              parser=float, note_valid=note_valid) | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |     def getsection(self, section): | 
					
						
							|  |  |  |         return ConfigWrapper(self.printer, self.fileconfig, | 
					
						
							|  |  |  |                              self.access_tracking, section) | 
					
						
							|  |  |  |     def has_section(self, section): | 
					
						
							|  |  |  |         return self.fileconfig.has_section(section) | 
					
						
							|  |  |  |     def get_prefix_sections(self, prefix): | 
					
						
							|  |  |  |         return [self.getsection(s) for s in self.fileconfig.sections() | 
					
						
							|  |  |  |                 if s.startswith(prefix)] | 
					
						
							| 
									
										
										
										
											2018-09-29 12:15:48 +03:00
										 |  |  |     def get_prefix_options(self, prefix): | 
					
						
							|  |  |  |         return [o for o in self.fileconfig.options(self.section) | 
					
						
							|  |  |  |                 if o.startswith(prefix)] | 
					
						
							| 
									
										
										
										
											2021-09-04 14:20:24 -04:00
										 |  |  |     def deprecate(self, option, value=None): | 
					
						
							|  |  |  |         if not self.fileconfig.has_option(self.section, option): | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         if value is None: | 
					
						
							|  |  |  |             msg = ("Option '%s' in section '%s' is deprecated." | 
					
						
							|  |  |  |                    % (option, self.section)) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             msg = ("Value '%s' in option '%s' in section '%s' is deprecated." | 
					
						
							|  |  |  |                    % (value, option, self.section)) | 
					
						
							|  |  |  |         pconfig = self.printer.lookup_object("configfile") | 
					
						
							|  |  |  |         pconfig.deprecate(self.section, option, value, msg) | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  | AUTOSAVE_HEADER = """
 | 
					
						
							|  |  |  | #*# <---------------------- SAVE_CONFIG ----------------------> | 
					
						
							|  |  |  | #*# DO NOT EDIT THIS BLOCK OR BELOW. The contents are auto-generated. | 
					
						
							|  |  |  | #*# | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | class PrinterConfig: | 
					
						
							|  |  |  |     def __init__(self, printer): | 
					
						
							|  |  |  |         self.printer = printer | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |         self.autosave = None | 
					
						
							| 
									
										
										
										
											2021-09-04 14:20:24 -04:00
										 |  |  |         self.deprecated = {} | 
					
						
							| 
									
										
										
										
											2021-01-14 22:22:33 -05:00
										 |  |  |         self.status_raw_config = {} | 
					
						
							|  |  |  |         self.status_settings = {} | 
					
						
							| 
									
										
										
										
											2021-09-04 14:20:24 -04:00
										 |  |  |         self.status_warnings = [] | 
					
						
							| 
									
										
										
										
											2020-09-28 00:05:55 -04:00
										 |  |  |         self.save_config_pending = False | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |         gcode = self.printer.lookup_object('gcode') | 
					
						
							|  |  |  |         gcode.register_command("SAVE_CONFIG", self.cmd_SAVE_CONFIG, | 
					
						
							|  |  |  |                                desc=self.cmd_SAVE_CONFIG_help) | 
					
						
							| 
									
										
										
										
											2019-12-05 09:03:37 -05:00
										 |  |  |     def get_printer(self): | 
					
						
							|  |  |  |         return self.printer | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |     def _read_config_file(self, filename): | 
					
						
							|  |  |  |         try: | 
					
						
							| 
									
										
										
										
											2020-06-12 13:04:16 -04:00
										 |  |  |             f = open(filename, 'r') | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |             data = f.read() | 
					
						
							|  |  |  |             f.close() | 
					
						
							|  |  |  |         except: | 
					
						
							|  |  |  |             msg = "Unable to open config file %s" % (filename,) | 
					
						
							|  |  |  |             logging.exception(msg) | 
					
						
							|  |  |  |             raise error(msg) | 
					
						
							|  |  |  |         return data.replace('\r\n', '\n') | 
					
						
							|  |  |  |     def _find_autosave_data(self, data): | 
					
						
							|  |  |  |         regular_data = data | 
					
						
							|  |  |  |         autosave_data = "" | 
					
						
							|  |  |  |         pos = data.find(AUTOSAVE_HEADER) | 
					
						
							|  |  |  |         if pos >= 0: | 
					
						
							|  |  |  |             regular_data = data[:pos] | 
					
						
							|  |  |  |             autosave_data = data[pos + len(AUTOSAVE_HEADER):].strip() | 
					
						
							|  |  |  |         # Check for errors and strip line prefixes | 
					
						
							|  |  |  |         if "\n#*# " in regular_data: | 
					
						
							|  |  |  |             logging.warn("Can't read autosave from config file" | 
					
						
							|  |  |  |                          " - autosave state corrupted") | 
					
						
							|  |  |  |             return data, "" | 
					
						
							|  |  |  |         out = [""] | 
					
						
							|  |  |  |         for line in autosave_data.split('\n'): | 
					
						
							|  |  |  |             if ((not line.startswith("#*#") | 
					
						
							|  |  |  |                  or (len(line) >= 4 and not line.startswith("#*# "))) | 
					
						
							|  |  |  |                 and autosave_data): | 
					
						
							|  |  |  |                 logging.warn("Can't read autosave from config file" | 
					
						
							|  |  |  |                              " - modifications after header") | 
					
						
							|  |  |  |                 return data, "" | 
					
						
							|  |  |  |             out.append(line[4:]) | 
					
						
							|  |  |  |         out.append("") | 
					
						
							|  |  |  |         return regular_data, "\n".join(out) | 
					
						
							|  |  |  |     comment_r = re.compile('[#;].*$') | 
					
						
							|  |  |  |     value_r = re.compile('[^A-Za-z0-9_].*$') | 
					
						
							|  |  |  |     def _strip_duplicates(self, data, config): | 
					
						
							|  |  |  |         fileconfig = config.fileconfig | 
					
						
							|  |  |  |         # Comment out fields in 'data' that are defined in 'config' | 
					
						
							|  |  |  |         lines = data.split('\n') | 
					
						
							|  |  |  |         section = None | 
					
						
							|  |  |  |         is_dup_field = False | 
					
						
							|  |  |  |         for lineno, line in enumerate(lines): | 
					
						
							|  |  |  |             pruned_line = self.comment_r.sub('', line).rstrip() | 
					
						
							|  |  |  |             if not pruned_line: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             if pruned_line[0].isspace(): | 
					
						
							|  |  |  |                 if is_dup_field: | 
					
						
							|  |  |  |                     lines[lineno] = '#' + lines[lineno] | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             is_dup_field = False | 
					
						
							|  |  |  |             if pruned_line[0] == '[': | 
					
						
							|  |  |  |                 section = pruned_line[1:-1].strip() | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             field = self.value_r.sub('', pruned_line) | 
					
						
							|  |  |  |             if config.fileconfig.has_option(section, field): | 
					
						
							|  |  |  |                 is_dup_field = True | 
					
						
							|  |  |  |                 lines[lineno] = '#' + lines[lineno] | 
					
						
							|  |  |  |         return "\n".join(lines) | 
					
						
							| 
									
										
										
										
											2019-03-22 17:31:40 -07:00
										 |  |  |     def _parse_config_buffer(self, buffer, filename, fileconfig): | 
					
						
							|  |  |  |         if not buffer: | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         data = '\n'.join(buffer) | 
					
						
							|  |  |  |         del buffer[:] | 
					
						
							|  |  |  |         sbuffer = StringIO.StringIO(data) | 
					
						
							|  |  |  |         fileconfig.readfp(sbuffer, filename) | 
					
						
							|  |  |  |     def _resolve_include(self, source_filename, include_spec, fileconfig, | 
					
						
							|  |  |  |                          visited): | 
					
						
							|  |  |  |         dirname = os.path.dirname(source_filename) | 
					
						
							|  |  |  |         include_spec = include_spec.strip() | 
					
						
							|  |  |  |         include_glob = os.path.join(dirname, include_spec) | 
					
						
							|  |  |  |         include_filenames = glob.glob(include_glob) | 
					
						
							|  |  |  |         if not include_filenames and not glob.has_magic(include_glob): | 
					
						
							|  |  |  |             # Empty set is OK if wildcard but not for direct file reference | 
					
						
							| 
									
										
										
										
											2019-09-02 00:28:18 +02:00
										 |  |  |             raise error("Include file '%s' does not exist" % (include_glob,)) | 
					
						
							| 
									
										
										
										
											2019-03-22 17:31:40 -07:00
										 |  |  |         include_filenames.sort() | 
					
						
							|  |  |  |         for include_filename in include_filenames: | 
					
						
							|  |  |  |             include_data = self._read_config_file(include_filename) | 
					
						
							|  |  |  |             self._parse_config(include_data, include_filename, fileconfig, | 
					
						
							|  |  |  |                                visited) | 
					
						
							|  |  |  |         return include_filenames | 
					
						
							|  |  |  |     def _parse_config(self, data, filename, fileconfig, visited): | 
					
						
							|  |  |  |         path = os.path.abspath(filename) | 
					
						
							|  |  |  |         if path in visited: | 
					
						
							|  |  |  |             raise error("Recursive include of config file '%s'" % (filename)) | 
					
						
							|  |  |  |         visited.add(path) | 
					
						
							| 
									
										
										
										
											2018-10-16 11:45:20 -04:00
										 |  |  |         lines = data.split('\n') | 
					
						
							| 
									
										
										
										
											2019-03-22 17:31:40 -07:00
										 |  |  |         # Buffer lines between includes and parse as a unit so that overrides | 
					
						
							|  |  |  |         # in includes apply linearly as they do within a single file | 
					
						
							|  |  |  |         buffer = [] | 
					
						
							|  |  |  |         for line in lines: | 
					
						
							|  |  |  |             # Strip trailing comment | 
					
						
							| 
									
										
										
										
											2018-10-16 11:45:20 -04:00
										 |  |  |             pos = line.find('#') | 
					
						
							|  |  |  |             if pos >= 0: | 
					
						
							| 
									
										
										
										
											2019-03-22 17:31:40 -07:00
										 |  |  |                 line = line[:pos] | 
					
						
							|  |  |  |             # Process include or buffer line | 
					
						
							| 
									
										
										
										
											2020-06-12 10:03:28 -04:00
										 |  |  |             mo = configparser.RawConfigParser.SECTCRE.match(line) | 
					
						
							| 
									
										
										
										
											2019-03-22 17:31:40 -07:00
										 |  |  |             header = mo and mo.group('header') | 
					
						
							|  |  |  |             if header and header.startswith('include '): | 
					
						
							|  |  |  |                 self._parse_config_buffer(buffer, filename, fileconfig) | 
					
						
							|  |  |  |                 include_spec = header[8:].strip() | 
					
						
							|  |  |  |                 self._resolve_include(filename, include_spec, fileconfig, | 
					
						
							|  |  |  |                                       visited) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 buffer.append(line) | 
					
						
							|  |  |  |         self._parse_config_buffer(buffer, filename, fileconfig) | 
					
						
							|  |  |  |         visited.remove(path) | 
					
						
							|  |  |  |     def _build_config_wrapper(self, data, filename): | 
					
						
							| 
									
										
										
										
											2020-06-12 10:03:28 -04:00
										 |  |  |         fileconfig = configparser.RawConfigParser() | 
					
						
							| 
									
										
										
										
											2019-03-22 17:31:40 -07:00
										 |  |  |         self._parse_config(data, filename, fileconfig, set()) | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |         return ConfigWrapper(self.printer, fileconfig, {}, 'printer') | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |     def _build_config_string(self, config): | 
					
						
							|  |  |  |         sfile = StringIO.StringIO() | 
					
						
							|  |  |  |         config.fileconfig.write(sfile) | 
					
						
							|  |  |  |         return sfile.getvalue().strip() | 
					
						
							|  |  |  |     def read_config(self, filename): | 
					
						
							| 
									
										
										
										
											2019-03-22 17:31:40 -07:00
										 |  |  |         return self._build_config_wrapper(self._read_config_file(filename), | 
					
						
							|  |  |  |                                           filename) | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |     def read_main_config(self): | 
					
						
							|  |  |  |         filename = self.printer.get_start_args()['config_file'] | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |         data = self._read_config_file(filename) | 
					
						
							|  |  |  |         regular_data, autosave_data = self._find_autosave_data(data) | 
					
						
							| 
									
										
										
										
											2019-03-22 17:31:40 -07:00
										 |  |  |         regular_config = self._build_config_wrapper(regular_data, filename) | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |         autosave_data = self._strip_duplicates(autosave_data, regular_config) | 
					
						
							| 
									
										
										
										
											2019-03-22 17:31:40 -07:00
										 |  |  |         self.autosave = self._build_config_wrapper(autosave_data, filename) | 
					
						
							| 
									
										
										
										
											2020-02-13 21:57:41 -05:00
										 |  |  |         cfg = self._build_config_wrapper(regular_data + autosave_data, filename) | 
					
						
							|  |  |  |         return cfg | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |     def check_unused_options(self, config): | 
					
						
							|  |  |  |         fileconfig = config.fileconfig | 
					
						
							|  |  |  |         objects = dict(self.printer.lookup_objects()) | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |         # Determine all the fields that have been accessed | 
					
						
							|  |  |  |         access_tracking = dict(config.access_tracking) | 
					
						
							|  |  |  |         for section in self.autosave.fileconfig.sections(): | 
					
						
							|  |  |  |             for option in self.autosave.fileconfig.options(section): | 
					
						
							|  |  |  |                 access_tracking[(section.lower(), option.lower())] = 1 | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |         # Validate that there are no undefined parameters in the config file | 
					
						
							|  |  |  |         valid_sections = { s: 1 for s, o in access_tracking } | 
					
						
							|  |  |  |         for section_name in fileconfig.sections(): | 
					
						
							|  |  |  |             section = section_name.lower() | 
					
						
							|  |  |  |             if section not in valid_sections and section not in objects: | 
					
						
							| 
									
										
										
										
											2020-12-22 20:06:15 -05:00
										 |  |  |                 raise error("Section '%s' is not a valid config section" | 
					
						
							|  |  |  |                             % (section,)) | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |             for option in fileconfig.options(section_name): | 
					
						
							|  |  |  |                 option = option.lower() | 
					
						
							|  |  |  |                 if (section, option) not in access_tracking: | 
					
						
							| 
									
										
										
										
											2020-12-22 20:06:15 -05:00
										 |  |  |                     raise error("Option '%s' is not valid in section '%s'" | 
					
						
							|  |  |  |                                 % (option, section)) | 
					
						
							| 
									
										
										
										
											2021-09-04 13:25:11 -04:00
										 |  |  |         # Setup get_status() | 
					
						
							|  |  |  |         self._build_status(config) | 
					
						
							| 
									
										
										
										
											2018-09-04 20:49:47 -04:00
										 |  |  |     def log_config(self, config): | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |         lines = ["===== Config file =====", | 
					
						
							|  |  |  |                  self._build_config_string(config), | 
					
						
							|  |  |  |                  "======================="] | 
					
						
							|  |  |  |         self.printer.set_rollover_info("config", "\n".join(lines)) | 
					
						
							| 
									
										
										
										
											2020-02-13 21:57:41 -05:00
										 |  |  |     # Status reporting | 
					
						
							| 
									
										
										
										
											2021-09-04 14:20:24 -04:00
										 |  |  |     def deprecate(self, section, option, value=None, msg=None): | 
					
						
							|  |  |  |         self.deprecated[(section, option, value)] = msg | 
					
						
							| 
									
										
										
										
											2020-02-13 21:57:41 -05:00
										 |  |  |     def _build_status(self, config): | 
					
						
							| 
									
										
										
										
											2021-01-14 22:22:33 -05:00
										 |  |  |         self.status_raw_config.clear() | 
					
						
							| 
									
										
										
										
											2020-02-13 21:57:41 -05:00
										 |  |  |         for section in config.get_prefix_sections(''): | 
					
						
							| 
									
										
										
										
											2021-01-14 22:22:33 -05:00
										 |  |  |             self.status_raw_config[section.get_name()] = section_status = {} | 
					
						
							| 
									
										
										
										
											2020-02-13 21:57:41 -05:00
										 |  |  |             for option in section.get_prefix_options(''): | 
					
						
							| 
									
										
										
										
											2020-05-21 17:50:51 -04:00
										 |  |  |                 section_status[option] = section.get(option, note_valid=False) | 
					
						
							| 
									
										
										
										
											2021-09-04 13:25:11 -04:00
										 |  |  |         self.status_settings = {} | 
					
						
							|  |  |  |         for (section, option), value in config.access_tracking.items(): | 
					
						
							|  |  |  |             self.status_settings.setdefault(section, {})[option] = value | 
					
						
							| 
									
										
										
										
											2021-09-04 14:20:24 -04:00
										 |  |  |         self.status_warnings = [] | 
					
						
							|  |  |  |         for (section, option, value), msg in self.deprecated.items(): | 
					
						
							|  |  |  |             if value is None: | 
					
						
							|  |  |  |                 res = {'type': 'deprecated_option'} | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 res = {'type': 'deprecated_value', 'value': value} | 
					
						
							|  |  |  |             res['message'] = msg | 
					
						
							|  |  |  |             res['section'] = section | 
					
						
							|  |  |  |             res['option'] = option | 
					
						
							|  |  |  |             self.status_warnings.append(res) | 
					
						
							| 
									
										
										
										
											2020-02-13 21:57:41 -05:00
										 |  |  |     def get_status(self, eventtime): | 
					
						
							| 
									
										
										
										
											2021-01-14 22:22:33 -05:00
										 |  |  |         return {'config': self.status_raw_config, | 
					
						
							|  |  |  |                 'settings': self.status_settings, | 
					
						
							| 
									
										
										
										
											2021-09-04 14:20:24 -04:00
										 |  |  |                 'warnings': self.status_warnings, | 
					
						
							| 
									
										
										
										
											2020-09-28 00:05:55 -04:00
										 |  |  |                 'save_config_pending': self.save_config_pending} | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |     # Autosave functions | 
					
						
							|  |  |  |     def set(self, section, option, value): | 
					
						
							|  |  |  |         if not self.autosave.fileconfig.has_section(section): | 
					
						
							|  |  |  |             self.autosave.fileconfig.add_section(section) | 
					
						
							|  |  |  |         svalue = str(value) | 
					
						
							|  |  |  |         self.autosave.fileconfig.set(section, option, svalue) | 
					
						
							| 
									
										
										
										
											2020-09-28 00:05:55 -04:00
										 |  |  |         self.save_config_pending = True | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |         logging.info("save_config: set [%s] %s = %s", section, option, svalue) | 
					
						
							|  |  |  |     def remove_section(self, section): | 
					
						
							|  |  |  |         self.autosave.fileconfig.remove_section(section) | 
					
						
							| 
									
										
										
										
											2020-09-28 00:05:55 -04:00
										 |  |  |         self.save_config_pending = True | 
					
						
							| 
									
										
										
										
											2019-03-22 17:31:40 -07:00
										 |  |  |     def _disallow_include_conflicts(self, regular_data, cfgname, gcode): | 
					
						
							|  |  |  |         config = self._build_config_wrapper(regular_data, cfgname) | 
					
						
							|  |  |  |         for section in self.autosave.fileconfig.sections(): | 
					
						
							|  |  |  |             for option in self.autosave.fileconfig.options(section): | 
					
						
							|  |  |  |                 if config.fileconfig.has_option(section, option): | 
					
						
							| 
									
										
										
										
											2020-12-22 20:06:15 -05:00
										 |  |  |                     msg = ("SAVE_CONFIG section '%s' option '%s' conflicts " | 
					
						
							|  |  |  |                            "with included value" % (section, option)) | 
					
						
							| 
									
										
										
										
											2019-03-22 17:31:40 -07:00
										 |  |  |                     raise gcode.error(msg) | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |     cmd_SAVE_CONFIG_help = "Overwrite config file and restart" | 
					
						
							| 
									
										
										
										
											2020-04-24 21:21:58 -04:00
										 |  |  |     def cmd_SAVE_CONFIG(self, gcmd): | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |         if not self.autosave.fileconfig.sections(): | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         gcode = self.printer.lookup_object('gcode') | 
					
						
							|  |  |  |         # Create string containing autosave data | 
					
						
							|  |  |  |         autosave_data = self._build_config_string(self.autosave) | 
					
						
							|  |  |  |         lines = [('#*# ' + l).strip() | 
					
						
							|  |  |  |                  for l in autosave_data.split('\n')] | 
					
						
							|  |  |  |         lines.insert(0, "\n" + AUTOSAVE_HEADER.rstrip()) | 
					
						
							|  |  |  |         lines.append("") | 
					
						
							|  |  |  |         autosave_data = '\n'.join(lines) | 
					
						
							|  |  |  |         # Read in and validate current config file | 
					
						
							|  |  |  |         cfgname = self.printer.get_start_args()['config_file'] | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             data = self._read_config_file(cfgname) | 
					
						
							|  |  |  |             regular_data, old_autosave_data = self._find_autosave_data(data) | 
					
						
							| 
									
										
										
										
											2019-03-22 17:31:40 -07:00
										 |  |  |             config = self._build_config_wrapper(regular_data, cfgname) | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |         except error as e: | 
					
						
							|  |  |  |             msg = "Unable to parse existing config on SAVE_CONFIG" | 
					
						
							|  |  |  |             logging.exception(msg) | 
					
						
							|  |  |  |             raise gcode.error(msg) | 
					
						
							|  |  |  |         regular_data = self._strip_duplicates(regular_data, self.autosave) | 
					
						
							| 
									
										
										
										
											2019-03-22 17:31:40 -07:00
										 |  |  |         self._disallow_include_conflicts(regular_data, cfgname, gcode) | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |         data = regular_data.rstrip() + autosave_data | 
					
						
							|  |  |  |         # Determine filenames | 
					
						
							|  |  |  |         datestr = time.strftime("-%Y%m%d_%H%M%S") | 
					
						
							|  |  |  |         backup_name = cfgname + datestr | 
					
						
							|  |  |  |         temp_name = cfgname + "_autosave" | 
					
						
							|  |  |  |         if cfgname.endswith(".cfg"): | 
					
						
							|  |  |  |             backup_name = cfgname[:-4] + datestr + ".cfg" | 
					
						
							|  |  |  |             temp_name = cfgname[:-4] + "_autosave.cfg" | 
					
						
							|  |  |  |         # Create new config file with temporary name and swap with main config | 
					
						
							|  |  |  |         logging.info("SAVE_CONFIG to '%s' (backup in '%s')", | 
					
						
							|  |  |  |                      cfgname, backup_name) | 
					
						
							|  |  |  |         try: | 
					
						
							| 
									
										
										
										
											2020-06-12 13:04:16 -04:00
										 |  |  |             f = open(temp_name, 'w') | 
					
						
							| 
									
										
										
										
											2018-09-16 11:37:00 -04:00
										 |  |  |             f.write(data) | 
					
						
							|  |  |  |             f.close() | 
					
						
							|  |  |  |             os.rename(cfgname, backup_name) | 
					
						
							|  |  |  |             os.rename(temp_name, cfgname) | 
					
						
							|  |  |  |         except: | 
					
						
							|  |  |  |             msg = "Unable to write config file during SAVE_CONFIG" | 
					
						
							|  |  |  |             logging.exception(msg) | 
					
						
							|  |  |  |             raise gcode.error(msg) | 
					
						
							|  |  |  |         # Request a restart | 
					
						
							|  |  |  |         gcode.request_restart('restart') |