chore(deps): 全量升级依赖,适配 gpui-kit 0.7 与 ureq 3

- gpui-kit 0.6→0.7:main.rs 改用 gpui_kit::open_window 自动挂载 Root,
  移除内容视图手动挂载的 dialog/sheet/notification 层
- ureq 2→3:Agent::config_builder 配置超时(global/connect/recv_response),
  响应体改用 read_json / read_to_string / into_body().into_reader()
- hmac 0.13 需显式导入 KeyInit;digest 0.11 的 Output 不再实现 LowerHex,
  sha256 转 hex 改为逐字节格式化,并补已知向量测试
- windows 0.62:SetWindowPos 插入位置参数改为 Option<HWND>
- dirs 6→7、base64 0.22→0.23,其余锁内更新(tokio/sentry/rustls 等)
- TLS 栈不变(rustls + webpki-roots),无 openssl/native-tls 混入

已验证:cargo check 零警告;35 测试通过;运行应用截图核验主界面、
新建连接对话框打开与关闭均正常
This commit is contained in:
2026-09-30 11:43:40 +08:00
parent b22a645588
commit 6f09a5729f
8 changed files with 209 additions and 127 deletions
+1 -1
View File
@@ -13,7 +13,7 @@
//! - password: HMAC-SHA256(DeviceSecret, "clientId{ProductKey}.{DeviceName}deviceName{DeviceName}productKey{ProductKey}timestamp{ts}")
use base64::Engine as _;
use hmac::{Hmac, Mac};
use hmac::{Hmac, KeyInit, Mac};
use serde::{Deserialize, Serialize};
use sha1::Sha1;
use sha2::Sha256;
+4 -4
View File
@@ -1,4 +1,4 @@
use gpui_kit::component::{Root, TitleBar};
use gpui_kit::component::TitleBar;
use gpui_kit::*;
use mqttx_desktop::ui;
@@ -70,9 +70,9 @@ fn main() {
..TitleBar::window_options()
};
cx.open_window(options, |window, cx| {
let view = cx.new(|cx| ui::app::MqttXApp::new(window, cx));
cx.new(|cx| Root::new(view.clone(), window, cx))
// 0.7 起 gpui_kit::open_window 自动挂载 Root(承载 dialog/sheet/notification 层)
gpui_kit::open_window(options, cx, |window, cx| {
cx.new(|cx| ui::app::MqttXApp::new(window, cx))
})
.expect("Failed to open window");
});
+2 -1
View File
@@ -31,7 +31,8 @@ fn set_topmost_windows(window: &gpui_kit::Window, topmost: bool) -> bool {
let hwnd = HWND(w.hwnd.get() as *mut core::ffi::c_void);
let insert = if topmost { HWND_TOPMOST } else { HWND_NOTOPMOST };
let flags = SET_WINDOW_POS_FLAGS(SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
unsafe { SetWindowPos(hwnd, insert, 0, 0, 0, 0, flags).is_ok() }
// windows 0.62 起 SetWindowPos 的插入位置参数改为 Option<HWND>
unsafe { SetWindowPos(hwnd, Some(insert), 0, 0, 0, 0, flags).is_ok() }
}
#[cfg(not(target_os = "windows"))]
+1 -4
View File
@@ -368,7 +368,7 @@ impl MqttXApp {
impl Render for MqttXApp {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
// 0.6.x 的 Root 不自动渲染 dialog/sheet/notification 层,需由内容视图挂载。
// 0.7 起 Root 自动渲染 dialog/sheet/notification 层,内容视图无需再手动挂载。
v_flex()
.size_full()
.bg(cx.theme().background)
@@ -381,8 +381,5 @@ impl Render for MqttXApp {
.child(self.render_sidebar(window, cx))
.child(self.render_main(window, cx)),
)
.children(gpui_kit::component::Root::render_sheet_layer(window, cx))
.children(gpui_kit::component::Root::render_dialog_layer(window, cx))
.children(gpui_kit::component::Root::render_notification_layer(window, cx))
}
}
+12 -9
View File
@@ -3,20 +3,23 @@
use super::*;
pub(super) fn agent() -> ureq::Agent {
ureq::AgentBuilder::new()
// 整体上限:防止慢速滴流的下载无限占用阻塞线程
.timeout(std::time::Duration::from_secs(10 * 60))
.timeout_connect(std::time::Duration::from_secs(HTTP_TIMEOUT_SECS))
.timeout_read(std::time::Duration::from_secs(HTTP_TIMEOUT_SECS))
.build()
ureq::Agent::new_with_config(
ureq::Agent::config_builder()
// 整体上限:防止慢速滴流的下载无限占用阻塞线程
.timeout_global(Some(std::time::Duration::from_secs(10 * 60)))
.timeout_connect(Some(std::time::Duration::from_secs(HTTP_TIMEOUT_SECS)))
.timeout_recv_response(Some(std::time::Duration::from_secs(HTTP_TIMEOUT_SECS)))
.build(),
)
}
pub(super) fn http_get_json(url: &str) -> Result<serde_json::Value, String> {
let resp = agent()
let mut body = agent()
.get(url)
.call()
.map_err(|e| format!("请求失败: {e}"))?;
resp.into_json::<serde_json::Value>()
.map_err(|e| format!("请求失败: {e}"))?
.into_body();
body.read_json::<serde_json::Value>()
.map_err(|e| format!("解析响应失败: {e}"))
}
+32 -5
View File
@@ -115,7 +115,8 @@ fn file_sha256(path: &Path) -> Result<String, String> {
}
hasher.update(&buf[..n]);
}
Ok(format!("{:x}", hasher.finalize()))
// digest 0.11 的 Output 不再实现 LowerHex,手动转 hex
Ok(hasher.finalize().iter().map(|b| format!("{b:02x}")).collect())
}
/// 校验 7z 归档的条目清单(只读归档头,不解压数据)。
@@ -240,7 +241,7 @@ fn download_to(info: &UpdateInfo, tmp: &std::path::Path, on_progress: &dyn Fn(u6
.get(&info.asset_url)
.call()
.map_err(|e| format!("下载失败: {e}"))?;
let mut reader = resp.into_reader();
let mut reader = resp.into_body().into_reader();
let total = info.size;
let mut file = std::fs::File::create(tmp).map_err(|e| format!("创建临时文件失败: {e}"))?;
let mut buf = [0u8; 64 * 1024];
@@ -274,7 +275,7 @@ fn download_to(info: &UpdateInfo, tmp: &std::path::Path, on_progress: &dyn Fn(u6
let expect = fetch_expected_sha256(info)?.ok_or_else(|| {
"Release 未提供 .sha256 校验文件,无法验证完整性,已拒绝安装".to_string()
})?;
let actual = format!("{:x}", hasher.finalize());
let actual: String = hasher.finalize().iter().map(|b| format!("{b:02x}")).collect();
if !actual.eq_ignore_ascii_case(&expect) {
return Err(format!("校验失败:期望 sha256 {expect},实际 {actual}"));
}
@@ -308,11 +309,13 @@ fn fetch_expected_sha256(info: &UpdateInfo) -> Result<Option<String>, String> {
let Some(url) = &info.sha256_url else {
return Ok(None);
};
let text = agent()
let mut body = agent()
.get(url)
.call()
.map_err(|e| format!("获取校验文件失败: {e}"))?
.into_string()
.into_body();
let text = body
.read_to_string()
.map_err(|e| format!("读取校验文件失败: {e}"))?;
// 常见格式:<hex> <filename> 或纯 hex
let hex = text
@@ -517,3 +520,27 @@ pub fn cleanup_old() {
}
// ─── 单元测试 ────────────────────────────────────────────────────────────────
#[cfg(test)]
mod tests {
use super::*;
/// sha256→hex 转换(digest 0.11 起 Output 不再实现 LowerHex)是 OTA
/// 完整性校验的关键路径,用固定向量锁定。
#[test]
fn file_sha256_matches_known_vector() {
let path = std::env::temp_dir().join(format!(
"mqttx-sha-test-{}-{}.txt",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0)
));
std::fs::write(&path, b"abc").expect("写入测试文件");
let hex = file_sha256(&path).expect("计算 sha256");
let _ = std::fs::remove_file(&path);
// echo -n abc | sha256sum
assert_eq!(hex, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
}
}