diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index b1947c4..cf901fb 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -285,11 +285,11 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" name = "bilibili-video-downloader" version = "0.1.0" dependencies = [ - "anyhow", "base64 0.22.1", "byteorder", "bytes", "chrono", + "eyre", "float-ord", "fs4", "md-5", @@ -1007,6 +1007,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -1850,6 +1860,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + [[package]] name = "indexmap" version = "1.9.3" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 21e6e24..1f283e2 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -35,7 +35,7 @@ reqwest = { version = "0.12.22", default-features = false, features = ["default- reqwest-retry = { version = "0.7.0" } reqwest-middleware = { version = "0.4.2" } -anyhow = { version = "1.0.98" } +eyre = { version = "0.6.12" } parking_lot = { version = "0.12.4", features = ["send_guard"] } tracing = { version = "0.1.41" } tracing-subscriber = { version = "0.3.19", features = ["json", "time", "local-time"] } @@ -62,3 +62,4 @@ strip = true lto = true codegen-units = 1 panic = "abort" + diff --git a/src-tauri/src/bili_client.rs b/src-tauri/src/bili_client.rs index 2b2b8c3..4092901 100644 --- a/src-tauri/src/bili_client.rs +++ b/src-tauri/src/bili_client.rs @@ -1,8 +1,8 @@ use std::time::Duration; -use anyhow::{Context, anyhow}; use base64::{Engine, engine::general_purpose}; use bytes::Bytes; +use eyre::{OptionExt, WrapErr, eyre}; use parking_lot::RwLock; use prost::Message; use reqwest::{Client, StatusCode}; @@ -18,7 +18,7 @@ use tokio::task::JoinSet; use crate::{ config::ProxyMode, - extensions::{AnyhowErrorToStringChain, AppHandleExt}, + extensions::{AppHandleExt, EyreToStringChain}, protobuf::DmSegMobileReply, types::{ bangumi_follow_info::BangumiFollowInfo, bangumi_info::BangumiInfo, @@ -74,7 +74,7 @@ impl BiliClient { *self.content_length_client.write() = content_length_client; } - pub async fn generate_qrcode(&self) -> anyhow::Result { + pub async fn generate_qrcode(&self) -> eyre::Result { // 发送生成二维码请求 let request = self .api_client @@ -85,28 +85,28 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为二维码数据 let data_str = data.to_string(); let qrcode_data: QrcodeData = serde_json::from_str(&data_str) - .context(format!("将data解析为QrcodeData失败: {data_str}"))?; + .wrap_err(format!("将data解析为QrcodeData失败: {data_str}"))?; Ok(qrcode_data) } - pub async fn get_qrcode_status(&self, qrcode_key: &str) -> anyhow::Result { + pub async fn get_qrcode_status(&self, qrcode_key: &str) -> eyre::Result { // 发送获取二维码状态请求 let params = json!({"qrcode_key": qrcode_key}); let request = self @@ -119,30 +119,30 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为二维码状态 let data_str = data.to_string(); let qrcode_status: QrcodeStatus = serde_json::from_str(&data_str) - .context(format!("将data解析为QrcodeStatus失败: {data_str}"))?; + .wrap_err(format!("将data解析为QrcodeStatus失败: {data_str}"))?; if ![0, 86101, 86090, 86038].contains(&qrcode_status.code) { - return Err(anyhow!("预料之外的二维码code: {qrcode_status:?}")); + return Err(eyre!("预料之外的二维码code: {qrcode_status:?}")); } Ok(qrcode_status) } - pub async fn get_user_info(&self, sessdata: &str) -> anyhow::Result { + pub async fn get_user_info(&self, sessdata: &str) -> eyre::Result { // 发送获取用户信息的请求 let request = self .api_client @@ -154,30 +154,30 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code == -101 { - return Err(anyhow!("cookie错误或已过期,请重新登录: {bili_resp:?}")); + return Err(eyre!("cookie错误或已过期,请重新登录: {bili_resp:?}")); } else if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为UserInfo let data_str = data.to_string(); let user_info: UserInfo = serde_json::from_str(&data_str) - .context(format!("将data解析为UserInfo失败: {data_str}"))?; + .wrap_err(format!("将data解析为UserInfo失败: {data_str}"))?; Ok(user_info) } - pub async fn get_normal_info(&self, params: GetNormalInfoParams) -> anyhow::Result { + pub async fn get_normal_info(&self, params: GetNormalInfoParams) -> eyre::Result { use GetNormalInfoParams::{Aid, Bvid}; let params = match params { Bvid(bvid) => json!({"bvid": bvid}), @@ -195,23 +195,23 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为NormalInfo let data_str = data.to_string(); let normal_info: NormalInfo = serde_json::from_str(&data_str) - .context(format!("将data解析为NormalInfo失败: {data_str}"))?; + .wrap_err(format!("将data解析为NormalInfo失败: {data_str}"))?; Ok(normal_info) } @@ -219,7 +219,7 @@ impl BiliClient { pub async fn get_bangumi_info( &self, params: GetBangumiInfoParams, - ) -> anyhow::Result { + ) -> eyre::Result { use GetBangumiInfoParams::{EpId, SeasonId}; let params = match params { EpId(ep_id) => json!({"ep_id": ep_id}), @@ -237,28 +237,28 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为BangumiInfo let data_str = data.to_string(); let bangumi_info: BangumiInfo = serde_json::from_str(&data_str) - .context(format!("将data解析为BangumiInfo失败: {data_str}"))?; + .wrap_err(format!("将data解析为BangumiInfo失败: {data_str}"))?; Ok(bangumi_info) } - pub async fn get_cheese_info(&self, params: GetCheeseInfoParams) -> anyhow::Result { + pub async fn get_cheese_info(&self, params: GetCheeseInfoParams) -> eyre::Result { use GetCheeseInfoParams::{EpId, SeasonId}; let params = match params { EpId(ep_id) => json!({"ep_id": ep_id}), @@ -276,23 +276,23 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为CheeseInfo let data_str = data.to_string(); let cheese_info: CheeseInfo = serde_json::from_str(&data_str) - .context(format!("将data解析为CheeseInfo失败: {data_str}"))?; + .wrap_err(format!("将data解析为CheeseInfo失败: {data_str}"))?; Ok(cheese_info) } @@ -300,7 +300,7 @@ impl BiliClient { pub async fn get_user_video_info( &self, params: GetUserVideoInfoParams, - ) -> anyhow::Result { + ) -> eyre::Result { const DM_IMG_INTER: &str = r#"{"ds":[],"wh":[0,0,0],"of":[0,0,0]}"#; fn random_base64() -> String { @@ -337,28 +337,28 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为UserVideoInfo let data_str = data.to_string(); let user_video_info: UserVideoInfo = serde_json::from_str(&data_str) - .context(format!("将data解析为UserVideoInfo失败: {data_str}"))?; + .wrap_err(format!("将data解析为UserVideoInfo失败: {data_str}"))?; Ok(user_video_info) } - pub async fn get_normal_url(&self, bvid: &str, cid: i64) -> anyhow::Result { + pub async fn get_normal_url(&self, bvid: &str, cid: i64) -> eyre::Result { let params = json!({ "bvid": bvid, "cid": cid, @@ -377,28 +377,28 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为NormalMediaUrl let data_str = data.to_string(); let media_url: NormalMediaUrl = serde_json::from_str(&data_str) - .context(format!("将data解析为NormalMediaUrl失败: {data_str}"))?; + .wrap_err(format!("将data解析为NormalMediaUrl失败: {data_str}"))?; Ok(media_url) } - pub async fn get_bangumi_url(&self, cid: i64) -> anyhow::Result { + pub async fn get_bangumi_url(&self, cid: i64) -> eyre::Result { let media_url_v2 = self.get_bangumi_url_v2(cid).await?; if media_url_v2.video_info.is_drm { self.get_bangumi_url_v1(cid).await @@ -407,7 +407,7 @@ impl BiliClient { } } - async fn get_bangumi_url_v1(&self, cid: i64) -> anyhow::Result { + async fn get_bangumi_url_v1(&self, cid: i64) -> eyre::Result { let params = json!({ "cid": cid, "qn": 127, @@ -426,32 +426,30 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code == -10403 { - return Err(anyhow!( - "地区限制,请使用代理或切换线路后重试: {bili_resp:?}" - )); + return Err(eyre!("地区限制,请使用代理或切换线路后重试: {bili_resp:?}")); } else if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为BangumiMediaUrl let data_str = data.to_string(); let media_url: BangumiMediaUrl = serde_json::from_str(&data_str) - .context(format!("将data解析为BangumiMediaUrl失败: {data_str}"))?; + .wrap_err(format!("将data解析为BangumiMediaUrl失败: {data_str}"))?; Ok(media_url) } - async fn get_bangumi_url_v2(&self, cid: i64) -> anyhow::Result { + async fn get_bangumi_url_v2(&self, cid: i64) -> eyre::Result { let params = json!({ "cid": cid, "qn": 127, @@ -471,32 +469,30 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code == -10403 { - return Err(anyhow!( - "地区限制,请使用代理或切换线路后重试: {bili_resp:?}" - )); + return Err(eyre!("地区限制,请使用代理或切换线路后重试: {bili_resp:?}")); } else if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为BangumiMediaUrlV2 let data_str = data.to_string(); let media_url: BangumiMediaUrlV2 = serde_json::from_str(&data_str) - .context(format!("将data解析为BangumiMediaUrlV2失败: {data_str}"))?; + .wrap_err(format!("将data解析为BangumiMediaUrlV2失败: {data_str}"))?; Ok(media_url) } - pub async fn get_cheese_url(&self, ep_id: i64) -> anyhow::Result { + pub async fn get_cheese_url(&self, ep_id: i64) -> eyre::Result { let params = json!({ "ep_id": ep_id, "qn": 127, @@ -515,30 +511,30 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code == -403 { - return Err(anyhow!("没有观看权限,请先购买: {bili_resp:?}")); + return Err(eyre!("没有观看权限,请先购买: {bili_resp:?}")); } else if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为CheeseMediaUrl let data_str = data.to_string(); let media_url: CheeseMediaUrl = serde_json::from_str(&data_str) - .context(format!("将data解析为CheeseMediaUrl失败: {data_str}"))?; + .wrap_err(format!("将data解析为CheeseMediaUrl失败: {data_str}"))?; Ok(media_url) } - pub async fn get_player_info(&self, aid: i64, cid: i64) -> anyhow::Result { + pub async fn get_player_info(&self, aid: i64, cid: i64) -> eyre::Result { let params = json!({ "aid": aid, "cid": cid, @@ -555,28 +551,28 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为PlayerInfo let data_str = data.to_string(); let player_info: PlayerInfo = serde_json::from_str(&data_str) - .context(format!("将data解析为PlayerInfo失败: {data_str}"))?; + .wrap_err(format!("将data解析为PlayerInfo失败: {data_str}"))?; Ok(player_info) } - pub async fn get_fav_folders(&self, uid: i64) -> anyhow::Result { + pub async fn get_fav_folders(&self, uid: i64) -> eyre::Result { let params = json!({"up_mid": uid}); // 发送获取收藏夹信息的请求 let request = self @@ -590,28 +586,28 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为FavFolders let data_str = data.to_string(); let fav_folders: FavFolders = serde_json::from_str(&data_str) - .context(format!("将data解析为FavFolders失败: {data_str}"))?; + .wrap_err(format!("将data解析为FavFolders失败: {data_str}"))?; Ok(fav_folders) } - pub async fn get_fav_info(&self, params: GetFavInfoParams) -> anyhow::Result { + pub async fn get_fav_info(&self, params: GetFavInfoParams) -> eyre::Result { let params = json!({ "media_id": params.media_list_id, "pn": params.pn, @@ -630,28 +626,28 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为FavInfo let data_str = data.to_string(); let fav_info: FavInfo = serde_json::from_str(&data_str) - .context(format!("将data解析为FavInfo失败: {data_str}"))?; + .wrap_err(format!("将data解析为FavInfo失败: {data_str}"))?; Ok(fav_info) } - pub async fn get_watch_later_info(&self, page: i32) -> anyhow::Result { + pub async fn get_watch_later_info(&self, page: i32) -> eyre::Result { // 发送获取稍后观看信息的请求 let params = json!({"ps": 20, "pn": page}); let request = self @@ -665,23 +661,23 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为WatchLaterInfo let data_str = data.to_string(); let watch_later_info: WatchLaterInfo = serde_json::from_str(&data_str) - .context(format!("将data解析为WatchLaterInfo失败: {data_str}"))?; + .wrap_err(format!("将data解析为WatchLaterInfo失败: {data_str}"))?; Ok(watch_later_info) } @@ -689,7 +685,7 @@ impl BiliClient { pub async fn get_bangumi_follow_info( &self, params: GetBangumiFollowInfoParams, - ) -> anyhow::Result { + ) -> eyre::Result { // 发送获取番剧追踪信息的请求 let params = json!({ "vmid": params.vmid, @@ -709,23 +705,23 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为BangumiFollowInfo let data_str = data.to_string(); let bangumi_follow_info: BangumiFollowInfo = serde_json::from_str(&data_str) - .context(format!("将data解析为BangumiFollowInfo失败: {data_str}"))?; + .wrap_err(format!("将data解析为BangumiFollowInfo失败: {data_str}"))?; Ok(bangumi_follow_info) } @@ -733,7 +729,7 @@ impl BiliClient { pub async fn get_history_info( &self, params: GetHistoryInfoParams, - ) -> anyhow::Result { + ) -> eyre::Result { let device_type: i64 = params.device_type.into(); let params = json!({ "pn": params.pn, @@ -756,23 +752,23 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为HistoryInfo let data_str = data.to_string(); let history_info: HistoryInfo = serde_json::from_str(&data_str) - .context(format!("将data解析为HistoryInfo失败: {data_str}"))?; + .wrap_err(format!("将data解析为HistoryInfo失败: {data_str}"))?; Ok(history_info) } @@ -782,7 +778,7 @@ impl BiliClient { media_url: &str, start: u64, end: u64, - ) -> anyhow::Result { + ) -> eyre::Result { let request = self .media_client .read() @@ -792,7 +788,7 @@ impl BiliClient { // 检查http响应状态码 let status = http_resp.status(); if status != StatusCode::PARTIAL_CONTENT { - return Err(anyhow!("预料之外的状态码({status})")); + return Err(eyre!("预料之外的状态码({status})")); } let bytes = http_resp.bytes().await?; @@ -800,32 +796,32 @@ impl BiliClient { Ok(bytes) } - pub async fn get_content_length(&self, media_url: &str) -> anyhow::Result { - fn parse_content_length(headers: &HeaderMap) -> anyhow::Result { + pub async fn get_content_length(&self, media_url: &str) -> eyre::Result { + fn parse_content_length(headers: &HeaderMap) -> eyre::Result { headers .get("Content-Length") - .context("缺少 Content-Length 响应头")? + .ok_or_eyre("缺少 Content-Length 响应头")? .to_str() - .context("Content-Length 响应头无法转换为字符串")? + .wrap_err("Content-Length 响应头无法转换为字符串")? .parse::() - .context("Content-Length 响应头无法转换为整数") + .wrap_err("Content-Length 响应头无法转换为整数") } - fn parse_total_from_content_range(headers: &HeaderMap) -> anyhow::Result { + fn parse_total_from_content_range(headers: &HeaderMap) -> eyre::Result { // Example: "bytes 0-0/12345" let content_range = headers .get("Content-Range") - .context("缺少 Content-Range 响应头")? + .ok_or_eyre("缺少 Content-Range 响应头")? .to_str() - .context("Content-Range 响应头无法转换为字符串")?; + .wrap_err("Content-Range 响应头无法转换为字符串")?; let Some((_, total)) = content_range.split_once('/') else { - return Err(anyhow!("预料之外的 Content-Range 格式: {content_range}")); + return Err(eyre!("预料之外的 Content-Range 格式: {content_range}")); }; total .parse::() - .context("Content-Range 总大小无法转换为整数") + .wrap_err("Content-Range 总大小无法转换为整数") } // 优先使用 HEAD 获取 Content-Length @@ -855,7 +851,7 @@ impl BiliClient { return parse_content_length(http_resp.headers()); } - Err(anyhow!("预料之外的状态码({status})")) + Err(eyre!("预料之外的状态码({status})")) } pub async fn get_url_with_content_length(&self, urls: Vec) -> Vec<(String, u64)> { @@ -889,7 +885,7 @@ impl BiliClient { aid: i64, cid: i64, duration: u64, - ) -> anyhow::Result> { + ) -> eyre::Result> { let client = self.api_client.read().clone(); // 以6分钟为单位分段 let segment_count = duration.div_ceil(360); @@ -916,11 +912,11 @@ impl BiliClient { let status = http_resp.status(); if status != StatusCode::OK { let body = http_resp.text().await?; - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } let body = http_resp.bytes().await?; let reply = - DmSegMobileReply::decode(body).context("将body解析为DmSegMobileReply失败")?; + DmSegMobileReply::decode(body).wrap_err("将body解析为DmSegMobileReply失败")?; Ok(reply) }); @@ -939,37 +935,37 @@ impl BiliClient { Ok(replies) } - pub async fn get_subtitle(&self, url: &str) -> anyhow::Result { + pub async fn get_subtitle(&self, url: &str) -> eyre::Result { let request = self.api_client.read().get(url); let http_resp = request.send().await?; let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为Subtitle let subtitle: Subtitle = - serde_json::from_str(&body).context(format!("将body解析为Subtitle失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为Subtitle失败: {body}"))?; Ok(subtitle) } - pub async fn get_cover_data_and_ext(&self, url: &str) -> anyhow::Result<(Bytes, String)> { + pub async fn get_cover_data_and_ext(&self, url: &str) -> eyre::Result<(Bytes, String)> { let request = self.api_client.read().get(url); let http_resp = request.send().await?; // 检查http响应状态码 let status = http_resp.status(); if status != StatusCode::OK { let body = http_resp.text().await?; - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } let content_type = http_resp .headers() .get("Content-Type") - .context("缺少 Content-Type 响应头")? + .ok_or_eyre("缺少 Content-Type 响应头")? .to_str() - .context("Content-Type 响应头无法转换为字符串")? + .wrap_err("Content-Type 响应头无法转换为字符串")? .to_string(); let ext = match content_type.as_str() { @@ -984,7 +980,7 @@ impl BiliClient { Ok((bytes, ext.to_string())) } - pub async fn get_tags(&self, aid: i64) -> anyhow::Result { + pub async fn get_tags(&self, aid: i64) -> eyre::Result { // 发送获取普通视频标签的请求 let params = json!({"aid": aid}); let request = self @@ -998,23 +994,23 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的code字段 if bili_resp.code != 0 { - return Err(anyhow!("预料之外的code: {bili_resp:?}")); + return Err(eyre!("预料之外的code: {bili_resp:?}")); } // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为Tags let data_str = data.to_string(); - let tags: Tags = - serde_json::from_str(&data_str).context(format!("将data解析为Tags失败: {data_str}"))?; + let tags: Tags = serde_json::from_str(&data_str) + .wrap_err(format!("将data解析为Tags失败: {data_str}"))?; Ok(tags) } @@ -1023,7 +1019,7 @@ impl BiliClient { &self, bvid: &str, cid: Option, - ) -> anyhow::Result { + ) -> eyre::Result { // 发送获取跳过片段的请求 let mut params = json!({ "videoID": bvid, @@ -1045,11 +1041,11 @@ impl BiliClient { if status == StatusCode::NOT_FOUND { return Ok(SkipSegments(Vec::new())); } else if status != StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为SkipSegments - let skip_segments: SkipSegments = - serde_json::from_str(&body).context(format!("将body解析为SkipSegments失败: {body}"))?; + let skip_segments: SkipSegments = serde_json::from_str(&body) + .wrap_err(format!("将body解析为SkipSegments失败: {body}"))?; Ok(skip_segments) } @@ -1132,7 +1128,7 @@ impl ClientBuilderExt for reqwest::ClientBuilder { let proxy_port = &config.proxy_port; let proxy_url = format!("http://{proxy_host}:{proxy_port}"); - match reqwest::Proxy::all(&proxy_url).map_err(anyhow::Error::from) { + match reqwest::Proxy::all(&proxy_url).map_err(eyre::Report::from) { Ok(proxy) => self.proxy(proxy), Err(err) => { let err_title = format!("{client_name}将`{proxy_url}`设为代理失败,将直连"); diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 2a6f492..cab4a8e 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1,4 +1,4 @@ -use anyhow::Context; +use eyre::WrapErr; use parking_lot::RwLock; use tauri::AppHandle; use tauri_plugin_opener::OpenerExt; @@ -366,10 +366,10 @@ pub async fn search(app: AppHandle, params: SearchParams) -> CommandResult CommandResult { let logs_dir = logger::logs_dir(&app) - .context("获取日志目录失败") + .wrap_err("获取日志目录失败") .map_err(|err| CommandError::from("获取日志目录大小失败", err))?; let logs_dir_size = std::fs::read_dir(&logs_dir) - .context(format!("读取日志目录`{}`失败", logs_dir.display())) + .wrap_err(format!("读取日志目录`{}`失败", logs_dir.display())) .map_err(|err| CommandError::from("获取日志目录大小失败", err))? .filter_map(Result::ok) .filter_map(|entry| entry.metadata().ok()) @@ -385,7 +385,7 @@ pub fn get_logs_dir_size(app: AppHandle) -> CommandResult { pub fn show_path_in_file_manager(app: AppHandle, path: &str) -> CommandResult<()> { app.opener() .reveal_item_in_dir(path) - .context(format!("在文件管理器中打开`{path}`失败")) + .wrap_err(format!("在文件管理器中打开`{path}`失败")) .map_err(|err| CommandError::from("在文件管理器中打开失败", err))?; Ok(()) } diff --git a/src-tauri/src/config.rs b/src-tauri/src/config.rs index c40b8ff..e4795f4 100644 --- a/src-tauri/src/config.rs +++ b/src-tauri/src/config.rs @@ -47,7 +47,7 @@ pub struct Config { } impl Config { - pub fn new(app: &AppHandle) -> anyhow::Result { + pub fn new(app: &AppHandle) -> eyre::Result { let app_data_dir = app.path().app_data_dir()?; let config_path = app_data_dir.join("config.json"); @@ -67,7 +67,7 @@ impl Config { Ok(config) } - pub fn save(&self, app: &AppHandle) -> anyhow::Result<()> { + pub fn save(&self, app: &AppHandle) -> eyre::Result<()> { let app_data_dir = app.path().app_data_dir()?; let config_path = app_data_dir.join("config.json"); let config_string = serde_json::to_string_pretty(self)?; diff --git a/src-tauri/src/danmaku_xml_to_ass/ass_writer.rs b/src-tauri/src/danmaku_xml_to_ass/ass_writer.rs index ff4d168..ffa9727 100644 --- a/src-tauri/src/danmaku_xml_to_ass/ass_writer.rs +++ b/src-tauri/src/danmaku_xml_to_ass/ass_writer.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use eyre::Result; use std::borrow::Cow; use std::fmt; use std::io::{BufWriter, Write}; diff --git a/src-tauri/src/danmaku_xml_to_ass/mod.rs b/src-tauri/src/danmaku_xml_to_ass/mod.rs index 1a13f7f..e2b6887 100644 --- a/src-tauri/src/danmaku_xml_to_ass/mod.rs +++ b/src-tauri/src/danmaku_xml_to_ass/mod.rs @@ -5,10 +5,10 @@ pub mod drawable; use std::{cmp::Ordering, fs::File}; -use anyhow::anyhow; use ass_writer::AssWriter; use canvas::CanvasConfig; use danmaku::{Danmaku, DanmakuType}; +use eyre::eyre; use yaserde::{YaDeserialize, YaSerialize}; #[derive(YaSerialize, YaDeserialize)] @@ -33,7 +33,7 @@ pub fn xml_to_ass( ass_file: File, title: String, config: CanvasConfig, -) -> anyhow::Result<()> { +) -> eyre::Result<()> { let mut writer = AssWriter::new(ass_file, title, config.clone())?; let mut canvas = config.canvas(); @@ -54,24 +54,24 @@ pub fn xml_to_ass( } trait ToDanmakuType { - fn to_danmaku_type(&self) -> anyhow::Result; + fn to_danmaku_type(&self) -> eyre::Result; } impl ToDanmakuType for u32 { - fn to_danmaku_type(&self) -> anyhow::Result { + fn to_danmaku_type(&self) -> eyre::Result { match self { 1 => Ok(DanmakuType::Float), 4 => Ok(DanmakuType::Bottom), 5 => Ok(DanmakuType::Top), 6 => Ok(DanmakuType::Reverse), - _ => Err(anyhow!("未知的弹幕类型:{self}")), + _ => Err(eyre!("未知的弹幕类型:{self}")), } } } -pub fn xml_to_danmakus(xml: &str) -> anyhow::Result> { +pub fn xml_to_danmakus(xml: &str) -> eyre::Result> { let xml = sanitize_xml(xml); - let i_tag: DanmakuXmlITag = yaserde::de::from_str(&xml).map_err(|e| anyhow!(e))?; + let i_tag: DanmakuXmlITag = yaserde::de::from_str(&xml).map_err(|e| eyre!(e))?; let mut danmakus = Vec::new(); @@ -83,7 +83,7 @@ pub fn xml_to_danmakus(xml: &str) -> anyhow::Result> { let mut p_attr = elem.p.split(','); let Some(timeline_s) = p_attr.next().and_then(|s| s.parse::().ok()) else { - return Err(anyhow!("弹幕`{content}`的p属性中没有时间")); + return Err(eyre!("弹幕`{content}`的p属性中没有时间")); }; let Some(r#type) = p_attr @@ -91,15 +91,15 @@ pub fn xml_to_danmakus(xml: &str) -> anyhow::Result> { .and_then(|s| s.parse::().ok()) .and_then(|num| num.to_danmaku_type().ok()) else { - return Err(anyhow!("弹幕`{content}`的p属性中没有弹幕类型")); + return Err(eyre!("弹幕`{content}`的p属性中没有弹幕类型")); }; let Some(fontsize) = p_attr.next().and_then(|s| s.parse::().ok()) else { - return Err(anyhow!("弹幕`{content}`的p属性中没有字体大小")); + return Err(eyre!("弹幕`{content}`的p属性中没有字体大小")); }; let Some(rgb) = p_attr.next().and_then(|s| s.parse::().ok()) else { - return Err(anyhow!("弹幕`{content}`的p属性中没有颜色")); + return Err(eyre!("弹幕`{content}`的p属性中没有颜色")); }; // rgb 是个数字,类似 0x010203 diff --git a/src-tauri/src/downloader/download_chunk_task.rs b/src-tauri/src/downloader/download_chunk_task.rs index 97da6d8..d8075b2 100644 --- a/src-tauri/src/downloader/download_chunk_task.rs +++ b/src-tauri/src/downloader/download_chunk_task.rs @@ -23,7 +23,7 @@ pub struct DownloadChunkTask { } impl DownloadChunkTask { - pub async fn process(self) -> anyhow::Result { + pub async fn process(self) -> eyre::Result { let download_chunk_task = self.download_chunk(); tokio::pin!(download_chunk_task); @@ -64,7 +64,7 @@ impl DownloadChunkTask { } } - async fn download_chunk(&self) -> anyhow::Result { + async fn download_chunk(&self) -> eyre::Result { let bili_client = self.download_task.app.get_bili_client(); let chunk_data = bili_client .get_media_chunk(&self.url, self.start, self.end) @@ -97,7 +97,7 @@ impl DownloadChunkTask { async fn acquire_chunk_permit<'a>( &'a self, permit: &mut Option>, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { *permit = match permit.take() { // 如果有permit,则直接用 Some(permit) => Some(permit), diff --git a/src-tauri/src/downloader/download_manager.rs b/src-tauri/src/downloader/download_manager.rs index e8084cb..4daff3d 100644 --- a/src-tauri/src/downloader/download_manager.rs +++ b/src-tauri/src/downloader/download_manager.rs @@ -8,7 +8,7 @@ use std::{ time::Duration, }; -use anyhow::Context; +use eyre::WrapErr; use parking_lot::RwLock; use tauri::{AppHandle, Manager}; use tauri_specta::Event; @@ -16,7 +16,7 @@ use tokio::sync::Semaphore; use crate::{ events::DownloadEvent, - extensions::{AnyhowErrorToStringChain, AppHandleExt}, + extensions::{AppHandleExt, EyreToStringChain}, types::{ create_download_task_params::CreateDownloadTaskParams, restart_download_task_params::RestartDownloadTaskParams, @@ -59,10 +59,10 @@ impl DownloadManager { manager } - pub fn restore_download_tasks(&self) -> anyhow::Result<()> { + pub fn restore_download_tasks(&self) -> eyre::Result<()> { let task_dir = self.get_task_dir()?; std::fs::create_dir_all(&task_dir) - .context(format!("创建下载任务目录`{}`失败", task_dir.display()))?; + .wrap_err(format!("创建下载任务目录`{}`失败", task_dir.display()))?; let mut tasks = self.download_tasks.write(); for entry in std::fs::read_dir(&task_dir)?.filter_map(Result::ok) { @@ -150,9 +150,9 @@ impl DownloadManager { continue; } - if let Err(err) = task.delete_sender.send(()).map_err(anyhow::Error::from) { + if let Err(err) = task.delete_sender.send(()).map_err(eyre::Report::from) { let err_title = "删除下载任务失败"; - let err = err.context(format!("通知ID为`{task_id}`的下载任务删除失败")); + let err = err.wrap_err(format!("通知ID为`{task_id}`的下载任务删除失败")); let string_chain = err.to_string_chain(); tracing::error!(err_title, message = string_chain); tasks.insert(task_id.clone(), task); @@ -173,9 +173,9 @@ impl DownloadManager { continue; }; - if let Err(err) = task.restart_sender.send(()).map_err(anyhow::Error::from) { + if let Err(err) = task.restart_sender.send(()).map_err(eyre::Report::from) { let err_title = "重来下载任务失败"; - let err = err.context(format!("通知ID为`{task_id}`的下载任务重来失败")); + let err = err.wrap_err(format!("通知ID为`{task_id}`的下载任务重来失败")); let string_chain = err.to_string_chain(); tracing::error!(err_title, message = string_chain); continue; @@ -217,9 +217,9 @@ impl DownloadManager { progress.audio_task.audio_quality = params.audio_quality; } - if let Err(err) = task.restart_sender.send(()).map_err(anyhow::Error::from) { + if let Err(err) = task.restart_sender.send(()).map_err(eyre::Report::from) { let err_title = "重来下载任务失败"; - let err = err.context(format!("通知ID为`{task_id}`的下载任务重来失败")); + let err = err.wrap_err(format!("通知ID为`{task_id}`的下载任务重来失败")); let string_chain = err.to_string_chain(); tracing::error!(err_title, message = string_chain); return; @@ -241,13 +241,13 @@ impl DownloadManager { } } - fn get_task_dir(&self) -> anyhow::Result { + fn get_task_dir(&self) -> eyre::Result { let app_data_dir = self.app.path().app_data_dir()?; let task_dir = app_data_dir.join(".下载任务"); Ok(task_dir) } - fn delete_progress_file(&self, task_id: &str) -> anyhow::Result<()> { + fn delete_progress_file(&self, task_id: &str) -> eyre::Result<()> { let task_dir = self.get_task_dir()?; let task_file = task_dir.join(format!("{task_id}.json")); if task_file.exists() { diff --git a/src-tauri/src/downloader/download_progress.rs b/src-tauri/src/downloader/download_progress.rs index a3c1306..fe516ca 100644 --- a/src-tauri/src/downloader/download_progress.rs +++ b/src-tauri/src/downloader/download_progress.rs @@ -4,7 +4,7 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; -use anyhow::{Context, anyhow}; +use eyre::{OptionExt, WrapErr, eyre}; use serde::{Deserialize, Serialize}; use specta::Type; use tauri::{AppHandle, Manager}; @@ -76,7 +76,7 @@ impl DownloadProgress { info: &NormalInfo, aid: i64, cid: Option, - ) -> anyhow::Result> { + ) -> eyre::Result> { let config = app.get_config().read().clone(); if let Some(ugc_season) = &info.ugc_season { @@ -87,10 +87,10 @@ impl DownloadProgress { } #[allow(clippy::cast_possible_wrap)] - pub fn from_bangumi(app: &AppHandle, info: &BangumiInfo, ep_id: i64) -> anyhow::Result { + pub fn from_bangumi(app: &AppHandle, info: &BangumiInfo, ep_id: i64) -> eyre::Result { let (episode, episode_order) = info.get_episode_with_order(ep_id)?; let Some(duration) = episode.duration else { - return Err(anyhow!("找不到ep_id为`{ep_id}`的番剧的时长")); + return Err(eyre!("找不到ep_id为`{ep_id}`的番剧的时长")); }; // 将毫秒转换为秒 let duration = duration / 1000; @@ -147,12 +147,12 @@ impl DownloadProgress { Ok(progress) } - pub fn from_cheese(app: &AppHandle, info: &CheeseInfo, ep_id: i64) -> anyhow::Result { + pub fn from_cheese(app: &AppHandle, info: &CheeseInfo, ep_id: i64) -> eyre::Result { let episode = info .episodes .iter() .find(|ep| ep.id == ep_id) - .context(format!("找不到ep_id为`{ep_id}`的课程"))?; + .ok_or_eyre(format!("找不到ep_id为`{ep_id}`的课程"))?; let config = app.get_config().read().clone(); @@ -196,7 +196,7 @@ impl DownloadProgress { Ok(progress) } - pub async fn process(&mut self, download_task: &Arc) -> anyhow::Result<()> { + pub async fn process(&mut self, download_task: &Arc) -> eyre::Result<()> { let ids_string = self.get_ids_string(); let _ = DownloadEvent::ProgressPreparing { @@ -206,14 +206,14 @@ impl DownloadProgress { self.prepare(&download_task.app) .await - .context("准备下载失败")?; + .wrap_err("准备下载失败")?; self.completed_ts = None; // 重置完成时间戳 download_task.update_progress(|p| *p = self.clone()); let (episode_dir, filename) = (&self.episode_dir, &self.filename); - std::fs::create_dir_all(episode_dir).context(format!( + std::fs::create_dir_all(episode_dir).wrap_err(format!( "{ids_string} 创建目录`{}`失败", episode_dir.display() ))?; @@ -234,7 +234,7 @@ impl DownloadProgress { video_task .process(download_task, self) .await - .context(format!("{ids_string} `{filename}`下载视频文件失败"))?; + .wrap_err(format!("{ids_string} `{filename}`下载视频文件失败"))?; tracing::debug!("{ids_string} `{filename}`视频下载任务完成"); } @@ -242,7 +242,7 @@ impl DownloadProgress { audio_task .process(download_task, self) .await - .context(format!("{ids_string} `{filename}`下载音频文件失败"))?; + .wrap_err(format!("{ids_string} `{filename}`下载音频文件失败"))?; tracing::debug!("{ids_string} `{filename}`音频下载任务完成"); } @@ -259,7 +259,7 @@ impl DownloadProgress { video_process_task .process(download_task, self, &mut player_info) .await - .context(format!("{ids_string} `{filename}`视频处理失败"))?; + .wrap_err(format!("{ids_string} `{filename}`视频处理失败"))?; tracing::debug!("{ids_string} `{filename}`视频处理任务完成"); } @@ -267,7 +267,7 @@ impl DownloadProgress { danmaku_task .process(download_task, self) .await - .context(format!("{ids_string} `{filename}`下载弹幕失败"))?; + .wrap_err(format!("{ids_string} `{filename}`下载弹幕失败"))?; tracing::debug!("{ids_string} `{filename}`弹幕下载任务完成"); } @@ -275,7 +275,7 @@ impl DownloadProgress { subtitle_task .process(download_task, self, &mut player_info) .await - .context(format!("{ids_string} `{filename}`下载字幕失败"))?; + .wrap_err(format!("{ids_string} `{filename}`下载字幕失败"))?; tracing::debug!("{ids_string} `{filename}`字幕下载任务完成"); } @@ -283,7 +283,7 @@ impl DownloadProgress { cover_task .process(download_task, self) .await - .context(format!("{ids_string} `{filename}`下载封面失败"))?; + .wrap_err(format!("{ids_string} `{filename}`下载封面失败"))?; tracing::debug!("{ids_string} `{filename}`封面下载任务完成"); } @@ -291,7 +291,7 @@ impl DownloadProgress { nfo_task .process(download_task, self, &mut episode_info) .await - .context(format!("{ids_string} `{filename}`下载NFO失败"))?; + .wrap_err(format!("{ids_string} `{filename}`下载NFO失败"))?; tracing::debug!("{ids_string} `{filename}`NFO下载任务完成"); } @@ -299,7 +299,7 @@ impl DownloadProgress { json_task .process(download_task, self, &mut episode_info) .await - .context(format!("{ids_string} `{filename}`下载JSON元数据失败"))?; + .wrap_err(format!("{ids_string} `{filename}`下载JSON元数据失败"))?; tracing::debug!("{ids_string} `{filename}`JSON元数据下载任务完成"); } @@ -314,7 +314,7 @@ impl DownloadProgress { Ok(()) } - async fn prepare(&mut self, app: &AppHandle) -> anyhow::Result<()> { + async fn prepare(&mut self, app: &AppHandle) -> eyre::Result<()> { let video_selected = self.video_task.selected; let video_completed = self.video_task.completed; let audio_selected = self.audio_task.selected; @@ -323,7 +323,7 @@ impl DownloadProgress { if (!video_selected && !audio_selected) || (video_completed && audio_completed) { // 如果视频和音频都没有选中,或者都已经完成,则更新需要格式化的字段就返回 self.update_fmt_fields(app) - .context("更新需要格式化的字段失败")?; + .wrap_err("更新需要格式化的字段失败")?; return Ok(()); } @@ -332,12 +332,12 @@ impl DownloadProgress { match self.episode_type { EpisodeType::Normal => { let Some(bvid) = &self.bvid else { - return Err(anyhow!("progress中的bvid为None,无法获取视频链接")); + return Err(eyre!("progress中的bvid为None,无法获取视频链接")); }; let media_url = bili_client .get_normal_url(bvid, self.cid) .await - .context("获取视频链接失败")?; + .wrap_err("获取视频链接失败")?; self.is_preview = !media_url.durl.is_empty() && media_url.dash.video.is_empty(); @@ -355,7 +355,7 @@ impl DownloadProgress { let media_url = bili_client .get_bangumi_url(self.cid) .await - .context("获取番剧视频链接失败")?; + .wrap_err("获取番剧视频链接失败")?; self.is_preview = media_url.is_preview != 0; @@ -371,12 +371,12 @@ impl DownloadProgress { } EpisodeType::Cheese => { let Some(ep_id) = self.ep_id else { - return Err(anyhow!("progress中的ep_id为None,无法获取课程视频链接")); + return Err(eyre!("progress中的ep_id为None,无法获取课程视频链接")); }; let media_url = bili_client .get_cheese_url(ep_id) .await - .context("获取课程视频链接失败")?; + .wrap_err("获取课程视频链接失败")?; self.is_drm = media_url.is_drm; self.is_preview = media_url.is_preview != 0; @@ -394,12 +394,12 @@ impl DownloadProgress { } self.update_fmt_fields(app) - .context("更新需要格式化的字段失败")?; + .wrap_err("更新需要格式化的字段失败")?; Ok(()) } - fn update_fmt_fields(&mut self, app: &AppHandle) -> anyhow::Result<()> { + fn update_fmt_fields(&mut self, app: &AppHandle) -> eyre::Result<()> { let fmt_params = self.create_fmt_params(); let config = app.get_config().read().clone(); @@ -435,7 +435,7 @@ impl DownloadProgress { } } - pub fn save(&self, app: &AppHandle, allow_create: bool) -> anyhow::Result<()> { + pub fn save(&self, app: &AppHandle, allow_create: bool) -> eyre::Result<()> { let progress = self.clone(); let file_name = format!("{}.json", progress.task_id); @@ -490,7 +490,7 @@ fn create_normal_progresses_for_single( info: &NormalInfo, cid: Option, config: &Config, -) -> anyhow::Result> { +) -> eyre::Result> { let tasks = Tasks::new(config, &info.pic); let create_ts = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); @@ -498,7 +498,7 @@ fn create_normal_progresses_for_single( if let Some(cid) = cid { // 如果有cid,则说明是要下载单个分P let Some(page) = info.pages.iter().find(|p| p.cid == cid) else { - return Err(anyhow!("找不到cid为`{cid}`的分P")); + return Err(eyre!("找不到cid为`{cid}`的分P")); }; let progress = DownloadProgress { task_id: Uuid::new_v4().to_string(), @@ -621,12 +621,12 @@ fn create_normal_progresses_for_season( aid: i64, cid: Option, config: &Config, -) -> anyhow::Result> { +) -> eyre::Result> { let section_index = ugc_season .sections .iter() .position(|s| s.episodes.iter().any(|e| e.aid == aid)) - .context(format!("找不到含有aid为`{aid}`的ep的section"))?; + .ok_or_eyre(format!("找不到含有aid为`{aid}`的ep的section"))?; let section = &ugc_season.sections[section_index]; #[allow(clippy::cast_possible_wrap)] let (ep, episode_order) = section @@ -635,7 +635,7 @@ fn create_normal_progresses_for_season( .enumerate() .map(|(i, e)| (e, i as i64 + 1)) .find(|(e, _)| e.aid == aid) - .context(format!("在section中找不到aid为`{aid}`的ep"))?; + .ok_or_eyre(format!("在section中找不到aid为`{aid}`的ep"))?; let tasks = Tasks::new(config, &ep.arc.pic); @@ -644,7 +644,7 @@ fn create_normal_progresses_for_season( if let Some(cid) = cid { // 如果有cid,则说明是要下载单个分P let Some(page) = ep.pages.iter().find(|p| p.cid == cid) else { - return Err(anyhow!("找不到cid为`{cid}`的分P")); + return Err(eyre!("找不到cid为`{cid}`的分P")); }; let progress = DownloadProgress { task_id: Uuid::new_v4().to_string(), diff --git a/src-tauri/src/downloader/download_task.rs b/src-tauri/src/downloader/download_task.rs index f0f7746..40576d5 100644 --- a/src-tauri/src/downloader/download_task.rs +++ b/src-tauri/src/downloader/download_task.rs @@ -1,6 +1,6 @@ use std::{sync::Arc, time::Duration}; -use anyhow::Context; +use eyre::WrapErr; use parking_lot::RwLock; use tauri::AppHandle; use tauri_specta::Event; @@ -11,7 +11,7 @@ use tokio::{ use crate::{ events::DownloadEvent, - extensions::{AnyhowErrorToStringChain, AppHandleExt}, + extensions::{AppHandleExt, EyreToStringChain}, types::create_download_task_params::CreateDownloadTaskParams, }; @@ -234,7 +234,7 @@ impl DownloadTask { if let Err(err) = progress .process(self) .await - .context("[继续]失败的任务可以断点续传") + .wrap_err("[继续]失败的任务可以断点续传") { let err_title = format!("{ids_string} `{episode_title}`下载失败"); let string_chain = err.to_string_chain(); @@ -283,7 +283,7 @@ impl DownloadTask { .task_sem .acquire() .await - .map_err(anyhow::Error::from) + .map_err(eyre::Report::from) { Ok(permit) => Some(permit), Err(err) => { @@ -306,7 +306,7 @@ impl DownloadTask { if let Err(err) = self .state_sender .send(DownloadTaskState::Downloading) - .map_err(anyhow::Error::from) + .map_err(eyre::Report::from) { let err_title = format!("{ids_string} `{episode_title}`发送状态`Downloading`失败"); let string_chain = err.to_string_chain(); @@ -350,7 +350,7 @@ impl DownloadTask { (progress.episode_title.clone(), progress.get_ids_string()) }; - if let Err(err) = self.state_sender.send(state).map_err(anyhow::Error::from) { + if let Err(err) = self.state_sender.send(state).map_err(eyre::Report::from) { let err_title = format!("{ids_string} `{episode_title}`发送状态`{state:?}`失败"); let string_chain = err.to_string_chain(); tracing::error!(err_title, message = string_chain); diff --git a/src-tauri/src/downloader/episode_info.rs b/src-tauri/src/downloader/episode_info.rs index ce85968..61edc8d 100644 --- a/src-tauri/src/downloader/episode_info.rs +++ b/src-tauri/src/downloader/episode_info.rs @@ -1,4 +1,4 @@ -use anyhow::Context; +use eyre::{OptionExt, WrapErr}; use tauri::AppHandle; use crate::{ @@ -23,7 +23,7 @@ pub trait GetOrInitEpisodeInfo { &'a mut self, app: &AppHandle, progress: &DownloadProgress, - ) -> anyhow::Result<&'a mut EpisodeInfo>; + ) -> eyre::Result<&'a mut EpisodeInfo>; } impl GetOrInitEpisodeInfo for Option { @@ -31,7 +31,7 @@ impl GetOrInitEpisodeInfo for Option { &'a mut self, app: &AppHandle, progress: &DownloadProgress, - ) -> anyhow::Result<&'a mut EpisodeInfo> { + ) -> eyre::Result<&'a mut EpisodeInfo> { if let Some(info) = self { return Ok(info); } @@ -44,23 +44,23 @@ impl GetOrInitEpisodeInfo for Option { let info = bili_client .get_normal_info(GetNormalInfoParams::Aid(aid)) .await - .context("获取普通视频信息失败")?; + .wrap_err("获取普通视频信息失败")?; EpisodeInfo::Normal(info) } EpisodeType::Bangumi => { - let ep_id = ep_id.context("ep_id为None")?; + let ep_id = ep_id.ok_or_eyre("ep_id为None")?; let info = bili_client .get_bangumi_info(GetBangumiInfoParams::EpId(ep_id)) .await - .context("获取番剧信息失败")?; + .wrap_err("获取番剧信息失败")?; EpisodeInfo::Bangumi(info, ep_id) } EpisodeType::Cheese => { - let ep_id = ep_id.context("ep_id为None")?; + let ep_id = ep_id.ok_or_eyre("ep_id为None")?; let info = bili_client .get_cheese_info(GetCheeseInfoParams::EpId(ep_id)) .await - .context("获取课程信息失败")?; + .wrap_err("获取课程信息失败")?; EpisodeInfo::Cheese(info, ep_id) } }; diff --git a/src-tauri/src/downloader/fmt_params.rs b/src-tauri/src/downloader/fmt_params.rs index 22eef7e..1b80935 100644 --- a/src-tauri/src/downloader/fmt_params.rs +++ b/src-tauri/src/downloader/fmt_params.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, path::PathBuf}; -use anyhow::Context; +use eyre::{OptionExt, WrapErr}; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; @@ -36,18 +36,15 @@ pub struct FmtParams { } impl FmtParams { - pub fn get_episode_dir_and_filename( - &self, - config: &Config, - ) -> anyhow::Result<(PathBuf, String)> { + pub fn get_episode_dir_and_filename(&self, config: &Config) -> eyre::Result<(PathBuf, String)> { use strfmt::strfmt; let mut json_value = - serde_json::to_value(self).context("将FmtParams转为serde_json::Value失败")?; + serde_json::to_value(self).wrap_err("将FmtParams转为serde_json::Value失败")?; let json_map = json_value .as_object_mut() - .context("FmtParams不是JSON对象")?; + .ok_or_eyre("FmtParams不是JSON对象")?; // 格式化时间字段 format_time_fields(json_map, &config.time_fmt); @@ -74,7 +71,7 @@ impl FmtParams { let dir_fmt_parts: Vec<&str> = dir_fmt.split('/').collect(); let mut dir_names = Vec::new(); for fmt in dir_fmt_parts { - let dir_name = strfmt(fmt, &vars).context("格式化目录名失败")?; + let dir_name = strfmt(fmt, &vars).wrap_err("格式化目录名失败")?; let dir_name = filename_filter(&dir_name); if !dir_name.is_empty() { dir_names.push(dir_name); @@ -82,7 +79,7 @@ impl FmtParams { } // 最后一部分是文件名 - let filename = dir_names.pop().context("没有找到文件名部分")?; + let filename = dir_names.pop().ok_or_eyre("没有找到文件名部分")?; // 剩下的部分是目录名 let mut episode_dir = config.download_dir.clone(); for dir_name in dir_names { diff --git a/src-tauri/src/downloader/tasks/audio_task.rs b/src-tauri/src/downloader/tasks/audio_task.rs index 18ccc5e..ffc1b27 100644 --- a/src-tauri/src/downloader/tasks/audio_task.rs +++ b/src-tauri/src/downloader/tasks/audio_task.rs @@ -4,7 +4,7 @@ use std::{ sync::Arc, }; -use anyhow::{Context, anyhow}; +use eyre::{WrapErr, eyre}; use fs4::fs_std::FileExt; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; @@ -18,7 +18,7 @@ use crate::{ download_chunk_task::DownloadChunkTask, download_progress::DownloadProgress, download_task::DownloadTask, media_chunk::MediaChunk, }, - extensions::{AnyhowErrorToStringChain, AppHandleExt}, + extensions::{AppHandleExt, EyreToStringChain}, types::{ audio_quality::AudioQuality, bangumi_media_url::BangumiMediaUrl, cheese_media_url::CheeseMediaUrl, normal_media_url::NormalMediaUrl, @@ -45,7 +45,7 @@ impl AudioTask { &mut self, app: &AppHandle, media_url: &NormalMediaUrl, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let mut join_set = JoinSet::new(); if let Some(medias) = &media_url.dash.audio { @@ -130,7 +130,7 @@ impl AudioTask { &mut self, app: &AppHandle, media_url: &BangumiMediaUrl, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let Some(dash) = &media_url.dash else { // 如果没有音频,则直接返回 self.completed = true; @@ -190,7 +190,7 @@ impl AudioTask { &mut self, app: &AppHandle, media_url: &CheeseMediaUrl, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let Some(dash) = &media_url.dash else { // 如果没有音频,则直接返回 self.completed = true; @@ -314,7 +314,7 @@ impl AudioTask { &self, download_task: &Arc, progress: &DownloadProgress, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (audio_task, episode_title, ids_string) = { ( @@ -378,7 +378,7 @@ impl AudioTask { }; join_set.spawn(async move { - download_chunk_task.process().await.context(format!( + download_chunk_task.process().await.wrap_err(format!( "分片`{chunk_index}/{chunk_count}`下载失败({start}-{end})" )) }); @@ -407,20 +407,20 @@ impl AudioTask { .iter() .all(|chunk| chunk.completed); if !download_completed { - return Err(anyhow!( + return Err(eyre!( "音频文件`{}`有分片未下载完成,[继续]可以跳过已下载分片断点续传", temp_file_path.display() )); } - let is_audio_file_complete = utils::is_mp4_complete(&temp_file_path).context(format!( + let is_audio_file_complete = utils::is_mp4_complete(&temp_file_path).wrap_err(format!( "检查音频文件`{}`是否完整失败", temp_file_path.display() ))?; if !is_audio_file_complete { download_task.update_progress(|p| p.audio_task.mark_uncompleted()); - return Err(anyhow!( + return Err(eyre!( "音频文件`{}`不完整,[继续]会重新下载所有分片", temp_file_path.display() )); @@ -429,9 +429,9 @@ impl AudioTask { // 重命名临时文件 if m4a_path.exists() { std::fs::remove_file(&m4a_path) - .context(format!("删除已存在的音频文件`{}`失败", m4a_path.display()))?; + .wrap_err(format!("删除已存在的音频文件`{}`失败", m4a_path.display()))?; } - std::fs::rename(&temp_file_path, &m4a_path).context(format!( + std::fs::rename(&temp_file_path, &m4a_path).wrap_err(format!( "将临时文件`{}`重命名为`{}`失败", temp_file_path.display(), m4a_path.display() diff --git a/src-tauri/src/downloader/tasks/cover_task.rs b/src-tauri/src/downloader/tasks/cover_task.rs index 8a68f05..478ea6f 100644 --- a/src-tauri/src/downloader/tasks/cover_task.rs +++ b/src-tauri/src/downloader/tasks/cover_task.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use anyhow::Context; +use eyre::WrapErr; use serde::{Deserialize, Serialize}; use specta::Type; @@ -30,18 +30,18 @@ impl CoverTask { &self, download_task: &Arc, progress: &DownloadProgress, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let bili_client = download_task.app.get_bili_client(); let (cover_data, ext) = bili_client .get_cover_data_and_ext(&progress.cover_task.url) .await - .context("获取封面失败")?; + .wrap_err("获取封面失败")?; let save_path = episode_dir.join(format!("{filename}.{ext}")); std::fs::write(&save_path, cover_data) - .context(format!("保存封面到`{}`失败", save_path.display()))?; + .wrap_err(format!("保存封面到`{}`失败", save_path.display()))?; download_task.update_progress(|p| p.cover_task.completed = true); diff --git a/src-tauri/src/downloader/tasks/danmaku_task.rs b/src-tauri/src/downloader/tasks/danmaku_task.rs index 79d328e..d38e33e 100644 --- a/src-tauri/src/downloader/tasks/danmaku_task.rs +++ b/src-tauri/src/downloader/tasks/danmaku_task.rs @@ -1,6 +1,6 @@ use std::{fs::File, sync::Arc}; -use anyhow::Context; +use eyre::WrapErr; use serde::{Deserialize, Serialize}; use specta::Type; @@ -37,7 +37,7 @@ impl DanmakuTask { &self, download_task: &Arc, progress: &DownloadProgress, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let danmaku_task = &progress.danmaku_task; let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let ids_string = progress.get_ids_string(); @@ -66,29 +66,29 @@ impl DanmakuTask { let replies = bili_client .get_danmaku(progress.aid, progress.cid, progress.duration) .await - .context("获取弹幕失败")?; + .wrap_err("获取弹幕失败")?; let xml = replies .to_xml(progress.cid) - .context("将弹幕转换为XML失败")?; + .wrap_err("将弹幕转换为XML失败")?; if danmaku_task.xml_selected { std::fs::write(&xml_path, &xml) - .context(format!("保存弹幕XML到`{}`失败", xml_path.display()))?; + .wrap_err(format!("保存弹幕XML到`{}`失败", xml_path.display()))?; } if danmaku_task.ass_selected { let config = download_task.app.get_config().read().danmaku_config.clone(); let ass_file = File::create(&ass_path) - .context(format!("创建弹幕ASS文件`{}`失败", ass_path.display()))?; + .wrap_err(format!("创建弹幕ASS文件`{}`失败", ass_path.display()))?; let title = filename.clone(); - xml_to_ass(&xml, ass_file, title, config).context("将弹幕XML转换为ASS失败")?; + xml_to_ass(&xml, ass_file, title, config).wrap_err("将弹幕XML转换为ASS失败")?; } if danmaku_task.json_selected { - let json_string = serde_json::to_string(&replies).context("将弹幕转换为JSON失败")?; + let json_string = serde_json::to_string(&replies).wrap_err("将弹幕转换为JSON失败")?; std::fs::write(&json_path, json_string) - .context(format!("保存弹幕JSON到`{}`失败", json_path.display()))?; + .wrap_err(format!("保存弹幕JSON到`{}`失败", json_path.display()))?; } download_task.update_progress(|p| p.danmaku_task.completed = true); diff --git a/src-tauri/src/downloader/tasks/json_task.rs b/src-tauri/src/downloader/tasks/json_task.rs index 5e89949..89a9024 100644 --- a/src-tauri/src/downloader/tasks/json_task.rs +++ b/src-tauri/src/downloader/tasks/json_task.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use anyhow::Context; +use eyre::WrapErr; use serde::{Deserialize, Serialize}; use specta::Type; @@ -31,7 +31,7 @@ impl JsonTask { download_task: &Arc, progress: &DownloadProgress, episode_info: &mut Option, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let episode_info = episode_info @@ -41,17 +41,17 @@ impl JsonTask { let json_path = episode_dir.join(format!("{filename}-元数据.json")); let json_string = match episode_info { EpisodeInfo::Normal(info) => { - serde_json::to_string(&info).context("将普通视频信息转换为JSON失败")? + serde_json::to_string(&info).wrap_err("将普通视频信息转换为JSON失败")? } EpisodeInfo::Bangumi(info, _ep_id) => { - serde_json::to_string(&info).context("将番剧信息转换为JSON失败")? + serde_json::to_string(&info).wrap_err("将番剧信息转换为JSON失败")? } EpisodeInfo::Cheese(info, _ep_id) => { - serde_json::to_string(&info).context("将课程信息转换为JSON失败")? + serde_json::to_string(&info).wrap_err("将课程信息转换为JSON失败")? } }; std::fs::write(&json_path, json_string) - .context(format!("保存JSON到`{}`失败", json_path.display()))?; + .wrap_err(format!("保存JSON到`{}`失败", json_path.display()))?; download_task.update_progress(|p| p.json_task.completed = true); diff --git a/src-tauri/src/downloader/tasks/nfo_task.rs b/src-tauri/src/downloader/tasks/nfo_task.rs index 9939d9c..f68c826 100644 --- a/src-tauri/src/downloader/tasks/nfo_task.rs +++ b/src-tauri/src/downloader/tasks/nfo_task.rs @@ -1,7 +1,7 @@ use std::sync::Arc; -use anyhow::{Context, anyhow}; use chrono::{DateTime, Datelike, NaiveDateTime}; +use eyre::{OptionExt, WrapErr, eyre}; use serde::{Deserialize, Serialize}; use specta::Type; use yaserde::{YaDeserialize, YaSerialize}; @@ -42,7 +42,7 @@ impl NfoTask { download_task: &Arc, progress: &DownloadProgress, episode_info: &mut Option, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let episode_info = episode_info .get_or_init(&download_task.app, progress) .await?; @@ -69,7 +69,7 @@ impl NfoTask { download_task: &Arc, progress: &DownloadProgress, info: &NormalInfo, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let ids_string = progress.get_ids_string(); let nfo_path = episode_dir.join(format!("{filename}.nfo")); @@ -89,21 +89,21 @@ impl NfoTask { let tags = bili_client .get_tags(progress.aid) .await - .context("获取视频标签失败")?; + .wrap_err("获取视频标签失败")?; let movie_nfo = info .to_movie_nfo(tags) - .context("将普通视频信息转换为movie NFO失败")?; + .wrap_err("将普通视频信息转换为movie NFO失败")?; std::fs::write(&nfo_path, movie_nfo) - .context(format!("保存普通视频NFO到`{}`失败", nfo_path.display()))?; + .wrap_err(format!("保存普通视频NFO到`{}`失败", nfo_path.display()))?; if let Some(ugc_season) = &info.ugc_season { let collection_cover = &ugc_season.cover; let (cover_data, ext) = bili_client .get_cover_data_and_ext(collection_cover) .await - .context("获取普通视频合集封面失败")?; + .wrap_err("获取普通视频合集封面失败")?; let cover_path = episode_dir.join(format!("poster.{ext}")); - std::fs::write(&cover_path, cover_data).context(format!( + std::fs::write(&cover_path, cover_data).wrap_err(format!( "保存普通视频合集封面到`{}`失败", cover_path.display() ))?; @@ -120,7 +120,7 @@ impl NfoTask { progress: &DownloadProgress, info: &BangumiInfo, ep_id: &i64, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let ids_string = progress.get_ids_string(); let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo")); @@ -139,16 +139,16 @@ impl NfoTask { let tvshow_nfo = info .to_tvshow_nfo() - .context("将番剧信息转换为tvshow NFO失败")?; + .wrap_err("将番剧信息转换为tvshow NFO失败")?; let tvshow_nfo_path = episode_dir.join("tvshow.nfo"); std::fs::write(&tvshow_nfo_path, tvshow_nfo) - .context(format!("保存番剧NFO到`{}`失败", tvshow_nfo_path.display()))?; + .wrap_err(format!("保存番剧NFO到`{}`失败", tvshow_nfo_path.display()))?; let episode_details_nfo = info .to_episode_details_nfo(*ep_id) - .context("将番剧信息转换为episodedetail NFO失败")?; + .wrap_err("将番剧信息转换为episodedetail NFO失败")?; let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo")); - std::fs::write(&episode_details_nfo_path, episode_details_nfo).context(format!( + std::fs::write(&episode_details_nfo_path, episode_details_nfo).wrap_err(format!( "保存番剧NFO到`{}`失败", episode_details_nfo_path.display() ))?; @@ -157,20 +157,20 @@ impl NfoTask { let (poster_data, ext) = bili_client .get_cover_data_and_ext(poster_url) .await - .context("获取番剧封面失败")?; + .wrap_err("获取番剧封面失败")?; let poster_path = episode_dir.join(format!("poster.{ext}")); std::fs::write(&poster_path, poster_data) - .context(format!("保存番剧封面到`{}`失败", poster_path.display()))?; + .wrap_err(format!("保存番剧封面到`{}`失败", poster_path.display()))?; let fanart_url = &info.bkg_cover; if !fanart_url.is_empty() { let (fanart_data, ext) = bili_client .get_cover_data_and_ext(fanart_url) .await - .context("获取番剧封面失败")?; + .wrap_err("获取番剧封面失败")?; let fanart_path = episode_dir.join(format!("fanart.{ext}")); std::fs::write(&fanart_path, fanart_data) - .context(format!("保存番剧封面到`{}`失败", fanart_path.display()))?; + .wrap_err(format!("保存番剧封面到`{}`失败", fanart_path.display()))?; } download_task.update_progress(|p| p.nfo_task.completed = true); @@ -184,7 +184,7 @@ impl NfoTask { progress: &DownloadProgress, info: &CheeseInfo, ep_id: &i64, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let ids_string = progress.get_ids_string(); let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo")); @@ -203,15 +203,15 @@ impl NfoTask { let tvshow_nfo = info .to_tvshow_nfo() - .context("将课程信息转换为tvshow NFO失败")?; + .wrap_err("将课程信息转换为tvshow NFO失败")?; let tvshow_nfo_path = episode_dir.join("tvshow.nfo"); std::fs::write(&tvshow_nfo_path, tvshow_nfo) - .context(format!("保存课程NFO到`{}`失败", tvshow_nfo_path.display()))?; + .wrap_err(format!("保存课程NFO到`{}`失败", tvshow_nfo_path.display()))?; let episode_details_nfo = info .to_episode_details_nfo(*ep_id) - .context("将课程信息转换为episodedetail NFO失败")?; - std::fs::write(&episode_details_nfo_path, episode_details_nfo).context(format!( + .wrap_err("将课程信息转换为episodedetail NFO失败")?; + std::fs::write(&episode_details_nfo_path, episode_details_nfo).wrap_err(format!( "保存课程NFO到`{}`失败", episode_details_nfo_path.display() ))?; @@ -220,10 +220,10 @@ impl NfoTask { let (poster_data, ext) = bili_client .get_cover_data_and_ext(poster_url) .await - .context("获取课程封面失败")?; + .wrap_err("获取课程封面失败")?; let poster_path = episode_dir.join(format!("poster.{ext}")); std::fs::write(&poster_path, poster_data) - .context(format!("保存课程封面到`{}`失败", poster_path.display()))?; + .wrap_err(format!("保存课程封面到`{}`失败", poster_path.display()))?; download_task.update_progress(|p| p.nfo_task.completed = true); @@ -299,7 +299,7 @@ struct EpisodeDetails { } impl NormalInfo { - pub fn to_movie_nfo(&self, tags: Tags) -> anyhow::Result { + pub fn to_movie_nfo(&self, tags: Tags) -> eyre::Result { let genre = vec![ "Bilibili视频".to_string(), self.tname.clone(), @@ -314,7 +314,7 @@ impl NormalInfo { let ts = self.pubdate; let date_time = DateTime::from_timestamp(ts, 0) - .context(format!("将视频发布时间戳转换为日期时间失败: {ts}"))? + .ok_or_eyre(format!("将视频发布时间戳转换为日期时间失败: {ts}"))? .with_timezone(&chrono::Local); let set = self.ugc_season.as_ref().map(|ugc_season| Set { @@ -357,16 +357,16 @@ impl NormalInfo { ..Default::default() }; - let nfo = yaserde::ser::to_string_with_config(&movie, &cfg).map_err(|e| anyhow!(e))?; + let nfo = yaserde::ser::to_string_with_config(&movie, &cfg).map_err(|e| eyre!(e))?; Ok(nfo) } } impl BangumiInfo { - pub fn to_tvshow_nfo(&self) -> anyhow::Result { + pub fn to_tvshow_nfo(&self) -> eyre::Result { let time_str = &self.publish.pub_time; - let date_time = NaiveDateTime::parse_from_str(time_str, "%Y-%m-%d %H:%M:%S").context( + let date_time = NaiveDateTime::parse_from_str(time_str, "%Y-%m-%d %H:%M:%S").wrap_err( format!("将番剧发布时间字符串转换为日期时间失败: {time_str}"), )?; @@ -394,30 +394,30 @@ impl BangumiInfo { ..Default::default() }; - let nfo = yaserde::ser::to_string_with_config(&tv_show, &cfg).map_err(|e| anyhow!(e))?; + let nfo = yaserde::ser::to_string_with_config(&tv_show, &cfg).map_err(|e| eyre!(e))?; Ok(nfo) } - pub fn to_episode_details_nfo(&self, ep_id: i64) -> anyhow::Result { + pub fn to_episode_details_nfo(&self, ep_id: i64) -> eyre::Result { let (episode, episode_order) = self.get_episode_with_order(ep_id)?; let ts = episode.pub_time; let date_time = DateTime::from_timestamp(ts, 0) - .context(format!("将番剧发布时间戳转换为日期时间失败: {ts}"))? + .ok_or_eyre(format!("将番剧发布时间戳转换为日期时间失败: {ts}"))? .with_timezone(&chrono::Local); let title = episode .show_title .clone() - .context("episode.show_title为None")?; + .ok_or_eyre("episode.show_title为None")?; let plot = episode .share_copy .clone() - .context("episode.share_copy为None")?; + .ok_or_eyre("episode.share_copy为None")?; - let duration = episode.duration.context("episode.duration为None")?; + let duration = episode.duration.ok_or_eyre("episode.duration为None")?; let episode_details = EpisodeDetails { title, @@ -440,7 +440,7 @@ impl BangumiInfo { }; let nfo = - yaserde::ser::to_string_with_config(&episode_details, &cfg).map_err(|e| anyhow!(e))?; + yaserde::ser::to_string_with_config(&episode_details, &cfg).map_err(|e| eyre!(e))?; Ok(nfo) } @@ -489,11 +489,11 @@ impl BangumiInfo { } impl CheeseInfo { - pub fn to_tvshow_nfo(&self) -> anyhow::Result { - let episode = self.episodes.first().context("episodes列表为空")?; + pub fn to_tvshow_nfo(&self) -> eyre::Result { + let episode = self.episodes.first().ok_or_eyre("episodes列表为空")?; let ts = episode.release_date; let date_time = DateTime::from_timestamp(ts, 0) - .context(format!("将课程的发布时间戳转换为日期时间失败: {ts}"))? + .ok_or_eyre(format!("将课程的发布时间戳转换为日期时间失败: {ts}"))? .with_timezone(&chrono::Local); let status = match self.release_status.as_str() { @@ -520,21 +520,21 @@ impl CheeseInfo { ..Default::default() }; - let nfo = yaserde::ser::to_string_with_config(&tv_show, &cfg).map_err(|e| anyhow!(e))?; + let nfo = yaserde::ser::to_string_with_config(&tv_show, &cfg).map_err(|e| eyre!(e))?; Ok(nfo) } - pub fn to_episode_details_nfo(&self, ep_id: i64) -> anyhow::Result { + pub fn to_episode_details_nfo(&self, ep_id: i64) -> eyre::Result { let episode = self .episodes .iter() .find(|ep| ep.id == ep_id) - .context(format!("找不到ep_id为`{ep_id}`的课程"))?; + .ok_or_eyre(format!("找不到ep_id为`{ep_id}`的课程"))?; let ts = episode.release_date; let date_time = DateTime::from_timestamp(ts, 0) - .context(format!("将课程发布时间戳转换为日期时间失败: {ts}"))? + .ok_or_eyre(format!("将课程发布时间戳转换为日期时间失败: {ts}"))? .with_timezone(&chrono::Local); let episode_details = EpisodeDetails { @@ -558,7 +558,7 @@ impl CheeseInfo { }; let nfo = - yaserde::ser::to_string_with_config(&episode_details, &cfg).map_err(|e| anyhow!(e))?; + yaserde::ser::to_string_with_config(&episode_details, &cfg).map_err(|e| eyre!(e))?; Ok(nfo) } diff --git a/src-tauri/src/downloader/tasks/subtitle_task.rs b/src-tauri/src/downloader/tasks/subtitle_task.rs index bf0d94c..b9a3d08 100644 --- a/src-tauri/src/downloader/tasks/subtitle_task.rs +++ b/src-tauri/src/downloader/tasks/subtitle_task.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use anyhow::Context; +use eyre::WrapErr; use serde::{Deserialize, Serialize}; use specta::Type; @@ -32,7 +32,7 @@ impl SubtitleTask { download_task: &Arc, progress: &DownloadProgress, player_info: &mut Option, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { use std::fmt::Write; let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); @@ -48,7 +48,7 @@ impl SubtitleTask { let subtitle = bili_client .get_subtitle(&url) .await - .context("获取字幕失败")?; + .wrap_err("获取字幕失败")?; let mut srt_content = String::new(); for (i, b) in subtitle.body.iter().enumerate() { diff --git a/src-tauri/src/downloader/tasks/video_process_task.rs b/src-tauri/src/downloader/tasks/video_process_task.rs index 7f020ea..32be99b 100644 --- a/src-tauri/src/downloader/tasks/video_process_task.rs +++ b/src-tauri/src/downloader/tasks/video_process_task.rs @@ -1,6 +1,6 @@ use std::{path::PathBuf, sync::Arc}; -use anyhow::{Context, anyhow}; +use eyre::{WrapErr, eyre}; use serde::{Deserialize, Serialize}; use specta::Type; use tauri::AppHandle; @@ -43,21 +43,21 @@ impl VideoProcessTask { download_task: &Arc, progress: &DownloadProgress, player_info: &mut Option, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let embed_selected = self.embed_chapter_selected || self.embed_skip_selected; if self.merge_selected && embed_selected { self.merge_and_embed(download_task, progress, player_info) .await - .context("自动合并+嵌入章节元数据失败")?; + .wrap_err("自动合并+嵌入章节元数据失败")?; } else if self.merge_selected { self.merge(download_task, progress) .await - .context("自动合并失败")?; + .wrap_err("自动合并失败")?; } else if embed_selected { self.embed(download_task, progress, player_info) .await - .context("嵌入章节元数据失败")?; + .wrap_err("嵌入章节元数据失败")?; } Ok(()) @@ -68,10 +68,10 @@ impl VideoProcessTask { download_task: &Arc, progress: &DownloadProgress, player_info: &mut Option, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); - let ffmpeg_program = utils::get_ffmpeg_program().context("获取FFmpeg程序路径失败")?; + let ffmpeg_program = utils::get_ffmpeg_program().wrap_err("获取FFmpeg程序路径失败")?; let video_path = episode_dir.join(format!("{filename}.mp4")); if !video_path.exists() { @@ -84,14 +84,14 @@ impl VideoProcessTask { // 如果音频文件不存在,则只嵌入章节元数据 self.embed(download_task, progress, player_info) .await - .context("嵌入章节元数据失败")?; + .wrap_err("嵌入章节元数据失败")?; return Ok(()); } let metadata_path = self .create_chapter_metadata(&download_task.app, progress, player_info) .await - .context("创建章节元数据失败")?; + .wrap_err("创建章节元数据失败")?; let output_path = episode_dir.join(format!("{filename}-merged.mp4")); @@ -134,24 +134,24 @@ impl VideoProcessTask { if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); - let err = anyhow!(format!("STDOUT: {stdout}")) - .context(format!("STDERR: {stderr}")) - .context("原因可能是视频或音频文件损坏,建议[重来]试试"); + let err = eyre!(format!("STDOUT: {stdout}")) + .wrap_err(format!("STDERR: {stderr}")) + .wrap_err("原因可能是视频或音频文件损坏,建议[重来]试试"); return Err(err); } std::fs::remove_file(&video_path) - .context(format!("删除视频文件`{}`失败", video_path.display()))?; + .wrap_err(format!("删除视频文件`{}`失败", video_path.display()))?; std::fs::remove_file(&audio_path) - .context(format!("删除音频文件`{}`失败", audio_path.display()))?; - std::fs::rename(&output_path, &video_path).context(format!( + .wrap_err(format!("删除音频文件`{}`失败", audio_path.display()))?; + std::fs::rename(&output_path, &video_path).wrap_err(format!( "将`{}`重命名为`{}`失败", output_path.display(), video_path.display() ))?; if let Some(metadata_path) = metadata_path { - std::fs::remove_file(&metadata_path).context(format!( + std::fs::remove_file(&metadata_path).wrap_err(format!( "删除章节元数据文件`{}`失败", metadata_path.display() ))?; @@ -166,7 +166,7 @@ impl VideoProcessTask { &self, download_task: &Arc, progress: &DownloadProgress, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let video_path = episode_dir.join(format!("{filename}.mp4")); @@ -183,7 +183,7 @@ impl VideoProcessTask { let output_path = episode_dir.join(format!("{filename}-merged.mp4")); - let ffmpeg_program = utils::get_ffmpeg_program().context("获取FFmpeg程序路径失败")?; + let ffmpeg_program = utils::get_ffmpeg_program().wrap_err("获取FFmpeg程序路径失败")?; let (tx, rx) = tokio::sync::oneshot::channel(); let video_path_clone = video_path.clone(); @@ -219,17 +219,17 @@ impl VideoProcessTask { if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); - let err = anyhow!(format!("STDOUT: {stdout}")) - .context(format!("STDERR: {stderr}")) - .context("原因可能是视频或音频文件损坏,建议[重来]试试"); + let err = eyre!(format!("STDOUT: {stdout}")) + .wrap_err(format!("STDERR: {stderr}")) + .wrap_err("原因可能是视频或音频文件损坏,建议[重来]试试"); return Err(err); } std::fs::remove_file(&video_path) - .context(format!("删除视频文件`{}`失败", video_path.display()))?; + .wrap_err(format!("删除视频文件`{}`失败", video_path.display()))?; std::fs::remove_file(&audio_path) - .context(format!("删除音频文件`{}`失败", audio_path.display()))?; - std::fs::rename(&output_path, &video_path).context(format!( + .wrap_err(format!("删除音频文件`{}`失败", audio_path.display()))?; + std::fs::rename(&output_path, &video_path).wrap_err(format!( "将`{}`重命名为`{}`失败", output_path.display(), video_path.display() @@ -245,10 +245,10 @@ impl VideoProcessTask { download_task: &Arc, progress: &DownloadProgress, player_info: &mut Option, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); - let ffmpeg_program = utils::get_ffmpeg_program().context("获取FFmpeg程序路径失败")?; + let ffmpeg_program = utils::get_ffmpeg_program().wrap_err("获取FFmpeg程序路径失败")?; let video_path = episode_dir.join(format!("{filename}.mp4")); if !video_path.exists() { @@ -261,7 +261,7 @@ impl VideoProcessTask { let metadata_path = self .create_chapter_metadata(&download_task.app, progress, player_info) .await - .context("创建章节元数据失败")?; + .wrap_err("创建章节元数据失败")?; let Some(metadata_path) = metadata_path else { download_task.update_progress(|p| p.video_process_task.completed = true); @@ -301,20 +301,20 @@ impl VideoProcessTask { if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); let stdout = String::from_utf8_lossy(&output.stdout); - let err = anyhow!(format!("STDOUT: {stdout}")) - .context(format!("STDERR: {stderr}")) - .context("原因可能是视频或音频文件损坏,建议[重来]试试"); + let err = eyre!(format!("STDOUT: {stdout}")) + .wrap_err(format!("STDERR: {stderr}")) + .wrap_err("原因可能是视频或音频文件损坏,建议[重来]试试"); return Err(err); } std::fs::remove_file(&video_path) - .context(format!("删除视频文件`{}`失败", video_path.display()))?; - std::fs::rename(&output_path, &video_path).context(format!( + .wrap_err(format!("删除视频文件`{}`失败", video_path.display()))?; + std::fs::rename(&output_path, &video_path).wrap_err(format!( "将`{}`重命名为`{}`失败", output_path.display(), video_path.display() ))?; - std::fs::remove_file(&metadata_path).context(format!( + std::fs::remove_file(&metadata_path).wrap_err(format!( "删除章节元数据文件`{}`失败", metadata_path.display() ))?; @@ -329,7 +329,7 @@ impl VideoProcessTask { app: &AppHandle, progress: &DownloadProgress, player_info: &mut Option, - ) -> anyhow::Result> { + ) -> eyre::Result> { let mut chapter_segments = ChapterSegments { segments: Vec::new(), }; @@ -368,7 +368,7 @@ impl VideoProcessTask { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let metadata_path = episode_dir.join(format!("{filename}.FFMETA.ini")); std::fs::write(&metadata_path, metadata_content) - .context(format!("保存章节元数据到`{}`失败", metadata_path.display()))?; + .wrap_err(format!("保存章节元数据到`{}`失败", metadata_path.display()))?; Ok(Some(metadata_path)) } diff --git a/src-tauri/src/downloader/tasks/video_task.rs b/src-tauri/src/downloader/tasks/video_task.rs index 3f63964..e2a0b5d 100644 --- a/src-tauri/src/downloader/tasks/video_task.rs +++ b/src-tauri/src/downloader/tasks/video_task.rs @@ -4,7 +4,7 @@ use std::{ sync::Arc, }; -use anyhow::{Context, anyhow}; +use eyre::{OptionExt, WrapErr, eyre}; use fs4::fs_std::FileExt; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; @@ -18,7 +18,7 @@ use crate::{ download_chunk_task::DownloadChunkTask, download_progress::DownloadProgress, download_task::DownloadTask, media_chunk::MediaChunk, }, - extensions::{AnyhowErrorToStringChain, AppHandleExt}, + extensions::{AppHandleExt, EyreToStringChain}, types::{ bangumi_media_url::BangumiMediaUrl, cheese_media_url::CheeseMediaUrl, codec_type::CodecType, normal_media_url::NormalMediaUrl, video_quality::VideoQuality, @@ -46,7 +46,7 @@ impl VideoTask { &mut self, app: &AppHandle, media_url: &NormalMediaUrl, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let mut join_set = JoinSet::new(); for media in &media_url.dash.video { @@ -110,7 +110,7 @@ impl VideoTask { &mut self, app: &AppHandle, media_url: &BangumiMediaUrl, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let mut medias: Vec = Vec::new(); let mut join_set = JoinSet::new(); @@ -180,7 +180,7 @@ impl VideoTask { &mut self, app: &AppHandle, media_url: &CheeseMediaUrl, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let mut medias: Vec = Vec::new(); let mut join_set = JoinSet::new(); @@ -246,16 +246,16 @@ impl VideoTask { Ok(()) } - fn prepare(&mut self, app: &AppHandle, medias: &[MediaForPrepare]) -> anyhow::Result<()> { + fn prepare(&mut self, app: &AppHandle, medias: &[MediaForPrepare]) -> eyre::Result<()> { if medias.is_empty() { - return Err(anyhow!("获取视频地址失败,medias为空")); + return Err(eyre!("获取视频地址失败,medias为空")); } let video_quality_is_unknown = self.video_quality == VideoQuality::Unknown; let codec_type_is_unknown = self.codec_type == CodecType::Unknown; if video_quality_is_unknown != codec_type_is_unknown { - return Err(anyhow!( + return Err(eyre!( "`video_quality`和`codec_type`必须同时为`Unknown`或同时不为`Unknown`" )); } @@ -269,7 +269,7 @@ impl VideoTask { select_exact_match_media(self, medias).or_else(|| select_media_by_priority(app, medias)) }; - let media = selected_media.context("获取视频地址失败,medias为空")?; + let media = selected_media.ok_or_eyre("获取视频地址失败,medias为空")?; self.video_quality = media.id.into(); self.codec_type = media.codecid.into(); @@ -322,7 +322,7 @@ impl VideoTask { &self, download_task: &Arc, progress: &DownloadProgress, - ) -> anyhow::Result<()> { + ) -> eyre::Result<()> { let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (video_task, episode_title, ids_string) = { let progress = download_task.progress.read(); @@ -389,7 +389,7 @@ impl VideoTask { let chunk_order = i + 1; join_set.spawn(async move { - download_chunk_task.process().await.context(format!( + download_chunk_task.process().await.wrap_err(format!( "分片`{chunk_order}/{chunk_count}`下载失败({start}-{end})" )) }); @@ -418,20 +418,20 @@ impl VideoTask { .iter() .all(|chunk| chunk.completed); if !download_completed { - return Err(anyhow!( + return Err(eyre!( "视频文件`{}`有分片未下载完成,[继续]可以跳过已下载分片断点续传", temp_file_path.display() )); } - let is_video_file_complete = utils::is_mp4_complete(&temp_file_path).context(format!( + let is_video_file_complete = utils::is_mp4_complete(&temp_file_path).wrap_err(format!( "检查视频文件`{}`是否完整失败", temp_file_path.display() ))?; if !is_video_file_complete { download_task.update_progress(|p| p.video_task.mark_uncompleted()); - return Err(anyhow!( + return Err(eyre!( "视频文件`{}`不完整,[继续]会重新下载所有分片", temp_file_path.display() )); @@ -440,9 +440,9 @@ impl VideoTask { // 重命名临时文件 if mp4_path.exists() { std::fs::remove_file(&mp4_path) - .context(format!("删除已存在的视频文件`{}`失败", mp4_path.display()))?; + .wrap_err(format!("删除已存在的视频文件`{}`失败", mp4_path.display()))?; } - std::fs::rename(&temp_file_path, &mp4_path).context(format!( + std::fs::rename(&temp_file_path, &mp4_path).wrap_err(format!( "将临时文件`{}`重命名为`{}`失败", temp_file_path.display(), mp4_path.display() diff --git a/src-tauri/src/errors.rs b/src-tauri/src/errors.rs index e82308e..9b0d2a7 100644 --- a/src-tauri/src/errors.rs +++ b/src-tauri/src/errors.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use specta::Type; -use crate::extensions::AnyhowErrorToStringChain; +use crate::extensions::EyreToStringChain; pub type CommandResult = Result; @@ -14,7 +14,7 @@ pub struct CommandError { impl CommandError { pub fn from(err_title: &str, err: E) -> Self where - E: Into, + E: Into, { let string_chain = err.into().to_string_chain(); tracing::error!(err_title, message = string_chain); diff --git a/src-tauri/src/extensions.rs b/src-tauri/src/extensions.rs index 96e265f..cd9a576 100644 --- a/src-tauri/src/extensions.rs +++ b/src-tauri/src/extensions.rs @@ -1,4 +1,4 @@ -use anyhow::Context; +use eyre::WrapErr; use parking_lot::RwLock; use tauri::{AppHandle, Manager, State}; @@ -9,8 +9,8 @@ use crate::{ types::player_info::PlayerInfo, }; -pub trait AnyhowErrorToStringChain { - /// 将 `anyhow::Error` 转换为chain格式 +pub trait EyreToStringChain { + /// 将 `eyre::Report` 转换为chain格式 /// # Example /// 0: error message\ /// 1: error message\ @@ -18,7 +18,7 @@ pub trait AnyhowErrorToStringChain { fn to_string_chain(&self) -> String; } -impl AnyhowErrorToStringChain for anyhow::Error { +impl EyreToStringChain for eyre::Report { fn to_string_chain(&self) -> String { use std::fmt::Write; self.chain() @@ -53,7 +53,7 @@ pub trait GetOrInitPlayerInfo { &'a mut self, app: &AppHandle, progress: &DownloadProgress, - ) -> anyhow::Result<&'a mut PlayerInfo>; + ) -> eyre::Result<&'a mut PlayerInfo>; } impl GetOrInitPlayerInfo for Option { @@ -61,7 +61,7 @@ impl GetOrInitPlayerInfo for Option { &'a mut self, app: &AppHandle, progress: &DownloadProgress, - ) -> anyhow::Result<&'a mut PlayerInfo> { + ) -> eyre::Result<&'a mut PlayerInfo> { if let Some(info) = self { return Ok(info); } @@ -70,7 +70,7 @@ impl GetOrInitPlayerInfo for Option { let info = bili_client .get_player_info(progress.aid, progress.cid) .await - .context("获取播放器信息失败")?; + .wrap_err("获取播放器信息失败")?; Ok(self.insert(info)) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 8d24ca4..92c29b7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -15,7 +15,6 @@ mod protobuf { include!("./bilibili.community.service.dm.v1.rs"); } -use anyhow::Context; use commands::{ create_download_tasks, delete_download_tasks, generate_qrcode, get_available_media_formats, get_bangumi_follow_info, get_bangumi_info, get_config, get_fav_folders, get_fav_info, @@ -25,6 +24,7 @@ use commands::{ save_config, search, show_path_in_file_manager, }; use config::Config; +use eyre::WrapErr; use parking_lot::RwLock; use tauri::{Manager, Wry}; @@ -99,9 +99,9 @@ pub fn run() { let app_data_dir = app .path() .app_data_dir() - .context("获取app_data_dir目录失败")?; + .wrap_err("获取app_data_dir目录失败")?; - std::fs::create_dir_all(&app_data_dir).context(format!( + std::fs::create_dir_all(&app_data_dir).wrap_err(format!( "创建app_data_dir目录`{:?}`失败", app_data_dir.display() ))?; diff --git a/src-tauri/src/logger.rs b/src-tauri/src/logger.rs index 294981b..322326e 100644 --- a/src-tauri/src/logger.rs +++ b/src-tauri/src/logger.rs @@ -1,6 +1,6 @@ use std::{io::Write, sync::OnceLock}; -use anyhow::Context; +use eyre::{OptionExt, WrapErr}; use notify::{RecommendedWatcher, Watcher}; use tauri::{AppHandle, Manager}; use tauri_specta::Event; @@ -20,7 +20,7 @@ use tracing_subscriber::{ use crate::{ events::LogEvent, - extensions::{AnyhowErrorToStringChain, AppHandleExt}, + extensions::{AppHandleExt, EyreToStringChain}, }; struct LogEventWriter { @@ -48,12 +48,12 @@ impl Write for LogEventWriter { } } -static RELOAD_FN: OnceLock anyhow::Result<()> + Send + Sync>> = OnceLock::new(); +static RELOAD_FN: OnceLock eyre::Result<()> + Send + Sync>> = OnceLock::new(); static GUARD: OnceLock>> = OnceLock::new(); -pub fn init(app: &AppHandle) -> anyhow::Result<()> { +pub fn init(app: &AppHandle) -> eyre::Result<()> { let lib_module_path = module_path!(); - let lib_target = lib_module_path.split("::").next().context(format!( + let lib_target = lib_module_path.split("::").next().ok_or_eyre(format!( "解析lib_target失败: lib_module_path={lib_module_path}" ))?; // 过滤掉来自其他库的日志 @@ -92,8 +92,8 @@ pub fn init(app: &AppHandle) -> anyhow::Result<()> { let app = app.clone(); Box::new(move || { let (file_layer, guard) = create_file_layer(&app)?; - reload_handle.reload(file_layer).context("reload失败")?; - *GUARD.get().context("GUARD未初始化")?.lock() = guard; + reload_handle.reload(file_layer).wrap_err("reload失败")?; + *GUARD.get().ok_or_eyre("GUARD未初始化")?.lock() = guard; Ok(()) }) }); @@ -102,12 +102,12 @@ pub fn init(app: &AppHandle) -> anyhow::Result<()> { Ok(()) } -pub fn reload_file_logger() -> anyhow::Result<()> { - RELOAD_FN.get().context("RELOAD_FN未初始化")?() +pub fn reload_file_logger() -> eyre::Result<()> { + RELOAD_FN.get().ok_or_eyre("RELOAD_FN未初始化")?() } -pub fn disable_file_logger() -> anyhow::Result<()> { - if let Some(guard) = GUARD.get().context("GUARD未初始化")?.lock().take() { +pub fn disable_file_logger() -> eyre::Result<()> { + if let Some(guard) = GUARD.get().ok_or_eyre("GUARD未初始化")?.lock().take() { drop(guard); } Ok(()) @@ -115,7 +115,7 @@ pub fn disable_file_logger() -> anyhow::Result<()> { fn create_file_layer( app: &AppHandle, -) -> anyhow::Result<(Box + Send + Sync>, Option)> +) -> eyre::Result<(Box + Send + Sync>, Option)> where S: Subscriber + for<'a> LookupSpan<'a>, { @@ -130,13 +130,13 @@ where .with_line_number(true); return Ok((Box::new(sink_layer), None)); } - let logs_dir = logs_dir(app).context("获取日志目录失败")?; + let logs_dir = logs_dir(app).wrap_err("获取日志目录失败")?; let file_appender = RollingFileAppender::builder() .filename_prefix("bilibili-video-downloader") .filename_suffix("log") .rotation(Rotation::DAILY) .build(&logs_dir) - .context("创建RollingFileAppender失败")?; + .wrap_err("创建RollingFileAppender失败")?; let (non_blocking_appender, guard) = tracing_appender::non_blocking(file_appender); let file_layer = layer() .with_writer(non_blocking_appender) @@ -152,7 +152,7 @@ async fn file_log_watcher(app: AppHandle) { let event_handler = move |res| { tauri::async_runtime::block_on(async { - if let Err(err) = sender.send(res).await.map_err(anyhow::Error::from) { + if let Err(err) = sender.send(res).await.map_err(eyre::Report::from) { let err_title = "发送日志文件watcher事件失败"; let string_chain = err.to_string_chain(); tracing::error!(err_title, message = string_chain); @@ -161,7 +161,7 @@ async fn file_log_watcher(app: AppHandle) { }; let mut watcher = match RecommendedWatcher::new(event_handler, notify::Config::default()) - .map_err(anyhow::Error::from) + .map_err(eyre::Report::from) { Ok(watcher) => watcher, Err(err) => { @@ -184,7 +184,7 @@ async fn file_log_watcher(app: AppHandle) { if let Err(err) = watcher .watch(&logs_dir, notify::RecursiveMode::NonRecursive) - .map_err(anyhow::Error::from) + .map_err(eyre::Report::from) { let err_title = "日志文件watcher监听日志目录失败"; let string_chain = err.to_string_chain(); @@ -193,7 +193,7 @@ async fn file_log_watcher(app: AppHandle) { } while let Some(res) = receiver.recv().await { - match res.map_err(anyhow::Error::from) { + match res.map_err(eyre::Report::from) { Ok(event) => { if let notify::EventKind::Remove(_) = event.kind && let Err(err) = reload_file_logger() @@ -212,10 +212,10 @@ async fn file_log_watcher(app: AppHandle) { } } -pub fn logs_dir(app: &AppHandle) -> anyhow::Result { +pub fn logs_dir(app: &AppHandle) -> eyre::Result { let app_data_dir = app .path() .app_data_dir() - .context("获取app_data_dir目录失败")?; + .wrap_err("获取app_data_dir目录失败")?; Ok(app_data_dir.join("日志")) } diff --git a/src-tauri/src/types/bangumi_info.rs b/src-tauri/src/types/bangumi_info.rs index 5c88b8d..5d28fe0 100644 --- a/src-tauri/src/types/bangumi_info.rs +++ b/src-tauri/src/types/bangumi_info.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, anyhow}; +use eyre::{OptionExt, eyre}; use serde::{Deserialize, Serialize}; use specta::Type; @@ -55,7 +55,7 @@ pub struct BangumiInfo { impl BangumiInfo { #[allow(clippy::cast_possible_wrap)] - pub fn get_episode_with_order(&self, ep_id: i64) -> anyhow::Result<(&EpInBangumi, i64)> { + pub fn get_episode_with_order(&self, ep_id: i64) -> eyre::Result<(&EpInBangumi, i64)> { let episode_with_order = self .episodes .iter() @@ -69,19 +69,19 @@ impl BangumiInfo { } else { // 如果在正片中没有找到对应的ep_id,则在section中查找 let Some(sections) = &self.section else { - return Err(anyhow!("找不到对应的ep_id为`{ep_id}`的番剧")); + return Err(eyre!("找不到对应的ep_id为`{ep_id}`的番剧")); }; let section_index = sections .iter() .position(|s| s.episodes.iter().any(|e| e.id == ep_id)) - .context(format!("找不到含有ep_id为`{ep_id}`的ep的section"))?; + .ok_or_eyre(format!("找不到含有ep_id为`{ep_id}`的ep的section"))?; sections[section_index] .episodes .iter() .enumerate() .map(|(i, e)| (e, i as i64 + 1)) .find(|(e, _)| e.id == ep_id) - .context(format!("在section中找不到ep_id为`{ep_id}`的ep"))? + .ok_or_eyre(format!("在section中找不到ep_id为`{ep_id}`的ep"))? }; Ok(episode_with_order) diff --git a/src-tauri/src/utils.rs b/src-tauri/src/utils.rs index a526e3d..9a4e4df 100644 --- a/src-tauri/src/utils.rs +++ b/src-tauri/src/utils.rs @@ -4,8 +4,8 @@ use std::{ path::{Path, PathBuf}, }; -use anyhow::{Context, anyhow}; use byteorder::{BigEndian, ReadBytesExt}; +use eyre::{OptionExt, WrapErr, eyre}; use crate::{ danmaku_xml_to_ass::{DamakuXmlDTag, DanmakuXmlITag}, @@ -48,11 +48,11 @@ impl From for BoxSizeField { } } -pub fn is_mp4_complete(file_path: &Path) -> anyhow::Result { - let file = File::open(file_path).context(format!("打开文件`{}`失败", file_path.display()))?; +pub fn is_mp4_complete(file_path: &Path) -> eyre::Result { + let file = File::open(file_path).wrap_err(format!("打开文件`{}`失败", file_path.display()))?; let real_size = file .metadata() - .context(format!("获取文件`{}`元数据失败", file_path.display()))? + .wrap_err(format!("获取文件`{}`元数据失败", file_path.display()))? .len(); let mut reader = BufReader::new(file); let mut total_size: u64 = 0; @@ -65,7 +65,7 @@ pub fn is_mp4_complete(file_path: &Path) -> anyhow::Result { let box_size_field: BoxSizeField = match reader.read_u32::() { Ok(s) => s.into(), Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break, // 正常结束 - Err(e) => return Err(anyhow!(e)), + Err(e) => return Err(eyre!(e)), }; // 读取Box类型字段 let mut box_type_bytes = [0u8; 4]; @@ -74,7 +74,7 @@ pub fn is_mp4_complete(file_path: &Path) -> anyhow::Result { if e.kind() == std::io::ErrorKind::UnexpectedEof { return Ok(false); } - return Err(anyhow!(e)); + return Err(eyre!(e)); } // 如果是第一个Box,检查是否是 'ftyp' Box if is_first_box { @@ -131,11 +131,11 @@ pub fn is_mp4_complete(file_path: &Path) -> anyhow::Result { } pub trait ToXml { - fn to_xml(&self, cid: i64) -> anyhow::Result; + fn to_xml(&self, cid: i64) -> eyre::Result; } impl ToXml for Vec { - fn to_xml(&self, cid: i64) -> anyhow::Result { + fn to_xml(&self, cid: i64) -> eyre::Result { let elems = self .iter() .flat_map(|reply| &reply.elems) @@ -157,7 +157,7 @@ impl ToXml for Vec { let i_tag = DanmakuXmlITag { chatid: cid, elems }; - let xml = yaserde::ser::to_string(&i_tag).map_err(|e| anyhow!(e))?; + let xml = yaserde::ser::to_string(&i_tag).map_err(|e| eyre!(e))?; Ok(xml) } @@ -177,11 +177,11 @@ pub fn seconds_to_srt_time(seconds: f64) -> String { format!("{h:02}:{m:02}:{s:02},{ms:03}") } -pub fn get_ffmpeg_program() -> anyhow::Result { +pub fn get_ffmpeg_program() -> eyre::Result { let ffmpeg_program = std::env::current_exe() - .context("获取当前可执行文件路径失败")? + .wrap_err("获取当前可执行文件路径失败")? .parent() - .context("获取当前可执行文件所在目录失败")? + .ok_or_eyre("获取当前可执行文件所在目录失败")? .join("com.lanyeeee.bilibili-video-downloader-ffmpeg"); Ok(ffmpeg_program) diff --git a/src-tauri/src/wbi.rs b/src-tauri/src/wbi.rs index 2c3006b..fea7e12 100644 --- a/src-tauri/src/wbi.rs +++ b/src-tauri/src/wbi.rs @@ -1,6 +1,6 @@ use std::time::{SystemTime, UNIX_EPOCH}; -use anyhow::{Context, anyhow}; +use eyre::{OptionExt, WrapErr, eyre}; use md5::{Digest, Md5}; use serde::Deserialize; @@ -25,8 +25,8 @@ struct WeiRespData { impl BiliClient { // 为请求参数进行 wbi 签名 - pub(crate) async fn wbi(&self, params: &mut Vec<(&str, String)>) -> anyhow::Result<()> { - let (img_key, sub_key) = self.get_wbi_keys().await.context("获取wbi keys失败")?; + pub(crate) async fn wbi(&self, params: &mut Vec<(&str, String)>) -> eyre::Result<()> { + let (img_key, sub_key) = self.get_wbi_keys().await.wrap_err("获取wbi keys失败")?; let mixin_key = get_mixin_key((img_key + &sub_key).as_bytes()); let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); @@ -46,7 +46,7 @@ impl BiliClient { Ok(()) } - async fn get_wbi_keys(&self) -> anyhow::Result<(String, String)> { + async fn get_wbi_keys(&self) -> eyre::Result<(String, String)> { let request = self .api_client .read() @@ -58,27 +58,27 @@ impl BiliClient { let status = http_resp.status(); let body = http_resp.text().await?; if status != reqwest::StatusCode::OK { - return Err(anyhow!("预料之外的状态码({status}): {body}")); + return Err(eyre!("预料之外的状态码({status}): {body}")); } // 尝试将body解析为BiliResp let bili_resp: BiliResp = - serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; + serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?; // 检查BiliResp的data是否存在 let Some(data) = bili_resp.data else { - return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); + return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}")); }; // 尝试将data解析为Data let data_str = data.to_string(); - let wei_resp_data: WeiRespData = - serde_json::from_str(&data_str).context(format!("将data解析为Data失败: {data_str}"))?; + let wei_resp_data: WeiRespData = serde_json::from_str(&data_str) + .wrap_err(format!("将data解析为Data失败: {data_str}"))?; let img_url = wei_resp_data.wbi_img.img_url; let sub_url = wei_resp_data.wbi_img.sub_url; let img_filename = - take_filename(&img_url).context(format!("从img_url中提取文件名失败: {img_url}"))?; + take_filename(&img_url).ok_or_eyre(format!("从img_url中提取文件名失败: {img_url}"))?; let sub_filename = - take_filename(&sub_url).context(format!("从sub_url中提取文件名失败: {sub_url}"))?; + take_filename(&sub_url).ok_or_eyre(format!("从sub_url中提取文件名失败: {sub_url}"))?; Ok((img_filename, sub_filename)) }