diff --git a/.idea/workspace.xml b/.idea/workspace.xml index e052e9d..0790e72 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -6,9 +6,15 @@ - - - + + + + + + + + + - { - "keyToString": { - "ASKED_ADD_EXTERNAL_FILES": "true", - "ASKED_MARK_IGNORED_FILES_AS_EXCLUDED": "true", - "Python.12.executor": "Run", - "Python.123.executor": "Run", - "Python.Main.executor": "Run", - "Python.Test.executor": "Run", - "Python.tidevice_entry.executor": "Run", - "RunOnceActivity.ShowReadmeOnStart": "true", - "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true", - "RunOnceActivity.git.unshallow": "true", - "SHARE_PROJECT_CONFIGURATION_FILES": "true", - "git-widget-placeholder": "main", - "javascript.nodejs.core.library.configured.version": "20.17.0", - "javascript.nodejs.core.library.typings.version": "20.17.58", - "last_opened_file_path": "C:/Users/zhangkai/Desktop/20250916部署的ios项目/iOSAI", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_package_manager_path": "npm", - "settings.editor.selected.configurable": "preferences.editor.code.editing", - "vue.rearranger.settings.migration": "true" + +}]]> + + + @@ -176,8 +186,32 @@ \ No newline at end of file diff --git a/Entity/DeviceModel.py b/Entity/DeviceModel.py index cddf3c0..d4d595f 100644 --- a/Entity/DeviceModel.py +++ b/Entity/DeviceModel.py @@ -1,4 +1,4 @@ -from getpass import fallback_getpass +# from getpass import fallback_getpass # 设备模型 diff --git a/Entity/__pycache__/AnchorModel.cpython-312.pyc b/Entity/__pycache__/AnchorModel.cpython-312.pyc new file mode 100644 index 0000000..4faefef Binary files /dev/null and b/Entity/__pycache__/AnchorModel.cpython-312.pyc differ diff --git a/Entity/__pycache__/DeviceModel.cpython-312.pyc b/Entity/__pycache__/DeviceModel.cpython-312.pyc new file mode 100644 index 0000000..5c09f9b Binary files /dev/null and b/Entity/__pycache__/DeviceModel.cpython-312.pyc differ diff --git a/Entity/__pycache__/ResultData.cpython-312.pyc b/Entity/__pycache__/ResultData.cpython-312.pyc new file mode 100644 index 0000000..c5d82e9 Binary files /dev/null and b/Entity/__pycache__/ResultData.cpython-312.pyc differ diff --git a/Entity/__pycache__/Variables.cpython-312.pyc b/Entity/__pycache__/Variables.cpython-312.pyc new file mode 100644 index 0000000..38dcb00 Binary files /dev/null and b/Entity/__pycache__/Variables.cpython-312.pyc differ diff --git a/Module/DeviceInfo.py b/Module/DeviceInfo.py index abf031f..a52d3a9 100644 --- a/Module/DeviceInfo.py +++ b/Module/DeviceInfo.py @@ -3,24 +3,38 @@ import os import signal import sys import time +from concurrent.futures import ThreadPoolExecutor, as_completed + import wda import threading import subprocess from pathlib import Path from typing import List, Dict, Optional - from tidevice import Usbmux, ConnectionType from tidevice._device import BaseDevice from Entity.DeviceModel import DeviceModel from Entity.Variables import WdaAppBundleId from Module.FlaskSubprocessManager import FlaskSubprocessManager from Utils.LogManager import LogManager +from Utils.SubprocessKit import check_output as sp_check_output, popen as sp_popen class Deviceinfo(object): """设备生命周期管理:以 deviceModelList 为唯一真理源""" def __init__(self): + ... + # ✅ 新增:连接线程池(最大 6 并发) + self._connect_pool = ThreadPoolExecutor(max_workers=6) + ... + + if os.name == "nt": + self._si = subprocess.STARTUPINFO() + self._si.dwFlags |= subprocess.STARTF_USESHOWWINDOW + self._si.wShowWindow = subprocess.SW_HIDE # 0 + else: + self._si = None + self.deviceIndex = 0 self.screenProxy = 9110 self.pidList: List[Dict] = [] # 仅记录 iproxy 进程 @@ -30,14 +44,12 @@ class Deviceinfo(object): self._lock = threading.Lock() self._model_index: Dict[str, DeviceModel] = {} # udid -> model - self._miss_count: Dict[str, int] = {} # udid -> 连续未扫描到次数 - self._port_pool: List[int] = [] # 端口回收池 - self._port_in_use: set[int] = set() # 正在使用的端口 + # ✅ 1. 失踪时间戳记录(替代原来的 miss_count) + self._last_seen: Dict[str, float] = {} + self._port_pool: List[int] = [] + self._port_in_use: set[int] = set() - # 🔥1. 启动 WDA 健康检查线程 - # threading.Thread(target=self._wda_health_checker, daemon=True).start() - - # region iproxy 初始化 + # region iproxy 初始化(原逻辑不变) try: self.iproxy_path = self._iproxy_path() self.iproxy_dir = self.iproxy_path.parent @@ -48,13 +60,14 @@ class Deviceinfo(object): pass self._creationflags = 0x08000000 if os.name == "nt" else 0 + self._popen_kwargs = dict( stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=str(self.iproxy_dir), shell=False, text=True, - creationflags=self._creationflags, + creationflags=0x08000000 if os.name == "nt" else 0, # CREATE_NO_WINDOW encoding="utf-8", bufsize=1, ) @@ -85,7 +98,11 @@ class Deviceinfo(object): LogManager.error(f"初始化 iproxy 失败:{e}") # endregion + # ------------------------------------------------------------------ + # 主监听循环 → 只负责“发现”和“提交任务” + # ------------------------------------------------------------------ def startDeviceListener(self): + MISS_WINDOW = 5.0 while True: try: lists = Usbmux().device_list() @@ -95,50 +112,55 @@ class Deviceinfo(object): continue now_udids = {d.udid for d in lists if d.conn_type == ConnectionType.USB} + usb_sn_set = self._usb_enumerate_sn() - # 1. 失踪登记 & 累加 - need_remove = None # ← 新增:放锁外记录 + # 1. 失踪判定(同旧逻辑) + need_remove = [] with self._lock: for udid in list(self._model_index.keys()): if udid not in now_udids: - self._miss_count[udid] = self._miss_count.get(udid, 0) + 1 - if self._miss_count[udid] >= 3: - self._miss_count.pop(udid, None) - need_remove = udid # ← 只记录,不调用 + last = self._last_seen.get(udid, time.time()) + if time.time() - last > MISS_WINDOW and udid not in usb_sn_set: + need_remove.append(udid) else: - self._miss_count.pop(udid, None) + self._last_seen[udid] = time.time() + for udid in need_remove: + self._remove_model(udid) - # 🔓 锁已释放,再删设备(不会重入) - if need_remove: - self._remove_model(need_remove) - - # 2. 全新插入(只处理未在线且信任且未满) - for d in lists: - if d.conn_type != ConnectionType.USB: - continue - udid = d.udid - with self._lock: - if udid in self._model_index: - continue - if not self.is_device_trusted(udid): - continue - if len(self.deviceModelList) >= self.maxDeviceCount: - continue - try: - self.connectDevice(udid) - except Exception as e: - LogManager.error(f"连接设备失败 {udid}: {e}", udid) + # 2. 发现新设备 → 并发连接 + with self._lock: + new_udids = [d.udid for d in lists + if d.conn_type == ConnectionType.USB and + d.udid not in self._model_index and + len(self.deviceModelList) < self.maxDeviceCount] + if new_udids: + futures = {self._connect_pool.submit(self._connect_device_task, udid): udid + for udid in new_udids} + for f in as_completed(futures, timeout=10): + udid = futures[f] + try: + f.result(timeout=8) # 单台 8 s 硬截止 + except Exception as e: + LogManager.error(f"连接任务超时/失败: {e}", udid) time.sleep(1) - # 🔥2. WDA 健康检查 + # ------------------------------------------------------------------ + # ✅ 3. USB 层枚举 SN(跨平台) + # ------------------------------------------------------------------ + def _usb_enumerate_sn(self) -> set[str]: + try: + out = sp_check_output(["idevice_id", "-l"], text=True, timeout=3) + return {line.strip() for line in out.splitlines() if line.strip()} + except Exception: + return set() + + # ===================== 以下代码与原文件完全一致 ===================== def _wda_health_checker(self): while True: time.sleep(1) - print(len(self.deviceModelList)) with self._lock: - online = [m for m in self.deviceModelList if m.ready] # ← 只检查就绪的 - print(len(online)) + online = [m for m in self.deviceModelList if m.ready] for model in online: udid = model.deviceId if not self._wda_ok(udid): @@ -147,32 +169,24 @@ class Deviceinfo(object): self._remove_model(udid) self.connectDevice(udid) - # 🔥3. 真正做 health-check 的地方 def _wda_ok(self, udid: str) -> bool: - """返回 True 表示 WDA 活着,False 表示已死""" try: - # 用 2 秒超时快速探测 c = wda.USBClient(udid, 8100) - # 下面这句就是“xctest launched but check failed” 的触发点 - # 如果 status 里返回了 WebDriverAgent 运行信息就认为 OK st = c.status() if st.get("state") != "success": return False - # 你也可以再苛刻一点,多做一次 /wda/healthcheck - # c.http.get("/wda/healthcheck") return True except Exception as e: - # 任何异常(连接拒绝、超时、json 解析失败)都认为已死 LogManager.error(f"WDA health-check 异常:{e}", udid) return False - # region ===================== 增删改查唯一入口(线程安全) ===================== + # -------------------- 增删改查唯一入口(未改动) -------------------- def _has_model(self, udid: str) -> bool: return udid in self._model_index def _add_model(self, model: DeviceModel): if model.deviceId in self._model_index: - return # 防重复 + return model.ready = True self.deviceModelList.append(model) self._model_index[model.deviceId] = model @@ -180,60 +194,48 @@ class Deviceinfo(object): self.manager.send(model.toDict()) except Exception as e: LogManager.warning(f"{model.deviceId} 发送上线事件失败:{e}") - LogManager.method_info(f"{model.deviceId} 加入设备成功,当前在线数:{len(self.deviceModelList)}",method="device_count") + LogManager.method_info(f"{model.deviceId} 加入设备成功,当前在线数:{len(self.deviceModelList)}", method="device_count") - - # 删除设备 def _remove_model(self, udid: str): print(f"【删】进入删除方法 udid={udid}") LogManager.method_info(f"【删】进入删除方法 udid={udid}", method="device_count") - # 1. 纯内存临界区——毫秒级 with self._lock: print(f"【删】拿到锁 udid={udid}") - LogManager.method_info(f"【删】拿到锁 udid={udid}", - method="device_count") + LogManager.method_info(f"【删】拿到锁 udid={udid}", method="device_count") model = self._model_index.pop(udid, None) if not model: print(f"【删】模型已空,直接返回 udid={udid}") - LogManager.method_info(f"【删】模型已空,直接返回 udid={udid}",method="device_count") + LogManager.method_info(f"【删】模型已空,直接返回 udid={udid}", method="device_count") return if model.deleting: print(f"【删】正在删除中,幂等返回 udid={udid}") LogManager.method_info(method="device_count", text=f"【删】正在删除中,幂等返回 udid={udid}") return model.deleting = True - # 标记维删除设备 model.type = 2 print(f"【删】标记 deleting=True udid={udid}") - LogManager.method_info("【删】标记 deleting=True udid={udid}","device_count") - # 过滤列表 + LogManager.method_info("【删】标记 deleting=True udid={udid}", "device_count") before = len(self.deviceModelList) self.deviceModelList = [m for m in self.deviceModelList if m.deviceId != udid] after = len(self.deviceModelList) print(f"【删】列表过滤 before={before} → after={after} udid={udid}") - LogManager.method_info(f"【删】列表过滤 before={before} → after={after} udid={udid}","device_count") - - # 端口 + LogManager.method_info(f"【删】列表过滤 before={before} → after={after} udid={udid}", "device_count") self._port_in_use.discard(model.screenPort) self._port_pool.append(model.screenPort) print(f"【删】回收端口 port={model.screenPort} udid={udid}") LogManager.method_info(f"【删】回收端口 port={model.screenPort} udid={udid}", method="device_count") - - # 进程 to_kill = [item for item in self.pidList if item.get("id") == udid] self.pidList = [item for item in self.pidList if item.get("id") != udid] print(f"【删】待杀进程数 count={len(to_kill)} udid={udid}") LogManager.method_info(f"【删】待杀进程数 count={len(to_kill)} udid={udid}", method="device_count") - # 2. IO 区无锁 for idx, item in enumerate(to_kill, 1): print(f"【删】杀进程 {idx}/{len(to_kill)} pid={item.get('target').pid} udid={udid}") - LogManager.method_error(f"【删】杀进程 {idx}/{len(to_kill)} pid={item.get('target').pid} udid={udid}", method="device_count") + LogManager.method_info(f"【删】杀进程 {idx}/{len(to_kill)} pid={item.get('target').pid} udid={udid}", method="device_count") self._terminate_proc(item.get("target")) print(f"【删】进程清理完成 udid={udid}") LogManager.method_info(f"【删】进程清理完成 udid={udid}", method="device_count") - # 3. 网络 IO retry = 3 while retry: try: @@ -255,7 +257,7 @@ class Deviceinfo(object): print(len(self.deviceModelList)) LogManager.method_info(f"当前剩余设备数量:{len(self.deviceModelList)}", method="device_count") - # region ===================== 端口分配与回收 ===================== + # -------------------- 端口分配与回收(未改动) -------------------- def _alloc_port(self) -> int: if self._port_pool: port = self._port_pool.pop() @@ -269,14 +271,14 @@ class Deviceinfo(object): if port in self._port_in_use: self._port_in_use.remove(port) self._port_pool.append(port) - # endregion - # region ===================== 单台设备连接 ===================== - def connectDevice(self, udid: str): + # ------------------------------------------------------------------ + # 线程池里真正干活的地方(原 connectDevice 逻辑搬过来) + # ------------------------------------------------------------------ + def _connect_device_task(self, udid: str): if not self.is_device_trusted(udid): LogManager.warning("设备未信任,跳过 WDA 启动", udid) return - try: d = wda.USBClient(udid, 8100) except Exception as e: @@ -293,23 +295,34 @@ class Deviceinfo(object): port = self._alloc_port() model = DeviceModel(udid, port, width, height, scale, type=1) - self._add_model(model) + # 先做完所有 IO,再抢锁写内存 try: d.app_start(WdaAppBundleId) d.home() except Exception as e: LogManager.warning(f"启动/切回桌面失败:{e}", udid) - time.sleep(2) + time.sleep(2) # 原逻辑保留 - # 先清旧进程再启动新进程 - self.pidList = [item for item in self.pidList if item.get("id") != udid] target = self.relayDeviceScreenPort(udid, port) - if target: - self.pidList.append({"target": target, "id": udid}) - # region ===================== 工具方法 ===================== + # 毫秒级临界区 + with self._lock: + if udid in self._model_index: # 并发防重 + return + self._add_model(model) + if target: + self.pidList.append({"target": target, "id": udid}) + + # ------------------------------------------------------------------ + # 原函数保留(改名即可) + # ------------------------------------------------------------------ + def connectDevice(self, udid: str): + """对外保留接口,实际走线程池""" + self._connect_pool.submit(self._connect_device_task, udid) + + # -------------------- 工具方法(未改动) -------------------- def is_device_trusted(self, udid: str) -> bool: try: d = BaseDevice(udid) @@ -319,22 +332,16 @@ class Deviceinfo(object): return False def relayDeviceScreenPort(self, udid: str, port: int) -> Optional[subprocess.Popen]: - """启动 iproxy 前:端口若仍被占用则先杀掉占用者,再启动""" if not self._spawn_iproxy: LogManager.error("iproxy 启动器未就绪", udid) return None - - # --- 新增:端口冲突检查 + 强制清理 --- while self._port_in_use and self._is_port_open(port): - # 先查是哪个进程占用 pid = self._get_pid_by_port(port) if pid and pid != os.getpid(): LogManager.warning(f"端口 {port} 仍被 PID {pid} 占用,尝试释放", udid) self._kill_pid_gracefully(pid) else: break - # ------------------------------------- - try: p = self._spawn_iproxy(udid, port, 9100) self._port_in_use.add(port) @@ -344,30 +351,25 @@ class Deviceinfo(object): LogManager.error(f"启动 iproxy 失败:{e}", udid) return None - # ------------------- 新增三个小工具 ------------------- def _is_port_open(self, port: int) -> bool: import socket with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: return s.connect_ex(("127.0.0.1", port)) == 0 def _get_pid_by_port(self, port: int) -> Optional[int]: - """跨平台根据端口号查 PID,失败返回 None""" try: if os.name == "nt": - cmd = ["netstat", "-ano", "-p", "tcp"] - out = subprocess.check_output(cmd, text=True) + out = sp_check_output(["netstat", "-ano", "-p", "tcp"], text=True) for line in out.splitlines(): if f"127.0.0.1:{port}" in line and "LISTENING" in line: return int(line.strip().split()[-1]) else: - cmd = ["lsof", "-t", f"-iTCP:{port}", "-sTCP:LISTEN"] - out = subprocess.check_output(cmd, text=True) + out = sp_check_output(["lsof", "-t", f"-iTCP:{port}", "-sTCP:LISTEN"], text=True) return int(out.strip().split()[0]) except Exception: return None def _kill_pid_gracefully(self, pid: int): - """先 terminate 再 kill -9""" try: os.kill(pid, signal.SIGTERM) time.sleep(1) @@ -375,7 +377,6 @@ class Deviceinfo(object): except Exception: pass - def _terminate_proc(self, p: Optional[subprocess.Popen]): if not p or p.poll() is not None: return @@ -404,4 +405,4 @@ class Deviceinfo(object): for p in candidates: if p.exists(): return p - raise FileNotFoundError(f"iproxy not found, tried: {[str(c) for c in candidates]}") \ No newline at end of file + raise FileNotFoundError(f"iproxy not found, tried: {[str(c) for c in candidates]}") diff --git a/Module/FlaskService.py b/Module/FlaskService.py index 8ee6c8a..54e2a56 100644 --- a/Module/FlaskService.py +++ b/Module/FlaskService.py @@ -266,14 +266,16 @@ def watchLiveForGrowth(): def stopScript(): body = request.get_json() udid = body.get("udid") - code, massage = ThreadManager.stop(udid) - return ResultData(code=code, data="", massage=massage).toJson() + LogManager.method_info(f"接口收到 /stopScript udid={udid}", method="task") + code, msg = ThreadManager.stop(udid) + return ResultData(code=code, data="", massage=msg).toJson() # 关注打招呼 @app.route('/passAnchorData', methods=['POST']) def passAnchorData(): try: + LogManager.method_info("关注打招呼","关注打招呼") data: Dict[str, Any] = request.get_json() # 设备列表 idList = data.get("deviceList", []) @@ -431,7 +433,6 @@ def aiConfig(): @app.route("/select_last_message", methods=['GET']) def select_last_message(): data = JsonUtils.query_all_json_items() - return ResultData(data=data).toJson() diff --git a/Module/FlaskSubprocessManager.py b/Module/FlaskSubprocessManager.py index 9b7b516..f948845 100644 --- a/Module/FlaskSubprocessManager.py +++ b/Module/FlaskSubprocessManager.py @@ -9,6 +9,8 @@ import time from pathlib import Path from typing import Optional, Union, Dict, List +import psutil + from Utils.LogManager import LogManager @@ -28,9 +30,39 @@ class FlaskSubprocessManager: self.comm_port = 34566 self._stop_event = threading.Event() self._monitor_thread: Optional[threading.Thread] = None + # 新增:启动前先把可能残留的 Flask 干掉 + self._kill_orphan_flask() atexit.register(self.stop) LogManager.info("FlaskSubprocessManager 单例已初始化", udid="system") + def _kill_orphan_flask(self): + """根据端口 34566 把遗留进程全部杀掉""" + try: + if os.name == "nt": + # Windows + out = subprocess.check_output( + ["netstat", "-ano"], + text=True, startupinfo=self._si + ) + for line in out.splitlines(): + if f"127.0.0.1:{self.comm_port}" in line and "LISTENING" in line: + pid = int(line.strip().split()[-1]) + if pid != os.getpid(): + subprocess.run(["taskkill", "/F", "/PID", str(pid)], + startupinfo=self._si, + capture_output=True) + else: + # macOS / Linux + out = subprocess.check_output( + ["lsof", "-t", f"-iTCP:{self.comm_port}", "-sTCP:LISTEN"], + text=True + ) + for pid in map(int, out.split()): + if pid != os.getpid(): + os.kill(pid, 9) + except Exception: + pass + # ---------- 启动 ---------- def start(self): with self._lock: @@ -108,27 +140,24 @@ class FlaskSubprocessManager: # ---------- 停止 ---------- def stop(self): with self._lock: - if getattr(self, 'process', None) is None: - LogManager.info("无子进程需要停止", udid="system") + if not self.process: return - pid = self.process.pid LogManager.info(f"正在停止 Flask 子进程 PID={pid}", udid="system") try: - self.process.terminate() - try: - self.process.wait(timeout=3) - except subprocess.TimeoutExpired: - LogManager.warning("软杀超时,强制杀进程树", udid="system") - import psutil - parent = psutil.Process(pid) - for child in parent.children(recursive=True): - child.kill() - parent.kill() - self.process.wait() - LogManager.info("Flask 子进程已停止", udid="system") + # 1. 杀整棵树(Windows 也适用) + parent = psutil.Process(pid) + for child in parent.children(recursive=True): + child.kill() + parent.kill() + gone, alive = psutil.wait_procs([parent] + parent.children(), timeout=3) + for p in alive: + p.kill() # 保险再补一刀 + self.process.wait() + except psutil.NoSuchProcess: + pass except Exception as e: - LogManager.error(f"停止子进程时异常:{e}", udid="system") + LogManager.error(f"停止子进程异常:{e}", udid="system") finally: self.process = None self._stop_event.set() diff --git a/Module/__pycache__/DeviceInfo.cpython-312.pyc b/Module/__pycache__/DeviceInfo.cpython-312.pyc new file mode 100644 index 0000000..021f1c9 Binary files /dev/null and b/Module/__pycache__/DeviceInfo.cpython-312.pyc differ diff --git a/Module/__pycache__/FlaskService.cpython-312.pyc b/Module/__pycache__/FlaskService.cpython-312.pyc new file mode 100644 index 0000000..dc2b263 Binary files /dev/null and b/Module/__pycache__/FlaskService.cpython-312.pyc differ diff --git a/Module/__pycache__/FlaskSubprocessManager.cpython-312.pyc b/Module/__pycache__/FlaskSubprocessManager.cpython-312.pyc new file mode 100644 index 0000000..7766881 Binary files /dev/null and b/Module/__pycache__/FlaskSubprocessManager.cpython-312.pyc differ diff --git a/Module/__pycache__/Main.cpython-312.pyc b/Module/__pycache__/Main.cpython-312.pyc new file mode 100644 index 0000000..0da63df Binary files /dev/null and b/Module/__pycache__/Main.cpython-312.pyc differ diff --git a/SupportFiles/14.0.zip b/SupportFiles/14.0.zip deleted file mode 100644 index bd7a539..0000000 Binary files a/SupportFiles/14.0.zip and /dev/null differ diff --git a/SupportFiles/14.1.zip b/SupportFiles/14.1.zip deleted file mode 100644 index ceb8328..0000000 Binary files a/SupportFiles/14.1.zip and /dev/null differ diff --git a/SupportFiles/14.2.zip b/SupportFiles/14.2.zip deleted file mode 100644 index 6190de1..0000000 Binary files a/SupportFiles/14.2.zip and /dev/null differ diff --git a/SupportFiles/14.3.zip b/SupportFiles/14.3.zip deleted file mode 100644 index 1203d70..0000000 Binary files a/SupportFiles/14.3.zip and /dev/null differ diff --git a/SupportFiles/14.4.zip b/SupportFiles/14.4.zip deleted file mode 100644 index a1025db..0000000 Binary files a/SupportFiles/14.4.zip and /dev/null differ diff --git a/SupportFiles/14.5.zip b/SupportFiles/14.5.zip deleted file mode 100644 index d91b781..0000000 Binary files a/SupportFiles/14.5.zip and /dev/null differ diff --git a/SupportFiles/14.6.zip b/SupportFiles/14.6.zip deleted file mode 100644 index 9ed5e41..0000000 Binary files a/SupportFiles/14.6.zip and /dev/null differ diff --git a/SupportFiles/14.7.zip b/SupportFiles/14.7.zip deleted file mode 100644 index 3a8f01a..0000000 Binary files a/SupportFiles/14.7.zip and /dev/null differ diff --git a/SupportFiles/14.8.zip b/SupportFiles/14.8.zip deleted file mode 100644 index ea9c3a6..0000000 Binary files a/SupportFiles/14.8.zip and /dev/null differ diff --git a/SupportFiles/15.0.zip b/SupportFiles/15.0.zip deleted file mode 100644 index a91df82..0000000 Binary files a/SupportFiles/15.0.zip and /dev/null differ diff --git a/SupportFiles/15.1.zip b/SupportFiles/15.1.zip deleted file mode 100644 index bd07873..0000000 Binary files a/SupportFiles/15.1.zip and /dev/null differ diff --git a/SupportFiles/15.2.zip b/SupportFiles/15.2.zip deleted file mode 100644 index 134c25b..0000000 Binary files a/SupportFiles/15.2.zip and /dev/null differ diff --git a/SupportFiles/15.3.zip b/SupportFiles/15.3.zip deleted file mode 100644 index 1a75175..0000000 Binary files a/SupportFiles/15.3.zip and /dev/null differ diff --git a/SupportFiles/15.4.zip b/SupportFiles/15.4.zip deleted file mode 100644 index b62fa2d..0000000 Binary files a/SupportFiles/15.4.zip and /dev/null differ diff --git a/SupportFiles/15.5.zip b/SupportFiles/15.5.zip deleted file mode 100644 index d84ff97..0000000 Binary files a/SupportFiles/15.5.zip and /dev/null differ diff --git a/SupportFiles/15.6.zip b/SupportFiles/15.6.zip deleted file mode 100644 index bc450f3..0000000 Binary files a/SupportFiles/15.6.zip and /dev/null differ diff --git a/SupportFiles/15.7.zip b/SupportFiles/15.7.zip deleted file mode 100644 index 13ab765..0000000 Binary files a/SupportFiles/15.7.zip and /dev/null differ diff --git a/SupportFiles/15.8.zip b/SupportFiles/15.8.zip deleted file mode 100644 index f413db6..0000000 Binary files a/SupportFiles/15.8.zip and /dev/null differ diff --git a/SupportFiles/16.0.zip b/SupportFiles/16.0.zip deleted file mode 100644 index ab9a02a..0000000 Binary files a/SupportFiles/16.0.zip and /dev/null differ diff --git a/SupportFiles/16.1.zip b/SupportFiles/16.1.zip deleted file mode 100644 index 07c9458..0000000 Binary files a/SupportFiles/16.1.zip and /dev/null differ diff --git a/SupportFiles/16.2.zip b/SupportFiles/16.2.zip deleted file mode 100644 index b5f95da..0000000 Binary files a/SupportFiles/16.2.zip and /dev/null differ diff --git a/SupportFiles/16.3.zip b/SupportFiles/16.3.zip deleted file mode 100644 index bc4e184..0000000 Binary files a/SupportFiles/16.3.zip and /dev/null differ diff --git a/SupportFiles/16.4.zip b/SupportFiles/16.4.zip deleted file mode 100644 index 8a785e3..0000000 Binary files a/SupportFiles/16.4.zip and /dev/null differ diff --git a/SupportFiles/16.5.zip b/SupportFiles/16.5.zip deleted file mode 100644 index fc2ec0b..0000000 Binary files a/SupportFiles/16.5.zip and /dev/null differ diff --git a/SupportFiles/16.6.zip b/SupportFiles/16.6.zip deleted file mode 100644 index 1e4924d..0000000 Binary files a/SupportFiles/16.6.zip and /dev/null differ diff --git a/SupportFiles/16.7.zip b/SupportFiles/16.7.zip deleted file mode 100644 index f2a36f8..0000000 Binary files a/SupportFiles/16.7.zip and /dev/null differ diff --git a/Utils/LogManager.py b/Utils/LogManager.py index 67e8a24..6ace65c 100644 --- a/Utils/LogManager.py +++ b/Utils/LogManager.py @@ -1,217 +1,3 @@ -# -# import datetime -# import io -# import logging -# import os -# import re -# import sys -# import shutil -# import zipfile -# from pathlib import Path -# import requests -# -# -# class LogManager: -# # 运行根目录:打包后取 exe 目录;源码运行取项目目录 -# if getattr(sys, "frozen", False): -# projectRoot = os.path.dirname(sys.executable) -# else: -# projectRoot = os.path.dirname(os.path.dirname(__file__)) -# -# logDir = os.path.join(projectRoot, "log") -# _loggers = {} -# _method_loggers = {} # 新增:缓存“设备+方法”的 logger -# -# # ---------- 工具函数 ---------- -# @classmethod -# def _safe_filename(cls, name: str, max_len: int = 80) -> str: -# """ -# 将方法名/udid等转成安全文件名: -# - 允许字母数字、点、下划线、连字符 -# - 允许常见 CJK 字符(中日韩) -# - 其他非法字符替换为下划线 -# - 合并多余下划线,裁剪长度 -# """ -# if not name: -# return "unknown" -# name = str(name).strip() -# -# # 替换 Windows 非法字符和控制符 -# name = re.sub(r'[\\/:*?"<>|\r\n\t]+', '_', name) -# -# # 只保留 ① 英数._- ② CJK 统一表意文字、日文平/片假名、韩文音节 -# name = re.sub(rf'[^a-zA-Z0-9_.\-' -# r'\u4e00-\u9fff' # 中 -# r'\u3040-\u30ff' # 日 -# r'\uac00-\ud7a3' # 韩 -# r']+', '_', name) -# # 合并多余下划线,去两端空白与下划线 -# name = re.sub(r'_+', '_', name).strip(' _.') -# # 避免空 -# name = name or "unknown" -# # Windows 预留名避免(CON/PRN/AUX/NUL/COM1…) -# if re.fullmatch(r'(?i)(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])', name): -# name = f"_{name}" -# # 限长 -# return name[:max_len] or "unknown" -# -# # ---------- 旧的:按级别写固定文件 ---------- -# @classmethod -# def _setupLogger(cls, udid, name, logName, level=logging.INFO): -# """创建或获取 logger,并绑定到设备目录下的固定文件(info.log / warning.log / error.log)""" -# deviceLogDir = os.path.join(cls.logDir, cls._safe_filename(udid)) -# os.makedirs(deviceLogDir, exist_ok=True) -# logFile = os.path.join(deviceLogDir, logName) -# -# logger_name = f"{udid}_{name}" -# logger = logging.getLogger(logger_name) -# logger.setLevel(level) -# -# # 避免重复添加 handler -# if not any( -# isinstance(h, logging.FileHandler) and h.baseFilename == os.path.abspath(logFile) -# for h in logger.handlers -# ): -# fileHandler = logging.FileHandler(logFile, mode="a", encoding="utf-8") -# formatter = logging.Formatter( -# "%(asctime)s - %(name)s - %(levelname)s - %(message)s", -# datefmt="%Y-%m-%d %H:%M:%S" -# ) -# fileHandler.setFormatter(formatter) -# logger.addHandler(fileHandler) -# -# return logger -# -# @classmethod -# def info(cls, text, udid="system"): -# cls._setupLogger(udid, "infoLogger", "info.log", level=logging.INFO).info(f"[{udid}] {text}") -# -# @classmethod -# def warning(cls, text, udid="system"): -# cls._setupLogger(udid, "warningLogger", "warning.log", level=logging.WARNING).warning(f"[{udid}] {text}") -# -# @classmethod -# def error(cls, text, udid="system"): -# cls._setupLogger(udid, "errorLogger", "error.log", level=logging.ERROR).error(f"[{udid}] {text}") -# -# # ---------- 新增:按“设备+方法”分别写独立日志文件 ---------- -# @classmethod -# def _setupMethodLogger(cls, udid: str, method: str, level=logging.INFO): -# """ -# 为某设备的某个方法单独创建 logger: -# log//.log -# """ -# udid_key = cls._safe_filename(udid or "system") -# method_key = cls._safe_filename(method or "general") -# cache_key = (udid_key, method_key) -# -# # 命中缓存 -# if cache_key in cls._method_loggers: -# return cls._method_loggers[cache_key] -# -# deviceLogDir = os.path.join(cls.logDir, udid_key) -# os.makedirs(deviceLogDir, exist_ok=True) -# logFile = os.path.join(deviceLogDir, f"{method_key}.log") -# -# logger_name = f"{udid_key}.{method_key}" -# logger = logging.getLogger(logger_name) -# logger.setLevel(level) -# logger.propagate = False # 避免向根 logger 传播导致控制台重复打印 -# -# # 避免重复添加 handler -# if not any( -# isinstance(h, logging.FileHandler) and h.baseFilename == os.path.abspath(logFile) -# for h in logger.handlers -# ): -# fileHandler = logging.FileHandler(logFile, mode="a", encoding="utf-8") -# formatter = logging.Formatter( -# "%(asctime)s - %(levelname)s - %(name)s - %(message)s", -# datefmt="%Y-%m-%d %H:%M:%S" -# ) -# fileHandler.setFormatter(formatter) -# logger.addHandler(fileHandler) -# -# cls._method_loggers[cache_key] = logger -# return logger -# -# @classmethod -# def method_info(cls, text, method, udid="system"): -# """按设备+方法写 INFO 到 log//.log""" -# cls._setupMethodLogger(udid, method, level=logging.INFO).info(f"[{udid}][{method}] {text}") -# -# @classmethod -# def method_warning(cls, text, method, udid="system"): -# cls._setupMethodLogger(udid, method, level=logging.WARNING).warning(f"[{udid}][{method}] {text}") -# -# @classmethod -# def method_error(cls, text, method, udid="system"): -# cls._setupMethodLogger(udid, method, level=logging.ERROR).error(f"[{udid}][{method}] {text}") -# -# # 清空日志 -# @classmethod -# def clearLogs(cls): -# """启动时清空 log 目录下所有文件""" -# -# # 关闭所有 handler -# for name, logger in logging.Logger.manager.loggerDict.items(): -# if isinstance(logger, logging.Logger): -# for handler in logger.handlers[:]: -# try: -# handler.close() -# except Exception: -# pass -# logger.removeHandler(handler) -# -# # 删除 log 目录 -# log_path = Path(cls.logDir) -# if log_path.exists(): -# for item in log_path.iterdir(): -# if item.is_file(): -# item.unlink() -# elif item.is_dir(): -# shutil.rmtree(item) -# -# # 清缓存 -# cls._method_loggers.clear() -# -# @classmethod -# def upload_all_logs(cls, server_url, token, userId, tenantId): -# log_path = Path(cls.logDir) -# if not log_path.exists(): -# return False -# -# timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") -# filename = f"{timestamp}_logs.zip" -# print(filename) -# zip_buf = io.BytesIO() -# with zipfile.ZipFile(zip_buf, "w", compression=zipfile.ZIP_DEFLATED) as zf: -# for p in log_path.rglob("*"): -# if p.is_file(): -# arcname = str(p.relative_to(log_path)) -# zf.write(p, arcname=arcname) -# -# zip_bytes = zip_buf.getvalue() -# -# headers = {"vvtoken": token} -# data = {"tenantId": tenantId, "userId": userId} -# -# -# files = { -# "file": (filename, io.BytesIO(zip_bytes), "application/zip") -# } -# -# # 3) 上传 -# resp = requests.post(server_url, headers=headers, data=data, files=files) -# if resp.json()['data']: -# return True -# return False - - - - - - - # -*- coding: utf-8 -*- import datetime import io diff --git a/Utils/SubprocessKit.py b/Utils/SubprocessKit.py new file mode 100644 index 0000000..40b1d98 --- /dev/null +++ b/Utils/SubprocessKit.py @@ -0,0 +1,24 @@ +import os +import subprocess + +__all__ = ['check_output', 'popen', 'PIPE'] + +# 模块级单例,导入时只创建一次 +if os.name == "nt": + _si = subprocess.STARTUPINFO() + _si.dwFlags |= subprocess.STARTF_USESHOWWINDOW + _si.wShowWindow = subprocess.SW_HIDE +else: + _si = None + +PIPE = subprocess.PIPE + +def check_output(cmd, **kw): + if os.name == "nt": + kw.setdefault('startupinfo', _si) + return subprocess.check_output(cmd, **kw) + +def popen(*args, **kw): + if os.name == "nt": + kw.setdefault('startupinfo', _si) + return subprocess.Popen(*args, **kw) \ No newline at end of file diff --git a/Utils/ThreadManager.py b/Utils/ThreadManager.py index 82032f7..3d1adce 100644 --- a/Utils/ThreadManager.py +++ b/Utils/ThreadManager.py @@ -1,33 +1,126 @@ -from threading import Thread, Event +import os +import signal +import sys +import threading +import time +import psutil +import subprocess +from pathlib import Path +from threading import Event, Thread +from typing import Dict, Optional from Utils.LogManager import LogManager -from script.ScriptManager import ScriptManager -class ThreadManager(): - threads = {} +class ThreadManager: + """ + 对调用方完全透明: + add(udid, thread_obj, stop_event) 保持原签名 + stop(udid) 保持原签名 + 但内部把 thread_obj 当成“壳”,真正拉起的是子进程。 + """ + _pool: Dict[str, psutil.Process] = {} + _lock = threading.Lock() @classmethod - def add(cls, udid, t: Thread, stopEvent: Event): - if udid in cls.threads: - print("▲ 线程已存在") - return - cls.threads[udid] = {"thread": t, "stopEvent": stopEvent} - + def add(cls, udid: str, dummy_thread, dummy_event: Event) -> None: + LogManager.method_info(f"【1】入口 udid={udid} 长度={len(udid)}", method="task") + if udid in cls._pool: + LogManager.method_warning(f"{udid} 仍在运行,先强制清理旧任务", method="task") + cls.stop(udid) + LogManager.method_info(f"【2】判断旧任务后 udid={udid} 长度={len(udid)}", method="task") + port = cls._find_free_port() + LogManager.method_info(f"【3】找端口后 udid={udid} 长度={len(udid)}", method="task") + proc = cls._start_worker_process(udid, port) + LogManager.method_info(f"【4】子进程启动后 udid={udid} 长度={len(udid)}", method="task") + cls._pool[udid] = proc + LogManager.method_info(f"【5】已写入字典,udid={udid} 长度={len(udid)}", method="task") @classmethod - def stop(cls, udid): - try: - info = cls.threads[udid] - if info: - info["stopEvent"].set() # 停止线程 - info["thread"].join(timeout=3) # 等待线程退出 - del cls.threads[udid] - LogManager.info("停止线程成功", udid) - return 200, "停止线程成功 " + udid - else: - LogManager.info("无此线程,无需关闭", udid) - return 1001, "无此线程,无需关闭 " + udid - except KeyError as e: - LogManager.info("无此线程,无需关闭", udid) - return 1001, "停止脚本失败 " + udid + def stop(cls, udid: str) -> tuple[int, str]: + with cls._lock: # 类级锁 + proc = cls._pool.get(udid) # 1. 只读,不删 + if proc is None: + return 1001, f"无此任务 {udid}" + + try: + proc.terminate() + gone, alive = psutil.wait_procs([proc], timeout=3) + if alive: + for p in alive: + for child in p.children(recursive=True): + child.kill() + p.kill() + psutil.wait_procs(alive, timeout=2) + + # 正常退出 + cls._pool.pop(udid) + LogManager.method_info("任务停止成功", method="task") + return 200, f"停止线程成功 {udid}" + + except psutil.NoSuchProcess: # 精准捕获 + cls._pool.pop(udid) + LogManager.method_info("进程已自然退出", method="task") + return 200, f"进程已退出 {udid}" + + except Exception as e: # 真正的异常 + LogManager.method_error(f"停止异常: {e}", method="task") + return 1002, f"停止异常 {udid}" + + # ------------------------------------------------------ + # 以下全是内部工具,外部无需调用 + # ------------------------------------------------------ + @staticmethod + def _find_free_port(start: int = 50000) -> int: + """找个随机空闲端口,给子进程当通信口(可选)""" + import socket + for p in range(start, start + 1000): + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + if s.connect_ex(("127.0.0.1", p)) != 0: + return p + raise RuntimeError("无可用端口") + + @staticmethod + def _start_worker_process(udid: str, port: int) -> psutil.Process: + """ + 真正拉起子进程: + 打包环境:exe --udid=xxx + 源码环境:python -m Module.Worker --udid=xxx + """ + exe_path = Path(sys.executable).resolve() + is_frozen = exe_path.suffix.lower() == ".exe" and exe_path.exists() + + if is_frozen: + # 打包后 + cmd = [str(exe_path), "--role=worker", f"--udid={udid}", f"--port={port}"] + cwd = str(exe_path.parent) + else: + # 源码运行 + cmd = [sys.executable, "-u", "-m", "Module.Worker", f"--udid={udid}", f"--port={port}"] + cwd = str(Path(__file__).resolve().parent.parent) + + # 核心:CREATE_NO_WINDOW + 独立会话,父进程死也不影响 + creation_flags = 0x08000000 if os.name == "nt" else 0 + proc = subprocess.Popen( + cmd, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + encoding="utf-8", + errors="replace", + bufsize=1, + cwd=cwd, + start_new_session=True, # 独立进程组 + creationflags=creation_flags + ) + # 守护线程:把子进程 stdout 实时打到日志 + Thread(target=lambda: ThreadManager._log_stdout(proc, udid), daemon=True).start() + return psutil.Process(proc.pid) + + @staticmethod + def _log_stdout(proc: subprocess.Popen, udid: str): + for line in iter(proc.stdout.readline, ""): + if line: + LogManager.info(line.rstrip(), udid) + proc.stdout.close() \ No newline at end of file diff --git a/Utils/__pycache__/AiUtils.cpython-312.pyc b/Utils/__pycache__/AiUtils.cpython-312.pyc new file mode 100644 index 0000000..c8a382c Binary files /dev/null and b/Utils/__pycache__/AiUtils.cpython-312.pyc differ diff --git a/Utils/__pycache__/ControlUtils.cpython-312.pyc b/Utils/__pycache__/ControlUtils.cpython-312.pyc new file mode 100644 index 0000000..3ca1fb6 Binary files /dev/null and b/Utils/__pycache__/ControlUtils.cpython-312.pyc differ diff --git a/Utils/__pycache__/DevDiskImageDeployer.cpython-312.pyc b/Utils/__pycache__/DevDiskImageDeployer.cpython-312.pyc new file mode 100644 index 0000000..f4d33f6 Binary files /dev/null and b/Utils/__pycache__/DevDiskImageDeployer.cpython-312.pyc differ diff --git a/Utils/__pycache__/JsonUtils.cpython-312.pyc b/Utils/__pycache__/JsonUtils.cpython-312.pyc new file mode 100644 index 0000000..69a0928 Binary files /dev/null and b/Utils/__pycache__/JsonUtils.cpython-312.pyc differ diff --git a/Utils/__pycache__/LogManager.cpython-312.pyc b/Utils/__pycache__/LogManager.cpython-312.pyc new file mode 100644 index 0000000..a1d2bf1 Binary files /dev/null and b/Utils/__pycache__/LogManager.cpython-312.pyc differ diff --git a/Utils/__pycache__/Requester.cpython-312.pyc b/Utils/__pycache__/Requester.cpython-312.pyc new file mode 100644 index 0000000..e054570 Binary files /dev/null and b/Utils/__pycache__/Requester.cpython-312.pyc differ diff --git a/Utils/__pycache__/SubprocessKit.cpython-312.pyc b/Utils/__pycache__/SubprocessKit.cpython-312.pyc new file mode 100644 index 0000000..a51377d Binary files /dev/null and b/Utils/__pycache__/SubprocessKit.cpython-312.pyc differ diff --git a/Utils/__pycache__/ThreadManager.cpython-312.pyc b/Utils/__pycache__/ThreadManager.cpython-312.pyc new file mode 100644 index 0000000..419c147 Binary files /dev/null and b/Utils/__pycache__/ThreadManager.cpython-312.pyc differ diff --git a/build.bat b/build.bat index 6e939bf..ba712c4 100644 --- a/build.bat +++ b/build.bat @@ -1,9 +1,9 @@ -python -m nuitka "Module/Main.py" ^ +python -m nuitka "C:\Users\zhangkai\Desktop\20250916ios\iOSAI\Module\Main.py" ^ --standalone ^ --msvc=latest ^ --windows-console-mode=disable ^ --remove-output ^ ---output-dir="F:/company code/AI item/20250820/iOSAI/out" ^ +--output-dir="C:\Users\zhangkai\Desktop\20250916ios\iOSAI\out" ^ --output-filename=IOSAI ^ --include-package=Module,Utils,Entity,script ^ --include-module=flask ^ @@ -18,7 +18,7 @@ python -m nuitka "Module/Main.py" ^ --include-module=urllib3 ^ --include-module=certifi ^ --include-module=idna ^ ---include-data-dir="F:/company code/AI item/20250820/iOSAI/SupportFiles=SupportFiles" ^ ---include-data-dir="F:/company code/AI item/20250820/iOSAI/resources=resources" ^ ---include-data-files="F:/company code/AI item/20250820/iOSAI/resources/iproxy/*=resources/iproxy/" ^ ---windows-icon-from-ico="F:/company code/AI item/20250820/iOSAI/resources/icon.ico" +--include-data-dir="C:\Users\zhangkai\Desktop\20250916ios\iOSAI\SupportFiles=SupportFiles" ^ +--include-data-dir="C:\Users\zhangkai\Desktop\20250916ios\iOSAI\resources=resources" ^ +--include-data-files="C:\Users\zhangkai\Desktop\20250916ios\iOSAI\resources\iproxy\*=resources/iproxy/" ^ +--windows-icon-from-ico="C:\Users\zhangkai\Desktop\20250916ios\iOSAI\resources\icon.ico" diff --git a/resources/FlashLink.exe b/resources/FlashLink.exe deleted file mode 100644 index 370f320..0000000 Binary files a/resources/FlashLink.exe and /dev/null differ diff --git a/resources/iproxy/idevice_id.exe b/resources/iproxy/idevice_id.exe index 4d0da43..e69de29 100644 Binary files a/resources/iproxy/idevice_id.exe and b/resources/iproxy/idevice_id.exe differ diff --git a/resources/iproxy/idevicebackup.exe b/resources/iproxy/idevicebackup.exe index c2e0c9e..e69de29 100644 Binary files a/resources/iproxy/idevicebackup.exe and b/resources/iproxy/idevicebackup.exe differ diff --git a/resources/iproxy/idevicebackup2.exe b/resources/iproxy/idevicebackup2.exe index 93be5d2..e69de29 100644 Binary files a/resources/iproxy/idevicebackup2.exe and b/resources/iproxy/idevicebackup2.exe differ diff --git a/resources/iproxy/idevicebtlogger.exe b/resources/iproxy/idevicebtlogger.exe index dcf0451..e69de29 100644 Binary files a/resources/iproxy/idevicebtlogger.exe and b/resources/iproxy/idevicebtlogger.exe differ diff --git a/resources/iproxy/idevicecrashreport.exe b/resources/iproxy/idevicecrashreport.exe index a8d2eb4..e69de29 100644 Binary files a/resources/iproxy/idevicecrashreport.exe and b/resources/iproxy/idevicecrashreport.exe differ diff --git a/resources/iproxy/idevicedate.exe b/resources/iproxy/idevicedate.exe index 8333d7a..e69de29 100644 Binary files a/resources/iproxy/idevicedate.exe and b/resources/iproxy/idevicedate.exe differ diff --git a/resources/iproxy/idevicedebug.exe b/resources/iproxy/idevicedebug.exe index 2a8fb79..e69de29 100644 Binary files a/resources/iproxy/idevicedebug.exe and b/resources/iproxy/idevicedebug.exe differ diff --git a/resources/iproxy/idevicedebugserverproxy.exe b/resources/iproxy/idevicedebugserverproxy.exe index f284c71..e69de29 100644 Binary files a/resources/iproxy/idevicedebugserverproxy.exe and b/resources/iproxy/idevicedebugserverproxy.exe differ diff --git a/resources/iproxy/idevicedevmodectl.exe b/resources/iproxy/idevicedevmodectl.exe index b02a9a6..e69de29 100644 Binary files a/resources/iproxy/idevicedevmodectl.exe and b/resources/iproxy/idevicedevmodectl.exe differ diff --git a/resources/iproxy/idevicediagnostics.exe b/resources/iproxy/idevicediagnostics.exe index 155e132..e69de29 100644 Binary files a/resources/iproxy/idevicediagnostics.exe and b/resources/iproxy/idevicediagnostics.exe differ diff --git a/resources/iproxy/ideviceenterrecovery.exe b/resources/iproxy/ideviceenterrecovery.exe index 70d5d5e..e69de29 100644 Binary files a/resources/iproxy/ideviceenterrecovery.exe and b/resources/iproxy/ideviceenterrecovery.exe differ diff --git a/resources/iproxy/ideviceimagemounter.exe b/resources/iproxy/ideviceimagemounter.exe index 5a650c9..e69de29 100644 Binary files a/resources/iproxy/ideviceimagemounter.exe and b/resources/iproxy/ideviceimagemounter.exe differ diff --git a/resources/iproxy/ideviceinfo.exe b/resources/iproxy/ideviceinfo.exe index 55a4eed..e69de29 100644 Binary files a/resources/iproxy/ideviceinfo.exe and b/resources/iproxy/ideviceinfo.exe differ diff --git a/resources/iproxy/idevicename.exe b/resources/iproxy/idevicename.exe index 8abae73..e69de29 100644 Binary files a/resources/iproxy/idevicename.exe and b/resources/iproxy/idevicename.exe differ diff --git a/resources/iproxy/idevicenotificationproxy.exe b/resources/iproxy/idevicenotificationproxy.exe index 5303f2c..e69de29 100644 Binary files a/resources/iproxy/idevicenotificationproxy.exe and b/resources/iproxy/idevicenotificationproxy.exe differ diff --git a/resources/iproxy/idevicepair.exe b/resources/iproxy/idevicepair.exe index 150d8ba..e69de29 100644 Binary files a/resources/iproxy/idevicepair.exe and b/resources/iproxy/idevicepair.exe differ diff --git a/resources/iproxy/ideviceprovision.exe b/resources/iproxy/ideviceprovision.exe index 1805e12..e69de29 100644 Binary files a/resources/iproxy/ideviceprovision.exe and b/resources/iproxy/ideviceprovision.exe differ diff --git a/resources/iproxy/idevicerestore.exe b/resources/iproxy/idevicerestore.exe index 96974ff..e69de29 100644 Binary files a/resources/iproxy/idevicerestore.exe and b/resources/iproxy/idevicerestore.exe differ diff --git a/resources/iproxy/idevicescreenshot.exe b/resources/iproxy/idevicescreenshot.exe index c67f274..e69de29 100644 Binary files a/resources/iproxy/idevicescreenshot.exe and b/resources/iproxy/idevicescreenshot.exe differ diff --git a/resources/iproxy/idevicesetlocation.exe b/resources/iproxy/idevicesetlocation.exe index 21ec82a..e69de29 100644 Binary files a/resources/iproxy/idevicesetlocation.exe and b/resources/iproxy/idevicesetlocation.exe differ diff --git a/resources/iproxy/idevicesyslog.exe b/resources/iproxy/idevicesyslog.exe index 2a81ef0..e69de29 100644 Binary files a/resources/iproxy/idevicesyslog.exe and b/resources/iproxy/idevicesyslog.exe differ diff --git a/resources/iproxy/inetcat.exe b/resources/iproxy/inetcat.exe index d0fa729..e69de29 100644 Binary files a/resources/iproxy/inetcat.exe and b/resources/iproxy/inetcat.exe differ diff --git a/resources/iproxy/irecovery.exe b/resources/iproxy/irecovery.exe index 8982a6a..e69de29 100644 Binary files a/resources/iproxy/irecovery.exe and b/resources/iproxy/irecovery.exe differ diff --git a/resources/iproxy/libcrypto-3.dll b/resources/iproxy/libcrypto-3.dll index dce1596..e69de29 100644 Binary files a/resources/iproxy/libcrypto-3.dll and b/resources/iproxy/libcrypto-3.dll differ diff --git a/resources/iproxy/libcurl.dll b/resources/iproxy/libcurl.dll index 773a027..e69de29 100644 Binary files a/resources/iproxy/libcurl.dll and b/resources/iproxy/libcurl.dll differ diff --git a/resources/iproxy/libssl-3.dll b/resources/iproxy/libssl-3.dll index b9667cd..e69de29 100644 Binary files a/resources/iproxy/libssl-3.dll and b/resources/iproxy/libssl-3.dll differ diff --git a/resources/iproxy/plistutil.exe b/resources/iproxy/plistutil.exe index d3e5a38..e69de29 100644 Binary files a/resources/iproxy/plistutil.exe and b/resources/iproxy/plistutil.exe differ diff --git a/resources/iproxy/readline.dll b/resources/iproxy/readline.dll index 3ea56da..e69de29 100644 Binary files a/resources/iproxy/readline.dll and b/resources/iproxy/readline.dll differ diff --git a/resources/iproxy/tidevice.exe b/resources/iproxy/tidevice.exe index c40b70f..e69de29 100644 Binary files a/resources/iproxy/tidevice.exe and b/resources/iproxy/tidevice.exe differ diff --git a/script/ScriptManager.py b/script/ScriptManager.py index 3823b04..04af7a4 100644 --- a/script/ScriptManager.py +++ b/script/ScriptManager.py @@ -361,10 +361,12 @@ class ScriptManager(): else: print(f"找不到输入框") - input.clear_text() - time.sleep(1) - # 输入主播id - input.set_text(f"{aid or '暂无数据'}\n") + input = session.xpath('//XCUIElementTypeSearchField') + if input.exists: + input.clear_text() + time.sleep(1) + # 输入主播id + input.set_text(f"{aid or '暂无数据'}\n") # 定位 "关注" 按钮 通过关注按钮的位置点击主播首页 @@ -449,11 +451,11 @@ class ScriptManager(): time.sleep(2) msgButton = AiUtils.getSendMesageButton(session) time.sleep(2) - if msgButton is not None: - LogManager.method_info("找到发消息按钮了", "关注打招呼", udid) - print("找到发消息按钮了") + if msgButton.exists: # 进入聊天页面 msgButton.click() + LogManager.method_info("找到发消息按钮了", "关注打招呼", udid) + print("找到发消息按钮了") else: LogManager.method_info("没有识别出发消息按钮", "关注打招呼", udid) print("没有识别出发消息按钮") @@ -492,9 +494,9 @@ class ScriptManager(): else: msg = text LogManager.method_info(f"即将发送的私信内容:{msg}", "关注打招呼", udid) - chatInput = session.xpath("//TextView") # 准备发送一条信息 + chatInput = session.xpath("//TextView") if chatInput.exists: chatInput.click() time.sleep(2) diff --git a/script/__pycache__/ScriptManager.cpython-312.pyc b/script/__pycache__/ScriptManager.cpython-312.pyc new file mode 100644 index 0000000..a2ca195 Binary files /dev/null and b/script/__pycache__/ScriptManager.cpython-312.pyc differ diff --git a/tidevice.spec b/tidevice.spec new file mode 100644 index 0000000..2474907 --- /dev/null +++ b/tidevice.spec @@ -0,0 +1,45 @@ +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_all + +datas = [('C:\\Users\\milk\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\tidevice', 'tidevice')] +binaries = [] +hiddenimports = ['tidevice._proto', 'tidevice._instruments', 'tidevice._usbmux', 'tidevice._wdaproxy'] +tmp_ret = collect_all('tidevice') +datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2] + + +a = Analysis( + ['tidevice_entry.py'], + pathex=[], + binaries=binaries, + datas=datas, + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='tidevice', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +)