#!/usr/bin/env python3
"""Download everything from VLC for iOS "Sharing via Wi-Fi".

Usage:  python vlc_download_all.py 192.168.1.50[:8080] [-o OUTDIR] [--passcode 1234] [--list]

Reads /libMediaVLC.xml from the phone, then fetches each /download/... URL.
Resumable: files whose size already matches are skipped, partial files continue.
"""
import argparse, os, re, sys, time, urllib.parse, urllib.request, urllib.error
import xml.etree.ElementTree as ET


def open_url(url, headers=None, timeout=30):
    req = urllib.request.Request(url, headers=headers or {})
    return urllib.request.urlopen(req, timeout=timeout)


def safe_name(name):
    name = re.sub(r'[<>:"/\\|?*\x00-\x1f]', "_", name).rstrip(" .")
    return name or "unnamed"


def get_items(base):
    raw = open_url(base + "/libMediaVLC.xml").read()
    root = ET.fromstring(raw)
    items = []
    for m in root.iter("Media"):
        for attr, kind in (("pathfile", "media"), ("pathSubtitle", "subtitle")):
            u = m.get(attr)
            if not u:
                continue
            # Phone embeds its own hostname; rewrite to the address we reached it on.
            p = urllib.parse.urlsplit(u)
            url = base + p.path
            name = safe_name(urllib.parse.unquote(p.path.rsplit("/", 1)[-1]))
            size = int(m.get("size") or 0) if kind == "media" else 0
            items.append((url, name, size))
    seen, out = set(), []
    for it in items:
        if it[0] not in seen:
            seen.add(it[0])
            out.append(it)
    return out


def unique_path(outdir, name, used):
    stem, ext = os.path.splitext(name)
    cand, n = name, 1
    while cand.lower() in used:
        n += 1
        cand = f"{stem} ({n}){ext}"
    used.add(cand.lower())
    return os.path.join(outdir, cand)


def download(url, dest, expected, retries=5):
    for attempt in range(1, retries + 1):
        have = os.path.getsize(dest) if os.path.exists(dest) else 0
        if expected and have == expected:
            return "skip"
        headers = {}
        if have and (not expected or have < expected):
            headers["Range"] = f"bytes={have}-"
        try:
            with open_url(url, headers, timeout=60) as r:
                resume = have and r.status == 206
                mode = "ab" if resume else "wb"
                done = have if resume else 0
                with open(dest, mode) as f:
                    while True:
                        chunk = r.read(1 << 20)
                        if not chunk:
                            break
                        f.write(chunk)
                        done += len(chunk)
                        if expected:
                            print(f"\r    {done/1e6:8.1f} / {expected/1e6:.1f} MB", end="", flush=True)
            print()
            return "ok"
        except (urllib.error.URLError, OSError, TimeoutError) as e:
            print(f"\n    attempt {attempt}/{retries} failed: {e}")
            time.sleep(2 * attempt)
    return "FAILED"


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("host", help="phone IP or name, optional :port (default 80)")
    ap.add_argument("-o", "--out", default="vlc_ios_download")
    ap.add_argument("--passcode", help="VLC passcode if one is set")
    ap.add_argument("--list", action="store_true", help="list files only")
    a = ap.parse_args()

    host = a.host.replace("http://", "").rstrip("/")
    base = "http://" + host

    if a.passcode:
        try:
            open_url(base + "/public/auth.html?code=" + urllib.parse.quote(a.passcode))
        except urllib.error.URLError:
            pass  # auth is tracked per client IP; real check is the list fetch below

    try:
        items = get_items(base)
    except urllib.error.HTTPError as e:
        sys.exit(f"HTTP {e.code} from phone. If 401, VLC has a passcode: use --passcode.")
    except Exception as e:
        sys.exit(f"Could not read library from {base}: {e}\n"
                 "Is 'Sharing via Wi-Fi' on, VLC open in the foreground, and same network?")

    total = sum(s for _, _, s in items)
    print(f"{len(items)} files, {total/1e9:.2f} GB")
    if a.list:
        for _, n, s in items:
            print(f"  {s/1e6:9.1f} MB  {n}")
        return

    os.makedirs(a.out, exist_ok=True)
    used, results = set(), {"ok": 0, "skip": 0, "FAILED": 0}
    for i, (url, name, size) in enumerate(items, 1):
        dest = unique_path(a.out, name, used)
        print(f"[{i}/{len(items)}] {os.path.basename(dest)}")
        results[download(url, dest, size)] += 1
    print(f"\nDone: {results['ok']} downloaded, {results['skip']} skipped, {results['FAILED']} failed -> {os.path.abspath(a.out)}")


if __name__ == "__main__":
    main()
