阶段0: 设备池(SQLite devices 表 + core/device_pool.py 清单/在线状态/CRUD,首次启动自动从 STF 导入)+ STF 摘除迁移计划文档

This commit is contained in:
2026-08-17 11:11:08 +08:00
parent c111cb9a5e
commit 52c4d3aab4
4 changed files with 342 additions and 0 deletions
+162
View File
@@ -0,0 +1,162 @@
"""设备池(STF 替代数据源):SQLite devices 表 = 设备清单,本地 adb = 在线状态。
阶段 0:纯新增模块,不改现有行为。task_manager / web_server 的 STF 调用
在阶段 1 逐个切换到本模块,阶段 3 摘除 STF 后删除 seed 相关代码。
设计:
- list_configured() — devices 表里 enabled 的设备(清单,管理页维护)
- list_online() — 本机 `adb devices` 里 state=device 的设备(实时)
- list_ready() — configured ∩ online(调度用,取代 STF list_free_devices)
- CRUD — add/update/remove/set_enabled(设备池管理页用)
- 首次启动自动从 STF 导入现有设备(一次性,app_meta 标记防重复)
红线:全模块不 connect / 不 kill-server / 不 disconnect,遵守既有技术约束。
后台线程(task_manager 等)调用时由本模块自行推 app context,调用方无需关心。
"""
import time
from sqlalchemy import text
from core.adb_helper import _adb
from core.logger import get_logger
from core.models import db, Device
_log = get_logger("core.device_pool")
_app = None
def init_app(app):
"""web_server 启动时调用:绑定 Flask app,并做首次设备导入。"""
global _app
_app = app
try:
_seed_from_stf_if_empty()
except Exception as e:
_log.warning(f"设备池初始化异常(不影响启动): {e}")
def _ctx():
"""后台线程访问 db 需自行推 app context(与 task_manager._db 同思路)。"""
if _app is None:
raise RuntimeError("device_pool 未关联 Flask app(web_server 启动时调用 init_app)")
return _app.app_context()
# ================== 清单 ==================
def list_configured(enabled_only=True):
"""devices 表里的设备清单(serial 列表)。"""
with _ctx():
q = Device.query
if enabled_only:
q = q.filter_by(enabled=True)
return [d.serial for d in q.order_by(Device.serial).all()]
def list_devices():
"""全量设备行(设备池管理页用)。"""
with _ctx():
return [d.to_dict() for d in Device.query.order_by(Device.serial).all()]
# ================== 在线状态 ==================
def list_online():
"""本机 adb devices 中 state=device 的设备(实时)。"""
out = _adb("devices") or ""
serials = []
for line in out.splitlines()[1:]:
parts = line.split()
if len(parts) >= 2 and parts[0] and parts[1] == "device":
serials.append(parts[0])
return serials
def is_online(serial):
"""设备当前是否在线(本机 adb 可达且状态正常)。"""
return serial in list_online()
def list_ready():
"""可调度设备:清单 ∩ 在线(取代 STF list_free_devices)。"""
online = set(list_online())
return [s for s in list_configured() if s in online]
# ================== 管理(CRUD) ==================
def add_device(serial, name="", note="", enabled=True):
"""添加/更新设备(upsert)。返回 True 新增 / False 已存在并更新。"""
serial = (serial or "").strip()
if not serial:
return False
with _ctx():
d = Device.query.get(serial)
if d:
d.name, d.note, d.enabled = name or "", note or "", enabled
db.session.commit()
return False
db.session.add(Device(serial=serial, name=name or "", note=note or "",
enabled=enabled,
created_at=time.strftime("%Y-%m-%d %H:%M")))
db.session.commit()
_log.info(f"设备池新增设备: {serial}")
return True
def remove_device(serial):
"""删除设备。返回是否删除成功。"""
with _ctx():
d = Device.query.get(serial)
if not d:
return False
db.session.delete(d)
db.session.commit()
_log.info(f"设备池删除设备: {serial}")
return True
def set_enabled(serial, enabled):
"""启用/停用设备(停用后不参与调度)。返回是否成功。"""
with _ctx():
d = Device.query.get(serial)
if not d:
return False
d.enabled = enabled
db.session.commit()
return True
# ================== 首次导入(阶段 3 摘除 STF 后删除) ==================
def _seed_from_stf_if_empty():
"""首次启动把 STF 现有设备导入本地清单。
条件:device 表为空 且 未导入过(app_meta 标记)。STF 不可达时跳过,
不写标记,下次启动重试。摘除 STF 后此函数随 stf_client 一起删除。
"""
try:
from core.stf_client import STFClient
except Exception:
return
with _ctx():
if Device.query.count() > 0:
return
seeded = db.session.execute(text(
"SELECT value FROM app_meta WHERE key='devices_seeded_from_stf'")).scalar()
if seeded:
return
try:
# 导入全部 serial(不限于 present):STF 池 = 配置的舰队,
# present=false 的是掉线/陈旧记录,导入后显示离线,管理页可删
serials = [d["serial"] for d in STFClient().list_all_devices()]
except Exception as e:
_log.warning(f"从 STF 导入设备清单失败(可稍后手动添加): {e}")
return
if not serials:
_log.warning("STF 无在线设备,跳过设备池首次导入")
return
now = time.strftime("%Y-%m-%d %H:%M")
for s in serials:
db.session.add(Device(serial=s, created_at=now))
db.session.execute(text(
"INSERT OR REPLACE INTO app_meta(key,value) VALUES('devices_seeded_from_stf','1')"))
db.session.commit()
_log.info(f"设备池首次导入 {len(serials)} 台设备(来源 STF)")