@@ -88,15 +88,16 @@ def load_component_templates(self) -> dict[str, list[dict]]:
8888
8989 return merged_templates
9090
91- def _load_system_templates (self ) -> dict [str , list [dict ]]:
91+ def _load_system_templates (self , filepath : str = "" ) -> dict [str , list [dict ]]:
9292 """
9393 Load system component templates.
9494
9595 :return: The system component templates as a dictionary
9696 """
97- templates_filename = "system_vehicle_components_template.json"
98- templates_dir = ProgramSettings .get_templates_base_dir ()
99- filepath = os_path .join (templates_dir , templates_filename )
97+ if not filepath :
98+ templates_filename = "system_vehicle_components_template.json"
99+ templates_dir = ProgramSettings .get_templates_base_dir ()
100+ filepath = os_path .join (templates_dir , templates_filename )
100101
101102 templates = {}
102103 try :
@@ -106,6 +107,10 @@ def _load_system_templates(self) -> dict[str, list[dict]]:
106107 logging_debug (_ ("System component templates file '%s' not found." ), filepath )
107108 except JSONDecodeError :
108109 logging_error (_ ("Error decoding JSON system component templates from file '%s'." ), filepath )
110+ except OSError as e :
111+ logging_error (_ ("Error reading system component templates from file '%s': %s" ), filepath , e )
112+ except Exception as e : # pylint: disable=broad-exception-caught
113+ logging_error (_ ("Unexpected error reading system component templates from file '%s': %s" ), filepath , e )
109114 return templates
110115
111116 def _load_user_templates (self ) -> dict [str , list [dict ]]:
@@ -126,6 +131,10 @@ def _load_user_templates(self) -> dict[str, list[dict]]:
126131 logging_debug (_ ("User component templates file '%s' not found." ), filepath )
127132 except JSONDecodeError :
128133 logging_error (_ ("Error decoding JSON user component templates from file '%s'." ), filepath )
134+ except OSError as e :
135+ logging_error (_ ("Error reading user component templates from file '%s': %s" ), filepath , e )
136+ except Exception as e : # pylint: disable=broad-exception-caught
137+ logging_error (_ ("Unexpected error reading user component templates from file '%s': %s" ), filepath , e )
129138 return templates
130139
131140 def save_component_templates (self , templates : dict ) -> tuple [bool , str ]: # pylint: disable=too-many-branches
@@ -138,7 +147,8 @@ def save_component_templates(self, templates: dict) -> tuple[bool, str]: # pyli
138147 :param templates: The templates to save
139148 :return: A tuple of (error_occurred, message), where message is the filepath on success or error message on failure
140149 """
141- # Load system templates to compare against
150+ # Load system templates to compare against, system templates are read-only and come with the software,
151+ # so we need to load them to determine which templates are user-modified
142152 system_templates = self ._load_system_templates ()
143153
144154 # Determine which templates need to be saved to user file
@@ -213,19 +223,21 @@ def save_component_templates(self, templates: dict) -> tuple[bool, str]: # pyli
213223
214224 def save_component_templates_to_file (self , templates_to_save : dict [str , list [dict [str , Any ]]]) -> tuple [bool , str ]:
215225 templates_dir = ProgramSettings .get_templates_base_dir ()
226+ existing_templates = {}
227+ filepath = ""
216228 if self .save_component_to_system_templates :
217- # Save to system templates file.
229+ # Save to "developer" system templates file, only for AMC developers with local a local git repository
230+ # copy of the software who want to add new templates to the system templates file in their local git
231+ # repository and create a github pull-request with the newly added component templates.
218232 # System templates are part of the software installation and get updated when the program
219233 # is updated and deleted when the program is un-installed.
220234 templates_filename = "system_vehicle_components_template.json"
221235 # Check if local system template file exists, use local dir if so.
222- # This allows developers to directly add templates to their local git repository
223- # system template file and create a github pull-request with the newly added component templates
224236 try :
225237 local_dir = importlib_files ("ardupilot_methodic_configurator" ) / "vehicle_templates"
226- local_filepath = local_dir / templates_filename
227- if os_path .exists (str ( local_filepath ) ):
228- templates_dir = str ( local_dir )
238+ filepath = str ( local_dir / templates_filename )
239+ if os_path .exists (filepath ):
240+ existing_templates = self . _load_system_templates ( filepath )
229241 except (OSError , ValueError ) as e :
230242 logging_debug ("Failed to check local template file: %s" , e )
231243 # Fall back to default templates_dir
@@ -234,23 +246,26 @@ def save_component_templates_to_file(self, templates_to_save: dict[str, list[dic
234246 # This file will not get deleted when the program is updated or un-installed.
235247 templates_filename = "user_vehicle_components_template.json"
236248
237- # Create the directory if it doesn't exist
238- try :
239- os_makedirs (templates_dir , exist_ok = True )
240- except Exception as e : # pylint: disable=broad-exception-caught
241- msg = _ ("Failed to create templates directory '{}': {}" ).format (templates_dir , str (e ))
242- logging_error (msg )
243- return True , msg
249+ # Create the directory if it doesn't exist
250+ try :
251+ os_makedirs (templates_dir , exist_ok = True )
252+ except Exception as e : # pylint: disable=broad-exception-caught
253+ msg = _ ("Failed to create templates directory '{}': {}" ).format (templates_dir , str (e ))
254+ logging_error (msg )
255+ return True , msg
256+
257+ # Now create the file path and write to it
258+ # Use a consistent forward-slash path representation for return value so
259+ # tests comparing literal strings work across platforms.
260+ filepath = os_path .join (templates_dir , templates_filename )
261+ existing_templates = self ._load_user_templates ()
244262
245- # Now create the file path and write to it
246- # Use a consistent forward-slash path representation for return value so
247- # tests comparing literal strings work across platforms.
248- filepath = os_path .join (templates_dir , templates_filename )
249263 normalized_filepath = filepath .replace ("\\ " , "/" )
264+ templates = {** existing_templates , ** templates_to_save } if existing_templates else templates_to_save
250265
251266 try :
252267 with open (filepath , "w" , encoding = "utf-8" , newline = "\n " ) as file : # use Linux line endings even on Windows
253- json_dump (templates_to_save , file , indent = 4 )
268+ json_dump (templates , file , indent = 4 )
254269 return False , normalized_filepath # Success, return the filepath
255270 except FileNotFoundError :
256271 msg = _ ("File not found when writing to '{}': {}" ).format (normalized_filepath , _ ("Path not found" ))
0 commit comments