临时提交
This commit is contained in:
@@ -1,111 +1,199 @@
|
||||
import subprocess
|
||||
import threading
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import wda
|
||||
import threading
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
from tidevice import Usbmux
|
||||
from Entity.DeviceModel import DeviceModel
|
||||
from Entity.Variables import WdaAppBundleId
|
||||
from Module.FlaskSubprocessManager import FlaskSubprocessManager
|
||||
from Utils.LogManager import LogManager
|
||||
|
||||
threadLock = threading.Lock()
|
||||
|
||||
class Deviceinfo(object):
|
||||
def __init__(self):
|
||||
self.deviceIndex = 0
|
||||
# 投屏端口
|
||||
# 投屏端口(本地映射端口起始值,会递增)
|
||||
self.screenProxy = 9110
|
||||
# 存放pid的数组
|
||||
self.pidList = []
|
||||
# 设备列表
|
||||
self.deviceArray = []
|
||||
# 获取到县城管理类
|
||||
self.manager = FlaskSubprocessManager.get_instance()
|
||||
# 给前端的设备模型数组
|
||||
self.deviceModelList = []
|
||||
|
||||
# 监听设备连接
|
||||
# 记录 iproxy Popen 进程:[{ "id": udid, "target": Popen }, ...]
|
||||
self.pidList: List[Dict] = []
|
||||
# 当前已连接的设备(tidevice 的 Device 对象列表)
|
||||
self.deviceArray: List = []
|
||||
|
||||
# 子进程通信(向前端发送设备信息)
|
||||
self.manager = FlaskSubprocessManager.get_instance()
|
||||
# 已发给前端的设备模型列表(用于拔出时发 type=2)
|
||||
self.deviceModelList: List[DeviceModel] = []
|
||||
|
||||
# ----------------------------
|
||||
# 监听设备连接(死循环,内部捕获异常)
|
||||
# ----------------------------
|
||||
def startDeviceListener(self):
|
||||
LogManager.info("Device Listener started", "listener")
|
||||
while True:
|
||||
lists = Usbmux().device_list()
|
||||
# 添加设备逻辑
|
||||
try:
|
||||
lists = Usbmux().device_list()
|
||||
except Exception as e:
|
||||
# 另一台电脑常见:usbmuxd 连接失败(未安装 iTunes/Apple Mobile Device Support)
|
||||
LogManager.warning(f"usbmuxd 连接失败: {e}。请确认已安装 iTunes/Apple Mobile Device Support,并在手机上“信任此电脑”", "listener")
|
||||
time.sleep(2)
|
||||
continue
|
||||
|
||||
# 新接入设备
|
||||
for device in lists:
|
||||
if device not in self.deviceArray:
|
||||
self.screenProxy += 1
|
||||
self.connectDevice(device.udid)
|
||||
self.deviceArray.append(device)
|
||||
try:
|
||||
self.connectDevice(device.udid)
|
||||
self.deviceArray.append(device)
|
||||
except Exception as e:
|
||||
LogManager.error(f"连接设备失败 {device.udid}: {e}", device.udid)
|
||||
|
||||
# 处理拔出设备的逻辑
|
||||
def removeDevice():
|
||||
set1 = set(self.deviceArray)
|
||||
set2 = set(lists)
|
||||
difference = set1 - set2
|
||||
differenceList = list(difference)
|
||||
for i in differenceList:
|
||||
for j in self.deviceArray:
|
||||
# 判断是否为差异设备
|
||||
if i.udid == j.udid:
|
||||
# 从设备模型中删除数据
|
||||
for a in self.deviceModelList:
|
||||
if i.udid == a.deviceId:
|
||||
a.type = 2
|
||||
# 发送数据
|
||||
self.manager.send(a.toDict())
|
||||
self.deviceModelList.remove(a)
|
||||
# 拔出设备处理
|
||||
self._removeDisconnected(lists)
|
||||
|
||||
for k in self.pidList:
|
||||
# 干掉端口短发进程
|
||||
if j.udid == k["id"]:
|
||||
target = k["target"]
|
||||
target.kill()
|
||||
self.pidList.remove(k)
|
||||
# 删除已经拔出的设备
|
||||
self.deviceArray.remove(j)
|
||||
|
||||
removeDevice()
|
||||
time.sleep(1)
|
||||
|
||||
# 连接设备
|
||||
def connectDevice(self, identifier):
|
||||
# ----------------------------
|
||||
# 连接单台设备:启动 WDA、读取屏参、通知前端、映射投屏端口
|
||||
# ----------------------------
|
||||
def connectDevice(self, identifier: str):
|
||||
# 1) 连接 WDA(USBClient -> 设备 8100)
|
||||
try:
|
||||
d = wda.USBClient(identifier, 8100)
|
||||
LogManager.info("启动wda成功", identifier)
|
||||
|
||||
size = d.window_size()
|
||||
width = size.width
|
||||
height = size.height
|
||||
scale = d.scale
|
||||
# 创建模型
|
||||
model = DeviceModel(identifier, self.screenProxy, width, height, scale, type=1)
|
||||
self.deviceModelList.append(model)
|
||||
# 发送数据
|
||||
self.manager.send(model.toDict())
|
||||
|
||||
LogManager.info("启动 WDA 成功", identifier)
|
||||
except Exception as e:
|
||||
LogManager.error("启动wda失败。请检查wda是否正常", identifier)
|
||||
return
|
||||
LogManager.error(f"启动 WDA 失败,请检查手机是否已信任、WDA 是否正常。错误: {e}", identifier)
|
||||
return # 不抛出到外层,保持监听循环健壮
|
||||
|
||||
d.app_start(WdaAppBundleId)
|
||||
d.home()
|
||||
time.sleep(2)
|
||||
target = self.relayDeviceScreenPort(identifier)
|
||||
self.pidList.append({
|
||||
"target": target,
|
||||
"id": identifier
|
||||
})
|
||||
|
||||
|
||||
|
||||
# 转发设备端口
|
||||
def relayDeviceScreenPort(self, udid):
|
||||
# 2) 读取屏幕信息(失败不影响主流程)
|
||||
width, height, scale = 0, 0, 1.0
|
||||
try:
|
||||
command = f"iproxy.exe -u {udid} {self.screenProxy} 9100"
|
||||
# 创建一个没有窗口的进程
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
startupinfo.wShowWindow = 0
|
||||
r = subprocess.Popen(command, shell=True, startupinfo=startupinfo)
|
||||
return r
|
||||
size = d.window_size()
|
||||
width, height = size.width, size.height
|
||||
scale = d.scale
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return 0
|
||||
LogManager.warning(f"读取屏幕信息失败:{e}", identifier)
|
||||
|
||||
# 3) 组装模型并发送给前端
|
||||
model = DeviceModel(identifier, self.screenProxy, width, height, scale, type=1)
|
||||
self.deviceModelList.append(model)
|
||||
try:
|
||||
self.manager.send(model.toDict())
|
||||
except Exception as e:
|
||||
LogManager.warning(f"向前端发送设备模型失败:{e}", identifier)
|
||||
|
||||
# 4) 可选:启动你的 app 并回到桌面
|
||||
try:
|
||||
d.app_start(WdaAppBundleId)
|
||||
d.home()
|
||||
except Exception as e:
|
||||
LogManager.warning(f"启动/切回桌面失败:{e}", identifier)
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# 5) 本地端口 -> 设备端口 的映射(投屏:本地 self.screenProxy -> 设备 9100)
|
||||
target = self.relayDeviceScreenPort(identifier)
|
||||
self.pidList.append({"target": target, "id": identifier})
|
||||
|
||||
# ----------------------------
|
||||
# 处理拔出设备:发通知、关掉 iproxy、移出状态
|
||||
# ----------------------------
|
||||
def _removeDisconnected(self, current_list):
|
||||
set1 = set(self.deviceArray)
|
||||
set2 = set(current_list)
|
||||
difference = list(set1 - set2) # 在旧集合中但不在新集合中 -> 已拔出
|
||||
|
||||
for i in difference:
|
||||
udid = i.udid
|
||||
# 1) 通知前端:type = 2
|
||||
for a in list(self.deviceModelList):
|
||||
if udid == a.deviceId:
|
||||
a.type = 2
|
||||
try:
|
||||
self.manager.send(a.toDict())
|
||||
except Exception as e:
|
||||
LogManager.warning(f"发送下线事件失败:{e}", udid)
|
||||
self.deviceModelList.remove(a)
|
||||
|
||||
# 2) 关掉对应的 iproxy
|
||||
for k in list(self.pidList):
|
||||
if udid == k["id"]:
|
||||
target = k.get("target")
|
||||
try:
|
||||
if target and target.poll() is None:
|
||||
target.kill()
|
||||
except Exception:
|
||||
pass
|
||||
self.pidList.remove(k)
|
||||
|
||||
# 3) 从已连接集合中移除
|
||||
try:
|
||||
self.deviceArray.remove(i)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# ----------------------------
|
||||
# 路径:打包/源码都能找到根目录、iproxy 目录和 iproxy 可执行文件
|
||||
# ----------------------------
|
||||
def _base_dir(self) -> Path:
|
||||
"""
|
||||
打包后:返回 exe 所在目录;
|
||||
源码运行:返回项目根目录(Module 的上一级)
|
||||
"""
|
||||
if getattr(sys, "frozen", False):
|
||||
return Path(sys.executable).resolve().parent
|
||||
return Path(__file__).resolve().parents[1] # iOSAI/ 作为根
|
||||
|
||||
def _iproxy_dir(self) -> Path:
|
||||
"""返回打包后的 iproxy 目录(你现在放在 Module/iproxy/)"""
|
||||
return self._base_dir() / "Module" / "iproxy"
|
||||
|
||||
def _iproxy_path(self) -> Path:
|
||||
"""返回 iproxy 可执行文件路径(Windows 为 iproxy.exe)"""
|
||||
exe_name = "iproxy.exe" if os.name == "nt" else "iproxy"
|
||||
return self._iproxy_dir() / exe_name
|
||||
|
||||
# ----------------------------
|
||||
# 端口映射:启动 iproxy(设置 cwd 和 PATH,隐藏窗口)
|
||||
# ----------------------------
|
||||
def relayDeviceScreenPort(self, udid: str) -> Optional[subprocess.Popen]:
|
||||
try:
|
||||
iproxy = self._iproxy_path()
|
||||
iproxy_dir = self._iproxy_dir()
|
||||
|
||||
if not iproxy.exists():
|
||||
raise FileNotFoundError(f"iproxy not found: {iproxy}")
|
||||
|
||||
# 继承环境并把 iproxy 目录加入 PATH,方便 DLL 解析
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = str(iproxy_dir) + os.pathsep + env.get("PATH", "")
|
||||
|
||||
# Windows 隐藏子进程窗口
|
||||
CREATE_NO_WINDOW = 0x08000000 if os.name == "nt" else 0
|
||||
|
||||
p = subprocess.Popen(
|
||||
[str(iproxy), "-u", udid, str(self.screenProxy), "9100"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
creationflags=CREATE_NO_WINDOW,
|
||||
cwd=str(iproxy_dir), # 关键:工作目录设为 iproxy 所在目录
|
||||
env=env, # 关键:把 iproxy_dir 注入 PATH
|
||||
text=True, # 你后面如果要读 stdout/stderr 的话更方便
|
||||
encoding="utf-8",
|
||||
bufsize=1
|
||||
)
|
||||
LogManager.info(f"启动 iproxy 成功,本地 {self.screenProxy} -> 设备 9100", udid)
|
||||
return p
|
||||
|
||||
except Exception as e:
|
||||
LogManager.error(f"启动 iproxy 失败:{e}", udid)
|
||||
return None
|
||||
279
Module/FlaskService.py
Normal file
279
Module/FlaskService.py
Normal file
@@ -0,0 +1,279 @@
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import threading
|
||||
import warnings
|
||||
from queue import Queue
|
||||
from typing import Any, Dict
|
||||
from Utils.AiUtils import AiUtils
|
||||
from Utils.Requester import Requester
|
||||
|
||||
import tidevice
|
||||
import wda
|
||||
from flask import Flask, request
|
||||
from flask_cors import CORS
|
||||
from Entity.ResultData import ResultData
|
||||
from Utils.ControlUtils import ControlUtils
|
||||
from Utils.ThreadManager import ThreadManager
|
||||
from script.ScriptManager import ScriptManager
|
||||
from Entity.Variables import accountToken
|
||||
from Entity.Variables import anchorList, addModelToAnchorList
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
||||
listData = []
|
||||
dataQueue = Queue()
|
||||
|
||||
|
||||
def start_socket_listener():
|
||||
port = int(os.getenv('FLASK_COMM_PORT', 0))
|
||||
print(f"Received port from environment: {port}")
|
||||
if port <= 0:
|
||||
print("⚠️ 未获取到通信端口,跳过Socket监听")
|
||||
return
|
||||
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
# 设置端口复用,避免端口被占用时无法绑定
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
||||
# 尝试绑定端口
|
||||
try:
|
||||
s.bind(('127.0.0.1', port))
|
||||
print(f"[INFO] Socket successfully bound to port {port}")
|
||||
except Exception as bind_error:
|
||||
print(f"[ERROR] ❌ 端口绑定失败: {bind_error}")
|
||||
return
|
||||
|
||||
# 开始监听
|
||||
s.listen()
|
||||
print(f"[INFO] Socket listener started on port {port}, waiting for connections...")
|
||||
|
||||
while True:
|
||||
try:
|
||||
print(f"[INFO] Waiting for a new connection on port {port}...")
|
||||
conn, addr = s.accept()
|
||||
print(f"[INFO] Connection accepted from: {addr}")
|
||||
|
||||
raw_data = conn.recv(1024).decode('utf-8').strip()
|
||||
print(f"[INFO] Raw data received: {raw_data}")
|
||||
|
||||
data = json.loads(raw_data)
|
||||
print(f"[INFO] Parsed data: {data}")
|
||||
dataQueue.put(data)
|
||||
except Exception as conn_error:
|
||||
print(f"[ERROR] ❌ 连接处理失败: {conn_error}")
|
||||
except Exception as e:
|
||||
print(f"[ERROR] ❌ Socket服务启动失败: {e}")
|
||||
|
||||
|
||||
# 在独立线程中启动Socket服务
|
||||
listener_thread = threading.Thread(target=start_socket_listener, daemon=True)
|
||||
listener_thread.start()
|
||||
|
||||
|
||||
@app.route('/passToken', methods=['POST'])
|
||||
def passToken():
|
||||
data = request.get_json()
|
||||
accountToken = data['token']
|
||||
print(accountToken)
|
||||
|
||||
Requester.requestComments()
|
||||
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
|
||||
# 获取设备列表
|
||||
@app.route('/deviceList', methods=['GET'])
|
||||
def deviceList():
|
||||
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()
|
||||
|
||||
|
||||
# 获取设备应用列表
|
||||
@app.route('/deviceAppList', methods=['POST'])
|
||||
def deviceAppList():
|
||||
param = request.get_json()
|
||||
udid = param["udid"]
|
||||
apps = ControlUtils.getDeviceAppList(udid)
|
||||
return ResultData(data=apps).toJson()
|
||||
|
||||
|
||||
# 打开指定app
|
||||
@app.route('/launchApp', methods=['POST'])
|
||||
def launchApp():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
bundleId = body.get("bundleId")
|
||||
t = tidevice.Device(udid)
|
||||
t.app_start(bundleId)
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
|
||||
# 回到首页
|
||||
@app.route('/toHome', methods=['POST'])
|
||||
def toHome():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
client = wda.USBClient(udid)
|
||||
client.home()
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
|
||||
# 点击事件
|
||||
@app.route('/tapAction', methods=['POST'])
|
||||
def tapAction():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
client = wda.USBClient(udid)
|
||||
session = client.session()
|
||||
session.appium_settings({"snapshotMaxDepth": 0})
|
||||
x = body.get("x")
|
||||
y = body.get("y")
|
||||
session.tap(x, y)
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
|
||||
# 拖拽事件
|
||||
@app.route('/swipeAction', methods=['POST'])
|
||||
def swipeAction():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
direction = body.get("direction")
|
||||
client = wda.USBClient(udid)
|
||||
session = client.session()
|
||||
session.appium_settings({"snapshotMaxDepth": 0})
|
||||
|
||||
if direction == 1:
|
||||
session.swipe_up()
|
||||
elif direction == 2:
|
||||
session.swipe_left()
|
||||
elif direction == 3:
|
||||
session.swipe_down()
|
||||
else:
|
||||
session.swipe_right()
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
|
||||
# 长按事件
|
||||
@app.route('/longPressAction', methods=['POST'])
|
||||
def longPressAction():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
x = body.get("x")
|
||||
y = body.get("y")
|
||||
client = wda.USBClient(udid)
|
||||
session = client.session()
|
||||
session.appium_settings({"snapshotMaxDepth": 5})
|
||||
session.tap_hold(x, y, 1.0)
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
|
||||
# 养号
|
||||
@app.route('/growAccount', methods=['POST'])
|
||||
def growAccount():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
|
||||
manager = ScriptManager()
|
||||
event = threading.Event()
|
||||
# 启动脚本
|
||||
thread = threading.Thread(target=manager.growAccount, args=(udid, event))
|
||||
thread.start()
|
||||
# 添加到线程管理
|
||||
ThreadManager.add(udid, thread, event)
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
|
||||
# 观看直播
|
||||
@app.route("/watchLiveForGrowth", methods=['POST'])
|
||||
def watchLiveForGrowth():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
manager = ScriptManager()
|
||||
event = threading.Event()
|
||||
thread = threading.Thread(target=manager.watchLiveForGrowth, args=(udid, event))
|
||||
thread.start()
|
||||
# 添加到线程管理
|
||||
ThreadManager.add(udid, thread, event)
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
|
||||
# 停止脚本
|
||||
@app.route("/stopScript", methods=['POST'])
|
||||
def stopScript():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
code, msg = ThreadManager.stop(udid)
|
||||
return ResultData(code=code, data="", msg=msg).toJson()
|
||||
|
||||
|
||||
# 传递主播数据
|
||||
@app.route('/passAnchorData', methods=['POST'])
|
||||
def passAnchorData():
|
||||
data: Dict[str, Any] = request.get_json()
|
||||
# 设备列表
|
||||
idList = data.get("deviceList", [])
|
||||
# 主播列表
|
||||
acList = data.get("anchorList", [])
|
||||
# 是否需要回复
|
||||
needReply = data.get("needReply", False)
|
||||
# 添加主播数据
|
||||
addModelToAnchorList(acList)
|
||||
# 启动线程,执行脚本
|
||||
for udid in idList:
|
||||
manager = ScriptManager()
|
||||
event = threading.Event()
|
||||
# 启动脚本
|
||||
thread = threading.Thread(target=manager.greetNewFollowers, args=(udid, needReply, event))
|
||||
thread.start()
|
||||
# 添加到线程管理
|
||||
ThreadManager.add(udid, thread, event)
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
|
||||
# 添加临时数据
|
||||
@app.route("/addTempAnchorData", methods=['POST'])
|
||||
def addTempAnchorData():
|
||||
data = request.get_json()
|
||||
addModelToAnchorList(data)
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
|
||||
# 获取当前屏幕上的聊天信息
|
||||
@app.route("/getChatTextInfo", methods=['POST'])
|
||||
def getChatTextInfo():
|
||||
data = request.get_json()
|
||||
udid = data.get("udid")
|
||||
client = wda.USBClient(udid)
|
||||
session = client.session()
|
||||
xml = session.source()
|
||||
result = AiUtils.extract_messages_from_xml(xml)
|
||||
return ResultData(data=result).toJson()
|
||||
|
||||
|
||||
# 监控消息
|
||||
@app.route("/replyMessages", methods=['POST'])
|
||||
def monitorMessages():
|
||||
body = request.get_json()
|
||||
udid = body.get("udid")
|
||||
manager = ScriptManager()
|
||||
event = threading.Event()
|
||||
thread = threading.Thread(target=manager.replyMessages, args=(udid, event))
|
||||
thread.start()
|
||||
# 添加到线程管理
|
||||
ThreadManager.add(udid, thread, event)
|
||||
return ResultData(data="").toJson()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run("0.0.0.0", port=5000, debug=True, use_reloader=False)
|
||||
@@ -1,10 +1,12 @@
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import atexit
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union, Dict, List
|
||||
|
||||
class FlaskSubprocessManager:
|
||||
@@ -20,38 +22,59 @@ class FlaskSubprocessManager:
|
||||
|
||||
def _init_manager(self):
|
||||
self.process: Optional[subprocess.Popen] = None
|
||||
self.comm_port = self._find_available_port()
|
||||
self.comm_port = 34567
|
||||
self._stop_event = threading.Event()
|
||||
atexit.register(self.stop)
|
||||
|
||||
def _find_available_port(self):
|
||||
"""动态获取可用端口"""
|
||||
# 可以把 _find_available_port 留着备用,但 start 前先校验端口是否被占用
|
||||
def _is_port_busy(self, port: int) -> bool:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind(('0.0.0.0', 0))
|
||||
return s.getsockname()[1]
|
||||
s.settimeout(0.2)
|
||||
return s.connect_ex(("127.0.0.1", port)) == 0
|
||||
|
||||
# 启动flask
|
||||
def start(self):
|
||||
"""启动子进程(Windows兼容方案)"""
|
||||
"""启动 Flask 子进程(兼容打包后的 exe 和源码运行)"""
|
||||
with self._lock:
|
||||
if self.process is not None:
|
||||
raise RuntimeError("子进程已在运行中!")
|
||||
# 通过环境变量传递通信端口
|
||||
|
||||
env = os.environ.copy()
|
||||
env['FLASK_COMM_PORT'] = str(self.comm_port)
|
||||
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 目录
|
||||
|
||||
print(f"[DEBUG] spawn: {cmd} (cwd={cwd}) exists(exe)={os.path.exists(cmd[0])}")
|
||||
|
||||
self.process = subprocess.Popen(
|
||||
['python', 'Flask/FlaskService.py'], # 启动一个子进程 FlaskService.py
|
||||
stdin=subprocess.PIPE, # 标准输入流,用于向子进程发送数据
|
||||
stdout=subprocess.PIPE, # 标准输出流,用于接收子进程的输出
|
||||
stderr=subprocess.PIPE, # 标准错误流,用于接收子进程的错误信息
|
||||
text=True, # 以文本模式打开流,否则以二进制模式打开
|
||||
bufsize=1, # 缓冲区大小设置为 1,表示行缓冲
|
||||
encoding='utf-8', # 指定编码为 UTF-8,确保控制台输出不会报错
|
||||
env=env # 指定子进程的环境变量
|
||||
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,
|
||||
)
|
||||
print(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()
|
||||
@@ -59,7 +82,6 @@ class FlaskSubprocessManager:
|
||||
break
|
||||
print(f"{stream_name}: {line.strip()}")
|
||||
|
||||
# 启动两个线程分别处理 stdout 和 stderr
|
||||
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()
|
||||
|
||||
|
||||
@@ -1,17 +1,66 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from Module.DeviceInfo import Deviceinfo
|
||||
from Module.FlaskSubprocessManager import FlaskSubprocessManager
|
||||
from Utils.LogManager import LogManager
|
||||
|
||||
# 项目入口
|
||||
if __name__ == "__main__":
|
||||
# 清空日志
|
||||
LogManager.clearLogs()
|
||||
time.sleep(1)
|
||||
# 确定 exe 或 py 文件所在目录
|
||||
BASE = Path(getattr(sys, 'frozen', False) and sys.executable or __file__).resolve().parent
|
||||
LOG_DIR = BASE / "log"
|
||||
LOG_DIR.mkdir(exist_ok=True) # 确保 log 目录存在
|
||||
|
||||
print(f"日志目录: {LOG_DIR}")
|
||||
|
||||
def _run_flask_role():
|
||||
from Module import FlaskService
|
||||
port = int(os.getenv("FLASK_COMM_PORT", "34567")) # 固定端口的兜底仍是 34567
|
||||
app_factory = getattr(FlaskService, "create_app", None)
|
||||
app = app_factory() if callable(app_factory) else FlaskService.app
|
||||
app.run(host="0.0.0.0", port=port, debug=False, use_reloader=False)
|
||||
|
||||
if "--role=flask" in sys.argv:
|
||||
_run_flask_role()
|
||||
sys.exit(0)
|
||||
|
||||
# 项目入口
|
||||
# ... 省略前面的 import 和函数 ...
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 清空日志等
|
||||
LogManager.clearLogs()
|
||||
|
||||
# 启动 Flask 子进程
|
||||
manager = FlaskSubprocessManager.get_instance()
|
||||
manager.start()
|
||||
|
||||
info = Deviceinfo()
|
||||
info.startDeviceListener()
|
||||
# 设备监听(即使失败/很快返回,也不会导致主进程退出)
|
||||
try:
|
||||
info = Deviceinfo()
|
||||
info.startDeviceListener()
|
||||
except Exception as e:
|
||||
print("[WARN] Device listener not running:", e)
|
||||
|
||||
# === 保活:阻塞主线程,直到收到 Ctrl+C/关闭 ===
|
||||
import threading, time, signal
|
||||
|
||||
stop = threading.Event()
|
||||
|
||||
def _handle(_sig, _frm):
|
||||
stop.set()
|
||||
|
||||
# Windows 上 SIGINT/SIGTERM 都可以拦到
|
||||
try:
|
||||
signal.signal(signal.SIGINT, _handle)
|
||||
signal.signal(signal.SIGTERM, _handle)
|
||||
except Exception:
|
||||
pass # 某些环境可能不支持,忽略
|
||||
|
||||
try:
|
||||
while not stop.is_set():
|
||||
time.sleep(1)
|
||||
finally:
|
||||
# 进程退出前记得把子进程关掉
|
||||
manager.stop()
|
||||
|
||||
BIN
Module/iproxy/bz2.dll
Normal file
BIN
Module/iproxy/bz2.dll
Normal file
Binary file not shown.
BIN
Module/iproxy/getopt.dll
Normal file
BIN
Module/iproxy/getopt.dll
Normal file
Binary file not shown.
BIN
Module/iproxy/idevice_id.exe
Normal file
BIN
Module/iproxy/idevice_id.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicebackup.exe
Normal file
BIN
Module/iproxy/idevicebackup.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicebackup2.exe
Normal file
BIN
Module/iproxy/idevicebackup2.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicebtlogger.exe
Normal file
BIN
Module/iproxy/idevicebtlogger.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicecrashreport.exe
Normal file
BIN
Module/iproxy/idevicecrashreport.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicedate.exe
Normal file
BIN
Module/iproxy/idevicedate.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicedebug.exe
Normal file
BIN
Module/iproxy/idevicedebug.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicedebugserverproxy.exe
Normal file
BIN
Module/iproxy/idevicedebugserverproxy.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicedevmodectl.exe
Normal file
BIN
Module/iproxy/idevicedevmodectl.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicediagnostics.exe
Normal file
BIN
Module/iproxy/idevicediagnostics.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/ideviceenterrecovery.exe
Normal file
BIN
Module/iproxy/ideviceenterrecovery.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/ideviceimagemounter.exe
Normal file
BIN
Module/iproxy/ideviceimagemounter.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/ideviceinfo.exe
Normal file
BIN
Module/iproxy/ideviceinfo.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicename.exe
Normal file
BIN
Module/iproxy/idevicename.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicenotificationproxy.exe
Normal file
BIN
Module/iproxy/idevicenotificationproxy.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicepair.exe
Normal file
BIN
Module/iproxy/idevicepair.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/ideviceprovision.exe
Normal file
BIN
Module/iproxy/ideviceprovision.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicerestore.exe
Normal file
BIN
Module/iproxy/idevicerestore.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicescreenshot.exe
Normal file
BIN
Module/iproxy/idevicescreenshot.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicesetlocation.exe
Normal file
BIN
Module/iproxy/idevicesetlocation.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/idevicesyslog.exe
Normal file
BIN
Module/iproxy/idevicesyslog.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/inetcat.exe
Normal file
BIN
Module/iproxy/inetcat.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/irecovery.exe
Normal file
BIN
Module/iproxy/irecovery.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/libcrypto-3.dll
Normal file
BIN
Module/iproxy/libcrypto-3.dll
Normal file
Binary file not shown.
BIN
Module/iproxy/libcurl.dll
Normal file
BIN
Module/iproxy/libcurl.dll
Normal file
Binary file not shown.
BIN
Module/iproxy/libssl-3.dll
Normal file
BIN
Module/iproxy/libssl-3.dll
Normal file
Binary file not shown.
BIN
Module/iproxy/plistutil.exe
Normal file
BIN
Module/iproxy/plistutil.exe
Normal file
Binary file not shown.
BIN
Module/iproxy/readline.dll
Normal file
BIN
Module/iproxy/readline.dll
Normal file
Binary file not shown.
BIN
Module/iproxy/zip.dll
Normal file
BIN
Module/iproxy/zip.dll
Normal file
Binary file not shown.
BIN
Module/iproxy/zlib1.dll
Normal file
BIN
Module/iproxy/zlib1.dll
Normal file
Binary file not shown.
Reference in New Issue
Block a user