fix(kc_format): eliminate duplicate silo/manifest.json entries in .kc files
Some checks failed
Build and Test / build (pull_request) Has been cancelled

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.
This commit is contained in:
forbes
2026-02-21 09:49:36 -06:00
parent 0f8fa0be86
commit 88efa2a6ae

View File

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