From 88efa2a6ae6eca60f0dcb040256d31d1af4d2b1f Mon Sep 17 00:00:00 2001 From: forbes Date: Sat, 21 Feb 2026 09:49:36 -0600 Subject: [PATCH] fix(kc_format): eliminate duplicate silo/manifest.json entries in .kc files Two code paths were appending silo/manifest.json to the ZIP without removing the previous entry, causing Python's zipfile module to warn about duplicate names: 1. slotFinishSaveDocument() re-injected the cached manifest from entries, then the modified_at update branch wrote a second copy. 2. update_manifest_fields() opened the ZIP in append mode and wrote an updated manifest without removing the old one. Fix slotFinishSaveDocument() by preparing the final manifest (with updated modified_at) in the entries dict before writing, so only one copy is written to the ZIP. Fix update_manifest_fields() by rewriting the ZIP atomically via a temp file, deduplicating any pre-existing duplicate entries in the process. --- src/Mod/Create/kc_format.py | 85 +++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/src/Mod/Create/kc_format.py b/src/Mod/Create/kc_format.py index 21e7bcaa45..f893ab490e 100644 --- a/src/Mod/Create/kc_format.py +++ b/src/Mod/Create/kc_format.py @@ -162,34 +162,28 @@ class _KcFormatObserver: f"kc_format: pre_reinject hook failed: {exc}\n" ) try: + # Ensure silo/manifest.json exists in entries and update modified_at. + # All manifest mutations happen here so only one copy is written. + if "silo/manifest.json" in entries: + try: + manifest = json.loads(entries["silo/manifest.json"]) + except (json.JSONDecodeError, ValueError): + manifest = _default_manifest() + else: + manifest = _default_manifest() + manifest["modified_at"] = datetime.now(timezone.utc).strftime( + "%Y-%m-%dT%H:%M:%SZ" + ) + entries["silo/manifest.json"] = ( + json.dumps(manifest, indent=2) + "\n" + ).encode("utf-8") + with zipfile.ZipFile(filename, "a") as zf: existing = set(zf.namelist()) - # Re-inject cached silo/ entries - if entries: - for name, data in entries.items(): - if name not in existing: - zf.writestr(name, data) - existing.add(name) - # Ensure silo/manifest.json exists - if "silo/manifest.json" not in existing: - manifest = _default_manifest() - zf.writestr( - "silo/manifest.json", - json.dumps(manifest, indent=2) + "\n", - ) - else: - # Update modified_at timestamp - raw = zf.read("silo/manifest.json") - manifest = json.loads(raw) - now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") - if manifest.get("modified_at") != now: - manifest["modified_at"] = now - # ZipFile append mode can't overwrite; write new entry - # (last duplicate wins in most ZIP readers) - zf.writestr( - "silo/manifest.json", - json.dumps(manifest, indent=2) + "\n", - ) + for name, data in entries.items(): + if name not in existing: + zf.writestr(name, data) + existing.add(name) except Exception as e: FreeCAD.Console.PrintWarning( f"kc_format: failed to update .kc silo/ entries: {e}\n" @@ -209,17 +203,36 @@ def update_manifest_fields(filename, updates): return if not os.path.isfile(filename): return + import shutil + import tempfile + try: - with zipfile.ZipFile(filename, "a") as zf: - if "silo/manifest.json" not in zf.namelist(): - return - raw = zf.read("silo/manifest.json") - manifest = json.loads(raw) - manifest.update(updates) - zf.writestr( - "silo/manifest.json", - json.dumps(manifest, indent=2) + "\n", - ) + fd, tmp = tempfile.mkstemp(suffix=".kc", dir=os.path.dirname(filename)) + os.close(fd) + try: + with ( + zipfile.ZipFile(filename, "r") as zin, + zipfile.ZipFile(tmp, "w", compression=zipfile.ZIP_DEFLATED) as zout, + ): + found = False + for item in zin.infolist(): + if item.filename == "silo/manifest.json": + if found: + continue # skip duplicate entries + found = True + raw = zin.read(item.filename) + manifest = json.loads(raw) + manifest.update(updates) + zout.writestr( + item.filename, + json.dumps(manifest, indent=2) + "\n", + ) + else: + zout.writestr(item, zin.read(item.filename)) + shutil.move(tmp, filename) + except BaseException: + os.unlink(tmp) + raise except Exception as e: FreeCAD.Console.PrintWarning(f"kc_format: failed to update manifest: {e}\n") -- 2.49.1