Files
svfs/pyproject.toml
T
chakcy e0d5bad7b2 feat: 消息模块(MQTT 推送 + 统一总线),并补退出登录/顺序编排/设置分页签
一、消息模块:服务端主动推送 + 插件定向分发
* topics.py:主题方案 <prefix>/<org>/user/<user>/message、.../user/<user>/plugin/<id>、
  .../broadcast/{message,plugin/<id>}。org 是租户位(多租户上线不用改格式,broker
  按 <prefix>/<org>/# 下发 ACL 即可隔离);用户名/插件 id 经**可逆**百分号编码占
  一个层级,客户端与示例服务端共用同一套拼主题函数;
* bus.py:统一消息总线——按**主题**分类(主机消息进消息中心、插件消息不进,仪表盘
  数据那类业务载荷永远不会混进给人看的列表)、归属校验(org/user 不符即丢弃,broker
  ACL 配错也挡得住)、按 id 去重(QoS 1 重投与"推送/补齐重叠"都不会重复显示)、
  插件消息按插件缓存 50 条,等页面打开后一次性补投;
* mqtt.py + mqtt_service.py:paho 适配(可选依赖 [mqtt])+ Qt 适配(paho 的网络
  线程经排队信号把消息交给主线程的总线);重连交给 paho(指数退避),重连成功
  自动重新订阅;没装 paho / 没配 broker 都只是"没有实时推送",HTTP 补齐照常;
* worker.py:登录后**先**补齐(GET /api/messages?since=)再连 MQTT,两条路进同一
  总线;在途结果带身份快照,退出登录/换账号后丢弃;
* 界面:侧栏 ✉ 未读徽标 + 消息中心(级别/标题/时间/来源/广播标记、立即同步、
  全部已读、清空、通道状态灯);warn/error 顺手发系统托盘通知;
* 插件侧:新权限 messages(PLUGIN_API_VERSION→5),页面里 svfs.messages.subscribe();
  宿主经 window.__svfsPush 投递,早到的先排队、订阅时补发(桥调试台已接上演示);
* examples/plugin_server.py:/api/messages、/api/push(kind=message|plugin)、
  /api/broadcast;配了 SVFS_MQTT_HOST 就真发布到 broker,并把 broker 地址随登录
  响应下发(/api/login 与 /api/me 都带),客户端不用手填。

二、退出登录 = 关掉主窗口 + 回到登录界面
* 强制登录下"未登录的主窗口"本就不该存在:重新登录成功用新会话开回主窗口,关掉
  登录窗即退出客户端;退出时断开推送、清掉该账号收到的消息与总线身份(下一个人
  不该看到上一个人的消息),并收掉已打开的插件页面;
* 用户中心的"退出登录"先关掉自己再进登录门,避免模态窗口叠模态。

三、插件顺序编排
* installer.arrange():编排过的在前、未编排的按名称;管理页卡片加 ↑/↓,侧栏/图标栏/
  管理页三者一致;存 settings 的 plugin_order,只影响显示(不动包与注册表);
* "启动时打开"的默认规则改为"显示顺序里第一个启用的插件"(原来是按 id 排);
* 新增「恢复默认顺序」(编排过才出现)。

四、修复
* 收缩侧栏后页面左上角露出一块纯白方块:页面栈对 Qt 是透明的(主题只给
  QMainWindow/QDialog 设了背景),侧栏让出的那条在重绘时序里露白。给 QStackedWidget
  不透明底色,并加像素级回归用例(撤掉修复即 FAIL,实测 1237 个白像素);
* build_opener() 每次都要隐式枚举 Windows 证书库:全量测试跑几百次后以断点异常
  崩在证书枚举里(faulthandler 定位在 _load_windows_store_certs)。改成进程内只建
  一次 SSL context、启动时在主线程预建;
* 在途的消息补齐结果会喂给新会话(退出登录时用户名已空 → 拼主题抛异常,被 Qt 回调
  静默吞掉):带身份快照,不匹配即丢弃。

五、设置界面按类型分页签(插件 / 服务器 / 存储 / 网络 / 诊断)
* 每页内容在滚动区里(窗口拉小或以后再加项是滚动条,而不是把控件压扁);
* 新增「消息推送(MQTT)」一节:broker(host / host:port / mqtts://host:8883)、组织、
  端口、TLS、主题前缀、broker 账号,保存即重连;状态行显示**生效**配置与来源
  (手配 / 服务端下发)。broker 密码不进界面:默认用登录 token(已加密存储),需要
  单独的密码时由服务端下发——与"凭据不明文落盘"保持一致。

测试与验证
* 新增 tests/test_client_messages.py(主题/封皮/总线,纯层)、tests/test_client_mqtt.py
  (装配层 + 自建最小 broker 的端到端,不依赖 mosquitto)、test_plugin_server.py 的
  消息接口与"/api/push → broker → 客户端总线"闭环;WebEngine 冒烟里加了页面级
  messages 探针;
* **测试入口改为 tests/run_tests.py**:QtWebEngine 在 offscreen 下的解释器退出路径会
  崩(测试全绿但退出码 139,CI 只看退出码就会判失败),runner 按 unittest 的结果
  os._exit 绕开那段清理;测试真崩在中途时退出码照样非 0(用故意失败的用例验证过)。
  README 与 CI 都切到这个入口;
* 全量 720 条通过(6 skip),exit 0。
2026-09-17 01:06:02 +08:00

90 lines
3.6 KiB
TOML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[project]
dependencies = []
description = "sqlite virtual file system"
name = "sqlite-vfs"
readme = "README.md"
# 版本门槛按实际用到的 API 推导,不是拍的:
# * contrib/fastapi_static.py 用了 hashlib.md5(..., usedforsecurity=False) → 3.9+
# * 测试用了 random.Random.randbytes → 3.9+
# 核心(core/compression/migration/publish/folder_*)本身只用到 3.7 就有的东西;
# 仓库里没有 3.10+ 的语法(无 match、无 PEP 604 注解、无 zip(strict=))。
# 说明:这里只在本机的 3.13 上跑过,"3.9 可用"是代码审查的结论,没有在 3.9 上实测。
requires-python = ">=3.9"
version = "0.3.0"
# 可选集成:核心功能只用标准库,第三方框架一律走 extras。
# 例如 pip install "sqlite-vfs[fastapi]" 才会装上 FastAPI/Starlette。
[project.optional-dependencies]
fastapi = [
"fastapi>=0.100",
"starlette>=0.27",
"uvicorn>=0.23",
"python-multipart>=0.0.6",
]
# 消息模块的 MQTT 推送通道(服务端主动推消息给客户端)。
# 不装也不影响消息中心:登录后的 HTTP 补齐照常工作,只是没有实时推送。
# 客户端在缺这个库时会在消息中心显示"未启用 MQTT",不会报错。
mqtt = [
"paho-mqtt>=2.0",
]
# 桌面工具用的 Qt 绑定。核心库与 CLI 依然零第三方依赖,所以放在 extra 里。
#
# 三个 extra 的分工(都装着 PySide6,区别只在要不要 WebEngine):
# studio —— svfs-studio(仅 QtCore/QtGui/QtWidgets,Essentials 就够)
# client —— svfs-client(QWebEngineView 在 PySide6-Essentials 里**没有**,
# 必须完整版)
# gui —— 两个桌面应用都装的合集,保留它是为了不破坏既有文档里的
# `uv sync --extra gui` / `sqlite-vfs[gui]` 写法
studio = [
"PySide6-Essentials>=6.6",
]
client = [
"PySide6>=6.6",
]
gui = [
"PySide6>=6.6",
]
[[tool.uv.index]]
default = true
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
# 说明:这里原先有 [tool.uv.workspace] members = ["devmo"],但仓库里没有 devmo 目录。
# 实测 uv 0.11.8 容忍这种写法(uv sync --dry-run 退出码 0),所以它不是阻塞项,
# 只是会误导人,因此删掉。
[build-system]
build-backend = "hatchling.build"
requires = ["hatchling"]
[dependency-groups]
dev = [
"twine>=4.0.2",
# starlette 1.6+ 的 TestClient(tests/test_plugin_server.py 用)需要它
"httpx2>=0.1",
# 消息模块的 MQTT 通道:测试里用真 paho 客户端对自建的最小 broker 跑端到端
# (见 tests/test_client_mqtt.py),所以开发环境需要它
"paho-mqtt>=2.0",
]
[project.scripts]
# 统一入口(推荐):svfs <子命令>
svfs = "sqlite_vfs.cli.main:main"
# 旧的独立命令保留为别名(与 svfs <同名子命令> 同一段实现)
vfspack = "sqlite_vfs.cli.packer:pack"
vfsunpack = "sqlite_vfs.cli.unpacker:unpack"
vfsverify = "sqlite_vfs.cli.verify:verify"
vfsmigrate = "sqlite_vfs.cli.migrate:main"
vfspublish = "sqlite_vfs.cli.publish:publish_objects"
vfsfetch = "sqlite_vfs.cli.fetch:fetch_objects"
svfs-studio = "svfs_studio.__main__:main"
svfs-client = "svfs_client.__main__:main"
[tool.hatch.build.targets.wheel]
# 三个包必须一起打:`svfs-studio` / `svfs-client` 两个 console script 在上面
# 声明了,svfs_client 还 import svfs_studio 的主题与基础部件(UI 套件)——
# 只打 sqlite_vfs 的话,pip 装完命令存在但模块不在(ImportError)。
# 不装 Qt([gui] extra)也能安全安装:这三个包在导入前都不会碰 PySide6
# (纯标准库层见 svfs_client/__init__.py 的分层说明)。
packages = ["src/sqlite_vfs", "src/svfs_studio", "src/svfs_client"]