feat: add RomM database path mismatch resolver script
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import os
|
||||
import csv
|
||||
import re
|
||||
import sys
|
||||
from config.config_manager import ConfigManager
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
def normalize(name):
|
||||
# Strip extension
|
||||
base, _ = os.path.splitext(name)
|
||||
# Strip everything inside parentheses/brackets
|
||||
base = re.sub(r"\([^)]*\)", "", base)
|
||||
base = re.sub(r"\[[^\]]*\]", "", base)
|
||||
# Remove non-alphanumeric characters and lowercase
|
||||
return re.sub(r"[^a-zA-Z0-9]", "", base).lower()
|
||||
|
||||
def main():
|
||||
dry_run = "--apply" not in sys.argv
|
||||
if dry_run:
|
||||
print("=== DRY RUN MODE: No database updates will be applied ===")
|
||||
print("To apply changes, run the script with --apply flag.")
|
||||
else:
|
||||
print("=== EXECUTION MODE: Database updates WILL be applied ===")
|
||||
|
||||
# 1. Parse CSV files and build mapping
|
||||
csv_dir = "/tmp/reports"
|
||||
mapping = {} # key: (platform, old_filename), value: new_filename
|
||||
|
||||
if os.path.exists(csv_dir):
|
||||
try:
|
||||
csv_files = [f for f in os.listdir(csv_dir) if f.startswith("_dry_") and f.endswith(".csv")]
|
||||
for csv_file in csv_files:
|
||||
platform = csv_file.replace("_dry_", "").replace(".csv", "")
|
||||
csv_path = os.path.join(csv_dir, csv_file)
|
||||
print(f"Parsing CSV {csv_file} (platform: {platform})...")
|
||||
try:
|
||||
with open(csv_path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
status = row.get("Status")
|
||||
if status != "FOUND":
|
||||
continue
|
||||
game_name = row.get("Game Name")
|
||||
rom_files_str = row.get("ROM Files")
|
||||
if not game_name or not rom_files_str:
|
||||
continue
|
||||
rom_files = [r.strip() for r in rom_files_str.split(",") if r.strip()]
|
||||
for rom_file in rom_files:
|
||||
old_filename = os.path.basename(rom_file)
|
||||
_, ext = os.path.splitext(old_filename)
|
||||
new_filename = game_name.replace("/", "-").replace("\\", "-") + ext
|
||||
mapping[(platform, old_filename)] = new_filename
|
||||
except Exception as e:
|
||||
print(f"Error parsing CSV {csv_file}: {e}")
|
||||
except Exception as e:
|
||||
print(f"Error listing CSV files: {e}")
|
||||
else:
|
||||
print("CSV directory /tmp/reports does not exist in container.")
|
||||
|
||||
print(f"Loaded {len(mapping)} mappings from CSVs.")
|
||||
|
||||
# 2. Connect to database
|
||||
engine = create_engine(ConfigManager.get_db_engine())
|
||||
|
||||
with engine.begin() as conn:
|
||||
result = conn.execute(text("""
|
||||
SELECT rf.id, rf.rom_id, rf.file_name, rf.file_path, r.fs_name, r.fs_path, rf.missing_from_fs, r.missing_from_fs
|
||||
FROM rom_files rf
|
||||
JOIN roms r ON rf.rom_id = r.id
|
||||
"""))
|
||||
|
||||
mismatches_resolved = 0
|
||||
mismatches_unresolved = 0
|
||||
missing_marked = 0
|
||||
found_cleared = 0
|
||||
|
||||
for row in result:
|
||||
rf_id, rom_id, file_name, file_path, fs_name, fs_path, rf_missing, r_missing = row
|
||||
|
||||
# Check if file exists on disk
|
||||
full_path = os.path.join("/romm/library", file_path, file_name)
|
||||
if os.path.exists(full_path):
|
||||
# File exists on disk at the stored name
|
||||
if rf_missing or r_missing:
|
||||
# Clear missing flag
|
||||
found_cleared += 1
|
||||
print(f"Found existing file on disk: Clear missing flag for rf_id={rf_id} rom_id={rom_id} ('{file_name}')")
|
||||
if not dry_run:
|
||||
conn.execute(text("UPDATE rom_files SET missing_from_fs = 0 WHERE id = :id"), {"id": rf_id})
|
||||
conn.execute(text("UPDATE roms SET missing_from_fs = 0 WHERE id = :id"), {"id": rom_id})
|
||||
continue
|
||||
|
||||
# File does NOT exist at stored path. Try to resolve.
|
||||
platform = file_path.split("/")[-1] if "/" in file_path else file_path
|
||||
new_name = None
|
||||
|
||||
# Match 1: CSV mapping
|
||||
csv_mapped_name = mapping.get((platform, file_name))
|
||||
if csv_mapped_name:
|
||||
csv_mapped_path = os.path.join("/romm/library", file_path, csv_mapped_name)
|
||||
if os.path.exists(csv_mapped_path):
|
||||
new_name = csv_mapped_name
|
||||
else:
|
||||
# Match 1b: CSV mapping with extension-insensitive search
|
||||
new_base, _ = os.path.splitext(csv_mapped_name)
|
||||
dir_path = os.path.join("/romm/library", file_path)
|
||||
if os.path.exists(dir_path):
|
||||
try:
|
||||
files_in_dir = os.listdir(dir_path)
|
||||
for f in files_in_dir:
|
||||
f_base, _ = os.path.splitext(f)
|
||||
if f_base.lower() == new_base.lower() and not os.path.isdir(os.path.join(dir_path, f)):
|
||||
new_name = f
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Match 2: Directory scan normalization
|
||||
if not new_name:
|
||||
dir_path = os.path.join("/romm/library", file_path)
|
||||
if os.path.exists(dir_path):
|
||||
try:
|
||||
files_in_dir = os.listdir(dir_path)
|
||||
norm_old = normalize(file_name)
|
||||
|
||||
# Match 2a: Exact normalized match
|
||||
for f in files_in_dir:
|
||||
if os.path.isdir(os.path.join(dir_path, f)):
|
||||
continue
|
||||
if normalize(f) == norm_old:
|
||||
new_name = f
|
||||
break
|
||||
|
||||
# Match 2b: Substring normalized match
|
||||
if not new_name:
|
||||
for f in files_in_dir:
|
||||
if os.path.isdir(os.path.join(dir_path, f)):
|
||||
continue
|
||||
norm_f = normalize(f)
|
||||
if (norm_old and norm_f) and (norm_old in norm_f or norm_f in norm_old):
|
||||
new_name = f
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if new_name:
|
||||
mismatches_resolved += 1
|
||||
print(f"RESOLVED: rf_id={rf_id} rom_id={rom_id} path='{file_path}': '{file_name}' -> '{new_name}'")
|
||||
if not dry_run:
|
||||
conn.execute(text("""
|
||||
UPDATE rom_files
|
||||
SET file_name = :new_name, missing_from_fs = 0, updated_at = NOW()
|
||||
WHERE id = :id
|
||||
"""), {"new_name": new_name, "id": rf_id})
|
||||
conn.execute(text("""
|
||||
UPDATE roms
|
||||
SET fs_name = :new_name, missing_from_fs = 0, updated_at = NOW()
|
||||
WHERE id = :id
|
||||
"""), {"new_name": new_name, "id": rom_id})
|
||||
else:
|
||||
mismatches_unresolved += 1
|
||||
if not rf_missing or not r_missing:
|
||||
missing_marked += 1
|
||||
print(f"UNRESOLVED: Mark as missing: rf_id={rf_id} rom_id={rom_id} ('{file_name}')")
|
||||
if not dry_run:
|
||||
conn.execute(text("UPDATE rom_files SET missing_from_fs = 1 WHERE id = :id"), {"id": rf_id})
|
||||
conn.execute(text("UPDATE roms SET missing_from_fs = 1 WHERE id = :id"), {"id": rom_id})
|
||||
|
||||
print("\n=== SUMMARY ===")
|
||||
print(f"Total resolved mismatches (updated names): {mismatches_resolved}")
|
||||
print(f"Total unresolved mismatches: {mismatches_unresolved}")
|
||||
print(f"Marked as missing (unresolved): {missing_marked}")
|
||||
print(f"Cleared missing flag (found on disk): {found_cleared}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user