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:
+8
-8
@@ -11,7 +11,7 @@ name = "mqttx"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
gpui-kit = "0.6"
|
||||
gpui-kit = "0.7"
|
||||
|
||||
anyhow = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
@@ -26,12 +26,12 @@ rumqttc-v4 = { package = "rumqttc-v4-next", version = "0.34", features = ["webso
|
||||
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
chrono = { version = "0.4", features = ["clock"] }
|
||||
dirs = "6"
|
||||
dirs = "7"
|
||||
|
||||
# 阿里云 MQTT 签名: HMAC-SHA1 -> Base64
|
||||
hmac = "0.12"
|
||||
sha1 = "0.10"
|
||||
base64 = "0.22"
|
||||
hmac = "0.13"
|
||||
sha1 = "0.11"
|
||||
base64 = "0.23"
|
||||
|
||||
# 自定义 TLS(自定义 CA / 双向认证 / 跳过校验),版本与 rumqttc 内部使用的 rustls 一致
|
||||
rustls = { version = "0.23", default-features = false, features = ["std", "tls12", "aws_lc_rs"] }
|
||||
@@ -42,8 +42,8 @@ rustls-native-certs = "0.8"
|
||||
rfd = "0.17"
|
||||
|
||||
# OTA 自更新:Gitea Release 检查/下载(ureq 用 rustls,与 rumqttc 依赖同源)
|
||||
ureq = { version = "2", features = ["json"] }
|
||||
sha2 = "0.10"
|
||||
ureq = { version = "3", features = ["json"] }
|
||||
sha2 = "0.11"
|
||||
# Release 附件为 7z 压缩包,纯 Rust 解压(客户端免装 7-Zip)
|
||||
sevenz-rust = "0.6"
|
||||
|
||||
@@ -59,7 +59,7 @@ sentry = { version = "0.49", default-features = false, features = [
|
||||
# 窗口置顶(raw HWND + SetWindowPos)与进程资源采样
|
||||
raw-window-handle = "0.6"
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows = { version = "0.58", features = [
|
||||
windows = { version = "0.62", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_Threading",
|
||||
"Win32_System_ProcessStatus",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -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"))]
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}"))
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user