"""khyos build android — 零配置、跨平台（Windows/Linux）的 Capacitor APK 构建编排器。

背景与选型纠偏（必读）
======================
khyos 的核心是 **Node.js 后端 + Vue3/Vite Web 前端**，不是纯 Python。因此
Android 出包用 **Capacitor**（WebView 包裹已构建的 Vue 前端）——项目里
``software/khyquant/frontend/android`` 已是完整的 Capacitor 工程。

这条路与 BeeWare/Briefcase **互斥**：Briefcase 只打包纯 Python 应用，对
Node+Vue 架构无能为力，强行套用只会产出跑不起来的 APK。相应地，APK 内不运行
Python，没有 C 扩展需要交叉编译，NDK 默认不安装。

本模块只做一件事：把「装好 pip 后还要手动配 JDK/SDK/NDK/gradle」的繁琐流程，
收敛成一条确定性的 ``khy build android``：

    a) JDK 预检（缺失 → 人类可读 [Action Required]，不抛 gradle 堆栈）
    b) Android SDK 自管理（缺失则按需下载到 ~/.khyos/android_sdk，断点续传+CN镜像+重试）
    c) 前端构建 + cap sync（vite build → npx cap sync android）
    d) gradlew 无界面 assembleDebug|assembleRelease
    e) 拷贝 APK 到输出目录

防呆红线
========
* 绝不把 SDK/NDK 打进 pip 包（数 GB），一律按需下载。
* 所有路径走 ``pathlib.Path``，不硬编码分隔符。
* Windows 不要求管理员权限：以「短缓存根 + 尽力开启长路径」规避 260 限制。
* Linux 自动 ``chmod +x gradlew``。
* 任何已知失败都翻译成 [Action Required] 指引；只在 --verbose 时透传原始堆栈。
* 只新增移动端逻辑，绝不改动桌面/内核构建行为。
"""

from __future__ import annotations

import os
import re
import shutil
import stat
import subprocess
import sys
import time
from pathlib import Path

# ── 常量与版本锁 ──────────────────────────────────────────────────────────

CACHE_ROOT = Path.home() / ".khyos"
SDK_ROOT = CACHE_ROOT / "android_sdk"
DOWNLOAD_DIR = CACHE_ROOT / "android_downloads"

# pyproject [tool.khyos.android] 的兜底默认值（解析失败或源码不可见时启用）。
_DEFAULT_PINS = {
    "compile_sdk": "36",
    "min_sdk": "24",
    "target_sdk": "36",
    "cmdline_tools": "11076708",
    "platform_tools": "latest",
    "build_tools": "35.0.0",
    "ndk": "",
    "sdk_base_url": "https://dl.google.com/android/repository",
    "sdk_mirror_urls": "",
}

_DOWNLOAD_RETRIES = 4
_RETRY_BACKOFF_SECONDS = 2


class ActionRequired(Exception):
    """人类可读的可纠正错误：携带一段给用户照做的指引，而非原始堆栈。"""

    def __init__(self, title: str, steps):
        super().__init__(title)
        self.title = title
        self.steps = list(steps)


# ── 版本锁解析（3.8 兼容，不依赖 tomllib）─────────────────────────────────

def _find_pyproject() -> "Path | None":
    """定位 pyproject.toml：源码树优先，其次 bundled 根。"""
    here = Path(__file__).resolve()
    # platform/khy_platform/android_build.py -> platform -> repo root
    candidates = [here.parent.parent.parent / "pyproject.toml"]
    try:
        from khy_platform.cli import _find_bundled_root  # type: ignore

        bundled = _find_bundled_root()
        if bundled:
            candidates.append(Path(bundled) / "pyproject.toml")
    except Exception:
        pass
    for c in candidates:
        if c.exists():
            return c
    return None


def parse_android_pins(text: "str | None" = None) -> dict:
    """从 pyproject 文本里抽取 [tool.khyos.android] 扁平表。

    仅解析 ``key = "value"`` / ``key = value`` 形式，足够覆盖本表，且不引入
    任何 toml 依赖（requires-python >=3.8，tomllib 要 3.11+）。环境变量
    ``KHY_ANDROID_<KEY>`` 逐项覆盖，便于 CI 与离线测试注入。
    """
    pins = dict(_DEFAULT_PINS)

    if text is None:
        pp = _find_pyproject()
        if pp is not None:
            try:
                text = pp.read_text(encoding="utf-8")
            except Exception:
                text = ""
        else:
            text = ""

    in_section = False
    for raw in text.splitlines():
        line = raw.strip()
        if not line or line.startswith("#"):
            continue
        if line.startswith("["):
            in_section = line.lower().startswith("[tool.khyos.android]")
            continue
        if not in_section or "=" not in line:
            continue
        key, _, val = line.partition("=")
        key = key.strip()
        # 去掉行尾注释与引号
        val = val.split(" #", 1)[0].strip().strip('"').strip("'")
        if key:
            pins[key] = val

    # 环境变量覆盖（最高优先级）
    for key in list(pins.keys()):
        env_key = "KHY_ANDROID_" + key.upper()
        if os.environ.get(env_key):
            pins[key] = os.environ[env_key]

    return pins


# ── 平台与路径工具 ────────────────────────────────────────────────────────

def _is_windows() -> bool:
    return os.name == "nt"


def _bin_name(name: str) -> str:
    """sdkmanager/gradlew 在 Windows 上是 .bat。"""
    return name + (".bat" if _is_windows() else "")


def _find_frontend_dir() -> "Path | None":
    """定位 Capacitor 前端工程（含 capacitor.config.* 与 android/ 子目录）。

    源码树与 bundled 安装两种布局都要覆盖。
    """
    here = Path(__file__).resolve()
    roots = [here.parent.parent.parent]  # repo root
    try:
        from khy_platform.cli import _find_bundled_root  # type: ignore

        bundled = _find_bundled_root()
        if bundled:
            roots.append(Path(bundled))
    except Exception:
        pass

    rel = Path("software") / "khyquant" / "frontend"
    for root in roots:
        cand = root / rel
        if (cand / "android").is_dir():
            return cand
    return None


def enable_windows_long_paths() -> bool:
    """尽力开启 Windows 长路径支持以规避 260 字符限制。

    主缓解手段是把 SDK 根放在很短的 ``~/.khyos/android_sdk``（本身就避开了
    深路径爆栈）；这里再尽力把系统长路径开关打开，但**不强制管理员权限**——
    失败就降级返回 False，由调用方决定是否打印提示。Linux 上恒为 no-op。
    """
    if not _is_windows():
        return False
    try:
        import winreg  # type: ignore

        # 优先尝试 HKLM（需管理员）；拿不到权限就静默放弃，不打断构建。
        key = winreg.OpenKey(
            winreg.HKEY_LOCAL_MACHINE,
            r"SYSTEM\CurrentControlSet\Control\FileSystem",
            0,
            winreg.KEY_SET_VALUE,
        )
        winreg.SetValueEx(key, "LongPathsEnabled", 0, winreg.REG_DWORD, 1)
        winreg.CloseKey(key)
        return True
    except Exception:
        # 无管理员权限属预期：短缓存根已经是主要缓解。
        return False


def _make_executable(path: Path) -> None:
    """Linux/macOS 下给 gradlew 加可执行位；Windows 上是 no-op。"""
    if _is_windows() or not path.exists():
        return
    mode = path.stat().st_mode
    path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)


# ── JDK 预检 ──────────────────────────────────────────────────────────────

def check_jdk() -> str:
    """确认宿主机存在可用 JDK。缺失 → ActionRequired（人类可读指引）。"""
    java = shutil.which("java")
    if not java:
        raise ActionRequired(
            "未检测到 Java JDK —— Android 构建的硬性前置依赖",
            [
                "Android/Gradle 构建必须有 JDK 17（或 11+），khyos 不会替你安装系统级 JDK。",
                "Windows：winget install Microsoft.OpenJDK.17  （或到 https://adoptium.net 下载 Temurin 17）",
                "Linux  ：sudo apt install openjdk-17-jdk   （或对应发行版的包管理器）",
                "装好后重开终端，确认 `java -version` 可用，再重跑 `khy build android`。",
            ],
        )
    return java


def detect_java_home(java_path: str) -> "Path | None":
    """由 java 可执行文件反推 JAVA_HOME（…/bin/java -> …）。"""
    try:
        p = Path(java_path).resolve()
        # …/<home>/bin/java
        if p.parent.name == "bin":
            return p.parent.parent
    except Exception:
        pass
    env_home = os.environ.get("JAVA_HOME")
    return Path(env_home) if env_home else None


# ── 工具链下载（断点续传 + 重试 + 镜像）────────────────────────────────────

def _candidate_urls(pins: dict, archive_name: str) -> list:
    """官方源置首，CN 镜像兜底，全部拼成完整下载地址。"""
    bases = [pins.get("sdk_base_url", "").rstrip("/")]
    extra = pins.get("sdk_mirror_urls", "") or ""
    for m in extra.split(","):
        m = m.strip().rstrip("/")
        if m:
            bases.append(m)
    return [b + "/" + archive_name for b in bases if b]


def download_with_resume(url: str, dest: Path, *, _opener=None) -> Path:
    """流式下载，支持断点续传（HTTP Range）。

    已部分下载则带 ``Range: bytes=<n>-`` 续传；服务器忽略 Range（返回 200）时
    退化为整文件覆盖下载。``_opener`` 仅供测试注入。
    """
    dest.parent.mkdir(parents=True, exist_ok=True)
    opener = _opener or _default_opener
    existing = dest.stat().st_size if dest.exists() else 0

    headers = {}
    if existing:
        headers["Range"] = "bytes=%d-" % existing

    resp = opener(url, headers)
    status = getattr(resp, "status_code", 200)
    mode = "ab" if (existing and status == 206) else "wb"
    if mode == "wb":
        existing = 0  # 服务器不支持续传 → 从头写

    with open(dest, mode) as fh:
        for chunk in resp.iter_content(chunk_size=1024 * 256):
            if chunk:
                fh.write(chunk)
    return dest


def _default_opener(url: str, headers: dict):
    import requests  # 延迟导入：requests 是核心依赖，但保持模块导入零副作用

    resp = requests.get(url, headers=headers, stream=True, timeout=60)
    resp.raise_for_status()
    return resp


def fetch_archive(pins: dict, archive_name: str, dest: Path, *, _opener=None) -> Path:
    """跨镜像 + 多次重试地把一个归档拉到本地，应对国内网络抖动。"""
    urls = _candidate_urls(pins, archive_name)
    if not urls:
        raise ActionRequired(
            "Android 工具链下载源未配置",
            ["pyproject [tool.khyos.android] 的 sdk_base_url 为空，请检查安装完整性。"],
        )

    last_err = None
    for attempt in range(1, _DOWNLOAD_RETRIES + 1):
        url = urls[(attempt - 1) % len(urls)]  # 轮换镜像
        try:
            print("  ↓ [%d/%d] %s" % (attempt, _DOWNLOAD_RETRIES, url))
            return download_with_resume(url, dest, _opener=_opener)
        except Exception as exc:  # noqa: BLE001 — 网络异常统一重试
            last_err = exc
            if attempt < _DOWNLOAD_RETRIES:
                time.sleep(_RETRY_BACKOFF_SECONDS * attempt)

    raise ActionRequired(
        "Android SDK 工具链下载失败（已重试 %d 次 / 轮换 %d 个镜像）" % (_DOWNLOAD_RETRIES, len(urls)),
        [
            "最后一次错误：%s" % last_err,
            "国内网络建议：设置代理 HTTPS_PROXY / 或预置 ~/.khyos/android_sdk 后重跑。",
            "也可手动下载 cmdline-tools 解压到 ~/.khyos/android_sdk/cmdline-tools/latest/ 后重跑。",
        ],
    )


# ── Android SDK 自管理 ────────────────────────────────────────────────────

def _sdk_archive_name(pins: dict) -> str:
    """按宿主机平台拼 commandlinetools 归档名。"""
    rev = pins.get("cmdline_tools", _DEFAULT_PINS["cmdline_tools"])
    if _is_windows():
        plat = "win"
    elif sys.platform == "darwin":
        plat = "mac"
    else:
        plat = "linux"
    return "commandlinetools-%s-%s_latest.zip" % (plat, rev)


def _sdkmanager_path() -> Path:
    return SDK_ROOT / "cmdline-tools" / "latest" / "bin" / _bin_name("sdkmanager")


def ensure_cmdline_tools(pins: dict, *, _opener=None, _runner=None) -> Path:
    """确保 cmdline-tools/latest/ 就位；缺失则下载并解压到规范布局。"""
    sdkmanager = _sdkmanager_path()
    if sdkmanager.exists():
        return sdkmanager

    archive = _sdk_archive_name(pins)
    dest = DOWNLOAD_DIR / archive
    fetch_archive(pins, archive, dest, _opener=_opener)

    # 解压：zip 内顶层是 cmdline-tools/，需重排成 cmdline-tools/latest/
    import zipfile

    staging = SDK_ROOT / "cmdline-tools" / "_unzip"
    if staging.exists():
        shutil.rmtree(staging)
    staging.mkdir(parents=True, exist_ok=True)
    with zipfile.ZipFile(dest) as zf:
        zf.extractall(staging)

    latest = SDK_ROOT / "cmdline-tools" / "latest"
    if latest.exists():
        shutil.rmtree(latest)
    extracted = staging / "cmdline-tools"
    src = extracted if extracted.is_dir() else staging
    shutil.move(str(src), str(latest))
    shutil.rmtree(staging, ignore_errors=True)

    _make_executable(latest / "bin" / _bin_name("sdkmanager"))
    if not sdkmanager.exists():
        raise ActionRequired(
            "cmdline-tools 解压后未找到 sdkmanager",
            ["归档结构异常，请删除 ~/.khyos/android_downloads 后重跑以重新下载。"],
        )
    return sdkmanager


def sdk_packages(pins: dict) -> list:
    """根据版本锁拼装 sdkmanager 安装的包列表。"""
    pkgs = [
        "platform-tools",
        "platforms;android-%s" % pins.get("compile_sdk", _DEFAULT_PINS["compile_sdk"]),
        "build-tools;%s" % pins.get("build_tools", _DEFAULT_PINS["build_tools"]),
    ]
    ndk = (pins.get("ndk") or "").strip()
    if ndk:  # 默认空 = Capacitor Vue 应用不需要 NDK
        pkgs.append("ndk;%s" % ndk)
    return pkgs


def install_sdk_packages(pins: dict, java_home: "Path | None", *, _runner=None) -> None:
    """接受许可并安装平台/构建工具（必要时含 NDK）。"""
    runner = _runner or _default_runner
    sdkmanager = _sdkmanager_path()
    env = _sdk_env(java_home)

    # 先接受全部许可（喂 'y' 到 stdin，跨平台，免交互）。
    runner(
        [str(sdkmanager), "--sdk_root=" + str(SDK_ROOT), "--licenses"],
        env=env,
        input_text="y\n" * 50,
    )
    # 再安装具体包。
    runner(
        [str(sdkmanager), "--sdk_root=" + str(SDK_ROOT), *sdk_packages(pins)],
        env=env,
        input_text="y\n" * 10,
    )


def _utf8_disabled() -> bool:
    """Honor the same KHY_WIN_FORCE_UTF8 escape hatch the JS side uses."""
    flag = str(os.environ.get("KHY_WIN_FORCE_UTF8", "")).strip().lower()
    return flag in ("0", "false", "off", "no")


def _sdk_env(java_home: "Path | None") -> dict:
    env = dict(os.environ)
    env["ANDROID_HOME"] = str(SDK_ROOT)
    env["ANDROID_SDK_ROOT"] = str(SDK_ROOT)
    if java_home:
        env["JAVA_HOME"] = str(java_home)
    # Pin the Gradle launcher JVM to UTF-8 so build diagnostics don't mojibake on a
    # legacy-locale (GBK) Windows console. GRADLE_OPTS is banner-free, unlike
    # JAVA_TOOL_OPTIONS which would print a noisy "Picked up ..." line.
    if not _utf8_disabled():
        utf8_opts = "-Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8"
        prev = env.get("GRADLE_OPTS")
        env["GRADLE_OPTS"] = (prev + " " + utf8_opts) if prev else utf8_opts
    return env


def write_local_properties(android_dir: Path) -> Path:
    """把 android/local.properties 的 sdk.dir 改写到托管 SDK，消灭硬编码旧路径。

    这正是「零手动配置」的关键：仓库里 local.properties 残留了构建机的绝对
    路径，必须被托管路径覆盖。Gradle 要求该值用正斜杠或转义反斜杠。
    """
    lp = android_dir / "local.properties"
    sdk_dir = str(SDK_ROOT).replace("\\", "\\\\")
    lp.write_text("sdk.dir=%s\n" % sdk_dir, encoding="utf-8")
    return lp


# ── 前端构建 + Gradle ────────────────────────────────────────────────────

def _default_runner(cmd, *, env=None, cwd=None, input_text=None):
    """确定性子进程执行：失败抛 CalledProcessError，由上层翻译成 ActionRequired。"""
    result = subprocess.run(
        cmd,
        env=env,
        cwd=str(cwd) if cwd else None,
        input=input_text,
        text=True,
        # Decode/encode in UTF-8 with replacement, matching the rest of the platform
        # runners (node_provisioner / cli) and the Gradle UTF-8 emit-side pinning.
        encoding="utf-8",
        errors="replace",
    )
    if result.returncode != 0:
        raise subprocess.CalledProcessError(result.returncode, cmd)
    return result


def build_web_assets(frontend_dir: Path, *, skip_web=False, _runner=None) -> None:
    """vite build → npx cap sync android。npm 缺失或 skip_web 时尽力降级。"""
    if skip_web:
        print("  · 跳过前端构建（--skip-web），直接用已有 dist/")
        return
    runner = _runner or _default_runner
    npm = shutil.which("npm")
    if not npm:
        dist = frontend_dir / "dist"
        if dist.is_dir():
            print("  · 未检测到 npm，但 dist/ 已存在 —— 跳过前端构建，继续打包。")
            return
        raise ActionRequired(
            "未检测到 npm（Node.js），无法构建 Vue 前端",
            [
                "khyos 自带 Node 运行时；若此处缺失，请安装 Node.js 18+：https://nodejs.org",
                "或先在 software/khyquant/frontend 下手动 `npm run build` 生成 dist/ 后，",
                "用 `khy build android --skip-web` 跳过前端构建步骤。",
            ],
        )

    npx = shutil.which("npx") or "npx"
    if not (frontend_dir / "node_modules").is_dir():
        print("  · 安装前端依赖（npm install）…")
        runner([npm, "install"], cwd=frontend_dir)
    print("  · 构建前端（vite build）…")
    runner([npm, "run", "build"], cwd=frontend_dir)
    print("  · 同步到 Android 工程（cap sync）…")
    runner([npx, "cap", "sync", "android"], cwd=frontend_dir)


def run_gradle(android_dir: Path, java_home: "Path | None", *, release=False, _runner=None) -> None:
    """无界面 gradlew assembleDebug|assembleRelease。"""
    runner = _runner or _default_runner
    gradlew = android_dir / _bin_name("gradlew")
    if not gradlew.exists():
        raise ActionRequired(
            "未找到 gradlew —— Capacitor Android 工程不完整",
            ["请在 software/khyquant/frontend 下先跑一次 `npx cap add android` 生成原生工程。"],
        )
    _make_executable(gradlew)
    task = "assembleRelease" if release else "assembleDebug"
    env = _sdk_env(java_home)
    cmd = [str(gradlew), task, "--no-daemon"]
    runner(cmd, cwd=android_dir, env=env)


def locate_apk(android_dir: Path, *, release=False) -> "Path | None":
    variant = "release" if release else "debug"
    out_dir = android_dir / "app" / "build" / "outputs" / "apk" / variant
    if not out_dir.is_dir():
        return None
    apks = sorted(out_dir.glob("*.apk"))
    return apks[0] if apks else None


# ── 编排入口 ──────────────────────────────────────────────────────────────

def _parse_args(args):
    opts = {"release": False, "skip_web": False, "skip_sdk": False, "verbose": False, "output": None}
    it = iter(args or [])
    for a in it:
        al = str(a).lower()
        if al in ("--release", "-r"):
            opts["release"] = True
        elif al == "--debug":
            opts["release"] = False
        elif al == "--skip-web":
            opts["skip_web"] = True
        elif al == "--skip-sdk":
            opts["skip_sdk"] = True
        elif al in ("--verbose", "-v"):
            opts["verbose"] = True
        elif al in ("--output", "-o"):
            opts["output"] = next(it, None)
        elif al.startswith("--output="):
            opts["output"] = a.split("=", 1)[1]
    return opts


def _print_action_required(err: ActionRequired) -> None:
    print("\n[Action Required] %s" % err.title, file=sys.stderr)
    for step in err.steps:
        print("  - %s" % step, file=sys.stderr)
    print("", file=sys.stderr)


def run_android_build(args) -> int:
    """``khy build android`` 主流程。返回进程退出码。"""
    opts = _parse_args(args)
    pins = parse_android_pins()

    print("khyos Android 构建（Capacitor，零配置）")
    print("=" * 44)

    try:
        frontend_dir = _find_frontend_dir()
        if frontend_dir is None:
            raise ActionRequired(
                "未找到 Capacitor 前端工程（software/khyquant/frontend/android）",
                ["请在完整的 khyos 源码树或 bundled 安装中运行此命令。"],
            )
        android_dir = frontend_dir / "android"

        # a) JDK 预检
        print("· 预检 JDK …")
        java_path = check_jdk()
        java_home = detect_java_home(java_path)

        # Windows：尽力开启长路径（失败不阻断，短缓存根已主要缓解）
        if _is_windows():
            if enable_windows_long_paths():
                print("· 已开启 Windows 长路径支持")
            else:
                print("· 未能改系统长路径开关（无管理员权限）——已用短缓存根规避 260 限制")

        # b) Android SDK 自管理
        if opts["skip_sdk"]:
            print("· 跳过 SDK 自管理（--skip-sdk）")
        else:
            print("· 准备 Android SDK（按需下载到 %s）…" % SDK_ROOT)
            ensure_cmdline_tools(pins)
            install_sdk_packages(pins, java_home)
        write_local_properties(android_dir)

        # c) 前端构建 + cap sync
        print("· 构建 Web 前端并同步到原生工程 …")
        build_web_assets(frontend_dir, skip_web=opts["skip_web"])

        # d) gradle 出包
        print("· Gradle 无界面构建（%s）…" % ("release" if opts["release"] else "debug"))
        run_gradle(android_dir, java_home, release=opts["release"])

        # e) 拷贝 APK
        apk = locate_apk(android_dir, release=opts["release"])
        if not apk:
            raise ActionRequired(
                "Gradle 构建完成但未定位到 APK 产物",
                ["请检查 app/build/outputs/apk/ 下是否有产物，或加 --verbose 重跑看原始日志。"],
            )
        out_dir = Path(opts["output"]) if opts["output"] else (Path.cwd() / "dist" / "android")
        out_dir.mkdir(parents=True, exist_ok=True)
        final = out_dir / apk.name
        shutil.copy2(apk, final)

        print("\n✓ 构建成功")
        print("  APK: %s" % final)
        return 0

    except ActionRequired as err:
        _print_action_required(err)
        return 2
    except subprocess.CalledProcessError as err:
        # 把 gradle/npm 的退码翻译成人类可读提示，默认不透传晦涩堆栈。
        msg = ActionRequired(
            "外部构建工具返回失败（退码 %s）" % err.returncode,
            [
                "命令：%s" % (" ".join(map(str, err.cmd)) if isinstance(err.cmd, (list, tuple)) else err.cmd),
                "常见原因：JDK 版本不符（需 17）、SDK 许可未接受、磁盘空间不足、网络中断。",
                "加 --verbose 重跑可看到底层工具的完整输出。",
            ],
        )
        _print_action_required(msg)
        if opts.get("verbose"):
            raise
        return 1
    except OSError as err:
        # 子进程无法启动（可执行格式错误、二进制缺失、权限不足等）也要人类可读，
        # 而不是甩出 subprocess 的原始堆栈。FileNotFoundError 等均属 OSError。
        msg = ActionRequired(
            "无法启动外部构建工具（%s）" % err.__class__.__name__,
            [
                "底层错误：%s" % err,
                "常见原因：Node/Gradle 可执行文件缺失或架构不匹配、PATH 不含所需工具、权限不足。",
                "请确认 `node -v`、`java -version` 在当前终端可用；加 --verbose 重跑看完整堆栈。",
            ],
        )
        _print_action_required(msg)
        if opts.get("verbose"):
            raise
        return 1
    except KeyboardInterrupt:
        print("\n构建已取消。已下载的工具链保留在缓存中，下次可断点续传。", file=sys.stderr)
        return 130
