from __future__ import annotations import argparse import json import os import shutil import signal import subprocess import time from common import touch_ready, wait_for_tcp def run_remote(rpc_port: int, *args: str, check: bool = True) -> subprocess.CompletedProcess: return subprocess.run( ["transmission-remote", f"127.0.0.1:{rpc_port}", *args], text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=check, ) def write_settings(config_dir: str, peer_port: int, rpc_port: int, data_dir: str) -> None: os.makedirs(config_dir, exist_ok=True) settings = { "download-dir": data_dir, "incomplete-dir-enabled": False, "dht-enabled": False, "pex-enabled": False, "lpd-enabled": False, "utp-enabled": False, "port-forwarding-enabled": False, "peer-port": peer_port, "peer-port-random-on-start": False, "rpc-enabled": True, "rpc-bind-address": "127.0.0.1", "rpc-port": rpc_port, "rpc-whitelist-enabled": False, "start-added-torrents": True, "trash-original-torrent-files": False, } with open(os.path.join(config_dir, "settings.json"), "w", encoding="utf-8") as f: json.dump(settings, f, indent=2, sort_keys=True) def main() -> int: ap = argparse.ArgumentParser(description="Seed a fixture with Transmission.") ap.add_argument("--torrent", required=True) ap.add_argument("--data", required=True) ap.add_argument("--peer-port", type=int, required=True) ap.add_argument("--rpc-port", type=int, required=True) ap.add_argument("--utp", action="store_true") ap.add_argument("--encryption", choices=["tolerated", "preferred", "required"], default="tolerated") args = ap.parse_args() config_dir = "/tmp/transmission-config" shutil.rmtree(config_dir, ignore_errors=True) write_settings(config_dir, args.peer_port, args.rpc_port, args.data) proc = subprocess.Popen(["transmission-daemon", "-f", "-g", config_dir]) stop = False def _stop(signum, frame): nonlocal stop stop = True proc.terminate() signal.signal(signal.SIGTERM, _stop) signal.signal(signal.SIGINT, _stop) try: wait_for_tcp("127.0.0.1", args.rpc_port, timeout=30) run_remote(args.rpc_port, "--no-dht", "--no-pex", "--no-lpd", "--no-portmap") run_remote(args.rpc_port, "--utp" if args.utp else "--no-utp") run_remote(args.rpc_port, { "tolerated": "--encryption-tolerated", "preferred": "--encryption-preferred", "required": "--encryption-required", }[args.encryption]) run_remote(args.rpc_port, "-a", args.torrent, "-w", args.data) run_remote(args.rpc_port, "-t", "all", "--start") wait_for_tcp("127.0.0.1", args.peer_port, timeout=30) deadline = time.time() + 60 while time.time() < deadline: info = run_remote(args.rpc_port, "-t", "all", "-i", check=False).stdout if "Percent Done: 100%" in info or "Seeding" in info: touch_ready() print(f"transmission seeding on {args.peer_port}", flush=True) break time.sleep(1) else: raise TimeoutError("Transmission did not report a complete seed") while not stop and proc.poll() is None: time.sleep(1) return proc.returncode or 0 finally: if proc.poll() is None: proc.terminate() try: proc.wait(timeout=10) except subprocess.TimeoutExpired: proc.kill() if __name__ == "__main__": raise SystemExit(main())