修复掉视频的bug
This commit is contained in:
@@ -161,8 +161,33 @@ class DeviceInfo:
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
def _wait_wda_ready_on_port(self, udid: str, local_port: int, total_timeout_sec: float = None) -> bool:
|
||||
"""在给定的本地映射端口上等待 /status 就绪。"""
|
||||
import http.client, time
|
||||
if total_timeout_sec is None:
|
||||
total_timeout_sec = self.WDA_READY_TIMEOUT
|
||||
deadline = _monotonic() + total_timeout_sec
|
||||
attempt = 0
|
||||
while _monotonic() < deadline:
|
||||
attempt += 1
|
||||
try:
|
||||
conn = http.client.HTTPConnection("127.0.0.1", local_port, timeout=1.8)
|
||||
conn.request("GET", "/status")
|
||||
resp = conn.getresponse()
|
||||
_ = resp.read(128)
|
||||
code = getattr(resp, "status", 0)
|
||||
ok = 200 <= code < 400
|
||||
print(f"[WDA] /status@{local_port} 第{attempt}次 code={code}, ok={ok} {udid}")
|
||||
if ok:
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[WDA] /status@{local_port} 异常({attempt}): {e}")
|
||||
time.sleep(0.5)
|
||||
print(f"[WDA] /status@{local_port} 等待超时 {udid}")
|
||||
return False
|
||||
|
||||
|
||||
def _add_device(self, udid: str):
|
||||
method = "_add_device"
|
||||
print(f"[Add] 开始新增设备 {udid}")
|
||||
|
||||
if not self._trusted(udid):
|
||||
@@ -177,8 +202,13 @@ class DeviceInfo:
|
||||
|
||||
if not self._wda_http_status_ok_once(udid):
|
||||
if major > 17:
|
||||
print("进入iOS17设备的分支")
|
||||
print(f"[WDA] iOS>17 调用 IOSActivator (port={wdaScreenPort})")
|
||||
IOSActivator().activate(udid)
|
||||
try:
|
||||
IOSActivator().activate(udid)
|
||||
print("wda启动完成")
|
||||
except Exception as e:
|
||||
print("错误信息:",e)
|
||||
else:
|
||||
print(f"[WDA] iOS<=17 启动 WDA app_start (port={wdaScreenPort})")
|
||||
dev = tidevice.Device(udid)
|
||||
@@ -196,7 +226,7 @@ class DeviceInfo:
|
||||
if not (w and h and s):
|
||||
# 再做几次快速重试(带超时)
|
||||
for i in range(4):
|
||||
print(f"[Screen] 第{i+1}次获取失败, 重试中... {udid}")
|
||||
print(f"[Screen] 第{i + 1}次获取失败, 重试中... {udid}")
|
||||
time.sleep(0.6)
|
||||
w, h, s = self._screen_info_with_timeout(udid, timeout=3.5)
|
||||
if w and h and s:
|
||||
@@ -254,18 +284,24 @@ class DeviceInfo:
|
||||
return False
|
||||
|
||||
def _wda_http_status_ok_once(self, udid: str, timeout_sec: float = 1.8) -> bool:
|
||||
method = "_wda_http_status_ok_once"
|
||||
tmp_port = self._alloc_port()
|
||||
"""只做一次 /status 探测。任何异常都返回 False,不让外层炸掉。"""
|
||||
tmp_port = None
|
||||
proc = None
|
||||
try:
|
||||
tmp_port = self._alloc_port() # 这里可能抛异常
|
||||
print(f"[WDA] 启动临时 iproxy 以检测 /status {udid}")
|
||||
proc = self._spawn_iproxy(udid, local_port=tmp_port, remote_port=wdaScreenPort)
|
||||
if not proc:
|
||||
print("[WDA] 启动临时 iproxy 失败")
|
||||
return False
|
||||
if not self._wait_until_listening(tmp_port, 3.0):
|
||||
print(f"[WDA] 临时端口未监听 {tmp_port}")
|
||||
self._release_port(tmp_port)
|
||||
return False
|
||||
|
||||
# 最多两次快速探测
|
||||
for i in (1, 2):
|
||||
try:
|
||||
import http.client
|
||||
conn = http.client.HTTPConnection("127.0.0.1", tmp_port, timeout=timeout_sec)
|
||||
conn.request("GET", "/status")
|
||||
resp = conn.getresponse()
|
||||
@@ -275,16 +311,21 @@ class DeviceInfo:
|
||||
print(f"[WDA] /status 第{i}次 code={code}, ok={ok}")
|
||||
if ok:
|
||||
return True
|
||||
time.sleep(0.25)
|
||||
except Exception as e:
|
||||
print(f"[WDA] /status 异常({i}): {e}")
|
||||
time.sleep(0.25)
|
||||
time.sleep(0.25)
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(f"[WDA][probe] 异常:{e}\n{traceback.format_exc()}")
|
||||
return False
|
||||
|
||||
finally:
|
||||
if proc:
|
||||
self._kill(proc)
|
||||
# 无论成功失败,都释放临时端口占用
|
||||
self._release_port(tmp_port)
|
||||
if tmp_port is not None:
|
||||
self._release_port(tmp_port)
|
||||
|
||||
def _wait_wda_ready_http(self, udid: str, total_timeout_sec: float) -> bool:
|
||||
print(f"[WDA] 等待 WDA Ready (超时 {total_timeout_sec}s) {udid}")
|
||||
|
||||
@@ -34,120 +34,138 @@ class IOSActivator:
|
||||
|
||||
# =============== 公共入口 ===============
|
||||
def activate(
|
||||
self,
|
||||
udid: str,
|
||||
wda_bundle_id: Optional[str] = WdaAppBundleId,
|
||||
ready_timeout_sec: float = 120.0,
|
||||
mount_retries: int = 3,
|
||||
backoff_seconds: float = 2.0,
|
||||
rsd_probe_retries: int = 5,
|
||||
rsd_probe_delay_sec: float = 3.0,
|
||||
pre_mount_first: bool = True,
|
||||
keep_tunnel: bool = False, # << 新增:默认 False,一次性用完就关
|
||||
broad_cleanup_on_exit: bool = True, # << 退出时顺带清理所有 pmd3 残留网卡
|
||||
self,
|
||||
udid: str,
|
||||
wda_bundle_id: str = WdaAppBundleId,
|
||||
ready_timeout_sec: float = 60.0,
|
||||
pre_mount_first: bool = True,
|
||||
mount_retries: int = 2,
|
||||
backoff_seconds: float = 1.5,
|
||||
keep_tunnel: bool = False,
|
||||
broad_cleanup_on_exit: bool = True,
|
||||
) -> str:
|
||||
"""
|
||||
执行:挂镜像(可选) -> 开隧道 -> (等待 RSD 就绪)-> 启动 WDA
|
||||
- 默认 keep_tunnel=False:WDA 启动后关闭隧道(避免虚拟网卡常驻)
|
||||
- keep_tunnel=True:让隧道常驻,交由 atexit/signal 或上层调用 stop_tunnel() 清理
|
||||
Windows 简版:不读任何 tunneld 日志,也不做 RSD 解析。
|
||||
逻辑:先探活 -> 开隧道 -> 直接用 HTTP 隧道端口反复尝试启动 WDA -> 探活成功即返回。
|
||||
"""
|
||||
import time, ctypes, traceback
|
||||
|
||||
if not udid or not isinstance(udid, str):
|
||||
raise ValueError("udid is required and must be a non-empty string")
|
||||
|
||||
print(f"[activate] UDID = {udid}")
|
||||
self._ensure_exit_hooks(broad_cleanup_on_exit=broad_cleanup_on_exit)
|
||||
print(f"[activate] UDID={udid}", flush=True)
|
||||
|
||||
# 管理员检测(Windows 清理网卡需要)
|
||||
if os.name == "nt":
|
||||
import ctypes
|
||||
# —— 管理员提示(Windows 清理虚拟网卡常用)——
|
||||
try:
|
||||
if ctypes.windll.shell32.IsUserAnAdmin() == 0:
|
||||
print("[⚠] 未以管理员运行:若需要移除虚拟网卡,可能失败。", flush=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# —— 退出钩子(可选)——
|
||||
try:
|
||||
self._ensure_exit_hooks(broad_cleanup_on_exit=broad_cleanup_on_exit) # type: ignore[attr-defined]
|
||||
except Exception as e:
|
||||
print(f"[activate] _ensure_exit_hooks warn: {e}", flush=True)
|
||||
|
||||
# —— 小工具:探活 WDA —— #
|
||||
def _wda_alive(timeout: float = 2.0) -> bool:
|
||||
try:
|
||||
is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
|
||||
if hasattr(self, "_wda_alive_now"):
|
||||
return bool(self._wda_alive_now(udid, timeout=timeout)) # type: ignore[attr-defined]
|
||||
if hasattr(self, "_wda_client"):
|
||||
cli = self._wda_client(udid) # type: ignore[attr-defined]
|
||||
if hasattr(cli, "wait_ready"):
|
||||
return bool(cli.wait_ready(timeout=timeout))
|
||||
except Exception:
|
||||
is_admin = False
|
||||
if not is_admin:
|
||||
print("[⚠] 未以管理员运行:若需要移除虚拟网卡,可能失败。")
|
||||
return False
|
||||
return False
|
||||
|
||||
import time as _t
|
||||
start_ts = _t.time()
|
||||
# 0) 快路径:WDA 已活
|
||||
if _wda_alive(2.0):
|
||||
print("[activate] WDA already alive, skip launching.", flush=True)
|
||||
return "WDA already alive"
|
||||
|
||||
# 1) 预挂载
|
||||
if pre_mount_first:
|
||||
# 1) 预挂载(失败不致命)
|
||||
if pre_mount_first and hasattr(self, "_auto_mount_developer_disk"):
|
||||
try:
|
||||
self._auto_mount_developer_disk(udid, retries=mount_retries, backoff_seconds=backoff_seconds)
|
||||
_t.sleep(2)
|
||||
self._auto_mount_developer_disk(udid, retries=mount_retries,
|
||||
backoff_seconds=backoff_seconds) # type: ignore[attr-defined]
|
||||
time.sleep(1.5)
|
||||
except Exception as e:
|
||||
print(f"[activate] 预挂载失败(继续尝试开隧道后再挂载一次):{e}")
|
||||
print(f"[activate] 预挂载失败(继续):{e}", flush=True)
|
||||
|
||||
# 2) 启动 tunneld
|
||||
http_host = http_port = rsd_host = rsd_port = None
|
||||
iface_names: Set[str] = set()
|
||||
proc, port = self._start_tunneld(udid)
|
||||
self._live_procs[udid] = proc
|
||||
self._live_ifaces[udid] = iface_names
|
||||
# 2) 开隧道(关键:拿到 HTTP 端口即可;不读取任何 stdout/stderr)
|
||||
proc = None
|
||||
http_host, http_port = "127.0.0.1", None
|
||||
try:
|
||||
ret = self._start_tunneld(udid) # type: ignore[attr-defined]
|
||||
if isinstance(ret, tuple):
|
||||
proc, http_port = ret[0], ret[1]
|
||||
else:
|
||||
proc = ret
|
||||
if http_port is None:
|
||||
# 若你的 _start_tunneld 固定端口,可在这里写死(例如 8100/某自定义端口)
|
||||
raise RuntimeError("未获取到 HTTP 隧道端口(_start_tunneld 未返回端口)")
|
||||
except Exception:
|
||||
# 即便开隧道失败,也再探活一次(可能本来就活)
|
||||
if _wda_alive(2.0):
|
||||
print("[activate] WDA already alive (tunnel start failed but OK).", flush=True)
|
||||
return "WDA already alive"
|
||||
raise
|
||||
|
||||
captured: List[str] = []
|
||||
wda_started = False
|
||||
mount_done = pre_mount_first
|
||||
print(f"[tunneld] HTTP tunnel at {http_host}:{http_port}", flush=True)
|
||||
|
||||
# 3) 直接用 HTTP 隧道反复尝试启动 WDA + 探活
|
||||
deadline = time.time() + (ready_timeout_sec if ready_timeout_sec > 0 else 60.0)
|
||||
launched = False
|
||||
|
||||
try:
|
||||
assert proc.stdout is not None
|
||||
for line in proc.stdout:
|
||||
captured.append(line)
|
||||
print(f"[tunneld] {line}", end="")
|
||||
|
||||
# 捕获虚拟网卡名
|
||||
for m in self.IFACE_RE.finditer(line):
|
||||
iface_names.add(m.group(1))
|
||||
|
||||
if proc.poll() is not None:
|
||||
while time.time() < deadline:
|
||||
# 已活则成功返回
|
||||
if _wda_alive(1.5):
|
||||
print("[activate] WDA detected alive.", flush=True)
|
||||
launched = True
|
||||
break
|
||||
|
||||
# 捕获 HTTP 网关端口
|
||||
if http_port is None:
|
||||
m = self.HTTP_RE.search(line)
|
||||
if m:
|
||||
http_host, http_port = m.group(1), m.group(2)
|
||||
print(f"[tunneld] Tunnel API: {http_host}:{http_port}")
|
||||
# 尝试发起一次 HTTP 启动(失败就下一轮重试)
|
||||
try:
|
||||
if hasattr(self, "_launch_wda_via_http_tunnel"):
|
||||
self._launch_wda_via_http_tunnel( # type: ignore[attr-defined]
|
||||
bundle_id=wda_bundle_id,
|
||||
http_host=http_host,
|
||||
http_port=str(http_port),
|
||||
udid=udid,
|
||||
)
|
||||
except Exception as e:
|
||||
# 仅打印,不中断;下一次循环再试
|
||||
print(f"[activate] _launch_wda_via_http_tunnel error: {e}", flush=True)
|
||||
|
||||
# 捕获 RSD(仅识别当前 UDID 的行)
|
||||
if not self._line_is_for_udid(line, udid):
|
||||
continue
|
||||
m = self.RSD_CREATED_RE.search(line) or self.RSD_FALLBACK_RE.search(line)
|
||||
if m and not rsd_host and not rsd_port:
|
||||
rsd_host, rsd_port = m.group(1), m.group(2)
|
||||
print(f"[tunneld] Device-level tunnel ready (RSD {rsd_host}:{rsd_port}).")
|
||||
# 启动后给一点时间让 WDA ready
|
||||
for _ in range(3):
|
||||
if _wda_alive(1.0):
|
||||
launched = True
|
||||
break
|
||||
time.sleep(0.5)
|
||||
|
||||
# 启动 WDA
|
||||
if (not wda_started) and wda_bundle_id and (rsd_host and rsd_port):
|
||||
if not mount_done:
|
||||
self._auto_mount_developer_disk(udid, retries=mount_retries, backoff_seconds=backoff_seconds)
|
||||
_t.sleep(2)
|
||||
mount_done = True
|
||||
|
||||
if self._wait_for_rsd_ready(rsd_host, rsd_port, retries=rsd_probe_retries, delay=rsd_probe_delay_sec):
|
||||
self._launch_wda_via_rsd(bundle_id=wda_bundle_id, rsd_host=rsd_host, rsd_port=rsd_port, udid=udid)
|
||||
wda_started = True
|
||||
elif http_host and http_port:
|
||||
self._launch_wda_via_http_tunnel(bundle_id=wda_bundle_id, http_host=http_host, http_port=http_port, udid=udid)
|
||||
wda_started = True
|
||||
else:
|
||||
raise RuntimeError("No valid tunnel endpoint for WDA.")
|
||||
|
||||
# 超时保护
|
||||
if (not wda_started) and ready_timeout_sec > 0 and (_t.time() - start_ts > ready_timeout_sec):
|
||||
print(f"[tunneld] Timeout waiting for device tunnel ({ready_timeout_sec}s). Aborting.")
|
||||
if launched:
|
||||
break
|
||||
|
||||
# 结束/收尾
|
||||
out = "".join(captured)
|
||||
time.sleep(1.0) # 下一轮重试
|
||||
|
||||
if not launched:
|
||||
raise RuntimeError(f"WDA not ready within {ready_timeout_sec}s via HTTP tunnel")
|
||||
|
||||
print("[activate] Done.", flush=True)
|
||||
return f"http://{http_host}:{http_port}"
|
||||
|
||||
finally:
|
||||
if not keep_tunnel:
|
||||
# 一次性模式:WDA 已启动后就关闭隧道并清理网卡
|
||||
self.stop_tunnel(udid, broad_cleanup=broad_cleanup_on_exit)
|
||||
try:
|
||||
self.stop_tunnel(udid, broad_cleanup=broad_cleanup_on_exit) # type: ignore[attr-defined]
|
||||
except Exception as e:
|
||||
print(f"[activate] stop_tunnel warn: {e}", flush=True)
|
||||
|
||||
print("[activate] Done.")
|
||||
return out
|
||||
|
||||
# =============== 外部可显式调用的清理 ===============
|
||||
def stop_tunnel(self, udid: str, broad_cleanup: bool = True):
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user