mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-04-19 07:12:36 -05:00
refactor: clear, consistent output for verify and generate_pack
Both tools now count by unique destination (what the user sees on disk), not by YAML entry or internal check. Same file shared by multiple systems = counted once. Same file checked for multiple inner ROMs = counted once with worst-case status. Output format: verify: "Platform: X/Y files OK, N wrong hash, M missing [mode]" pack: "pack.zip: P files packed, X/Y files OK, N wrong hash [mode]" X/Y is the same number in both tools for the same platform. "files packed" differs from "files OK" when data_directories or EmuDeck MD5-only entries are involved — this is expected and clear from the numbers (e.g. 34 packed but 161 verified for EmuDeck).
This commit is contained in:
+40
-41
@@ -288,24 +288,25 @@ def generate_pack(
|
|||||||
os.makedirs(output_dir, exist_ok=True)
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
total_files = 0
|
total_files = 0
|
||||||
total_checks = 0
|
|
||||||
verified_checks = 0
|
|
||||||
missing_files = []
|
missing_files = []
|
||||||
untested_files = []
|
|
||||||
user_provided = []
|
user_provided = []
|
||||||
seen_destinations = set()
|
seen_destinations = set()
|
||||||
|
# Per-file status: worst status wins (missing > wrong_hash > ok)
|
||||||
|
file_status: dict[str, str] = {}
|
||||||
|
file_reasons: dict[str, str] = {}
|
||||||
|
|
||||||
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
|
||||||
for sys_id, system in sorted(config.get("systems", {}).items()):
|
for sys_id, system in sorted(config.get("systems", {}).items()):
|
||||||
for file_entry in system.get("files", []):
|
for file_entry in system.get("files", []):
|
||||||
total_checks += 1
|
|
||||||
dest = _sanitize_path(file_entry.get("destination", file_entry["name"]))
|
dest = _sanitize_path(file_entry.get("destination", file_entry["name"]))
|
||||||
if not dest:
|
if not dest:
|
||||||
# EmuDeck-style entries (system:md5 whitelist, no filename).
|
# EmuDeck-style entries (system:md5 whitelist, no filename).
|
||||||
# Count as verified if file exists in DB by MD5.
|
fkey = f"{sys_id}/{file_entry.get('name', '')}"
|
||||||
md5 = file_entry.get("md5", "")
|
md5 = file_entry.get("md5", "")
|
||||||
if md5 and md5 in db.get("indexes", {}).get("by_md5", {}):
|
if md5 and md5 in db.get("indexes", {}).get("by_md5", {}):
|
||||||
verified_checks += 1
|
file_status.setdefault(fkey, "ok")
|
||||||
|
else:
|
||||||
|
file_status[fkey] = "missing"
|
||||||
continue
|
continue
|
||||||
if base_dest:
|
if base_dest:
|
||||||
full_dest = f"{base_dest}/{dest}"
|
full_dest = f"{base_dest}/{dest}"
|
||||||
@@ -321,7 +322,7 @@ def generate_pack(
|
|||||||
if already_packed:
|
if already_packed:
|
||||||
continue
|
continue
|
||||||
seen_destinations.add(dedup_key)
|
seen_destinations.add(dedup_key)
|
||||||
verified_checks += 1
|
file_status.setdefault(dedup_key, "ok")
|
||||||
instructions = file_entry.get("instructions", "Please provide this file manually.")
|
instructions = file_entry.get("instructions", "Please provide this file manually.")
|
||||||
instr_name = f"INSTRUCTIONS_{file_entry['name']}.txt"
|
instr_name = f"INSTRUCTIONS_{file_entry['name']}.txt"
|
||||||
instr_path = f"{base_dest}/{instr_name}" if base_dest else instr_name
|
instr_path = f"{base_dest}/{instr_name}" if base_dest else instr_name
|
||||||
@@ -355,24 +356,26 @@ def generate_pack(
|
|||||||
if status == "not_found":
|
if status == "not_found":
|
||||||
if not already_packed:
|
if not already_packed:
|
||||||
missing_files.append(file_entry["name"])
|
missing_files.append(file_entry["name"])
|
||||||
|
file_status[dedup_key] = "missing"
|
||||||
continue
|
continue
|
||||||
|
|
||||||
check_passed = True
|
if status == "hash_mismatch" and verification_mode != "existence":
|
||||||
if status == "hash_mismatch":
|
zf_name = file_entry.get("zipped_file")
|
||||||
if verification_mode != "existence":
|
if zf_name and local_path:
|
||||||
zf_name = file_entry.get("zipped_file")
|
from verify import check_inside_zip
|
||||||
if zf_name and local_path:
|
inner_md5 = file_entry.get("md5", "")
|
||||||
from verify import check_inside_zip
|
inner_result = check_inside_zip(local_path, zf_name, inner_md5)
|
||||||
inner_md5 = file_entry.get("md5", "")
|
if inner_result != "ok":
|
||||||
result = check_inside_zip(local_path, zf_name, inner_md5)
|
file_status[dedup_key] = "wrong_hash"
|
||||||
if result != "ok":
|
reason = f"{zf_name} hash mismatch inside ZIP"
|
||||||
untested_files.append(file_entry["name"])
|
file_reasons[dedup_key] = reason
|
||||||
check_passed = False
|
|
||||||
else:
|
else:
|
||||||
untested_files.append(file_entry["name"])
|
file_status.setdefault(dedup_key, "ok")
|
||||||
check_passed = False
|
else:
|
||||||
if check_passed:
|
file_status[dedup_key] = "wrong_hash"
|
||||||
verified_checks += 1
|
file_reasons[dedup_key] = "container hash mismatch"
|
||||||
|
else:
|
||||||
|
file_status.setdefault(dedup_key, "ok")
|
||||||
|
|
||||||
if already_packed:
|
if already_packed:
|
||||||
continue
|
continue
|
||||||
@@ -436,27 +439,23 @@ def generate_pack(
|
|||||||
zf.write(src, full)
|
zf.write(src, full)
|
||||||
total_files += 1
|
total_files += 1
|
||||||
|
|
||||||
if missing_files:
|
files_ok = sum(1 for s in file_status.values() if s == "ok")
|
||||||
print(f" Missing ({len(missing_files)}): {', '.join(missing_files[:10])}")
|
files_wrong = sum(1 for s in file_status.values() if s == "wrong_hash")
|
||||||
if len(missing_files) > 10:
|
files_miss = sum(1 for s in file_status.values() if s == "missing")
|
||||||
print(f" ... and {len(missing_files) - 10} more")
|
total_checked = len(file_status)
|
||||||
|
|
||||||
if untested_files:
|
parts = [f"{files_ok}/{total_checked} files OK"]
|
||||||
print(f" Untested ({len(untested_files)}): {', '.join(untested_files[:10])}")
|
if files_wrong:
|
||||||
if len(untested_files) > 10:
|
parts.append(f"{files_wrong} wrong hash")
|
||||||
print(f" ... and {len(untested_files) - 10} more")
|
if files_miss:
|
||||||
|
parts.append(f"{files_miss} missing")
|
||||||
|
extras_msg = f", {extra_count} extras" if extra_count else ""
|
||||||
|
print(f" {zip_path}: {total_files} files packed{extras_msg}, {', '.join(parts)} [{verification_mode}]")
|
||||||
|
|
||||||
if user_provided:
|
for key, reason in file_reasons.items():
|
||||||
print(f" User-provided ({len(user_provided)}): {', '.join(user_provided)}")
|
print(f" WRONG HASH: {key} — {reason}")
|
||||||
|
for name in missing_files:
|
||||||
extras_msg = f" + {extra_count} emulator extras" if extra_count else ""
|
print(f" MISSING: {name}")
|
||||||
if verification_mode == "existence":
|
|
||||||
print(f" Generated {zip_path}: {total_files} files ({total_files - extra_count} platform{extras_msg}, {len(missing_files)} missing) [verification: existence]")
|
|
||||||
else:
|
|
||||||
checks_detail = ""
|
|
||||||
if total_checks != total_files:
|
|
||||||
checks_detail = f" ({total_checks - total_files} duplicate/inner checks)"
|
|
||||||
print(f" Generated {zip_path}: {total_files} files, {verified_checks}/{total_checks} checks verified{checks_detail}, {len(untested_files)} untested, {len(missing_files)} missing [verification: {verification_mode}]")
|
|
||||||
return zip_path
|
return zip_path
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+40
-31
@@ -203,6 +203,7 @@ def verify_platform(config: dict, db: dict) -> dict:
|
|||||||
zip_contents = _build_zip_contents_index(db) if has_zipped else {}
|
zip_contents = _build_zip_contents_index(db) if has_zipped else {}
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
|
file_status: dict[str, str] = {}
|
||||||
for sys_id, system in config.get("systems", {}).items():
|
for sys_id, system in config.get("systems", {}).items():
|
||||||
for file_entry in system.get("files", []):
|
for file_entry in system.get("files", []):
|
||||||
local_path, resolve_status = resolve_to_local_path(
|
local_path, resolve_status = resolve_to_local_path(
|
||||||
@@ -215,24 +216,30 @@ def verify_platform(config: dict, db: dict) -> dict:
|
|||||||
result["system"] = sys_id
|
result["system"] = sys_id
|
||||||
results.append(result)
|
results.append(result)
|
||||||
|
|
||||||
ok = sum(1 for r in results if r["status"] == Status.OK)
|
# Aggregate by destination (what the user sees on disk)
|
||||||
untested = sum(1 for r in results if r["status"] == Status.UNTESTED)
|
dest = file_entry.get("destination", file_entry.get("name", ""))
|
||||||
missing = sum(1 for r in results if r["status"] == Status.MISSING)
|
if not dest:
|
||||||
|
dest = f"{sys_id}/{file_entry.get('name', '')}"
|
||||||
|
cur = result["status"]
|
||||||
|
prev = file_status.get(dest)
|
||||||
|
if prev is None:
|
||||||
|
file_status[dest] = cur
|
||||||
|
elif cur == Status.MISSING:
|
||||||
|
file_status[dest] = Status.MISSING
|
||||||
|
elif cur == Status.UNTESTED and prev != Status.MISSING:
|
||||||
|
file_status[dest] = Status.UNTESTED
|
||||||
|
|
||||||
# Count unique files (by system/destination) for reporting
|
files_ok = sum(1 for s in file_status.values() if s == Status.OK)
|
||||||
unique_files = set()
|
files_mismatch = sum(1 for s in file_status.values() if s == Status.UNTESTED)
|
||||||
for r in results:
|
files_missing = sum(1 for s in file_status.values() if s == Status.MISSING)
|
||||||
dest = r.get("path") or r["name"]
|
|
||||||
unique_files.add(dest)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"platform": platform,
|
"platform": platform,
|
||||||
"verification_mode": mode,
|
"verification_mode": mode,
|
||||||
"total": len(results),
|
"total_files": len(file_status),
|
||||||
"unique_files": len(unique_files),
|
"files_ok": files_ok,
|
||||||
"ok": ok,
|
"files_mismatch": files_mismatch,
|
||||||
"untested": untested,
|
"files_missing": files_missing,
|
||||||
"missing": missing,
|
|
||||||
"details": results,
|
"details": results,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,26 +273,28 @@ def main():
|
|||||||
|
|
||||||
if not args.json:
|
if not args.json:
|
||||||
mode = result["verification_mode"]
|
mode = result["verification_mode"]
|
||||||
uf = result["unique_files"]
|
total = result["total_files"]
|
||||||
total = result["total"]
|
ok = result["files_ok"]
|
||||||
checks_detail = f" ({total - uf} duplicate/inner checks)" if total != uf else ""
|
mismatch = result["files_mismatch"]
|
||||||
if mode == "existence":
|
miss = result["files_missing"]
|
||||||
print(f"{result['platform']}: {uf} files, {result['ok']}/{total} checks present{checks_detail}, "
|
|
||||||
f"{result['missing']} missing [verification: {mode}]")
|
|
||||||
else:
|
|
||||||
print(f"{result['platform']}: {uf} files, {result['ok']}/{total} checks verified{checks_detail}, "
|
|
||||||
f"{result['untested']} untested, {result['missing']} missing [verification: {mode}]")
|
|
||||||
|
|
||||||
for d in result["details"]:
|
parts = [f"{ok}/{total} files OK"]
|
||||||
if d["status"] == Status.UNTESTED:
|
if mismatch:
|
||||||
reason = d.get("reason", "")
|
parts.append(f"{mismatch} wrong hash")
|
||||||
if not reason and "expected_md5" in d:
|
if miss:
|
||||||
reason = f"expected={d['expected_md5'][:16]}... got={d['actual_md5'][:16]}..."
|
parts.append(f"{miss} missing")
|
||||||
print(f" UNTESTED: {d['system']}/{d['name']} - {reason}")
|
print(f"{result['platform']}: {', '.join(parts)} [{mode}]")
|
||||||
|
|
||||||
for d in result["details"]:
|
for d in result["details"]:
|
||||||
if d["status"] == Status.MISSING:
|
if d["status"] == Status.UNTESTED:
|
||||||
print(f" MISSING: {d['system']}/{d['name']}")
|
reason = d.get("reason", "")
|
||||||
|
if not reason and "expected_md5" in d:
|
||||||
|
reason = f"expected {d['expected_md5'][:12]}… got {d['actual_md5'][:12]}…"
|
||||||
|
print(f" WRONG HASH: {d['system']}/{d['name']} — {reason}")
|
||||||
|
|
||||||
|
for d in result["details"]:
|
||||||
|
if d["status"] == Status.MISSING:
|
||||||
|
print(f" MISSING: {d['system']}/{d['name']}")
|
||||||
|
|
||||||
if args.json:
|
if args.json:
|
||||||
for r in all_results.values():
|
for r in all_results.values():
|
||||||
|
|||||||
Reference in New Issue
Block a user