99"""
1010
1111import os
12+ import shutil
1213import subprocess
1314import sys
1415import tempfile
1819from logging import error as logging_error
1920from logging import info as logging_info
2021from logging import shutdown as logging_shutdown
22+ from pathlib import Path
2123from typing import Any , Callable , Optional
2224from urllib .parse import urljoin
2325from webbrowser import open as webbrowser_open
2426
27+ from platformdirs import user_config_dir
2528from requests import HTTPError as requests_HTTPError
2629from requests import RequestException as requests_RequestException
2730from requests import Timeout as requests_Timeout
2831from requests import get as requests_get
2932from requests .exceptions import RequestException
3033
31- from ardupilot_methodic_configurator import _
34+ from ardupilot_methodic_configurator import _ , __version__
3235
3336# Constants
3437GITHUB_API_URL_RELEASES = "https://api.github.com/repos/ArduPilot/MethodicConfigurator/releases/"
@@ -146,6 +149,58 @@ def get_release_info(name: str, should_be_pre_release: bool, timeout: int = 30)
146149 raise
147150
148151
152+ def create_backup (progress_callback : Optional [Callable [[float , str ], None ]] = None ) -> bool :
153+ """
154+ Backup AMC installation and Vehicles folder.
155+
156+ Returns:
157+ True on success, False on any error.
158+
159+ """
160+ try :
161+ version = __version__
162+ config_dir = Path (user_config_dir (".ardupilot_methodic_configurator" , appauthor = False , roaming = True ))
163+ backups_dir = config_dir / "backups" / version
164+ backups_dir .mkdir (parents = True , exist_ok = True )
165+
166+ # Backup Vehicles folder
167+ vehicles_dir = config_dir / "vehicles"
168+ if vehicles_dir .exists ():
169+ shutil .copytree (vehicles_dir , backups_dir / "vehicles" , dirs_exist_ok = True )
170+ logging_info (_ ("Vehicles folder backed up to %s" ), backups_dir / "Vehicles" )
171+ else :
172+ logging_info (_ ("No Vehicles folder found to backup." ))
173+
174+ # Backup AMC wheel
175+ try :
176+ subprocess .run ( # noqa: S603
177+ [
178+ sys .executable ,
179+ "-m" ,
180+ "pip" ,
181+ "download" ,
182+ f"ardupilot_methodic_configurator=={ version } " ,
183+ "-d" ,
184+ str (backups_dir ),
185+ ],
186+ check = True ,
187+ )
188+ logging_info (_ ("AMC wheel backup complete at %s" ), backups_dir )
189+
190+ except subprocess .CalledProcessError as e :
191+ logging_error (_ ("Failed to backup AMC wheel: %s" ), e )
192+ return False
193+
194+ if progress_callback :
195+ progress_callback (100.0 , _ ("Backup complete" ))
196+
197+ return True
198+
199+ except (PermissionError , OSError ) as e :
200+ logging_error (_ ("Backup failed: %s" ), e )
201+ return False
202+
203+
149204def download_and_install_on_windows (
150205 download_url : str ,
151206 file_name : str ,
@@ -172,6 +227,9 @@ def download_and_install_on_windows(
172227
173228 """
174229 logging_info (_ ("Downloading and installing new version for Windows..." ))
230+
231+ create_backup (progress_callback )
232+
175233 try :
176234 with tempfile .TemporaryDirectory () as temp_dir :
177235 temp_path = os .path .join (temp_dir , file_name )
@@ -237,6 +295,18 @@ def download_and_install_pip_release(progress_callback: Optional[Callable[[float
237295 logging_info (_ ("Updating via pip for Linux and macOS..." ))
238296
239297 if progress_callback :
298+ progress_callback (0.0 , _ ("Backing up current version..." ))
299+
300+ backup_ok = create_backup (progress_callback )
301+
302+ if not backup_ok :
303+ logging_error (_ ("Backup failed. Aborting update." ))
304+ if progress_callback :
305+ progress_callback (0.0 , _ ("Backup failed. Update aborted." ))
306+ return False
307+
308+ if progress_callback :
309+ progress_callback (100.0 , _ ("Backup complete" ))
240310 progress_callback (0.0 , _ ("Starting installation..." ))
241311
242312 ret = subprocess .check_call ( # noqa: S603
0 commit comments