فهرست منبع

Fix external folder scan 500 on 3MF files (#846)

  scan_external_folder stored raw 3MF metadata containing _thumbnail_data
  bytes directly in the JSON column, causing a serialization error. Applied
  the same clean_metadata() pattern used by upload/zip extraction and
  removed the call to the non-existent parser.extract_thumbnail().
maziggy 1 ماه پیش
والد
کامیت
f3b2a24fa0
2فایلهای تغییر یافته به همراه28 افزوده شده و 10 حذف شده
  1. 1 0
      CHANGELOG.md
  2. 27 10
      backend/app/api/routes/library.py

+ 1 - 0
CHANGELOG.md

@@ -13,6 +13,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **Queue Page Visual Refresh** — Compact stats bar replaces the five summary cards (saves vertical space), color-coded left borders on all queue items for instant status scanning, collapsible history section (collapsed by default), and condensed single-line rows for history items showing more prints at a glance.
 
 ### Fixed
+- **External Folder Scan 500 Error on 3MF Files** ([#846](https://github.com/maziggy/bambuddy/issues/846)) — Scanning an external folder containing .3mf files crashed with "Object of type bytes is not JSON serializable". The parsed 3MF metadata contained raw thumbnail bytes (`_thumbnail_data`) that were stored directly in the database JSON column without cleaning. Also removed a call to the non-existent `parser.extract_thumbnail()` method — thumbnail data is already available in the parsed metadata. Now uses the same `clean_metadata()` pattern as upload and zip extraction. Reported by @SMAW.
 - **Archives Capped at 50 Items** ([#843](https://github.com/maziggy/bambuddy/issues/843)) — The archives page only showed the 50 most recent prints due to a hardcoded API limit. Users with more than 50 archives could not see or access older entries. Fixed by fetching all archives and adding client-side pagination with configurable page sizes (25, 50, 100, 200, or All). Page size preference is persisted. Reported by @dcbaldwin.
 - **Filament Usage Not Recorded When Auto-Archive Disabled** — When a printer had "Auto-archive completed prints" turned off, filament consumption was silently lost. The `on_print_complete` callback returned early before reaching the usage tracking code, so neither the internal inventory (AMS remain% deltas) nor Spoolman received usage data. Moved filament tracking to run before the archive check so usage is always recorded regardless of the auto-archive setting.
 - **H2D External Spool Uses Wrong Nozzle** ([#836](https://github.com/maziggy/bambuddy/issues/836)) — Prints sent from Bambuddy to dual-nozzle printers (H2D, H2D Pro) with external spools always routed to the wrong nozzle. The old `ams_mapping2` format used a shared `ams_id: 255` with `slot_id: 0/1` to differentiate external slots, but the firmware interpreted slot_id as the nozzle index (0=main/right, 1=deputy/left), routing filament to the opposite nozzle. Already fixed by the #797 `ams_mapping2` format change (per-tray `ams_id` instead of shared unit), but users on older builds still experience this. Printing the same file directly from the slicer worked correctly. Reported by @NoahTingey.

+ 27 - 10
backend/app/api/routes/library.py

@@ -839,16 +839,33 @@ async def scan_external_folder(
             if file_type == "3mf":
                 try:
                     parser = ThreeMFParser(str(filepath))
-                    meta = parser.parse()
-                    if meta:
-                        file_metadata = meta
-                    thumb_data = parser.extract_thumbnail()
-                    if thumb_data:
-                        thumb_dir = get_library_thumbnails_dir()
-                        thumb_filename = f"{uuid.uuid4().hex}.png"
-                        thumb_full = thumb_dir / thumb_filename
-                        thumb_full.write_bytes(thumb_data)
-                        thumbnail_path = to_relative_path(thumb_full)
+                    raw_metadata = parser.parse()
+                    if raw_metadata:
+                        # Extract thumbnail before cleaning metadata
+                        thumb_data = raw_metadata.get("_thumbnail_data")
+                        thumbnail_ext = raw_metadata.get("_thumbnail_ext", ".png")
+                        if thumb_data:
+                            thumb_dir = get_library_thumbnails_dir()
+                            thumb_filename = f"{uuid.uuid4().hex}{thumbnail_ext}"
+                            thumb_full = thumb_dir / thumb_filename
+                            thumb_full.write_bytes(thumb_data)
+                            thumbnail_path = to_relative_path(thumb_full)
+
+                        # Clean metadata - remove non-JSON-serializable data (bytes, etc.)
+                        def clean_metadata(obj):
+                            if isinstance(obj, dict):
+                                return {
+                                    k: clean_metadata(v)
+                                    for k, v in obj.items()
+                                    if not isinstance(v, bytes) and k not in ("_thumbnail_data", "_thumbnail_ext")
+                                }
+                            elif isinstance(obj, list):
+                                return [clean_metadata(i) for i in obj if not isinstance(i, bytes)]
+                            elif isinstance(obj, bytes):
+                                return None
+                            return obj
+
+                        file_metadata = clean_metadata(raw_metadata)
                 except Exception as e:
                     logger.debug("Failed to extract metadata from external 3mf %s: %s", filepath, e)