fix: 删除任务/分组持久化(重启不再复活)+ feat: APK 安装支持本机 USB 设备
This commit is contained in:
+25
-16
@@ -274,27 +274,36 @@ class ApkManager:
|
|||||||
def _install_one(self, serial, apk_path, package_name=""):
|
def _install_one(self, serial, apk_path, package_name=""):
|
||||||
"""直连设备安装 APK。
|
"""直连设备安装 APK。
|
||||||
|
|
||||||
serial 即设备的 adb 网络地址(IP:5555),直接 adb connect 即可。
|
serial 为 adb 网络地址(IP:5555)时直接 adb connect;
|
||||||
|
本机 USB 有线设备(serial 无冒号,如 ZY322XXXX)已在 adb 中,无需 connect。
|
||||||
不经过 STF occupy/release,安装完断开连接,app 永久保留。
|
不经过 STF occupy/release,安装完断开连接,app 永久保留。
|
||||||
"""
|
"""
|
||||||
from .adb_helper import _adb
|
from .adb_helper import _adb
|
||||||
self._set_item(serial, "installing", f"正在连接 {serial}...")
|
self._set_item(serial, "installing", f"正在连接 {serial}...")
|
||||||
try:
|
try:
|
||||||
# adb connect(带重试,设备网络可能需要几次握手)
|
if ":" in serial:
|
||||||
connected = False
|
# 网络设备:adb connect(带重试,设备网络可能需要几次握手)
|
||||||
last_out = ""
|
connected = False
|
||||||
for attempt in range(1, 4): # 最多 3 次
|
last_out = ""
|
||||||
out = _adb("connect", serial)
|
for attempt in range(1, 4): # 最多 3 次
|
||||||
last_out = out
|
out = _adb("connect", serial)
|
||||||
if "connected" in out.lower() and "failed" not in out.lower():
|
last_out = out
|
||||||
connected = True
|
if "connected" in out.lower() and "failed" not in out.lower():
|
||||||
break
|
connected = True
|
||||||
_log.info(f"[{serial}] adb connect 第{attempt}次: {out}")
|
break
|
||||||
time.sleep(1 + attempt)
|
_log.info(f"[{serial}] adb connect 第{attempt}次: {out}")
|
||||||
if not connected:
|
time.sleep(1 + attempt)
|
||||||
_log.warning(f"[{serial}] adb connect 失败: 输出={last_out}")
|
if not connected:
|
||||||
self._set_item(serial, "failed", f"adb connect 失败: {last_out[:150]}")
|
_log.warning(f"[{serial}] adb connect 失败: 输出={last_out}")
|
||||||
return
|
self._set_item(serial, "failed", f"adb connect 失败: {last_out[:150]}")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# 本机 USB 设备:确认已在 adb devices 列表中
|
||||||
|
dev_out = _adb("devices")
|
||||||
|
if serial not in (dev_out or ""):
|
||||||
|
self._set_item(serial, "failed",
|
||||||
|
"设备未在本地 adb 列表中,请检查 USB 连接/驱动")
|
||||||
|
return
|
||||||
|
|
||||||
# 安装
|
# 安装
|
||||||
self._set_item(serial, "installing", "正在安装...")
|
self._set_item(serial, "installing", "正在安装...")
|
||||||
|
|||||||
@@ -458,6 +458,12 @@ class TaskManager:
|
|||||||
if name in self.groups:
|
if name in self.groups:
|
||||||
del self.groups[name]
|
del self.groups[name]
|
||||||
self._save_groups()
|
self._save_groups()
|
||||||
|
# 同上:显式删数据库行,避免删除的分组重启后复活
|
||||||
|
with self._db():
|
||||||
|
row = GroupRow.query.filter_by(name=name).first()
|
||||||
|
if row:
|
||||||
|
db.session.delete(row)
|
||||||
|
db.session.commit()
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -492,6 +498,13 @@ class TaskManager:
|
|||||||
if job_id in self.jobs:
|
if job_id in self.jobs:
|
||||||
del self.jobs[job_id]
|
del self.jobs[job_id]
|
||||||
self._save_jobs()
|
self._save_jobs()
|
||||||
|
# _save_jobs 只做 upsert 不会删行:必须显式删数据库行,
|
||||||
|
# 否则删除的任务重启后会从数据库重新加载回来
|
||||||
|
with self._db():
|
||||||
|
row = JobRow.query.get(job_id)
|
||||||
|
if row:
|
||||||
|
db.session.delete(row)
|
||||||
|
db.session.commit()
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -2365,11 +2365,10 @@ async function deleteApk(id){
|
|||||||
async function openInstallModal(apkId){
|
async function openInstallModal(apkId){
|
||||||
const apk=_apksCache.find(a=>a.id===apkId);
|
const apk=_apksCache.find(a=>a.id===apkId);
|
||||||
const apkName=apk?(apk.display_name||'APK'):'APK';
|
const apkName=apk?(apk.display_name||'APK'):'APK';
|
||||||
const sr=await apiGet('/api/status');if(!sr||!sr.ok)return;
|
const r=await apiGet('/api/apks/install/devices');
|
||||||
const devices=sr.devices||[];
|
const online=(r&&r.ok)?(r.devices||[]):[];
|
||||||
const online=devices.filter(d=>d.present);
|
|
||||||
if(!online.length){
|
if(!online.length){
|
||||||
showToast('没有在线设备','error');return;
|
showToast('没有可用设备(STF 池或本机 adb 均无)','error');return;
|
||||||
}
|
}
|
||||||
const body=
|
const body=
|
||||||
'<div style="margin-bottom:12px">'+
|
'<div style="margin-bottom:12px">'+
|
||||||
@@ -2383,11 +2382,11 @@ async function openInstallModal(apkId){
|
|||||||
'</div>'+
|
'</div>'+
|
||||||
'<div id="install-dev-list" class="device-list">'+
|
'<div id="install-dev-list" class="device-list">'+
|
||||||
online.map(d=>{
|
online.map(d=>{
|
||||||
const running=d.worker_status==='running'||d.worker_status==='connecting';
|
const isUsb=d.source==='usb';
|
||||||
const st='<span class="label label-'+(running?'success':'default')+'" style="margin-left:6px">'+(running?'运行中':'空闲')+'</span>';
|
const st='<span class="label label-'+(isUsb?'primary':'default')+'" style="margin-left:6px">'+(isUsb?'本地USB':(d.source==='stf'?'STF':'adb'))+'</span>';
|
||||||
// serial 唯一,显示 IP + 型号便于区分同型号设备
|
// serial 唯一,显示 IP + 型号便于区分同型号设备
|
||||||
const label=(d.serial||'').replace(/:5555$/,'')+' · '+(d.device_name||d.model||'设备');
|
const label=(d.serial||'').replace(/:5555$/,'')+' · '+(d.model||(isUsb?'USB 有线设备':'设备'));
|
||||||
return '<label><input type="checkbox" value="'+esc(d.serial)+'"'+(running?'':' checked')+'>'+esc(label)+st+'</label>';
|
return '<label><input type="checkbox" value="'+esc(d.serial)+'" checked>'+esc(label)+st+'</label>';
|
||||||
}).join('')+
|
}).join('')+
|
||||||
'</div>';
|
'</div>';
|
||||||
|
|
||||||
|
|||||||
+30
-2
@@ -363,7 +363,7 @@ def api_groups_update(name):
|
|||||||
|
|
||||||
|
|
||||||
@app.route("/api/groups/<name>", methods=["DELETE"])
|
@app.route("/api/groups/<name>", methods=["DELETE"])
|
||||||
@login_required
|
@perm_required(PERM_TASKS)
|
||||||
def api_groups_delete(name):
|
def api_groups_delete(name):
|
||||||
if mgr.delete_group(name):
|
if mgr.delete_group(name):
|
||||||
return jsonify({"ok": True, "msg": "分组已删除"})
|
return jsonify({"ok": True, "msg": "分组已删除"})
|
||||||
@@ -728,8 +728,36 @@ def api_apks_delete(apk_id):
|
|||||||
return jsonify({"ok": False, "error": msg}), 400
|
return jsonify({"ok": False, "error": msg}), 400
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/api/apks/install/devices")
|
||||||
|
@perm_required(PERM_APKS)
|
||||||
|
def api_apks_install_devices():
|
||||||
|
"""可安装设备列表(应用管理):STF 在线设备池 + 本机 adb 设备(含 USB 有线连接)。
|
||||||
|
|
||||||
|
source: stf=STF 池 / usb=本机有线 adb / adb=本机网络 adb。
|
||||||
|
"""
|
||||||
|
devices, seen = [], set()
|
||||||
|
try:
|
||||||
|
for d in stf.list_all_devices():
|
||||||
|
serial = d.get("serial", "")
|
||||||
|
if serial and d.get("present") and d.get("ready"):
|
||||||
|
devices.append({"serial": serial,
|
||||||
|
"model": d.get("model") or d.get("manufacturer") or "",
|
||||||
|
"source": "stf"})
|
||||||
|
seen.add(serial)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
out = _adb("devices")
|
||||||
|
for line in (out or "").splitlines()[1:]:
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) >= 2 and parts[0] and parts[0] not in seen:
|
||||||
|
devices.append({"serial": parts[0], "model": "",
|
||||||
|
"source": "usb" if ":" not in parts[0] else "adb"})
|
||||||
|
seen.add(parts[0])
|
||||||
|
return jsonify({"ok": True, "devices": devices})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/apks/install", methods=["POST"])
|
@app.route("/api/apks/install", methods=["POST"])
|
||||||
@login_required
|
@perm_required(PERM_APKS)
|
||||||
def api_apks_install():
|
def api_apks_install():
|
||||||
"""批量安装 APK 到指定设备。参数: {apk_id, serials:[]}"""
|
"""批量安装 APK 到指定设备。参数: {apk_id, serials:[]}"""
|
||||||
data = request.json or {}
|
data = request.json or {}
|
||||||
|
|||||||
Reference in New Issue
Block a user