#!/usr/bin/env python3 """ CLI entry point: fetch mods that were previously missing but have since been added to the file server, then link them. Flow: 1. Load missing_report.json 2. Re-check server index for newly available mods 3. Download newly available mods to their correct group folder 4. Update missing_report.json (remove the ones now downloaded) 5. Run link_group for each affected group Usage: python sync_missing.py """ import json import sys from tqdm import tqdm from arma_modlist_tools.compat import fix_console_encoding from arma_modlist_tools.config import load_config from arma_modlist_tools.fetcher import ( build_server_index, find_mod_folder, list_mod_files, download_file, make_session, ) from arma_modlist_tools.linker import link_group from arma_modlist_tools.reporter import save_missing_report fix_console_encoding() def _fmt_bytes(n: int) -> str: for unit in ("B", "KB", "MB", "GB"): if n < 1024: return f"{n:.1f} {unit}" n /= 1024 return f"{n:.1f} TB" def main() -> None: cfg = load_config() # ---- Load missing report ---- if not cfg.missing_report.exists(): print(f"\nERROR: {cfg.missing_report} not found.") print("Run report_missing.py or fetch_mods.py first.\n") sys.exit(1) report = json.loads(cfg.missing_report.read_text(encoding="utf-8")) previously_missing = report["missing_mods"] if not previously_missing: print("\nNo mods in missing report — nothing to sync.\n") sys.exit(0) print(f"\nLoading missing report: {len(previously_missing)} mods previously missing") # ---- Re-check server index ---- print("Re-checking server index...") index = build_server_index(cfg.server_url, cfg.server_auth) print(f" {len(index['by_steam_id'])} mods indexed\n") session = make_session(cfg.server_auth) # ---- Resolve which are now available ---- now_available: list[tuple[dict, str]] = [] # (mod, folder_url) still_missing: list[dict] = [] for mod in previously_missing: url = find_mod_folder(mod, index) if url: now_available.append((mod, url)) else: still_missing.append(mod) print(f" Newly available: {len(now_available)} mods") print(f" Still missing : {len(still_missing)} mods\n") if not now_available: print("No new mods available on server yet.\n") sys.exit(0) # ---- Download newly available mods ---- affected_groups: set[str] = set() total_bytes = 0 with tqdm(total=len(now_available), unit="mod", desc="Downloading", position=0, dynamic_ncols=True) as mod_bar: for mod, folder_url in now_available: group = mod["group"] folder_name = folder_url.rstrip("/").split("/")[-1] dest_path = cfg.downloads / group / folder_name tqdm.write(f" [+] {folder_name:<48} -> downloads/{group}/") files = list_mod_files(folder_url, session) mod_bytes = 0 for rel, file_url, size in files: dest_file = dest_path / rel if dest_file.exists(): continue with tqdm( total=size if size else None, unit="B", unit_scale=True, unit_divisor=1024, desc=f" {rel[-45:]:45s}", position=1, leave=False, dynamic_ncols=True, ) as file_bar: n = download_file( file_url, dest_file, session, on_chunk=lambda b: file_bar.update(b), ) mod_bytes += n tqdm.write(f" Done {_fmt_bytes(mod_bytes)}") total_bytes += mod_bytes affected_groups.add(group) mod_bar.update(1) print(f"\n Downloaded: {len(now_available)} mods {_fmt_bytes(total_bytes)}") # ---- Update missing report ---- report["missing_mods"] = still_missing report["missing"] = len(still_missing) report["on_server"] = report["total_mods"] - len(still_missing) save_missing_report(report, cfg.missing_report) print(f" Missing report updated: {len(still_missing)} still missing\n") # ---- Link newly downloaded mods ---- if not cfg.arma_dir.exists(): print(f" NOTE: Arma dir not found ({cfg.arma_dir}) — skipping link step.") print(" Run link_mods.py manually when ready.\n") sys.exit(0) print("Linking newly added mods...") for group in sorted(affected_groups): group_dir = cfg.downloads / group result = link_group(group_dir, cfg.arma_dir) print(f" {group:<30} " f"{result['linked']} new linked, " f"{result['already_linked']} already linked" + (f", {result['failed']} failed" if result["failed"] else "")) print("\nDone.\n") if __name__ == "__main__": main()