#!/usr/bin/env python3 """ CLI entry point: manage Arma 3 Server junction/symlink links for downloaded mods. Usage: python link_mods.py status --group shared python link_mods.py link --group shared python link_mods.py unlink --group shared Omit --group to list available groups. """ import argparse import sys from arma_modlist_tools.compat import fix_console_encoding from arma_modlist_tools.config import load_config from arma_modlist_tools.linker import get_link_status, link_group, unlink_group, create_junction, remove_junction fix_console_encoding() def _available_groups(cfg) -> list[str]: if not cfg.downloads.is_dir(): return [] return sorted(p.name for p in cfg.downloads.iterdir() if p.is_dir()) def _print_separator(width: int = 60) -> None: print(" " + "-" * width) # --------------------------------------------------------------------------- # Subcommands # --------------------------------------------------------------------------- def cmd_status(cfg, group: str) -> None: group_dir = cfg.downloads / group if not group_dir.is_dir(): print(f"ERROR: group folder not found: {group_dir}") sys.exit(1) status = get_link_status(group_dir, cfg.arma_dir) print() print(f" Group : {group}") print(f" Path : {group_dir}") print(f" Arma : {cfg.arma_dir}") print() print(f" {'Mod':<50} Status") _print_separator() for s in status: icon = "[LINKED]" if s["is_linked"] else "[------]" print(f" {s['name']:<50} {icon}") linked = sum(1 for s in status if s["is_linked"]) print() print(f" {linked} / {len(status)} linked") print() def cmd_link(cfg, group: str) -> None: group_dir = cfg.downloads / group if not group_dir.is_dir(): print(f"ERROR: group folder not found: {group_dir}") sys.exit(1) status = get_link_status(group_dir, cfg.arma_dir) if not status: print(f"No @Mod folders found in {group_dir}") sys.exit(0) print() print(f" Linking group: {group} -> {cfg.arma_dir}") print() linked = already_linked = failed = 0 for s in status: if s["is_linked"]: print(f" [=] {s['name']:<48} already linked") already_linked += 1 continue if s["link_path"].exists(): print(f" [!] {s['name']:<48} SKIP — path exists, not a junction") failed += 1 continue ok = create_junction(s["link_path"], s["source_path"]) if ok: print(f" [+] {s['name']:<48} linked") linked += 1 else: print(f" [X] {s['name']:<48} FAILED") failed += 1 print() print(f" Done: {linked} linked, {already_linked} already linked, {failed} failed") print() def cmd_unlink(cfg, group: str, yes: bool = False) -> None: group_dir = cfg.downloads / group if not group_dir.is_dir(): print(f"ERROR: group folder not found: {group_dir}") sys.exit(1) status = get_link_status(group_dir, cfg.arma_dir) linked_count = sum(1 for s in status if s["is_linked"]) if linked_count == 0: print(f"\n Nothing to unlink — 0 active links in group '{group}'.\n") sys.exit(0) print() print(f" WARNING: This will remove {linked_count} link(s) from Arma 3 Server:") print(f" {cfg.arma_dir}") print(f" Group: {group} ({linked_count} linked mod(s))") print() if yes: print(" (--yes flag set — skipping confirmation)") else: choice = input(" Continue? [y/N]: ").strip().lower() if choice not in ("y", "yes"): print(" Aborted.\n") sys.exit(0) print() unlinked = not_linked = failed = 0 for s in status: if not s["is_linked"]: print(f" [=] {s['name']:<48} not linked") not_linked += 1 continue ok, err = remove_junction(s["link_path"]) if ok: print(f" [-] {s['name']:<48} unlinked") unlinked += 1 else: print(f" [X] {s['name']:<48} FAILED: {err}") failed += 1 print() print(f" Done: {unlinked} removed, {not_linked} not linked" + (f", {failed} failed" if failed else "")) print() # --------------------------------------------------------------------------- # Entry point # --------------------------------------------------------------------------- def main() -> None: cfg = load_config() parser = argparse.ArgumentParser( description="Manage Arma 3 Server junction/symlink links for downloaded mods." ) parser.add_argument("command", choices=["status", "link", "unlink"]) parser.add_argument("--group", "-g", metavar="GROUP") parser.add_argument("--yes", "-y", action="store_true", help="Skip confirmation prompt (for non-interactive use)") args = parser.parse_args() if not args.group: groups = _available_groups(cfg) if groups: print(f"\nAvailable groups in '{cfg.downloads}':") for g in groups: print(f" {g}") print(f"\nUsage: python link_mods.py {args.command} --group \n") else: print(f"No groups found in '{cfg.downloads}'. Run fetch_mods.py first.") sys.exit(0) if args.command == "status": cmd_status(cfg, args.group) elif args.command == "link": cmd_link(cfg, args.group) elif args.command == "unlink": cmd_unlink(cfg, args.group, yes=args.yes) if __name__ == "__main__": main()