Pipeline: parse HTML presets, compare modlists, download from Caddy file server, create junctions/symlinks to Arma 3 Server directory. Includes update/sync flows, missing-mod reporting, OS compat layer, shared config, dep checker, comprehensive test suite (71 tests). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
150 lines
4.9 KiB
Python
150 lines
4.9 KiB
Python
#!/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()
|