Browse Source

Fix plate calibration backup/restore with printer ID remapping

- Add "Plate Detection" option to backup modal
- Store printer_id_to_serial mapping in backup for calibration files
- Remap printer IDs during restore based on serial numbers so files
  like printer_3_ref_0.jpg get correctly renamed if printer 3 becomes
  printer 2 on the new system
- Add parents=True to mkdir calls in config.py to fix startup crash
- Labels/metadata are now correctly restored via the metadata.json files
maziggy 4 months ago
parent
commit
945121b799
3 changed files with 65 additions and 8 deletions
  1. 64 7
      backend/app/api/routes/settings.py
  2. 0 0
      static/assets/index-C8T392Mv.js
  3. 1 1
      static/index.html

+ 64 - 7
backend/app/api/routes/settings.py

@@ -435,11 +435,24 @@ async def export_backup(
     if include_printers and include_plate_calibration:
     if include_printers and include_plate_calibration:
         plate_cal_dir = app_settings.plate_calibration_dir
         plate_cal_dir = app_settings.plate_calibration_dir
         if plate_cal_dir.exists():
         if plate_cal_dir.exists():
-            backup["plate_calibration"] = []
+            backup["plate_calibration"] = {
+                "files": [],
+                "printer_id_to_serial": {},  # Map old printer IDs to serial numbers for restore
+            }
             for cal_file in plate_cal_dir.iterdir():
             for cal_file in plate_cal_dir.iterdir():
                 if cal_file.is_file():
                 if cal_file.is_file():
-                    backup["plate_calibration"].append(cal_file.name)
-            if backup["plate_calibration"]:
+                    backup["plate_calibration"]["files"].append(cal_file.name)
+                    # Extract printer ID from filename (e.g., "printer_1_ref_0.jpg" -> 1)
+                    if cal_file.name.startswith("printer_"):
+                        parts = cal_file.name.split("_")
+                        if len(parts) >= 2 and parts[1].isdigit():
+                            old_printer_id = int(parts[1])
+                            if old_printer_id not in backup["plate_calibration"]["printer_id_to_serial"]:
+                                # Look up serial number for this printer ID
+                                backup["plate_calibration"]["printer_id_to_serial"][old_printer_id] = (
+                                    printer_id_to_serial.get(old_printer_id)
+                                )
+            if backup["plate_calibration"]["files"]:
                 backup["included"].append("plate_calibration")
                 backup["included"].append("plate_calibration")
 
 
     # Filaments
     # Filaments
@@ -883,6 +896,8 @@ async def import_backup(
         content = await file.read()
         content = await file.read()
         base_dir = app_settings.base_dir
         base_dir = app_settings.base_dir
         files_restored = 0
         files_restored = 0
+        # Store plate calibration files for later (need printer ID remapping after printers restored)
+        plate_cal_files: dict[str, bytes] = {}
 
 
         # Check if it's a ZIP file
         # Check if it's a ZIP file
         if file.filename and file.filename.endswith(".zip"):
         if file.filename and file.filename.endswith(".zip"):
@@ -903,12 +918,12 @@ async def import_backup(
                         # Ensure path is safe (no path traversal)
                         # Ensure path is safe (no path traversal)
                         if ".." in zip_path or zip_path.startswith("/"):
                         if ".." in zip_path or zip_path.startswith("/"):
                             continue
                             continue
-                        # Plate calibration files go to plate_calibration_dir (may differ from base_dir)
+                        # Plate calibration files - store for later processing after printers are restored
                         if zip_path.startswith("plate_calibration/"):
                         if zip_path.startswith("plate_calibration/"):
                             filename = zip_path.replace("plate_calibration/", "", 1)
                             filename = zip_path.replace("plate_calibration/", "", 1)
-                            target_path = app_settings.plate_calibration_dir / filename
-                        else:
-                            target_path = base_dir / zip_path
+                            plate_cal_files[filename] = zf.read(zip_path)
+                            continue
+                        target_path = base_dir / zip_path
                         target_path.parent.mkdir(parents=True, exist_ok=True)
                         target_path.parent.mkdir(parents=True, exist_ok=True)
                         with zf.open(zip_path) as src, open(target_path, "wb") as dst:
                         with zf.open(zip_path) as src, open(target_path, "wb") as dst:
                             dst.write(src.read())
                             dst.write(src.read())
@@ -1054,6 +1069,48 @@ async def import_backup(
         # Flush printers so other sections can look them up
         # Flush printers so other sections can look them up
         await db.flush()
         await db.flush()
 
 
+    # Restore plate calibration files (remap printer IDs based on serial numbers)
+    if plate_cal_files:
+        # Build serial_number -> new_printer_id mapping
+        serial_to_new_id: dict[str, int] = {}
+        pr_result = await db.execute(select(Printer))
+        for pr in pr_result.scalars().all():
+            serial_to_new_id[pr.serial_number] = pr.id
+
+        # Get old_id -> serial mapping from backup (supports both old list format and new dict format)
+        plate_cal_data = backup.get("plate_calibration", {})
+        if isinstance(plate_cal_data, dict):
+            old_id_to_serial: dict[int, str | None] = {
+                int(k): v for k, v in plate_cal_data.get("printer_id_to_serial", {}).items()
+            }
+        else:
+            old_id_to_serial = {}
+
+        # Build old_id -> new_id mapping
+        old_id_to_new_id: dict[int, int] = {}
+        for old_id, serial in old_id_to_serial.items():
+            if serial and serial in serial_to_new_id:
+                old_id_to_new_id[old_id] = serial_to_new_id[serial]
+
+        app_settings.plate_calibration_dir.mkdir(parents=True, exist_ok=True)
+
+        for filename, file_data in plate_cal_files.items():
+            # Parse old printer ID from filename (e.g., "printer_3_ref_0.jpg" -> 3)
+            new_filename = filename
+            if filename.startswith("printer_"):
+                parts = filename.split("_")
+                if len(parts) >= 2 and parts[1].isdigit():
+                    old_printer_id = int(parts[1])
+                    if old_printer_id in old_id_to_new_id:
+                        new_printer_id = old_id_to_new_id[old_printer_id]
+                        # Replace old ID with new ID in filename
+                        new_filename = filename.replace(f"printer_{old_printer_id}_", f"printer_{new_printer_id}_", 1)
+
+            target_path = app_settings.plate_calibration_dir / new_filename
+            with open(target_path, "wb") as f:
+                f.write(file_data)
+            files_restored += 1
+
     # Restore notification providers (skip or overwrite duplicates by name)
     # Restore notification providers (skip or overwrite duplicates by name)
     # Build printer serial to ID lookup (printers were restored first)
     # Build printer serial to ID lookup (printers were restored first)
     if "notification_providers" in backup:
     if "notification_providers" in backup:

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-C8T392Mv.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-jEq4K9YC.js"></script>
+    <script type="module" crossorigin src="/assets/index-C8T392Mv.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-BQIOMqJ9.css">
     <link rel="stylesheet" crossorigin href="/assets/index-BQIOMqJ9.css">
   </head>
   </head>
   <body>
   <body>

Some files were not shown because too many files changed in this diff