From ba4bcff7e1687916002095052453706de8c8aa11 Mon Sep 17 00:00:00 2001 From: milk <53408947@qq.com> Date: Fri, 24 Oct 2025 16:24:09 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AE=BE=E5=A4=87=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Module/DeviceInfo.py | 359 ++++++++++++------ Module/__pycache__/DeviceInfo.cpython-312.pyc | Bin 31265 -> 40489 bytes 2 files changed, 250 insertions(+), 109 deletions(-) diff --git a/Module/DeviceInfo.py b/Module/DeviceInfo.py index 301cde0..4df70e7 100644 --- a/Module/DeviceInfo.py +++ b/Module/DeviceInfo.py @@ -4,14 +4,16 @@ import signal import subprocess import threading import time -from concurrent.futures import ThreadPoolExecutor, as_completed +from concurrent.futures import ThreadPoolExecutor, as_completed, TimeoutError from pathlib import Path from typing import Dict, Optional, List import random import socket import http.client import psutil -import hashlib # 仍保留,如需后续扩展 +import hashlib # 保留扩展 +import platform + import tidevice import wda from tidevice import Usbmux, ConnectionType @@ -24,13 +26,34 @@ from Module.IOSActivator import IOSActivator from Utils.LogManager import LogManager +def _monotonic() -> float: + """统一用 monotonic 计时,避免系统时钟跳变影响定时/退避。""" + return time.monotonic() + + class DeviceInfo: - # --- 时序参数(更稳) --- - REMOVE_GRACE_SEC = 8.0 # 设备离线宽限期(秒) - ADD_STABLE_SEC = 2.5 # 设备上线稳定期(秒) - ORPHAN_COOLDOWN = 8.0 # 拓扑变更后暂停孤儿清理(秒) - HEAL_INTERVAL = 5.0 # 健康巡检间隔(秒) + # --- 时序参数(支持环境变量覆盖) --- + REMOVE_GRACE_SEC = float(os.getenv("REMOVE_GRACE_SEC", "8.0")) # 设备离线宽限期 + ADD_STABLE_SEC = float(os.getenv("ADD_STABLE_SEC", "2.5")) # 设备上线稳定期 + ORPHAN_COOLDOWN = float(os.getenv("ORPHAN_COOLDOWN", "8.0")) # 拓扑变更后暂停孤儿清理 + HEAL_INTERVAL = float(os.getenv("HEAL_INTERVAL", "5.0")) # 健康巡检间隔 + + # 端口策略(支持环境变量覆盖) + PORT_RAND_LOW_1 = int(os.getenv("PORT_RAND_LOW_1", "9111")) + PORT_RAND_HIGH_1 = int(os.getenv("PORT_RAND_HIGH_1", "9499")) + PORT_RAND_LOW_2 = int(os.getenv("PORT_RAND_LOW_2", "20000")) + PORT_RAND_HIGH_2 = int(os.getenv("PORT_RAND_HIGH_2", "48000")) + PORT_SCAN_START = int(os.getenv("PORT_SCAN_START", "49152")) + PORT_SCAN_LIMIT = int(os.getenv("PORT_SCAN_LIMIT", "10000")) + + # 自愈退避 + BACKOFF_MAX_SEC = float(os.getenv("BACKOFF_MAX_SEC", "15.0")) + BACKOFF_MIN_SEC = float(os.getenv("BACKOFF_MIN_SEC", "1.5")) + BACKOFF_GROWTH = float(os.getenv("BACKOFF_GROWTH", "1.7")) + + # WDA Ready 等待(HTTP 轮询方式,不触发 xctest) + WDA_READY_TIMEOUT = float(os.getenv("WDA_READY_TIMEOUT", "35.0")) def __init__(self): # 自增端口游标仅作兜底扫描使用 @@ -54,6 +77,10 @@ class DeviceInfo: self._first_seen: Dict[str, float] = {} # udid -> ts(首次在线) self._last_topology_change_ts = 0.0 + # 短缓存:设备信任、WDA运行态(仅作节流) + self._trusted_cache: Dict[str, float] = {} # udid -> expire_ts + self._wda_ok_cache: Dict[str, float] = {} # udid -> expire_ts + LogManager.info("DeviceInfo init 完成;日志已启用", udid="system") # ---------------- 主循环 ---------------- @@ -62,7 +89,7 @@ class DeviceInfo: LogManager.method_info("进入主循环", method, udid="system") orphan_gc_tick = 0 while True: - now = time.time() + now = _monotonic() try: usb = Usbmux().device_list() online_now = {d.udid for d in usb if d.conn_type == ConnectionType.USB} @@ -95,12 +122,18 @@ class DeviceInfo: if to_add: LogManager.info(f"新增设备稳定上线:{to_add}", udid="system") futures = {self._pool.submit(self._add_device, u): u for u in to_add} - for f in as_completed(futures, timeout=45): - try: - f.result() - self._last_topology_change_ts = time.time() - except Exception as e: - LogManager.error(f"异步连接失败:{e}", udid="system") + try: + for f in as_completed(futures, timeout=45): + try: + f.result() + self._last_topology_change_ts = _monotonic() + except Exception as e: + LogManager.error(f"异步连接失败:{e}", udid="system") + except TimeoutError: + for fut, u in futures.items(): + if not fut.done(): + fut.cancel() + LogManager.method_warning("新增设备任务超时已取消", method, udid=u) # 定期健康检查 + 自愈 self._check_and_heal_tunnels(interval=self.HEAL_INTERVAL) @@ -109,7 +142,7 @@ class DeviceInfo: orphan_gc_tick += 1 if orphan_gc_tick >= 10: orphan_gc_tick = 0 - if (time.time() - self._last_topology_change_ts) >= self.ORPHAN_COOLDOWN: + if (_monotonic() - self._last_topology_change_ts) >= self.ORPHAN_COOLDOWN: self._cleanup_orphan_iproxy() time.sleep(1) @@ -123,20 +156,48 @@ class DeviceInfo: LogManager.method_warning("未信任设备,跳过", method, udid=udid) return - r = self.startWda(udid) - if r is False: - LogManager.method_error("启动 WDA 失败,放弃新增", method, udid=udid) - return + # 获取系统主版本 + try: + dev = tidevice.Device(udid) + system_version_major = int(dev.product_version.split(".")[0]) + except Exception as e: + LogManager.method_warning(f"读取系统版本失败:{e}", method, udid=udid) + system_version_major = 0 # 保底 - # iOS 17+ 激活/信任阶段更抖,稍等更稳 - time.sleep(5) + # === iOS>17:先“被动探测”WDA,未运行则交给 IOSActivator,并通过 HTTP 轮询等待 === + if system_version_major > 17: + if self._wda_is_running(udid): + LogManager.method_info("检测到 WDA 已运行,直接映射", method, udid=udid) + else: + LogManager.method_info("WDA 未运行,调用 IOSActivator(pymobiledevice3 自动挂载)", method, udid=udid) + try: + ios = IOSActivator() + threading.Thread(target=ios.activate, args=(udid,), daemon=True).start() + except Exception as e: + LogManager.method_error(f"IOSActivator 启动异常:{e}", method, udid=udid) + return + # 关键:HTTP 轮询等待 WDA Ready(默认最多 35s),不会触发 xctest + if not self._wait_wda_ready_http(udid, total_timeout_sec=self.WDA_READY_TIMEOUT): + LogManager.method_error("WDA 未在超时内就绪(iOS>17 分支)", method, udid=udid) + return + else: + # iOS <= 17:保持原逻辑(app_start + 简单等待) + try: + dev = tidevice.Device(udid) + LogManager.method_info(f"app_start WDA: {WdaAppBundleId}", method, udid=udid) + dev.app_start(WdaAppBundleId) + time.sleep(3) + except Exception as e: + LogManager.method_error(f"WDA 启动异常:{e}", method, udid=udid) + return + # 获取屏幕信息 w, h, s = self._screen_info(udid) if w == 0 or h == 0 or s == 0: LogManager.method_warning("未获取到屏幕信息,放弃新增", method, udid=udid) return - # 不复用端口:直接起一个新端口 + # 启动 iproxy(不复用端口:直接新端口) proc = self._start_iproxy(udid, port=None) if not proc: LogManager.method_error("启动 iproxy 失败,放弃新增", method, udid=udid) @@ -152,7 +213,7 @@ class DeviceInfo: LogManager.method_info(f"设备添加完成,port={port}, {w}x{h}@{s}", method, udid=udid) self._manager_send(model) - # ---------------- 移除设备 ---------------- + # ---------------- 移除设备(修复:总是发送离线通知) ---------------- def _remove_device(self, udid: str): method = "_remove_device" LogManager.method_info("开始移除设备", method, udid=udid) @@ -161,45 +222,118 @@ class DeviceInfo: proc = self._procs.pop(udid, None) pid = self._pid_by_udid.pop(udid, None) self._port_by_udid.pop(udid, None) + # 清缓存,防止误判 + self._trusted_cache.pop(udid, None) + self._wda_ok_cache.pop(udid, None) + self._last_seen.pop(udid, None) + self._first_seen.pop(udid, None) - if not model: - LogManager.method_warning("未找到设备模型,可能重复移除", method, udid=udid) - return - - model.type = 2 self._kill(proc) if pid: self._kill_pid_gracefully(pid) - self._manager_send(model) - LogManager.method_info("设备移除完毕", method, udid=udid) + + if model is None: + # 构造一个最小的“离线模型”通知前端 + try: + offline = DeviceModel(deviceId=udid, screenPort=-1, width=0, height=0, scale=0.0, type=2) + offline.ready = False + self._manager_send(offline) + LogManager.method_info("设备移除完毕(无原模型,已发送离线通知)", method, udid=udid) + except Exception as e: + LogManager.method_warning(f"离线通知(构造模型)异常:{e}", method, udid=udid) + return + + model.type = 2 + model.ready = False + model.screenPort = -1 + try: + self._manager_send(model) + finally: + LogManager.method_info("设备移除完毕(已发送离线通知)", method, udid=udid) # ---------------- 工具函数 ---------------- def _trusted(self, udid: str) -> bool: + # 30s 短缓存,减少 IO + now = _monotonic() + exp = self._trusted_cache.get(udid, 0.0) + if exp > now: + return True try: BaseDevice(udid).get_value("DeviceName") + self._trusted_cache[udid] = now + 30.0 return True except Exception: return False - def startWda(self, udid): - method = "startWda" - LogManager.method_info("进入启动流程", method, udid=udid) + # ======= WDA 探测/等待(仅走 iproxy+HTTP,不触发 xctest) ======= + def _wda_http_status_ok(self, udid: str, timeout_sec: float = 1.2) -> bool: + """临时 iproxy 转发到 wdaFunctionPort,GET /status 成功视为 OK。""" + method = "_wda_http_status_ok" + tmp_port = self._pick_new_port() + proc = None try: - dev = tidevice.Device(udid) - systemVersion = int(dev.product_version.split(".")[0]) - if systemVersion > 17: - LogManager.method_info(f"iOS 主版本 {systemVersion},使用 IOSActivator", method, udid=udid) - ios = IOSActivator() - threading.Thread(target=ios.activate, args=(udid,), daemon=True).start() - else: - LogManager.method_info(f"app_start WDA: {WdaAppBundleId}", method, udid=udid) - dev.app_start(WdaAppBundleId) - LogManager.method_info("WDA 启动完成,等待稳定...", method, udid=udid) - time.sleep(3) + cmd = [self._iproxy_path, "-u", udid, str(tmp_port), str(wdaFunctionPort)] + proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + if not self._wait_until_listening(tmp_port, initial_timeout=0.8): + LogManager.method_info(f"WDA探测:临时端口未监听({tmp_port})", method, udid=udid) + return False + + conn = http.client.HTTPConnection("127.0.0.1", tmp_port, timeout=timeout_sec) + try: + conn.request("GET", "/status") + resp = conn.getresponse() + _ = resp.read(256) + code = getattr(resp, "status", 0) + ok = 200 <= code < 400 + LogManager.method_info(f"WDA探测:/status code={code}, ok={ok}", method, udid=udid) + return ok + except Exception as e: + LogManager.method_info(f"WDA探测异常:{e}", method, udid=udid) + return False + finally: + try: + conn.close() + except Exception: + pass + finally: + if proc: + try: + p = psutil.Process(proc.pid) + p.terminate() + p.wait(timeout=0.6) + except Exception: + try: + p.kill() + except Exception: + pass + + def _wait_wda_ready_http(self, udid: str, total_timeout_sec: float = None, interval_sec: float = 0.6) -> bool: + """ + 通过 _wda_http_status_ok 轮询等待 WDA Ready。 + total_timeout_sec 默认取环境变量 WDA_READY_TIMEOUT(默认 35s)。 + """ + method = "_wait_wda_ready_http" + if total_timeout_sec is None: + total_timeout_sec = self.WDA_READY_TIMEOUT + deadline = _monotonic() + total_timeout_sec + while _monotonic() < deadline: + if self._wda_http_status_ok(udid, timeout_sec=1.2): + LogManager.method_info("WDA 就绪(HTTP轮询)", method, udid=udid) + return True + time.sleep(interval_sec) + LogManager.method_warning(f"WDA 等待超时(HTTP轮询,{total_timeout_sec}s)", method, udid=udid) + return False + + def _wda_is_running(self, udid: str, cache_sec: float = 2.0) -> bool: + """轻量速查,走 HTTP /status(短缓存节流),避免触发 xctest。""" + now = _monotonic() + exp = self._wda_ok_cache.get(udid, 0.0) + if exp > now: return True - except Exception as e: - LogManager.method_error(f"WDA 启动异常:{e}", method, udid=udid) - return False + ok = self._wda_http_status_ok(udid, timeout_sec=1.2) + if ok: + self._wda_ok_cache[udid] = now + cache_sec + return ok def _screen_info(self, udid: str): method = "_screen_info" @@ -215,7 +349,7 @@ class DeviceInfo: return 0, 0, 0 # ---------------- 端口/进程:不复用端口 ---------------- - def _is_port_bindable(self, port: int, host: str = "127.0.0.1") -> bool: + def _is_port_free(self, port: int, host: str = "127.0.0.1") -> bool: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -228,36 +362,38 @@ class DeviceInfo: def _pick_new_port(self, tries: int = 40) -> int: method = "_pick_new_port" - # 先在 9111~9499 随机尝试 - for _ in range(tries // 2): - p = random.randint(9111, 9499) - if self._is_port_bindable(p): - LogManager.method_info(f"端口候选可用(9k段):{p}", method, udid="system") + for _ in range(max(1, tries // 2)): + p = random.randint(self.PORT_RAND_LOW_1, self.PORT_RAND_HIGH_1) + if self._is_port_free(p): + LogManager.method_info(f"端口候选可用(首段):{p}", method, udid="system") return p else: - LogManager.method_info(f"端口候选占用(9k段):{p}", method, udid="system") - # 再在 20000~48000 随机尝试 + LogManager.method_info(f"端口候选占用(首段):{p}", method, udid="system") for _ in range(tries): - p = random.randint(20000, 48000) - if self._is_port_bindable(p): - LogManager.method_info(f"端口候选可用(20k-48k):{p}", method, udid="system") + p = random.randint(self.PORT_RAND_LOW_2, self.PORT_RAND_HIGH_2) + if self._is_port_free(p): + LogManager.method_info(f"端口候选可用(次段):{p}", method, udid="system") return p else: - LogManager.method_info(f"端口候选占用(20k-48k):{p}", method, udid="system") + LogManager.method_info(f"端口候选占用(次段):{p}", method, udid="system") LogManager.method_warning("随机端口尝试耗尽,改顺序扫描", method, udid="system") - return self._pick_free_port(start=49152, limit=10000) + return self._pick_free_port(start=self.PORT_SCAN_START, limit=self.PORT_SCAN_LIMIT) - def _wait_until_listening(self, port: int, timeout: float = 2.0) -> bool: + def _wait_until_listening(self, port: int, initial_timeout: float = 2.0) -> bool: + """自适应等待端口监听:2s -> 3s -> 5s(最多约10s)。""" method = "_wait_until_listening" - deadline = time.time() + timeout - while time.time() < deadline: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.settimeout(0.2) - if s.connect_ex(("127.0.0.1", port)) == 0: - LogManager.method_info(f"端口已开始监听:{port}", method, udid="system") - return True - time.sleep(0.05) - LogManager.method_warning(f"监听验收超时:{port}", method, udid="system") + timeouts = [initial_timeout, 3.0, 5.0] + for to in timeouts: + deadline = _monotonic() + to + while _monotonic() < deadline: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(0.2) + if s.connect_ex(("127.0.0.1", port)) == 0: + LogManager.method_info(f"端口已开始监听:{port}", method, udid="system") + return True + time.sleep(0.05) + LogManager.method_info(f"监听验收阶段超时:{port},扩展等待", method, udid="system") + LogManager.method_warning(f"监听验收最终超时:{port}", method, udid="system") return False def _start_iproxy(self, udid: str, port: Optional[int] = None) -> Optional[subprocess.Popen]: @@ -269,13 +405,12 @@ class DeviceInfo: LogManager.method_info(f"发现旧 iproxy,准备结束:pid={old_pid}", method, udid=udid) self._kill_pid_gracefully(old_pid) self._pid_by_udid.pop(udid, None) - time.sleep(0.2) attempts = 0 while attempts < 3: attempts += 1 local_port = port if (attempts == 1 and port is not None) else self._pick_new_port() - if not self._is_port_bindable(local_port): + if not self._is_port_free(local_port): LogManager.method_info(f"[attempt {attempts}] 端口竞争,换候选:{local_port}", method, udid=udid) continue @@ -301,10 +436,10 @@ class DeviceInfo: startupinfo=startupinfo ) except Exception as e: - LogManager.method_warning(f"创建进程失败:{e}", method, udid=udid) + LogManager.method_warning(f"创建 iproxy 进程失败:{e}", method, udid=udid) continue - if not self._wait_until_listening(local_port, timeout=2.0): + if not self._wait_until_listening(local_port, initial_timeout=2.0): LogManager.method_warning(f"[attempt {attempts}] iproxy 未监听,重试换端口", method, udid=udid) self._kill(proc) continue @@ -329,20 +464,26 @@ class DeviceInfo: if not proc: return try: - proc.terminate() - proc.wait(timeout=2) - LogManager.method_info("进程已正常终止", method, udid="system") - except Exception: + p = psutil.Process(proc.pid) + p.terminate() try: - os.kill(proc.pid, signal.SIGKILL) + p.wait(timeout=2.0) + LogManager.method_info("进程已正常终止", method, udid="system") + except psutil.TimeoutExpired: + p.kill() LogManager.method_warning("进程被强制杀死", method, udid="system") - except Exception as e: - LogManager.method_warning(f"强杀失败:{e}", method, udid="system") + except Exception as e: + LogManager.method_warning(f"结束进程异常:{e}", method, udid="system") # ---------------- 自愈:直接换新端口重启 + 指数退避 ---------------- + def _next_backoff(self, prev_backoff: float) -> float: + if prev_backoff <= 0: + return self.BACKOFF_MIN_SEC + return min(prev_backoff * self.BACKOFF_GROWTH, self.BACKOFF_MAX_SEC) + def _restart_iproxy(self, udid: str): method = "_restart_iproxy" - now = time.time() + now = _monotonic() next_allowed = self._heal_backoff.get(udid, 0.0) if now < next_allowed: delta = round(next_allowed - now, 2) @@ -354,7 +495,6 @@ class DeviceInfo: if proc: LogManager.method_info(f"为重启准备清理旧 iproxy,pid={proc.pid}", method, udid=udid) self._kill(proc) - time.sleep(0.2) model = self._models.get(udid) if not model: LogManager.method_warning("模型不存在,取消自愈", method, udid=udid) @@ -362,13 +502,13 @@ class DeviceInfo: proc2 = self._start_iproxy(udid, port=None) if not proc2: - backoff_old = max(1.5, next_allowed - now + 1.0) if next_allowed > now else 1.5 - backoff = min(backoff_old * 1.7, 15.0) + prev = max(0.0, next_allowed - now) + backoff = self._next_backoff(prev) self._heal_backoff[udid] = now + backoff LogManager.method_warning(f"重启失败,扩展退避 {round(backoff,2)}s", method, udid=udid) return - # 成功后短退避 + # 成功后短退避(抑制频繁重启) self._heal_backoff[udid] = now + 1.2 # 通知前端新端口 @@ -388,23 +528,19 @@ class DeviceInfo: conn.request("HEAD", "/") resp = conn.getresponse() _ = resp.read(128) + code = getattr(resp, "status", 0) conn.close() - return True + return 200 <= code < 400 except Exception: return False def _health_check_wda(self, udid: str) -> bool: - method = "_health_check_wda" - try: - c = wda.USBClient(udid, wdaFunctionPort) - st = c.status() - return bool(st) - except Exception: - return False + # 使用 HTTP 探测(带短缓存),避免触发 xctest + return self._wda_is_running(udid, cache_sec=1.0) def _check_and_heal_tunnels(self, interval: float = 5.0): method = "_check_and_heal_tunnels" - now = time.time() + now = _monotonic() if now - self._last_heal_check_ts < interval: return self._last_heal_check_ts = now @@ -429,16 +565,20 @@ class DeviceInfo: LogManager.method_warning(f"检测到不健康,触发重启;port={port}", method, udid=udid) self._restart_iproxy(udid) - # ---------------- Windows 专用:列出所有 iproxy 命令行 ---------------- + # ---------------- Windows/*nix:列出所有 iproxy 命令行 ---------------- def _get_all_iproxy_cmdlines(self) -> List[str]: method = "_get_all_iproxy_cmdlines" lines: List[str] = [] with self._lock: live_pids = set(self._pid_by_udid.values()) + + is_windows = os.name == "nt" + target_name = "iproxy.exe" if is_windows else "iproxy" + for p in psutil.process_iter(attrs=["name", "cmdline", "pid"]): try: name = (p.info.get("name") or "").lower() - if name != "iproxy.exe": + if name != target_name: continue if p.info["pid"] in live_pids: continue @@ -464,12 +604,14 @@ class DeviceInfo: for ln in self._get_all_iproxy_cmdlines(): parts = ln.split() try: + if "-u" not in parts: + continue udid = parts[parts.index('-u') + 1] pid = int(parts[-1]) if pid not in live_pids and udid not in live_udids: self._kill_pid_gracefully(pid) cleaned += 1 - LogManager.method_warning(f"孤儿 iproxy 已清理:udid={udid}, pid={pid}", method, udid="system") + LogManager.method_warning(f"孤儿 iproxy 已清理:udid={udid}, pid={pid}", method, udid=udid) except (ValueError, IndexError): continue @@ -492,16 +634,6 @@ class DeviceInfo: LogManager.method_warning(f"kill 进程异常:pid={pid}, err={e}", method, udid="system") # ---------------- 端口工具(兜底) ---------------- - def _is_port_free(self, port: int) -> bool: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.settimeout(0.2) - try: - s.bind(("127.0.0.1", port)) - return True - except OSError: - return False - def _pick_free_port(self, start: int = None, limit: int = 2000) -> int: method = "_pick_free_port" p = self._port if start is None else start @@ -525,13 +657,22 @@ class DeviceInfo: LogManager.method_warning(f"通知管理器异常:{e}", method, udid=model.deviceId) def _find_iproxy(self) -> str: + """优先环境变量 IPROXY_PATH;否则按平台在 resources/iproxy 查找。""" method = "_find_iproxy" + + env_path = os.getenv("IPROXY_PATH") + if env_path and Path(env_path).is_file(): + LogManager.method_info(f"使用环境变量指定的 iproxy 路径:{env_path}", method, udid="system") + return env_path + base = Path(__file__).resolve().parent.parent - name = "iproxy.exe" + is_windows = os.name == "nt" + name = "iproxy.exe" if is_windows else "iproxy" path = base / "resources" / "iproxy" / name LogManager.method_info(f"查找 iproxy 路径:{path}", method, udid="system") if path.is_file(): return str(path) + err = f"iproxy 不存在: {path}" LogManager.method_error(err, method, udid="system") raise FileNotFoundError(err) \ No newline at end of file diff --git a/Module/__pycache__/DeviceInfo.cpython-312.pyc b/Module/__pycache__/DeviceInfo.cpython-312.pyc index 5b87331cfb8f54d96c9b8134c7efa04ff43df008..fb33a677418d3014b852850fdf312d271fbec2f3 100644 GIT binary patch literal 40489 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�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 Ih3xhJKM(}_$N&HU delta 13090 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