Merge remote-tracking branch 'origin/main'
# Conflicts: # .idea/workspace.xml
This commit is contained in:
118
.idea/workspace.xml
generated
118
.idea/workspace.xml
generated
@@ -5,12 +5,11 @@
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="eceeff5e-51c1-459c-a911-d21ec090a423" name="Changes" comment="20250904-初步功能已完成">
|
||||
<change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Module/DeviceInfo.py" beforeDir="false" afterPath="$PROJECT_DIR$/Module/DeviceInfo.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Utils/AiUtils.py" beforeDir="false" afterPath="$PROJECT_DIR$/Utils/AiUtils.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Utils/LogManager.py" beforeDir="false" afterPath="$PROJECT_DIR$/Utils/LogManager.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/script/ScriptManager.py" beforeDir="false" afterPath="$PROJECT_DIR$/script/ScriptManager.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Module/FlaskService.py" beforeDir="false" afterPath="$PROJECT_DIR$/Module/FlaskService.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/build-tidevice.bat" beforeDir="false" afterPath="$PROJECT_DIR$/build-tidevice.bat" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/resources/iproxy/tidevice.exe" beforeDir="false" afterPath="$PROJECT_DIR$/resources/iproxy/tidevice.exe" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/tidevice_entry.py" beforeDir="false" afterPath="$PROJECT_DIR$/tidevice_entry.py" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -41,9 +40,6 @@
|
||||
<component name="HighlightingSettingsPerFile">
|
||||
<setting file="file://$PROJECT_DIR$/build.bat" root0="SKIP_INSPECTION" />
|
||||
</component>
|
||||
<component name="PerforceDirect.Settings">
|
||||
<option name="CHARSET" value="无" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"customColor": "",
|
||||
"associatedIndex": 5
|
||||
@@ -56,37 +52,53 @@
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"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.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": "F:/company code/AI item/20250820/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"
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"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": "F:/company code/AI item/20250820/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"
|
||||
}
|
||||
}</component>
|
||||
}]]></component>
|
||||
<component name="RecentsManager">
|
||||
<key name="MoveFile.RECENT_KEYS">
|
||||
<recent name="E:\Code\python\iOSAI\resources" />
|
||||
<recent name="E:\Code\python\iOSAI" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunAnythingCache">
|
||||
<myKeys>
|
||||
<visibility group="Grunt" flag="true" />
|
||||
<visibility group="Gulp" flag="true" />
|
||||
<visibility group="HTTP 请求" flag="true" />
|
||||
<visibility group="Node.js" flag="true" />
|
||||
<visibility group="Run Python file" flag="true" />
|
||||
<visibility group="Run conda command" flag="true" />
|
||||
<visibility group="Run pip command" flag="true" />
|
||||
<visibility group="npm" flag="true" />
|
||||
<visibility group="yarn" flag="true" />
|
||||
<visibility group="最近的项目" flag="true" />
|
||||
<visibility group="运行配置" flag="true" />
|
||||
</myKeys>
|
||||
</component>
|
||||
<component name="RunManager" selected="Python.Main">
|
||||
<configuration name="12" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="iOSAI" />
|
||||
@@ -157,18 +169,40 @@
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="Test" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||
<module name="iOSAI" />
|
||||
<option name="ENV_FILES" value="" />
|
||||
<option name="INTERPRETER_OPTIONS" value="" />
|
||||
<option name="PARENT_ENVS" value="true" />
|
||||
<envs>
|
||||
<env name="PYTHONUNBUFFERED" value="1" />
|
||||
</envs>
|
||||
<option name="SDK_HOME" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/Utils" />
|
||||
<option name="IS_MODULE_SDK" value="true" />
|
||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/Utils/Test.py" />
|
||||
<option name="PARAMETERS" value="" />
|
||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||
<option name="EMULATE_TERMINAL" value="false" />
|
||||
<option name="MODULE_MODE" value="false" />
|
||||
<option name="REDIRECT_INPUT" value="false" />
|
||||
<option name="INPUT_FILE" value="" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Python.123" />
|
||||
<item itemvalue="Python.Test" />
|
||||
<item itemvalue="Python.12" />
|
||||
<item itemvalue="Python.123" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-js-predefined-1d06a55b98c1-0b3e54e931b4-JavaScript-PY-241.18034.82" />
|
||||
<option value="bundled-python-sdk-975db3bf15a3-2767605e8bc2-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-241.18034.82" />
|
||||
<option value="bundled-python-sdk-ce6832f46686-7b97d883f26b-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-252.25557.178" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
@@ -233,7 +267,6 @@
|
||||
<workItem from="1757498954175" duration="6736000" />
|
||||
<workItem from="1757506636968" duration="5910000" />
|
||||
<workItem from="1757567423145" duration="16668000" />
|
||||
<workItem from="1757912502766" duration="32552000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="ai 开始测试">
|
||||
<option name="closed" value="true" />
|
||||
@@ -303,18 +336,15 @@
|
||||
</component>
|
||||
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
||||
<SUITE FILE_PATH="coverage/iOSAI$LogManager.coverage" NAME="LogManager 覆盖结果" MODIFIED="1756711414832" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Utils" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$123__1_.coverage" NAME="123 (1) 覆盖结果" MODIFIED="1756897091135" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$FlaskService.coverage" NAME="FlaskService 覆盖结果" MODIFIED="1756730187792" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/Module" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$test.coverage" NAME="test 覆盖结果" MODIFIED="1756467664420" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$1352.coverage" NAME="1352 覆盖结果" MODIFIED="1757662777051" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$windows_run.coverage" NAME="windows_run Coverage Results" MODIFIED="1756473558532" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/script" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$mac_wda_agent.coverage" NAME="mac_wda_agent Coverage Results" MODIFIED="1756473148639" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/script" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$123456.coverage" NAME="123456 覆盖结果" MODIFIED="1757672582575" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$2111.coverage" NAME="2111 覆盖结果" MODIFIED="1757330714370" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$456.coverage" NAME="456 覆盖结果" MODIFIED="1757654671631" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$123__1_.coverage" NAME="123 (1) 覆盖结果" MODIFIED="1756897091135" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$tidevice_entry.coverage" NAME="tidevice_entry 覆盖结果" MODIFIED="1757061969626" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$mac_wda_agent.coverage" NAME="mac_wda_agent Coverage Results" MODIFIED="1756473148639" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/script" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$ScriptManager.coverage" NAME="ScriptManager 覆盖结果" MODIFIED="1756896057801" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/script" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$Main.coverage" NAME="Main 覆盖结果" MODIFIED="1757946254179" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$123.coverage" NAME="123 覆盖结果" MODIFIED="1757946194042" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$Main.coverage" NAME="Main 覆盖结果" MODIFIED="1757579400023" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$123.coverage" NAME="123 覆盖结果" MODIFIED="1757587713569" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||
<SUITE FILE_PATH="coverage/iOSAI$2111.coverage" NAME="2111 覆盖结果" MODIFIED="1757330714370" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -119,7 +119,6 @@ class Deviceinfo(object):
|
||||
continue
|
||||
if len(self.deviceModelList) >= self.maxDeviceCount:
|
||||
continue
|
||||
port = self._alloc_port()
|
||||
try:
|
||||
self.connectDevice(udid) # 内部会 _add_model
|
||||
except Exception as e:
|
||||
|
||||
@@ -62,27 +62,66 @@ def start_socket_listener():
|
||||
print(f"[INFO] Socket listener started on port {port}, waiting for connections...")
|
||||
while True:
|
||||
try:
|
||||
LogManager.info(f"[INFO] Waiting for a new connection on port {port}...")
|
||||
print(f"[INFO] Waiting for a new connection on port {port}...")
|
||||
conn, addr = s.accept()
|
||||
LogManager.info(f"[INFO] Connection accepted from: {addr}")
|
||||
print(f"[INFO] Connection accepted from: {addr}")
|
||||
LogManager.info(f"[INFO] Connection from {addr}")
|
||||
except Exception as e:
|
||||
LogManager.error(f"[ERROR] accept 失败: {e}")
|
||||
continue
|
||||
|
||||
raw_data = conn.recv(1024).decode('utf-8').strip()
|
||||
LogManager.info(f"[INFO] Raw data received: {raw_data}")
|
||||
print(f"[INFO] Raw data received: {raw_data}")
|
||||
|
||||
data = json.loads(raw_data)
|
||||
LogManager.info(f"[INFO] Parsed data: {data}")
|
||||
print(f"[INFO] Parsed data: {data}")
|
||||
dataQueue.put(data)
|
||||
except Exception as conn_error:
|
||||
LogManager.error(f"[ERROR]连接处理失败: {conn_error}")
|
||||
print(f"[ERROR]连接处理失败: {conn_error}")
|
||||
# 独立线程处理单条连接,避免单客户端异常拖垮监听线程
|
||||
threading.Thread(target=_handle_conn, args=(conn, addr), daemon=True).start()
|
||||
# while True:
|
||||
# try:
|
||||
# LogManager.info(f"[INFO] Waiting for a new connection on port {port}...")
|
||||
# print(f"[INFO] Waiting for a new connection on port {port}...")
|
||||
# conn, addr = s.accept()
|
||||
# LogManager.info(f"[INFO] Connection accepted from: {addr}")
|
||||
# print(f"[INFO] Connection accepted from: {addr}")
|
||||
#
|
||||
# raw_data = conn.recv(1024).decode('utf-8').strip()
|
||||
# LogManager.info(f"[INFO] Raw data received: {raw_data}")
|
||||
# print(f"[INFO] Raw data received: {raw_data}")
|
||||
#
|
||||
# data = json.loads(raw_data)
|
||||
# LogManager.info(f"[INFO] Parsed data: {data}")
|
||||
# print(f"[INFO] Parsed data: {data}")
|
||||
# dataQueue.put(data)
|
||||
# except Exception as conn_error:
|
||||
# LogManager.error(f"[ERROR]连接处理失败: {conn_error}")
|
||||
# print(f"[ERROR]连接处理失败: {conn_error}")
|
||||
except Exception as e:
|
||||
LogManager.error(f"[ERROR]Socket服务启动失败: {e}")
|
||||
print(f"[ERROR]Socket服务启动失败: {e}")
|
||||
|
||||
def _handle_conn(conn: socket.socket, addr):
|
||||
try:
|
||||
with conn:
|
||||
# 1. 循环收包直到拿到完整 JSON
|
||||
buffer = ""
|
||||
while True:
|
||||
data = conn.recv(1024)
|
||||
if not data: # 对端关闭
|
||||
break
|
||||
buffer += data.decode('utf-8', errors='ignore')
|
||||
# 2. 尝试切出完整 JSON(简单按行,也可按长度头、分隔符)
|
||||
while True:
|
||||
line, sep, buffer = buffer.partition('\n')
|
||||
if not sep: # 没找到完整行
|
||||
break
|
||||
line = line.strip()
|
||||
if not line: # 空行跳过
|
||||
continue
|
||||
try:
|
||||
obj = json.loads(line)
|
||||
except json.JSONDecodeError as e:
|
||||
LogManager.warning(f"[WARN] 非法 JSON 丢弃: {line[:100]} {e}")
|
||||
continue
|
||||
# 3. 收到合法数据,塞进队列
|
||||
dataQueue.put(obj)
|
||||
LogManager.info(f"[INFO] 收到合法消息: {obj}")
|
||||
except Exception as e:
|
||||
LogManager.error(f"[ERROR] 连接处理异常: {e}")
|
||||
|
||||
|
||||
# 在独立线程中启动Socket服务
|
||||
listener_thread = threading.Thread(target=start_socket_listener, daemon=True)
|
||||
|
||||
@@ -27,104 +27,150 @@ class FlaskSubprocessManager:
|
||||
self.process: Optional[subprocess.Popen] = None
|
||||
self.comm_port = 34566
|
||||
self._stop_event = threading.Event()
|
||||
self._monitor_thread: Optional[threading.Thread] = None
|
||||
atexit.register(self.stop)
|
||||
LogManager.info("FlaskSubprocessManager 单例已初始化", udid="system")
|
||||
|
||||
# 可以把 _find_available_port 留着备用,但 start 前先校验端口是否被占用
|
||||
# ---------- 启动 ----------
|
||||
def start(self):
|
||||
with self._lock:
|
||||
if self._is_alive():
|
||||
LogManager.warning("子进程已在运行,无需重复启动", udid="system")
|
||||
return
|
||||
|
||||
env = os.environ.copy()
|
||||
env["FLASK_COMM_PORT"] = str(self.comm_port)
|
||||
|
||||
exe_path = Path(sys.executable).resolve()
|
||||
if exe_path.name.lower() in ("python.exe", "pythonw.exe"):
|
||||
exe_path = Path(sys.argv[0]).resolve()
|
||||
is_frozen = exe_path.suffix.lower() == ".exe" and exe_path.exists()
|
||||
|
||||
if is_frozen:
|
||||
cmd = [str(exe_path), "--role=flask"]
|
||||
cwd = str(exe_path.parent)
|
||||
else:
|
||||
cmd = [sys.executable, "-u", "-m", "Module.Main", "--role=flask"]
|
||||
cwd = str(Path(__file__).resolve().parent)
|
||||
|
||||
LogManager.info(f"准备启动 Flask 子进程: {cmd} cwd={cwd}", udid="system")
|
||||
|
||||
# 关键:不再自己 open 文件,直接走 LogManager
|
||||
# 用 PIPE 捕获,再转存到 system 级日志
|
||||
self.process = subprocess.Popen(
|
||||
cmd,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
bufsize=1,
|
||||
env=env,
|
||||
cwd=cwd,
|
||||
start_new_session=True
|
||||
)
|
||||
|
||||
# 守护线程:把子进程 stdout → LogManager.info/system
|
||||
threading.Thread(target=self._flush_stdout, daemon=True).start()
|
||||
|
||||
LogManager.info(f"Flask 子进程已启动,PID={self.process.pid},端口={self.comm_port}", udid="system")
|
||||
|
||||
if not self._wait_port_open(timeout=10):
|
||||
LogManager.error("等待端口监听超时,启动失败", udid="system")
|
||||
self.stop()
|
||||
raise RuntimeError("Flask 启动后 10 s 内未监听端口")
|
||||
|
||||
self._monitor_thread = threading.Thread(target=self._monitor, daemon=True)
|
||||
self._monitor_thread.start()
|
||||
LogManager.info("端口守护线程已启动", udid="system")
|
||||
|
||||
# ---------- 实时把子进程 stdout 刷到 system 日志 ----------
|
||||
def _flush_stdout(self):
|
||||
for line in iter(self.process.stdout.readline, ""):
|
||||
if line:
|
||||
LogManager.info(line.rstrip(), udid="system")
|
||||
self.process.stdout.close()
|
||||
|
||||
# ---------- 发送 ----------
|
||||
def send(self, data: Union[str, Dict, List]) -> bool:
|
||||
if isinstance(data, (dict, list)):
|
||||
data = json.dumps(data, ensure_ascii=False)
|
||||
try:
|
||||
with socket.create_connection(("127.0.0.1", self.comm_port), timeout=3.0) as s:
|
||||
s.sendall((data + "\n").encode("utf-8"))
|
||||
LogManager.info(f"数据已成功发送到 Flask 端口:{self.comm_port}", udid="system")
|
||||
return True
|
||||
except Exception as e:
|
||||
LogManager.error(f"发送失败:{e}", udid="system")
|
||||
return False
|
||||
|
||||
# ---------- 停止 ----------
|
||||
def stop(self):
|
||||
with self._lock:
|
||||
if getattr(self, 'process', None) is None:
|
||||
LogManager.info("无子进程需要停止", udid="system")
|
||||
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")
|
||||
except Exception as e:
|
||||
LogManager.error(f"停止子进程时异常:{e}", udid="system")
|
||||
finally:
|
||||
self.process = None
|
||||
self._stop_event.set()
|
||||
|
||||
# ---------- 端口守护 ----------
|
||||
def _monitor(self):
|
||||
LogManager.info("守护线程开始运行,周期性检查端口存活", udid="system")
|
||||
while not self._stop_event.wait(1.0):
|
||||
if not self._port_alive():
|
||||
LogManager.error("检测到端口不通,准备重启 Flask", udid="system")
|
||||
with self._lock:
|
||||
if self.process and self.process.poll() is None:
|
||||
self.stop()
|
||||
try:
|
||||
self.start()
|
||||
except Exception as e:
|
||||
LogManager.error(f"自动重启失败:{e}", udid="system")
|
||||
time.sleep(2)
|
||||
|
||||
# ---------- 辅助 ----------
|
||||
def _is_port_busy(self, port: int) -> bool:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.settimeout(0.2)
|
||||
return s.connect_ex(("127.0.0.1", port)) == 0
|
||||
|
||||
# 启动flask
|
||||
def start(self):
|
||||
"""启动 Flask 子进程(兼容打包后的 exe 和源码运行)"""
|
||||
with self._lock:
|
||||
if self.process is not None:
|
||||
LogManager.warning("子进程正在运行中!")
|
||||
raise RuntimeError("子进程已在运行中!")
|
||||
|
||||
env = os.environ.copy()
|
||||
env["FLASK_COMM_PORT"] = str(self.comm_port)
|
||||
|
||||
# —— 解析打包 exe 的稳健写法 ——
|
||||
exe_path = Path(sys.executable).resolve()
|
||||
if exe_path.name.lower() in ("python.exe", "pythonw.exe"):
|
||||
# Nuitka 某些场景里 sys.executable 可能指向 dist\python.exe(并不存在)
|
||||
exe_path = Path(sys.argv[0]).resolve()
|
||||
|
||||
is_frozen = exe_path.suffix.lower() == ".exe" and exe_path.exists()
|
||||
|
||||
if is_frozen:
|
||||
# 打包后的 exe:用当前 exe 自举
|
||||
cmd = [str(exe_path), "--role=flask"]
|
||||
cwd = str(exe_path.parent)
|
||||
else:
|
||||
# 源码运行:模块方式更稳
|
||||
cmd = [sys.executable, "-m", "Module.Main", "--role=flask"]
|
||||
cwd = str(Path(__file__).resolve().parent) # Module 目录
|
||||
|
||||
LogManager.info(f"[DEBUG] spawn: {cmd} (cwd={cwd}) exists(exe)={os.path.exists(cmd[0])}")
|
||||
print(f"[DEBUG] spawn: {cmd} (cwd={cwd}) exists(exe)={os.path.exists(cmd[0])}")
|
||||
|
||||
self.process = subprocess.Popen(
|
||||
cmd,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace", # 新增:遇到非 UTF-8 字节用 <20> 代替,避免崩溃
|
||||
bufsize=1,
|
||||
env=env,
|
||||
cwd=cwd,
|
||||
)
|
||||
|
||||
LogManager.info(f"Flask子进程启动 (PID: {self.process.pid}, 端口: {self.comm_port})")
|
||||
print(f"Flask子进程启动 (PID: {self.process.pid}, 端口: {self.comm_port})")
|
||||
|
||||
def print_output(stream, stream_name):
|
||||
while True:
|
||||
line = stream.readline()
|
||||
if not line:
|
||||
break
|
||||
print(f"{stream_name}: {line.strip()}")
|
||||
|
||||
threading.Thread(target=print_output, args=(self.process.stdout, "STDOUT"), daemon=True).start()
|
||||
threading.Thread(target=print_output, args=(self.process.stderr, "STDERR"), daemon=True).start()
|
||||
|
||||
def send(self, data: Union[str, Dict, List]) -> bool:
|
||||
"""通过Socket发送数据"""
|
||||
def _port_alive(self) -> bool:
|
||||
try:
|
||||
if not isinstance(data, str):
|
||||
data = json.dumps(data)
|
||||
# 等待子进程启动并准备好
|
||||
time.sleep(1) # 延时1秒,根据实际情况调整
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.connect(('127.0.0.1', self.comm_port))
|
||||
s.sendall((data + "\n").encode('utf-8'))
|
||||
with socket.create_connection(("127.0.0.1", self.comm_port), timeout=0.5):
|
||||
return True
|
||||
except ConnectionRefusedError:
|
||||
LogManager.error(f"连接被拒绝,确保子进程在端口 {self.comm_port} 上监听")
|
||||
print(f"连接被拒绝,确保子进程在端口 {self.comm_port} 上监听")
|
||||
return False
|
||||
except Exception as e:
|
||||
LogManager.error(f"发送失败: {e}")
|
||||
print(f"发送失败: {e}")
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def stop(self):
|
||||
with self._lock:
|
||||
if self.process and self.process.poll() is None:
|
||||
print(f"[INFO] Stopping Flask child process (PID: {self.process.pid})...")
|
||||
LogManager.info(f"[INFO] Stopping Flask child process (PID: {self.process.pid})...")
|
||||
self.process.terminate()
|
||||
self.process.wait()
|
||||
print("[INFO] Flask child process stopped.")
|
||||
LogManager.info("[INFO] Flask child process stopped.")
|
||||
self._stop_event.set()
|
||||
else:
|
||||
LogManager.info("[INFO] No Flask child process to stop.")
|
||||
print("[INFO] No Flask child process to stop.")
|
||||
def _wait_port_open(self, timeout: float) -> bool:
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < timeout:
|
||||
if self._port_alive():
|
||||
return True
|
||||
time.sleep(0.2)
|
||||
return False
|
||||
|
||||
def _is_alive(self) -> bool:
|
||||
return self.process is not None and self.process.poll() is None and self._port_alive()
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls) -> 'FlaskSubprocessManager':
|
||||
|
||||
@@ -88,8 +88,8 @@ class DevDiskImageDeployer:
|
||||
exists = dst.exists()
|
||||
if exists and not self.overwrite:
|
||||
skipped += 1
|
||||
if self.verbose:
|
||||
print(f"[SKIP] {dst} 已存在(目录)")
|
||||
# if self.verbose:
|
||||
# print(f"[SKIP] {dst} 已存在(目录)")
|
||||
continue
|
||||
if exists and self.overwrite and not self.dry_run:
|
||||
shutil.rmtree(dst)
|
||||
@@ -105,8 +105,8 @@ class DevDiskImageDeployer:
|
||||
exists = dst.exists()
|
||||
if exists and not self.overwrite:
|
||||
skipped += 1
|
||||
if self.verbose:
|
||||
print(f"[SKIP] {dst} 已存在(zip)")
|
||||
# if self.verbose:
|
||||
# print(f"[SKIP] {dst} 已存在(zip)")
|
||||
continue
|
||||
if exists and self.overwrite and not self.dry_run:
|
||||
dst.unlink()
|
||||
|
||||
Binary file not shown.
@@ -1,3 +1,18 @@
|
||||
# from tidevice.__main__ import main
|
||||
# if __name__ == '__main__':
|
||||
# main()
|
||||
|
||||
|
||||
# tidevice_entry.py
|
||||
import sys, traceback, os
|
||||
from tidevice.__main__ import main
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception:
|
||||
# 把 traceback 写到日志文件,但**不输出到控制台**
|
||||
with open(os.path.expanduser("~/tidevice_crash.log"), "a", encoding="utf-8") as f:
|
||||
traceback.print_exc(file=f)
|
||||
# 静默退出,**返回码 1**(父进程只认 returncode)
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user