feat: add orphan mod cleanup tool with GUI integration and live-server tests

- Add arma_modlist_tools/cleaner.py: find_orphan_folders() detects @ModName
  folders no longer referenced in comparison.json; uses _normalize_name from
  fetcher for consistent three-level matching
- Add clean_orphans.py: CLI with --dry-run and --yes/-y flags; junction-safe
  deletion via _is_junction() guard before shutil.rmtree
- Add Clean Orphans tab to gui/views/tools.py: scrollable checkbox list,
  background scan/delete threads, pending-done-msg pattern for post-scan
  status, EN/VI localization strings in gui/locales.py
- Add 23 unit tests (section 12), 6 E2E subprocess tests (section 13),
  23 coverage-gap tests (section 14), 9 live-server fetcher tests (section 15)
- Fix leaked builtins.open mock in _test_read_os_release_parses_file
- Overall coverage: 84% → 93%; fetcher.py: 36% → 72%
This commit is contained in:
Tran G. (Revernomad) Khoa
2026-04-08 20:02:42 +07:00
parent 85bc406236
commit 90cc6c00ff
6 changed files with 1252 additions and 8 deletions

View File

@@ -224,6 +224,36 @@ _EN: dict[str, str] = {
"tools.rm_btn": "Generate Report",
"tools.rm_last": "Last generated: {ts}",
"tools.rm_none": "No report yet.",
# ── Tools — Clean Orphans ────────────────────────────────────────────────
"tools.oc_desc": (
"Scan the downloads folder for mod folders that are no longer "
"referenced in comparison.json. These orphans accumulate when you "
"remove mods from your presets and re-run the pipeline. "
"Select the ones you want to remove to free up disk space."
),
"tools.oc_warn": (
"⚠ Deleting orphans permanently removes mod files from disk. "
"This cannot be undone."
),
"tools.oc_scan_btn": "Scan for Orphans",
"tools.oc_scanning": "Scanning…",
"tools.oc_no_config": "No config found. Complete Setup first.",
"tools.oc_no_comparison": "No comparison.json found — run the pipeline first.",
"tools.oc_none_found": "No orphans found. Your downloads folder is clean.",
"tools.oc_found": "{count} orphan(s) found — {size} total",
"tools.oc_sel_all": "Select All",
"tools.oc_sel_none": "Deselect All",
"tools.oc_delete_btn": "Delete Selected",
"tools.oc_confirm_title": "Confirm Delete",
"tools.oc_confirm_body": (
"Permanently delete {count} orphan folder(s) ({size})?\n\n"
"This cannot be undone."
),
"tools.oc_done": "Deleted {count} folder(s), freed {size}.",
"tools.oc_error": "Error deleting {path}: {e}",
"tools.oc_error_title": "Delete errors",
"tools.oc_scan_error": "Scan error: {e}",
}
_VI: dict[str, str] = {
@@ -431,6 +461,36 @@ _VI: dict[str, str] = {
"tools.rm_btn": "Tạo báo cáo",
"tools.rm_last": "Tạo lần cuối: {ts}",
"tools.rm_none": "Chưa có báo cáo.",
# ── Tools — Clean Orphans ────────────────────────────────────────────────
"tools.oc_desc": (
"Quét thư mục downloads để tìm các thư mục mod không còn được "
"tham chiếu trong comparison.json. Các mod mồ côi này tích tụ khi "
"bạn xóa mod khỏi preset và chạy lại pipeline. "
"Chọn các thư mục muốn xóa để giải phóng dung lượng."
),
"tools.oc_warn": (
"⚠ Xóa mod mồ côi sẽ xóa vĩnh viễn tệp mod khỏi ổ đĩa. "
"Thao tác này không thể hoàn tác."
),
"tools.oc_scan_btn": "Quét mod mồ côi",
"tools.oc_scanning": "Đang quét…",
"tools.oc_no_config": "Chưa tìm thấy cấu hình. Vui lòng hoàn thành thiết lập.",
"tools.oc_no_comparison": "Chưa có comparison.json — hãy chạy pipeline trước.",
"tools.oc_none_found": "Không tìm thấy mod mồ côi. Thư mục downloads sạch.",
"tools.oc_found": "Tìm thấy {count} mod mồ côi — tổng {size}",
"tools.oc_sel_all": "Chọn tất cả",
"tools.oc_sel_none": "Bỏ chọn",
"tools.oc_delete_btn": "Xóa đã chọn",
"tools.oc_confirm_title": "Xác nhận xóa",
"tools.oc_confirm_body": (
"Xóa vĩnh viễn {count} thư mục mồ côi ({size})?\n\n"
"Thao tác này không thể hoàn tác."
),
"tools.oc_done": "Đã xóa {count} thư mục, giải phóng {size}.",
"tools.oc_error": "Lỗi khi xóa {path}: {e}",
"tools.oc_error_title": "Lỗi xóa",
"tools.oc_scan_error": "Lỗi quét: {e}",
}
# Guard: both dicts must have identical key sets