diff --git a/.gitignore b/.gitignore index 8b81175..90e4b38 100644 --- a/.gitignore +++ b/.gitignore @@ -122,4 +122,6 @@ dmypy.json .pytype/ # Cython debug symbols -cython_debug/ \ No newline at end of file +cython_debug/ + +*.bat \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..10b731c --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/.idea/iOSAI.iml b/.idea/iOSAI.iml index df5cbff..f571432 100644 --- a/.idea/iOSAI.iml +++ b/.idea/iOSAI.iml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index c27b771..db8786c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1dd..8306744 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index a95bbca..61e18b9 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -66,6 +66,8 @@ "Python.Main.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", @@ -142,8 +144,7 @@ - diff --git a/Module/DeviceInfo.py b/Module/DeviceInfo.py index 0ff26f3..8b90693 100644 --- a/Module/DeviceInfo.py +++ b/Module/DeviceInfo.py @@ -214,45 +214,45 @@ class Deviceinfo(object): def _removeDisconnected(self, current_list): try: - prev_udids = {getattr(d, "udid", None) for d in self.deviceArray if getattr(d, "udid", None)} - now_udids = {getattr(d, "udid", None) for d in current_list if getattr(d, "udid", None)} + # 当前在线的 UDID 集合 + now_udids = {d.udid for d in current_list if hasattr(d, 'udid')} + + # 上一次记录的 UDID 集合 + prev_udids = {d.udid for d in self.deviceArray if hasattr(d, 'udid')} except Exception as e: LogManager.error(f"收集 UDID 失败:{e}", "") return + removed_udids = prev_udids - now_udids if not removed_udids: return - if not hasattr(self, "_lock"): - self._lock = threading.RLock() with self._lock: - for udid in list(removed_udids): - for a in list(self.deviceModelList): - if udid == getattr(a, "deviceId", None): - a.type = 2 - try: - self.manager.send(a.toDict()) - except Exception as e: - LogManager.warning(f"发送下线事件失败:{e}", udid) - try: - self.deviceModelList.remove(a) - except ValueError: - pass + # 清理 deviceModelList + for model in list(self.deviceModelList): + if model.deviceId in removed_udids: + model.type = 2 + try: + self.manager.send(model.toDict()) + except Exception as e: + LogManager.warning(f"发送下线事件失败:{e}", model.deviceId) + self.deviceModelList.remove(model) + # 清理 pidList survivors = [] - for k in list(self.pidList): - kid = k.get("id") - if kid in removed_udids: - p = k.get("target") + for item in self.pidList: + if item.get("id") in removed_udids: + p = item.get("target") try: self._terminate_proc(p) except Exception as e: - LogManager.warning(f"关闭 iproxy 异常:{e}", kid) + LogManager.warning(f"关闭 iproxy 异常:{e}", item.get("id")) else: - survivors.append(k) + survivors.append(item) self.pidList = survivors - self.deviceArray = [d for d in self.deviceArray if getattr(d, "udid", None) not in removed_udids] + # 清理 deviceArray + self.deviceArray = [d for d in self.deviceArray if d.udid not in removed_udids] for udid in removed_udids: LogManager.info("设备已拔出,清理完成(下线通知 + 端口映射关闭 + 状态移除)", udid) diff --git a/Module/FlaskService.py b/Module/FlaskService.py index 38f6fa0..9cd9748 100644 --- a/Module/FlaskService.py +++ b/Module/FlaskService.py @@ -26,6 +26,8 @@ app.config['JSON_AS_ASCII'] = False # Flask jsonify 不转义中文/emoji app.config['JSONIFY_MIMETYPE'] = "application/json; charset=utf-8" listData = [] +listLock = threading.Lock() + dataQueue = Queue() def start_socket_listener(): @@ -83,39 +85,29 @@ def start_socket_listener(): listener_thread = threading.Thread(target=start_socket_listener, daemon=True) listener_thread.start() -# 传递token,暂时用不到了 -# @app.route('/passToken', methods=['POST']) -# def passToken(): -# try: -# data = request.get_json() -# token = data['token'] -# Requester.requestPrologue(token) -# return ResultData(data="").toJson() -# except Exception as e: -# print(e) -# return ResultData(data="").toJson() - - # 获取设备列表 @app.route('/deviceList', methods=['GET']) def deviceList(): try: - while not dataQueue.empty(): - obj = dataQueue.get() - type = obj["type"] - if type == 1: - listData.append(obj) - else: - for data in listData: - if data.get("deviceId") == obj.get("deviceId") and data.get("screenPort") == obj.get("screenPort"): - listData.remove(data) - return ResultData(data=listData).toJson() + with listLock: # 1. 加锁 + # 先一次性把队列全部消费完 + while not dataQueue.empty(): + obj = dataQueue.get() + if obj["type"] == 1: + listData.append(obj) + else: + # 倒序删除,安全 + for i in range(len(listData) - 1, -1, -1): + d = listData[i] + if d.get("deviceId") == obj.get("deviceId") and \ + d.get("screenPort") == obj.get("screenPort"): + listData.pop(i) + break # 同一端口同一设备只删一次 + return ResultData(data=listData.copy()).toJson() # 2. 返回副本 except Exception as e: - print(e) LogManager.error("获取设备列表失败:", e) return ResultData(data=[]).toJson() - # 获取设备应用列表 @app.route('/deviceAppList', methods=['POST']) def deviceAppList(): @@ -124,7 +116,6 @@ def deviceAppList(): apps = ControlUtils.getDeviceAppList(udid) return ResultData(data=apps).toJson() - # 打开指定app @app.route('/launchApp', methods=['POST']) def launchApp(): diff --git a/build.bat b/build.bat index eb76759..928b8ca 100644 --- a/build.bat +++ b/build.bat @@ -1,25 +1,24 @@ -python -m nuitka "Module/Main.py"^ - --standalone^ - --msvc=latest^ - --windows-console-mode=disable^ - --remove-output^ - --output-dir=out^ - --output-filename=IOSAI^ - --include-package=Module,Utils,Entity,script^ - --include-module=flask^ - --include-module=flask_cors^ - --include-module=jinja2^ - --include-module=werkzeug^ - --include-module=cv2^ - --include-module=numpy^ - --include-module=lxml^ - --include-module=lxml.etree^ - --include-module=requests^ - --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" - +python -m nuitka "Module/Main.py" ^ +--standalone ^ +--msvc=latest ^ +--windows-console-mode=disable ^ +--remove-output ^ +--output-dir=out ^ +--output-filename=IOSAI ^ +--include-package=Module,Utils,Entity,script ^ +--include-module=flask ^ +--include-module=flask_cors ^ +--include-module=jinja2 ^ +--include-module=werkzeug ^ +--include-module=cv2 ^ +--include-module=numpy ^ +--include-module=lxml ^ +--include-module=lxml.etree ^ +--include-module=requests ^ +--include-module=urllib3 ^ +--include-module=certifi ^ +--include-module=idna ^ +--include-data-dir="E:/code/Python/iOSAI/SupportFiles=SupportFiles" ^ +--include-data-dir="E:/code/Python/iOSAI/resources=resources" ^ +--include-data-files="E:/code/Python/iOSAI/resources/iproxy/*=resources/iproxy/" ^ +--windows-icon-from-ico="E:/code/Python/iOSAI/resources/icon.ico" \ No newline at end of file