Compare commits
2 Commits
da2ac45177
...
26fc6bb8e6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26fc6bb8e6 | ||
|
|
690b17ec58 |
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Editor-based HTTP Client requests
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
6
.idea/git_toolbox_blame.xml
generated
6
.idea/git_toolbox_blame.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="GitToolBoxBlameSettings">
|
|
||||||
<option name="version" value="2" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
15
.idea/git_toolbox_prj.xml
generated
15
.idea/git_toolbox_prj.xml
generated
@@ -1,15 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="GitToolBoxProjectSettings">
|
|
||||||
<option name="commitMessageIssueKeyValidationOverride">
|
|
||||||
<BoolValueOverride>
|
|
||||||
<option name="enabled" value="true" />
|
|
||||||
</BoolValueOverride>
|
|
||||||
</option>
|
|
||||||
<option name="commitMessageValidationEnabledOverride">
|
|
||||||
<BoolValueOverride>
|
|
||||||
<option name="enabled" value="true" />
|
|
||||||
</BoolValueOverride>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
.idea/iOSAI.iml
generated
6
.idea/iOSAI.iml
generated
@@ -1,10 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="PYTHON_MODULE" version="4">
|
<module type="PYTHON_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" />
|
||||||
</content>
|
|
||||||
<orderEntry type="jdk" jdkName="Python 3.12 (IOS-AI)" jdkType="Python SDK" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
5
.idea/inspectionProfiles/Project_Default.xml
generated
5
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -4,8 +4,11 @@
|
|||||||
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
<option name="ignoredPackages">
|
<option name="ignoredPackages">
|
||||||
<value>
|
<value>
|
||||||
<list size="1">
|
<list size="4">
|
||||||
<item index="0" class="java.lang.String" itemvalue="PySide6" />
|
<item index="0" class="java.lang.String" itemvalue="PySide6" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="pyusb" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="PyGObject-stubs" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="PyGObject" />
|
||||||
</list>
|
</list>
|
||||||
</value>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
5
.idea/misc.xml
generated
5
.idea/misc.xml
generated
@@ -1,7 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="Black">
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12" project-jdk-type="Python SDK" />
|
||||||
<option name="sdkName" value="Python 3.12 (iOSAI)" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (IOS-AI)" project-jdk-type="Python SDK" />
|
|
||||||
</project>
|
</project>
|
||||||
118
.idea/workspace.xml
generated
Normal file
118
.idea/workspace.xml
generated
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="eceeff5e-51c1-459c-a911-d21ec090a423" name="Changes" comment="">
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/.gitignore" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/git_toolbox_blame.xml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/git_toolbox_prj.xml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/iOSAI.iml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/inspectionProfiles/Project_Default.xml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/inspectionProfiles/profiles_settings.xml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/misc.xml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/modules.xml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/.idea/vcs.xml" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/Module/FlaskService.py" beforeDir="false" afterPath="$PROJECT_DIR$/Module/FlaskService.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/Utils/AiUtils.py" beforeDir="false" afterPath="$PROJECT_DIR$/Utils/AiUtils.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/IOSAI.exe" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/certifi/cacert.pem" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/cv2/opencv_videoio_ffmpeg4120_64.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/jaraco/text/Lorem ipsum.txt" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/libcrypto-3.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/libffi-8.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/libssl-3.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/numpy.libs/libscipy_openblas64_-43e11ff0749b8cbe0a615c9cf6737e0e.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/numpy.libs/msvcp140-263139962577ecda4cd9469ca360a746.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/python3.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/python312.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/pythoncom312.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/pywintypes312.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/resources/add.png" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/resources/advertisement.png" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/resources/back.png" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/resources/comment.png" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/resources/icon.ico" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/resources/like.png" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/resources/search.png" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/tcl86t.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/tk86t.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/vcruntime140.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/vcruntime140_1.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/out/Main.dist/zlib1.dll" beforeDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/script/ScriptManager.py" beforeDir="false" afterPath="$PROJECT_DIR$/script/ScriptManager.py" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectColorInfo"><![CDATA[{
|
||||||
|
"customColor": "",
|
||||||
|
"associatedIndex": 5
|
||||||
|
}]]></component>
|
||||||
|
<component name="ProjectId" id="31K5VnrGt5SBVafKVv8gi0HPOF6" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
|
"keyToString": {
|
||||||
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
|
"git-widget-placeholder": "Merging main",
|
||||||
|
"last_opened_file_path": "E:/Code/python/iOSAI/Module/Main.py",
|
||||||
|
"nodejs_package_manager_path": "npm",
|
||||||
|
"vue.rearranger.settings.migration": "true"
|
||||||
|
}
|
||||||
|
}]]></component>
|
||||||
|
<component name="RunManager">
|
||||||
|
<configuration name="Main" type="PythonConfigurationType" factoryName="Python" 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="SDK_NAME" value="Python 3.12" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/Module/Main.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>
|
||||||
|
</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" />
|
||||||
|
</set>
|
||||||
|
</attachedChunks>
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="eceeff5e-51c1-459c-a911-d21ec090a423" name="Changes" comment="" />
|
||||||
|
<created>1755259950275</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1755259950275</updated>
|
||||||
|
<workItem from="1755259951484" duration="38000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,111 +1,199 @@
|
|||||||
import subprocess
|
# -*- coding: utf-8 -*-
|
||||||
import threading
|
import os
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
import wda
|
import wda
|
||||||
|
import threading
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Dict, Optional
|
||||||
|
|
||||||
from tidevice import Usbmux
|
from tidevice import Usbmux
|
||||||
from Entity.DeviceModel import DeviceModel
|
from Entity.DeviceModel import DeviceModel
|
||||||
from Entity.Variables import WdaAppBundleId
|
from Entity.Variables import WdaAppBundleId
|
||||||
from Module.FlaskSubprocessManager import FlaskSubprocessManager
|
from Module.FlaskSubprocessManager import FlaskSubprocessManager
|
||||||
from Utils.LogManager import LogManager
|
from Utils.LogManager import LogManager
|
||||||
|
|
||||||
threadLock = threading.Lock()
|
|
||||||
|
|
||||||
class Deviceinfo(object):
|
class Deviceinfo(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.deviceIndex = 0
|
self.deviceIndex = 0
|
||||||
# 投屏端口
|
# 投屏端口(本地映射端口起始值,会递增)
|
||||||
self.screenProxy = 9110
|
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):
|
def startDeviceListener(self):
|
||||||
|
LogManager.info("Device Listener started", "listener")
|
||||||
while True:
|
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:
|
for device in lists:
|
||||||
if device not in self.deviceArray:
|
if device not in self.deviceArray:
|
||||||
self.screenProxy += 1
|
self.screenProxy += 1
|
||||||
self.connectDevice(device.udid)
|
try:
|
||||||
self.deviceArray.append(device)
|
self.connectDevice(device.udid)
|
||||||
|
self.deviceArray.append(device)
|
||||||
|
except Exception as e:
|
||||||
|
LogManager.error(f"连接设备失败 {device.udid}: {e}", device.udid)
|
||||||
|
|
||||||
# 处理拔出设备的逻辑
|
# 拔出设备处理
|
||||||
def removeDevice():
|
self._removeDisconnected(lists)
|
||||||
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)
|
|
||||||
|
|
||||||
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)
|
time.sleep(1)
|
||||||
|
|
||||||
# 连接设备
|
# ----------------------------
|
||||||
def connectDevice(self, identifier):
|
# 连接单台设备:启动 WDA、读取屏参、通知前端、映射投屏端口
|
||||||
|
# ----------------------------
|
||||||
|
def connectDevice(self, identifier: str):
|
||||||
|
# 1) 连接 WDA(USBClient -> 设备 8100)
|
||||||
try:
|
try:
|
||||||
d = wda.USBClient(identifier, 8100)
|
d = wda.USBClient(identifier, 8100)
|
||||||
LogManager.info("启动wda成功", identifier)
|
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())
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LogManager.error("启动wda失败。请检查wda是否正常", identifier)
|
LogManager.error(f"启动 WDA 失败,请检查手机是否已信任、WDA 是否正常。错误: {e}", identifier)
|
||||||
return
|
return # 不抛出到外层,保持监听循环健壮
|
||||||
|
|
||||||
d.app_start(WdaAppBundleId)
|
# 2) 读取屏幕信息(失败不影响主流程)
|
||||||
d.home()
|
width, height, scale = 0, 0, 1.0
|
||||||
time.sleep(2)
|
|
||||||
target = self.relayDeviceScreenPort(identifier)
|
|
||||||
self.pidList.append({
|
|
||||||
"target": target,
|
|
||||||
"id": identifier
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 转发设备端口
|
|
||||||
def relayDeviceScreenPort(self, udid):
|
|
||||||
try:
|
try:
|
||||||
command = f"iproxy.exe -u {udid} {self.screenProxy} 9100"
|
size = d.window_size()
|
||||||
# 创建一个没有窗口的进程
|
width, height = size.width, size.height
|
||||||
startupinfo = subprocess.STARTUPINFO()
|
scale = d.scale
|
||||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
||||||
startupinfo.wShowWindow = 0
|
|
||||||
r = subprocess.Popen(command, shell=True, startupinfo=startupinfo)
|
|
||||||
return r
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
LogManager.warning(f"读取屏幕信息失败:{e}", identifier)
|
||||||
return 0
|
|
||||||
|
# 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
|
||||||
@@ -137,10 +137,8 @@ def tapAction():
|
|||||||
client = wda.USBClient(udid)
|
client = wda.USBClient(udid)
|
||||||
session = client.session()
|
session = client.session()
|
||||||
session.appium_settings({"snapshotMaxDepth": 0})
|
session.appium_settings({"snapshotMaxDepth": 0})
|
||||||
|
|
||||||
x = body.get("x")
|
x = body.get("x")
|
||||||
y = body.get("y")
|
y = body.get("y")
|
||||||
|
|
||||||
session.tap(x, y)
|
session.tap(x, y)
|
||||||
return ResultData(data="").toJson()
|
return ResultData(data="").toJson()
|
||||||
|
|
||||||
@@ -6,6 +6,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
from typing import Optional, Union, Dict, List
|
from typing import Optional, Union, Dict, List
|
||||||
|
|
||||||
class FlaskSubprocessManager:
|
class FlaskSubprocessManager:
|
||||||
@@ -21,46 +22,59 @@ class FlaskSubprocessManager:
|
|||||||
|
|
||||||
def _init_manager(self):
|
def _init_manager(self):
|
||||||
self.process: Optional[subprocess.Popen] = None
|
self.process: Optional[subprocess.Popen] = None
|
||||||
self.comm_port = self._find_available_port()
|
self.comm_port = 34567
|
||||||
self._stop_event = threading.Event()
|
self._stop_event = threading.Event()
|
||||||
atexit.register(self.stop)
|
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:
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
s.bind(('0.0.0.0', 0))
|
s.settimeout(0.2)
|
||||||
return s.getsockname()[1]
|
return s.connect_ex(("127.0.0.1", port)) == 0
|
||||||
|
|
||||||
|
# 启动flask
|
||||||
def start(self):
|
def start(self):
|
||||||
"""启动子进程(Windows兼容方案)"""
|
"""启动 Flask 子进程(兼容打包后的 exe 和源码运行)"""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
if self.process is not None:
|
if self.process is not None:
|
||||||
raise RuntimeError("子进程已在运行中!")
|
raise RuntimeError("子进程已在运行中!")
|
||||||
# 通过环境变量传递通信端口
|
|
||||||
base_dir = os.path.dirname(os.path.abspath(__file__)) # 当前脚本所在路径
|
|
||||||
script_path = os.path.abspath(os.path.join(base_dir, "../Flask/FlaskService.py"))
|
|
||||||
python_executable = os.path.abspath(sys.executable) # 获取当前解释器路径
|
|
||||||
|
|
||||||
if not os.path.isfile(script_path):
|
|
||||||
raise FileNotFoundError(f"❌ 找不到 FlaskService.py: {script_path}")
|
|
||||||
|
|
||||||
# 通过环境变量传递通信端口
|
|
||||||
env = os.environ.copy()
|
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(
|
self.process = subprocess.Popen(
|
||||||
[python_executable, script_path], # 启动一个子进程 FlaskService.py
|
cmd,
|
||||||
stdin=subprocess.PIPE, # 标准输入流,用于向子进程发送数据
|
stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE, # 标准输出流,用于接收子进程的输出
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE, # 标准错误流,用于接收子进程的错误信息
|
stderr=subprocess.PIPE,
|
||||||
text=True, # 以文本模式打开流,否则以二进制模式打开
|
text=True,
|
||||||
bufsize=1, # 缓冲区大小设置为 1,表示行缓冲
|
encoding="utf-8",
|
||||||
encoding='utf-8', # 指定编码为 UTF-8,确保控制台输出不会报错
|
errors="replace", # 新增:遇到非 UTF-8 字节用 <20> 代替,避免崩溃
|
||||||
env=env # 指定子进程的环境变量
|
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):
|
def print_output(stream, stream_name):
|
||||||
while True:
|
while True:
|
||||||
line = stream.readline()
|
line = stream.readline()
|
||||||
@@ -68,7 +82,6 @@ class FlaskSubprocessManager:
|
|||||||
break
|
break
|
||||||
print(f"{stream_name}: {line.strip()}")
|
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.stdout, "STDOUT"), daemon=True).start()
|
||||||
threading.Thread(target=print_output, args=(self.process.stderr, "STDERR"), 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
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from Module.DeviceInfo import Deviceinfo
|
from Module.DeviceInfo import Deviceinfo
|
||||||
from Module.FlaskSubprocessManager import FlaskSubprocessManager
|
from Module.FlaskSubprocessManager import FlaskSubprocessManager
|
||||||
from Utils.LogManager import LogManager
|
from Utils.LogManager import LogManager
|
||||||
|
|
||||||
# 项目入口
|
# 确定 exe 或 py 文件所在目录
|
||||||
if __name__ == "__main__":
|
BASE = Path(getattr(sys, 'frozen', False) and sys.executable or __file__).resolve().parent
|
||||||
# 清空日志
|
LOG_DIR = BASE / "log"
|
||||||
LogManager.clearLogs()
|
LOG_DIR.mkdir(exist_ok=True) # 确保 log 目录存在
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
|
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 = FlaskSubprocessManager.get_instance()
|
||||||
manager.start()
|
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.
@@ -1,78 +1,76 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
class LogManager:
|
class LogManager:
|
||||||
# 获取项目根目录
|
# 运行根目录:打包后取 exe 目录;源码运行取项目目录
|
||||||
projectRoot = os.path.dirname(os.path.dirname(__file__))
|
if getattr(sys, "frozen", False):
|
||||||
logDir = os.path.join(projectRoot, "log")
|
projectRoot = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
projectRoot = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
# 类变量,存储日志记录器
|
logDir = os.path.join(projectRoot, "log")
|
||||||
_loggers = {}
|
_loggers = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _setupLogger(cls, udid, name, logName, level=logging.INFO):
|
def _setupLogger(cls, udid, name, logName, level=logging.INFO):
|
||||||
"""设置日志记录器"""
|
"""创建或获取 logger,并绑定到文件"""
|
||||||
deviceLogDir = os.path.join(cls.logDir, udid)
|
deviceLogDir = os.path.join(cls.logDir, udid)
|
||||||
os.makedirs(deviceLogDir, exist_ok=True) # 确保日志目录存在
|
os.makedirs(deviceLogDir, exist_ok=True)
|
||||||
logFile = os.path.join(deviceLogDir, logName)
|
logFile = os.path.join(deviceLogDir, logName)
|
||||||
logger = logging.getLogger(f"{udid}_{name}")
|
|
||||||
|
logger_name = f"{udid}_{name}"
|
||||||
|
logger = logging.getLogger(logger_name)
|
||||||
logger.setLevel(level)
|
logger.setLevel(level)
|
||||||
fileHandler = logging.FileHandler(logFile, mode="a", encoding="utf-8")
|
|
||||||
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
# 避免重复添加 handler
|
||||||
datefmt="%Y-%m-%d %H:%M:%S")
|
if not any(
|
||||||
fileHandler.setFormatter(formatter)
|
isinstance(h, logging.FileHandler) and h.baseFilename == os.path.abspath(logFile)
|
||||||
logger.addHandler(fileHandler)
|
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
|
return logger
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _getLogger(cls, udid, name, logName, level=logging.INFO):
|
|
||||||
"""获取或初始化日志记录器"""
|
|
||||||
if udid not in cls._loggers:
|
|
||||||
cls._loggers[udid] = {}
|
|
||||||
if name not in cls._loggers[udid]:
|
|
||||||
cls._loggers[udid][name] = cls._setupLogger(udid, name, logName, level)
|
|
||||||
return cls._loggers[udid][name]
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def info(cls, text, udid):
|
def info(cls, text, udid):
|
||||||
"""记录 INFO 级别的日志"""
|
cls._setupLogger(udid, "infoLogger", "info.log", level=logging.INFO).info(f"[{udid}] {text}")
|
||||||
logger = cls._getLogger(udid, "infoLogger", "info.log", level=logging.INFO)
|
|
||||||
logger.info(f"[{udid}] {text}")
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def warning(cls, text, udid):
|
def warning(cls, text, udid):
|
||||||
"""记录 WARNING 级别的日志"""
|
cls._setupLogger(udid, "warningLogger", "warning.log", level=logging.WARNING).warning(f"[{udid}] {text}")
|
||||||
logger = cls._getLogger(udid, "warningLogger", "warning.log", level=logging.WARNING)
|
|
||||||
logger.warning(f"[{udid}] {text}")
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def error(cls, text, udid):
|
def error(cls, text, udid):
|
||||||
"""记录 ERROR 级别的日志"""
|
cls._setupLogger(udid, "errorLogger", "error.log", level=logging.ERROR).error(f"[{udid}] {text}")
|
||||||
logger = cls._getLogger(udid, "errorLogger", "error.log", level=logging.ERROR)
|
|
||||||
logger.error(f"[{udid}] {text}")
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def clearLogs(cls):
|
def clearLogs(cls):
|
||||||
"""清空整个 log 目录下的所有内容"""
|
"""启动时清空 log 目录"""
|
||||||
print("开始清空日志...")
|
print("开始清空日志...")
|
||||||
|
|
||||||
# 关闭所有日志记录器的处理器
|
# 关闭所有 handler
|
||||||
for udid in cls._loggers:
|
for name, logger in logging.Logger.manager.loggerDict.items():
|
||||||
for name in cls._loggers[udid]:
|
if isinstance(logger, logging.Logger):
|
||||||
logger = cls._loggers[udid][name]
|
for handler in logger.handlers[:]:
|
||||||
for handler in logger.handlers[:]: # 使用切片避免在迭代时修改列表
|
try:
|
||||||
handler.close()
|
handler.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
logger.removeHandler(handler)
|
logger.removeHandler(handler)
|
||||||
print(f"关闭了 {udid}_{name} 的处理器")
|
|
||||||
|
|
||||||
# 删除整个 log 目录
|
# 删除并重建日志目录
|
||||||
if os.path.exists(cls.logDir):
|
if os.path.exists(cls.logDir):
|
||||||
shutil.rmtree(cls.logDir) # 删除目录及其所有内容
|
shutil.rmtree(cls.logDir)
|
||||||
print(f"删除了 {cls.logDir}")
|
print(f"删除了 {cls.logDir}")
|
||||||
os.makedirs(cls.logDir, exist_ok=True) # 重新创建空的 log 目录
|
os.makedirs(cls.logDir, exist_ok=True)
|
||||||
print(f"重新创建了 {cls.logDir}")
|
print(f"重新创建了 {cls.logDir}")
|
||||||
else:
|
print("日志清空完成")
|
||||||
print(f"{cls.logDir} 不存在,无需删除")
|
|
||||||
print("日志清空完成")
|
|
||||||
|
|||||||
23
build.bat
Normal file
23
build.bat
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
python -m nuitka Module/Main.py ^
|
||||||
|
--standalone ^
|
||||||
|
--msvc=latest ^
|
||||||
|
--windows-console-mode=force ^
|
||||||
|
--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=resources=resources ^
|
||||||
|
--include-data-dir=Module/iproxy=Module/iproxy ^
|
||||||
|
--windows-icon-from-ico=resources/icon.ico
|
||||||
BIN
resources/icon.ico
Normal file
BIN
resources/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@@ -451,6 +451,7 @@ class ScriptManager():
|
|||||||
print("监控回复消息")
|
print("监控回复消息")
|
||||||
# 执行回复消息逻辑
|
# 执行回复消息逻辑
|
||||||
self.monitorMessages(session, udid)
|
self.monitorMessages(session, udid)
|
||||||
|
# 判断是否有首页按钮
|
||||||
homeButton = AiUtils.findHomeButton(udid)
|
homeButton = AiUtils.findHomeButton(udid)
|
||||||
if homeButton.exists:
|
if homeButton.exists:
|
||||||
homeButton.click()
|
homeButton.click()
|
||||||
@@ -469,12 +470,12 @@ class ScriptManager():
|
|||||||
session.appium_settings({"snapshotMaxDepth": 15})
|
session.appium_settings({"snapshotMaxDepth": 15})
|
||||||
# 点击搜索按钮
|
# 点击搜索按钮
|
||||||
ControlUtils.clickSearch(session)
|
ControlUtils.clickSearch(session)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
session.appium_settings({"snapshotMaxDepth": 15})
|
session.appium_settings({"snapshotMaxDepth": 15})
|
||||||
# 点击搜索按钮
|
# 点击搜索按钮
|
||||||
ControlUtils.clickSearch(session)
|
ControlUtils.clickSearch(session)
|
||||||
|
|
||||||
|
|
||||||
def replyMessages(self, udid, event):
|
def replyMessages(self, udid, event):
|
||||||
client = wda.USBClient(udid)
|
client = wda.USBClient(udid)
|
||||||
session = client.session()
|
session = client.session()
|
||||||
|
|||||||
Reference in New Issue
Block a user