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")